Skip to content

The Laravel factory

Every new Laravel application ships this, and WS001 reports it as HIGH:

database/factories/UserFactory.php
protected static ?string $password;
// …
'password' => static::$password ??= Hash::make('password'),

This is a real report, not a bug — and in this specific case it is almost certainly not a problem in your code. The honest reason it is not silenced automatically is that the analyzer cannot tell this apart from the shapes that are bugs. All three of these are the same syntax to a parser:

static::$password ??= Hash::make('password'); // fixed literal — harmless
static::$user ??= auth()->user(); // request #1's user, forever
static::$tenant ??= request('tenant'); // request #1's tenant, forever

The last two are genuine cross-request leaks: because ??= writes only into an empty slot, whichever request stores a value there hands it to the requests that follow, until something resets or replaces it. Any rule broad enough to silence the first line — “the class name ends in Factory”, “the file is under database/factories”, “the assignment uses ??=” — silences those two as well. Deciding properly would mean knowing what Hash::make, auth() and request() return, which is a question about the framework’s behaviour, not about the syntax.

So the tool reports what it can prove — the assignment writes only into an empty slot, so a stored value gets reused — states which question decides whether that matters (where the value comes from), and leaves the answer to you. It does not claim how long the value lasts: that depends on whether the initializer ever returns null and on resets it cannot see. For the stock factory the answer is that the input is a fixed string, so record that decision next to the code:

// worker-safety-ignore WS001 memoized hash of a constant test password
protected static ?string $password;

Or, if you would rather not touch the file, accept it once with a baseline.

Know what you are buying in either case. Both the directive and the baseline entry are anchored to the property declaration, not to the initializer, so they keep applying if the initializer is later changed:

// worker-safety-ignore WS001 memoized hash of a constant test password
protected static ?string $password; // declaration: unchanged
'password' => static::$password ??= request('tenant'), // now a real leak, still silent

That is the normal trade-off of any suppression — it is a decision recorded about a line — but it is worth knowing here, because the thing that decides the risk lives somewhere else in the file. If that matters to you, suppress WS001 per file rather than project-wide, and re-read the initializer when the factory changes.

database is one of the default scan paths for Laravel, so this finding appears on a stock application the first time you run a scan. Handling it once, either way, is part of adopting the tool.