expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); } public function deleteUser(PDO $pdo) : void { $userId = $this->getAppendedUserId(); $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromFunctionReturnType() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromExplicitTaintSource() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromExplicitTaintSourceStaticMethod() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromGetArray() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromReturnToInclude() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromReturnToEval() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromReturnTypeToEcho() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); } public function deleteUser(PDO $pdo) : void { $userId = $this->getAppendedUserId(); echo $userId; } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputInCreatedArrayNotEchoed() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', ' $name, "id" => $id]; echo "

" . htmlentities($data["name"]) . "

"; echo "

" . $data["id"] . "

";' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputInCreatedArrayIsEchoed() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', ' $name]; echo "

" . $data["name"] . "

";' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputInAssignedArrayNotEchoed() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', '" . htmlentities($data["name"]) . ""; echo "

" . $data["id"] . "

";' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputInAssignedArrayIsEchoed() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', '" . $data["name"] . "";' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputDirectly() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputDirectlySuppressed() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputDirectlySuppressedWithOtherUse() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'exec("delete from users where user_id = " . $userId); } public function deleteUserSafer(PDOWrapper $pdo) : void { $userId = $this->getSafeId(); $pdo->exec("delete from users where user_id = " . $userId); } public function getSafeId() : string { return "5"; } } class PDOWrapper { /** * @psalm-taint-sink sql $sql */ public function exec(string $sql) : void {} }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromReturnTypeWithBranch() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); if (rand(0, 1)) { $userId .= "aaa"; } else { $userId .= "bb"; } return $userId; } public function deleteUser(PDO $pdo) : void { $userId = $this->getAppendedUserId(); $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testSinkAnnotation() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); } public function deleteUser(PDOWrapper $pdo) : void { $userId = $this->getAppendedUserId(); $pdo->exec("delete from users where user_id = " . $userId); } } class PDOWrapper { /** * @psalm-taint-sink sql $sql */ public function exec(string $sql) : void {} }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromParam() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput - somefile.php:17:36 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (somefile.php:4:41) -> A::getUserId (somefile.php:3:51) -> concat (somefile.php:8:32) -> A::getAppendedUserId (somefile.php:7:59) -> $userId (somefile.php:12:25) -> A::deleteUser#2 (somefile.php:16:65) -> concat (somefile.php:17:36) -> PDO::exec#1'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); } public function doDelete(PDO $pdo) : void { $userId = $this->getAppendedUserId(); $this->deleteUser($pdo, $userId); } public function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputToParam() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'deleteUser( $pdo, $this->getAppendedUserId((string) $_GET["user_id"]) ); } public function getAppendedUserId(string $user_id) : string { return "aaa" . $user_id; } public function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputToParamAfterAssignment() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'deleteUser( $pdo, $this->getAppendedUserId((string) $_GET["user_id"]) ); } public function getAppendedUserId(string $user_id) : string { return "aaa" . $user_id; } public function deleteUser(PDO $pdo, string $userId) : void { $userId2 = $userId; $pdo->exec("delete from users where user_id = " . $userId2); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputToParamButSafe() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'deleteUser( $pdo, $this->getAppendedUserId((string) $_GET["user_id"]) ); } public function getAppendedUserId(string $user_id) : string { return "aaa" . $user_id; } public function deleteUser(PDO $pdo, string $userId) : void { $userId2 = strlen($userId); $pdo->exec("delete from users where user_id = " . $userId2); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputToParamAlternatePath() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput - somefile.php:23:40 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (somefile.php:7:63) -> A::getAppendedUserId#1 (somefile.php:11:62) -> concat (somefile.php:12:32) -> A::getAppendedUserId (somefile.php:11:37) -> A::deleteUser#3 (somefile.php:19:81) -> concat (somefile.php:23:40) -> PDO::exec#1'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'deleteUser( $pdo, self::doFoo(), $this->getAppendedUserId((string) $_GET["user_id"]) ); } public function getAppendedUserId(string $user_id) : string { return "aaa" . $user_id; } public static function doFoo() : string { return "hello"; } public function deleteUser(PDO $pdo, string $userId, string $userId2) : void { $pdo->exec("delete from users where user_id = " . $userId); if (rand(0, 1)) { $pdo->exec("delete from users where user_id = " . $userId2); } } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInParentLoader() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput - somefile.php:16:40 - Detected tainted sql in path: $_GET -> $_GET[\'user_id\'] (somefile.php:28:39) -> C::foo#1 (somefile.php:23:48) -> AGrandChild::loadFull#1 (somefile.php:5:60) -> A::loadFull#1 (somefile.php:24:47) -> A::loadPartial#1 (somefile.php:3:72) -> AChild::loadPartial#1 (somefile.php:6:45) -> concat (somefile.php:16:40) -> PDO::exec#1'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'exec("select * from foo where bar = " . $sink); } } class AGrandChild extends AChild {} class C { public function foo(string $user_id) : void { AGrandChild::loadFull($user_id); } } (new C)->foo((string) $_GET["user_id"]);' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testValidatedInputFromParam() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); validateUserId($userId); $this->deleteUser($pdo, $userId); } public function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testUntaintedInput() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'getUserId(); } public function deleteUser(PDO $pdo) : void { $userId = $this->getAppendedUserId(); $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromProperty() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'userId = (string) $_GET["user_id"]; } public function getAppendedUserId() : string { return "aaaa" . $this->userId; } public function doDelete(PDO $pdo) : void { $userId = $this->getAppendedUserId(); $this->deleteUser($pdo, $userId); } public function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testSpecializedCoreFunctionCall() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputFromPropertyViaMixin() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'userId = (string) $_GET["user_id"]; } } /** @mixin A */ class B { private A $a; public function __construct(A $a) { $this->a = $a; } public function __get(string $name) { return $this->a->$name; } } class C { private B $b; public function __construct(B $b) { $this->b = $b; } public function getAppendedUserId() : string { return "aaaa" . $this->b->userId; } public function doDelete(PDO $pdo) : void { $userId = $this->getAppendedUserId(); $this->deleteUser($pdo, $userId); } public function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } }' ); $this->analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputViaStaticFunction() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testTaintedInputViaPureStaticFunction() { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testUntaintedInputViaStaticFunctionWithSafePath() { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } /** * @return void */ public function testUntaintedInputViaStaticFunctionWithoutSafePath() { $this->project_analyzer->trackTaintedInputs(); $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintedInputFromMagicProperty() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', ' */ private $vars = []; public function __get(string $s) : string { return $this->vars[$s]; } public function __set(string $s, string $t) { $this->vars[$s] = $t; } } function getAppendedUserId() : void { $a = new A(); $a->userId = (string) $_GET["user_id"]; echo $a->userId; }' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintOverMixed() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintStrConversion() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintHtmlEntities() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintIntoExec() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintIntoExecMultipleConcat() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintIntoNestedArrayUnnestedSeparately() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintIntoArrayAndThenOutAgain() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintAppendedToArray() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'analyzeFile('somefile.php', new Context()); } public function testTaintOnSubstrCall() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = (string) $_GET["FOO"]; } } class V1 extends V { public function foo(O1 $o) : void { echo U::shorten($o->s); } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintOnStrReplaceCallSimple() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = (string) $_GET["FOO"]; } } class V1 extends V { public function foo(O1 $o) : void { echo U::shorten($o->s); } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintOnStrReplaceCallRemovedInFunction() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = (string) $_GET["FOO"]; } } class V1 extends V { public function foo(O1 $o) : void { echo U::shorten($o->s); } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintOnPregReplaceCallRemovedInFunction() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = (string) $_GET["FOO"]; } } class V1 extends V { public function foo(O1 $o) : void { echo U::shorten($o->s); } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintOnStrReplaceCallRemovedInline() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = (string) $_GET["FOO"]; } } class V1 extends V { public function foo(O1 $o) : void { /** * @psalm-taint-escape html */ $a = str_replace("foo", "bar", $o->s); echo $a; } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintOnPregReplaceCall() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = (string) $_GET["FOO"]; } } class V1 extends V { public function foo(O1 $o) : void { echo U::shorten($o->s); } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testNoTaintsOnSimilarPureCall() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 's = $s; } } class O2 { public string $t; public function __construct() { $this->t = (string) $_GET["FOO"]; } } class V1 { public function foo() : void { $o = new O1((string) $_GET["FOO"]); echo U::escape(U::shorten($o->s)); } } class V2 { public function foo(O2 $o) : void { echo U::shorten(U::escape($o->t)); } }' ); $this->analyzeFile('somefile.php', new Context()); } public function testIndirectGetAssignment() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'name = $name; } /** * @psalm-specialize-call */ public function getArg(string $method, string $type) { $arg = null; switch ($method) { case "post": if (isset($_POST[$this->name])) { $arg = $_POST[$this->name]; } break; case "get": if (isset($_GET[$this->name])) { $arg = $_GET[$this->name]; } break; } return $this->filterInput($type, $arg); } protected function filterInput(string $type, $arg) { // input is null if ($arg === null) { return null; } // set to null if sanitize clears arg if ($arg === "") { $arg = null; } // type casting if ($arg !== null) { $arg = $this->typeCastInput($type, $arg); } return $arg; } protected function typeCastInput(string $type, $arg) { if ($type === "string") { return (string) $arg; } return null; } } echo (new InputFilter("hello"))->getArg("get", "string");' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintPropertyPassingObject() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'id = $userId; } } class UserUpdater { public static function doDelete(PDO $pdo, User $user) : void { self::deleteUser($pdo, $user->id); } public static function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } } $userObj = new User((string) $_GET["user_id"]); UserUpdater::doDelete(new PDO(), $userObj);' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintPropertyPassingObjectSettingValueLater() : void { $this->expectException(\Psalm\Exception\CodeException::class); $this->expectExceptionMessage('TaintedInput'); $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'id = $userId; } public function setId(string $userId) : void { $this->id = $userId; } } class UserUpdater { public static function doDelete(PDO $pdo, User $user) : void { self::deleteUser($pdo, $user->id); } public static function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } } $userObj = new User("5"); $userObj->setId((string) $_GET["user_id"]); UserUpdater::doDelete(new PDO(), $userObj);' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintPropertyPassingObjectWithDifferentValue() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'id = $userId; } } class UserUpdater { public static function doDelete(PDO $pdo, User $user) : void { self::deleteUser($pdo, $user->name); } public static function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } } $userObj = new User((string) $_GET["user_id"]); UserUpdater::doDelete(new PDO(), $userObj);' ); $this->analyzeFile('somefile.php', new Context()); } public function testTaintPropertyWithoutPassingObject() : void { $this->project_analyzer->trackTaintedInputs(); $this->addFile( 'somefile.php', 'id = $userId; } } class UserUpdater { public static function doDelete(PDO $pdo, User $user) : void { self::deleteUser($pdo, $user->id); } public static function deleteUser(PDO $pdo, string $userId) : void { $pdo->exec("delete from users where user_id = " . $userId); } } $userObj = new User((string) $_GET["user_id"]);' ); $this->analyzeFile('somefile.php', new Context()); } }