PHPackages                             lychee-org/phpstan-sensitive-parameter-values - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. lychee-org/phpstan-sensitive-parameter-values

ActivePhpstan-extension[Security](/categories/security)

lychee-org/phpstan-sensitive-parameter-values
=============================================

PHPStan extension for better support of SensitiveParameter and SensitiveParameterValue attributes.

1.2.0(1mo ago)09.6k↑117.5%1MITPHPPHP ^8.2CI passing

Since Jul 18Pushed 1mo agoCompare

[ Source](https://github.com/LycheeOrg/phpstan-sensitive-parameter-values)[ Packagist](https://packagist.org/packages/lychee-org/phpstan-sensitive-parameter-values)[ Docs](https://github.com/LycheeOrg/phpstan-sensitive-parameter-values)[ GitHub Sponsors](https://github.com/LycheeOrg)[ Fund](https://opencollective.com/LycheeOrg)[ RSS](/packages/lychee-org-phpstan-sensitive-parameter-values/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (3)Dependencies (3)Versions (4)Used By (1)

PHPStan SensitiveParameter Detector
===================================

[](#phpstan-sensitiveparameter-detector)

[![CI](https://github.com/LycheeOrg/phpstan-sensitive-parameter-values/workflows/CI/badge.svg)](https://github.com/LycheeOrg/phpstan-sensitive-parameter-values/actions)

[![License](https://camo.githubusercontent.com/d6388c65d51bf902d3e648c21303ccbab2a6bbf80f8813ba428cce322bb35c9e/68747470733a2f2f706f7365722e707567782e6f72672f4c79636865654f72672f7068707374616e2d73656e7369746976652d706172616d657465722d76616c7565732f6c6963656e7365)](https://packagist.org/packages/LycheeOrg/phpstan-sensitive-parameter-values)[![OpenSSF Scorecard](https://camo.githubusercontent.com/f81fa6bc69ac78cd6c40163ea964b838675926f96a8031cc39f2845b80fd39ab/68747470733a2f2f6170692e736563757269747973636f726563617264732e6465762f70726f6a656374732f6769746875622e636f6d2f4c79636865654f72672f7068707374616e2d73656e7369746976652d706172616d657465722d76616c7565732f6261646765)](https://securityscorecards.dev/viewer/?uri=github.com/LycheeOrg/phpstan-sensitive-parameter-values)

A PHPStan extension that detects parameters that might contain sensitive information and should be marked with the `#[\SensitiveParameter]` attribute (added in PHP 8.2+).

About SensitiveParameter
------------------------

[](#about-sensitiveparameter)

The `#[\SensitiveParameter]` attribute was introduced in PHP 8.2 to mark sensitive data that should be hidden from stack traces and debugging output. This extension helps you identify parameters that should use this attribute for better security.

Learn more: [PHP RFC: Redact parameters in back traces](https://wiki.php.net/rfc/redact_parameters_in_back_traces)

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- PHPStan 2.1.3 or higher (`SensitiveParameterPropagationRule` relies on `getAttributes()` reflection support added in 2.1.3)

Installation
------------

[](#installation)

```
composer require --dev lychee-org/phpstan-sensitive-parameter-values
```

Usage
-----

[](#usage)

The extension will be automatically registered if you use [PHPStan's extension installer](https://github.com/phpstan/extension-installer).

Alternatively, include the extension in your PHPStan configuration:

```
includes:
    - vendor/lychee-org/phpstan-sensitive-parameter-values/extension.neon
```

Typed `SensitiveParameterValue`
-------------------------------

[](#typed-sensitiveparametervalue)

PHP's built-in `\SensitiveParameterValue::getValue()` is natively typed as `mixed`, so calling it normally loses type information. This extension ships a PHPStan stub that declares `SensitiveParameterValue` as generic over the type of the value passed to its constructor, so PHPStan can narrow the return type of `getValue()` accordingly:

```
function example(string $password): void {
    $sensitive = new \SensitiveParameterValue($password);

    // PHPStan now sees $sensitive as SensitiveParameterValue
    // and infers the return type of getValue() as string, not mixed.
    $plain = $sensitive->getValue();
}
```

This is most useful when inspecting exception traces, where PHP replaces sensitive arguments with `SensitiveParameterValue` instances:

```
foreach ($exception->getTrace() as $frame) {
    foreach ($frame['args'] ?? [] as $arg) {
        if ($arg instanceof \SensitiveParameterValue) {
            // getValue() keeps the original argument's type.
            $original = $arg->getValue();
        }
    }
}
```

Propagating sensitivity through the call graph
----------------------------------------------

[](#propagating-sensitivity-through-the-call-graph)

Marking a parameter `#[\SensitiveParameter]` only protects that one call frame. If the value is then forwarded unchanged into a callee whose corresponding parameter is *not* marked sensitive, protection stops there: an exception thrown from inside the callee will still expose the value in plaintext.

```
class AuthService {
    // $password is marked sensitive here...
    public function authenticate(#[\SensitiveParameter] string $password): bool {
        // ...but login()'s parameter isn't, so the value is unprotected
        // as soon as it enters login()'s stack frame.
        return $this->login($password);
    }

    public function login(string $password): bool {
        // ...
    }
}
```

`SensitiveParameterPropagationRule` flags `login()`'s `$password`in this example, with:

```
Parameter $password is marked #[\SensitiveParameter] but is passed to a
parameter ($password) that is not itself marked with #[\SensitiveParameter].
Add the attribute there too or ignore with
`@phpstan-ignore sensitiveParameter.propagation`.

```

This is detected across method calls, static calls, constructors, and plain function calls. Only simple, unmodified pass-through arguments (a bare `$variable` matching a sensitive parameter of the enclosing function/method) are tracked — values that are transformed, wrapped, or reassigned before being passed on are not.

### Cryptographic callees are never flagged

[](#cryptographic-callees-are-never-flagged)

Passing a sensitive value directly into a hashing or encryption function is the *intended* usage — there is nothing to propagate. The rule ships with a built-in allowlist of well-known cryptographic functions and methods that will never trigger a propagation warning:

```
function hashPassword(#[\SensitiveParameter] string $password): string
{
    return password_hash($password, PASSWORD_BCRYPT); // ✅ not flagged
}

class AuthService
{
    public function store(#[\SensitiveParameter] string $password): void
    {
        $hash = Hash::make($password); // ✅ not flagged (Laravel)
    }
}
```

The built-in allowlist covers:

- **PHP core** — `password_hash`, `password_verify`, `hash`, `hash_hmac`, `hash_pbkdf2`, `hash_equals`, `crypt`, `md5`, `sha1`
- **OpenSSL** — `openssl_encrypt`, `openssl_decrypt`, `openssl_digest`, `openssl_sign`, `openssl_verify`
- **Sodium** — `sodium_crypto_pwhash`, `sodium_crypto_pwhash_str`, `sodium_crypto_pwhash_str_verify`, `sodium_crypto_secretbox`, `sodium_crypto_secretbox_open`, `sodium_crypto_auth`, `sodium_crypto_auth_verify`, `sodium_crypto_box`, `sodium_crypto_box_open`, `sodium_crypto_sign`, and others
- **Laravel** — `Illuminate\Support\Facades\Hash::make/check/needsRehash`and the concrete `BcryptHasher`, `ArgonHasher`, `Argon2IdHasher` variants
- **LdapRecord** — `LdapRecord\Auth\Guard::attempt`

See [Configuring the cryptographic callee allowlist](#configuring-the-cryptographic-callee-allowlist)for how to add your own entries.

Storing sensitive values safely
-------------------------------

[](#storing-sensitive-values-safely)

Marking a parameter sensitive prevents it from leaking through stack traces, but that protection is undone if the raw value is then saved into a property — anything that inspects, dumps, or serializes the object exposes it again. `SensitiveParameterStorageRule` requires sensitive values to be wrapped in `\SensitiveParameterValue` before being stored:

```
class Credentials {
    private string $password; // ❌ raw storage

    public function __construct(#[\SensitiveParameter] string $password) {
        $this->password = $password; // flagged: sensitiveParameter.unwrappedStorage
    }
}
```

```
class Credentials {
    private \SensitiveParameterValue $password; // ✅ wrapped storage

    public function __construct(#[\SensitiveParameter] string $password) {
        $this->password = new \SensitiveParameterValue($password);
    }
}
```

Constructor property promotion is also checked, since promotion assigns the raw value directly with no place to wrap it:

```
class Credentials {
    public function __construct(
        // flagged: sensitiveParameter.unwrappedPromotion
        #[\SensitiveParameter] private readonly string $password,
    ) {}
}
```

A value that's already wrapped is also checked: unwrapping it via `->getValue()` right before storing defeats the point of wrapping it in the first place, so it's flagged too:

```
class Credentials {
    private string $password;

    public function __construct(\SensitiveParameterValue $password) {
        // flagged: sensitiveParameter.unwrappedGetValue
        $this->password = $password->getValue();
    }
}
```

Only direct, unmodified assignments of a bare `$variable` (or a bare `->getValue()` call on one) into a property are detected; values transformed before being stored are not tracked.

What it detects
---------------

[](#what-it-detects)

The rule detects parameters with names containing common sensitive keywords:

- Authentication: `password`, `secret`, `token`, `credential`, `auth`, `bearer`
- API Security: `apikey` (matches `apisecret`, `clientsecret` via `secret`)
- Financial: `credit`, `card`, `ccv`, `cvv`, `ssn`, `pin`
- Security: `private`, `signature`, `hash`, `salt`, `nonce`, `otp`, `passcode`, `csrf`

Note: Due to substring matching, `secret` catches `apisecret`/`clientsecret` and `token` catches `refreshtoken`/`accesstoken`.

It works with:

- Regular functions
- Class methods (public, private, protected, static)
- Constructors
- Case-insensitive matching (`Password`, `SECRET`, etc.)
- Partial matches (`userPassword`, `secretKey`, etc.)

Examples
--------

[](#examples)

### ❌ Will trigger warnings:

[](#-will-trigger-warnings)

```
function login(string $username, string $password) {
    // Parameter $password should use #[\SensitiveParameter]
}

class AuthService {
    public function setCredentials(string $apikey, string $secret) {
        // Both $apikey and $secret should be marked sensitive
    }
}
```

### ✅ Properly protected:

[](#-properly-protected)

```
// Function-level protection
#[\SensitiveParameter]
function login(string $username, string $password) {
    // All parameters are protected
}

// Parameter-level protection
function authenticate(
    string $username,
    #[\SensitiveParameter] string $password
) {
    // Only $password is protected
}

// Mixed protection
class AuthService {
    public function verify(
        #[\SensitiveParameter] string $token,
        string $userId,
        string $apikey  // This will still trigger a warning
    ) {
        // $token is protected, $apikey needs protection
    }
}
```

Advanced Configuration
----------------------

[](#advanced-configuration)

### Configuring sensitive keywords

[](#configuring-sensitive-keywords)

To use custom sensitive keywords instead of the defaults, set `sensitiveParameter.keywords` in your `phpstan.neon`:

```
parameters:
    sensitiveParameter:
        keywords:
            - password
            - apikey
            - token
            - banking
            - medical
```

Providing a non-empty list **completely replaces** the default keyword list.

### Configuring the cryptographic callee allowlist

[](#configuring-the-cryptographic-callee-allowlist)

If your project uses a custom hashing or encryption wrapper that should not trigger a propagation warning, add it to `sensitiveParameter.cryptoCallees`:

```
parameters:
    sensitiveParameter:
        cryptoCallees:
            - 'App\Security\Hasher::hash'
            - 'App\Security\Hasher::verify'
```

Entries are matched as:

- **Plain function name** for global PHP functions (e.g. `my_hash_fn`)
- **`FullyQualifiedClass::method`** for static calls and instance method calls (e.g. `Illuminate\Support\Facades\Hash::make`)

Providing a non-empty list **completely replaces** the built-in allowlist, so include any built-in entries you still want to keep:

```
parameters:
    sensitiveParameter:
        cryptoCallees:
            - password_hash
            - password_verify
            - hash
            - hash_hmac
            - 'Illuminate\Support\Facades\Hash::make'
            - 'Illuminate\Support\Facades\Hash::check'
            - 'App\Security\Hasher::hash'
```

Suppressing Warnings
--------------------

[](#suppressing-warnings)

You can suppress warnings using PHPStan's ignore comments:

```
// @phpstan-ignore-next-line sensitiveParameter.missing
function legacyFunction(string $password) {
    // Legacy code that cannot be updated
}

// @phpstan-ignore-next-line sensitiveParameter.missing
function anotherLegacyFunction(string $secret) {
    // Another legacy function
}

function modernFunction(string $password): void // @phpstan-ignore-line sensitiveParameter.missing
{
    // Function with inline ignore comment
}
```

### Constructor Parameters

[](#constructor-parameters)

Due to a PHPStan limitation, ignore comments for constructor parameters must be placed before the constructor:

```
// @phpstan-ignore-next-line sensitiveParameter.missing
public function __construct(
    private readonly SomeService $serviceWithSensitiveKeywordInName
) {}
```

**Note:** This ignores ALL parameter warnings for that constructor. For functions with multiple parameters where only some are false positives, consider renaming the problematic parameter to avoid the sensitive keyword match.

Common Issues
-------------

[](#common-issues)

### False Positives

[](#false-positives)

The rule uses substring matching, which can occasionally trigger false positives:

- `$appInstall` triggers due to "install" containing "pin"
- `$passwordService` triggers due to containing "password"
- `$signatureMethod` triggers due to containing "signature"

For these cases, use ignore comments as shown above or consider renaming parameters to be more specific (e.g., `$applicationToInstall`, `$authService`, `$verificationMethod`).

Reporting Issues
----------------

[](#reporting-issues)

Found a bug or have a feature request? Please [report it on GitHub](https://github.com/LycheeOrg/phpstan-sensitive-parameter-values/issues).

When reporting issues, please include:

- PHP version
- PHPStan version
- Code sample that demonstrates the issue
- Expected vs actual behavior

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

**Development setup:**

```
git clone https://github.com/LycheeOrg/phpstan-sensitive-parameter-values.git
cd phpstan-sensitive-parameter-values
composer install
```

**Running tests:**

```
vendor/bin/pest             # Run tests
vendor/bin/phpstan analyze  # Static analysis
vendor/bin/pint --test      # Code style check
```

License
-------

[](#license)

MIT License - see [`LICENSE`](./LICENSE) for details.

###  Health Score

47

—

FairBetter than 93% of packages

Maintenance90

Actively maintained with recent releases

Popularity27

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 62.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

45d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/627094?v=4)[Benoît Viguier](/maintainers/ildyria)[@ildyria](https://github.com/ildyria)

---

Top Contributors

[![itspriddle](https://avatars.githubusercontent.com/u/49571?v=4)](https://github.com/itspriddle "itspriddle (5 commits)")[![ildyria](https://avatars.githubusercontent.com/u/627094?v=4)](https://github.com/ildyria "ildyria (3 commits)")

---

Tags

phpPHPStanstatic analysissecuritycode qualityphpstan-extensionsensitive-parameter

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/lychee-org-phpstan-sensitive-parameter-values/health.svg)

```
[![Health](https://phpackages.com/badges/lychee-org-phpstan-sensitive-parameter-values/health.svg)](https://phpackages.com/packages/lychee-org-phpstan-sensitive-parameter-values)
```

###  Alternatives

[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.5k66.6M11.3k](/packages/larastan-larastan)[staabm/phpstan-dba

2972.7M2](/packages/staabm-phpstan-dba)[shipmonk/dead-code-detector

Dead code detector to find unused PHP code via PHPStan extension. Can automatically remove dead PHP code. Supports libraries like Symfony, Doctrine, PHPUnit etc. Detects dead cycles. Can detect dead code that is tested.

5124.9M120](/packages/shipmonk-dead-code-detector)[phpstan/phpstan-doctrine

Doctrine extensions for PHPStan

67577.1M1.6k](/packages/phpstan-phpstan-doctrine)[tomasvotruba/type-coverage

Measure type coverage of your project

21813.1M498](/packages/tomasvotruba-type-coverage)[staabm/phpstan-todo-by

2062.3M96](/packages/staabm-phpstan-todo-by)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
