Custom Taint Sources
You can define your own taint sources with an annotation or a plugin.
Taint source annotation
You can use the annotation @psalm-taint-source <taint-type>
to indicate a function or method that provides user input.
In the below example the input
taint type is specified as a standin for input taints as defined in Psalm\Type\TaintKindGroup.
<?php
/**
* @psalm-taint-source input
*/
function getQueryParam(string $name) : string {}
Custom taint plugin
For example this plugin treats all variables named $bad_data
as taint sources.
<?php
namespace Psalm\Example\Plugin;
use PhpParser\Node\Expr\Variable;
use Psalm\Codebase;
use Psalm\Plugin\EventHandler\AddTaintsInterface;
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
use Psalm\Type\TaintKind;
/**
* Add input taints to all variables named 'bad_data' or 'even_badder_data'.
*
* RemoveTaintsInterface is also available to remove taints.
*/
class TaintBadDataPlugin implements AddTaintsInterface
{
private static int $myCustomTaint;
private static int $myCustomTaintAlias;
/**
* Must be called by the PluginEntryPointInterface (__invoke) of your plugin.
*/
public static function init(Codebase $codebase): void
{
// Register a new custom taint
// The taint name may be used in @psalm-taint-* annotations in the code.
self::$myCustomTaint = $codebase->getOrRegisterTaint("my_custom_taint");
// Register a taint alias that combines multiple pre-registered taint types
// Taint alias names may be used in @psalm-taint-* annotations in the code.
self::$myCustomTaintAlias = $codebase->registerTaintAlias(
"my_custom_taint_alias",
self::$myCustomTaint | TaintKind::ALL_INPUT
);
}
/**
* Called to see what taints should be added
*
* @return int A bitmap of taint from the IDs
*/
public static function addTaints(AddRemoveTaintsEvent $event): int
{
$expr = $event->getExpr();
if ($expr instanceof Variable && $expr->name === 'bad_data') {
return TaintKind::ALL_INPUT;
}
if ($expr instanceof Variable && $expr->name === 'even_badder_data') {
return self::$myCustomTaint;
}
if ($expr instanceof Variable && $expr->name === 'even_badder_data_2') {
return self::$myCustomTaintAlias;
}
if ($expr instanceof Variable && $expr->name === 'secret_even_badder_data_3') {
// Combine taints using |
return self::$myCustomTaintAlias | USER_SECRET;
}
if ($expr instanceof Variable && $expr->name === 'bad_data_but_ok_cookie') {
// Remove taints using & and ~ to negate a taint (group)
return self::$myCustomTaintAlias & ~TaintKind::INPUT_COOKIE;
}
// No taints
return 0;
}
}