Advisory data
One rule needs to know something SQLens cannot read off your database:
SEC.CFG.PATCH_EOL asks whether your server's release series
still receives security fixes, and the answer lives in a vendor's support policy rather than in
pg_catalog.
So the package ships that answer as data, and this page is about where that data comes from, what it can and cannot say, and how you keep it current.
Nothing is fetched unless you ask
SQLens makes no network request during a check. Not to look up a version, not to report a result, not to count a run.
That is not caution for its own sake. A tool you install to audit your database security is a tool with credentials and catalog access, and one that quietly talks to a third party is a supply-chain risk wearing a safety label. There is also nothing to switch off: the package carries no telemetry, so it carries no opt-out either — an opt-out is a confession.
The one exception is a command you type, described below.
How you can check rather than take our word for it. tests/Feature/NoImplicitNetworkTest.php
runs sqlens:lint, sqlens:audit and sqlens:security with a handler in front of the HTTP
client and fails if any of them reaches it even once. A static scan of src and config runs
beside it, naming the only files allowed to contain a network call at all.
The three files that can answer, in order
A run resolves the advisory data through a fixed order and stops at the first candidate:
sqlens.security.advisories.path— a path you configured.- Your application's published copy, at
resources/data/eol.jsonunder your base path. - The copy bundled with the package.
The order matters more than it looks. Two of those files can exist at once, both perfectly valid, and only one of them is being read — so the file that actually answered is named in every finding:
judged from the published end-of-life data at "/app/resources/data/eol.json", compiled 2026-08-03
Without that line, a refresh that landed somewhere nothing reads would look exactly like a refresh that changed nothing.
A configured path that does not answer is a hard stop
If you set sqlens.security.advisories.path and the file is missing or unreadable, the run does
not fall through to the next candidate. It reports an undetermined naming the path.
Falling through would mean reading a different file than the one you deliberately named — and you would see verdicts from data you thought you had replaced, with nothing on screen to say so.
What the bundled data can and cannot tell you
It records, per release series: the release date, the end-of-support date, and whether the series is long-term support.
It records no patch level, and that is deliberate. A patch number shipped inside a package is wrong within a month of any release, and a wrong "latest" produces either invented findings or invented confidence. So the package answers the question it can answer — is this series still supported — and does not answer the one it cannot.
For MySQL the dates are premier support, not extended. Extended support exists, is purchased separately, and is not what most projects are on; recording the later date would let a server that stopped receiving routine fixes read as supported.
Refreshing, when you decide to
php artisan sqlens:security --refresh-advisories
It fetches, validates, writes, and prints what moved. It never runs as part of a check.
There is no default source, on purpose
// config/sqlens.php
'security' => [
'advisories' => [
'source' => env('SQLENS_ADVISORIES_SOURCE'),
],
],
Out of the box that is null, and the refresh says so rather than fetching from somewhere we picked. Shipping an endpoint would mean this package accepting a third party's license, rate limit and attribution on behalf of you — none of which are ours to agree to.
The URL must serve a document in this package's own advisory format. That keeps SQLens from ever parsing somebody else's shape, so it never inherits a change to that shape, and it leaves you free to generate the file from whatever source your own terms allow.
https only. A security baseline fetched over plain HTTP can be answered by anybody on the path
with a file the package then trusts.
Where it writes
The path you configured, or your application's published copy. Never into the installed
package: the next composer install would undo it silently, and you would be certain you had
refreshed data that is old again. Asked to write there, the refresh refuses and names the two
places it will write instead.
Reviewing the diff
The output is a line per release series rather than a line per file line, because this file is meant to be committed:
Wrote /app/resources/data/eol.json:
+ postgresql 19 — new, supported until 2031-11-13
~ postgresql 14 — end of life 2026-11-12 → 2026-12-10
- postgresql 12 — no longer in the data
Read the removals. A series that vanished is either a vendor retiring it or a source that broke, and those are different problems with the same shape.
When it fails, your file is untouched
An unreachable source, an error response, or a document the package cannot read all end the same way: the refresh stops, says which of those happened, and leaves the existing file exactly as it was. A half-written advisory file would replace a working security baseline with one the next run cannot read.
Validation is done by reading the response with the same parser the rules judge from, so a refresh can never accept a document a later run would reject.
Related
SEC.CFG.PATCH_EOL— the rule this data answers for.- Understanding
undetermined— why "the data could not be read" is reported rather than swallowed.