Repository navigation
Releases
v0.9.0 — The inspector seam graded, and what is deliberately not a finding
The inspector seam is graded, and what this tool does not flag is now published
inspector
Declared once in the whole harness and implemented nowhere — the string occurs exactly once across
the 224 installed @deepseek-ai packages, no file reads ctx.inspector, and its two type names
appear only in that same bundle.
It is graded security-relevant anyway, and the entry refuses the bypass framing it does not deserve:
providing it takes no decision away from anyone, so it does not belong beside approval. What it
takes is the position observations pass through. A substituted publisher chooses which topics reach
the carrier and what each carries, so it can withhold the record of something that happened or
publish one for something that did not.
That is the property the set already recognised in sessionPersistence and sessionTelemetry —
neither of which removes a constraint either. The set's own header sentence was already false of
those two, and now says what it always meant: replacement either removes a constraint or takes over
the record by which one could be checked afterwards.
This is the one entry graded from the catalogue rather than from running code, and it says so.
What is deliberately not a finding
New section in docs/checks.md. The thirteen core rows added in 0.1.2-rc.1 were each considered
for SECURITY_ROW_IDS, and none qualifies:
ui-approvalcarries the listener that asks the user — but a missing answerer resolves to
'unavailable', which is fail closed ({kind:'deny'}at the tool gate,throwon sandbox
escalation). Disabling it refuses tool calls rather than permitting them.web-fetch-httpis a rowdsh-netguard's own documentation instructs operators to disable.session-log-deepseekandplugin-package-inventory-deepseekdisclose to a third party;
disabling either is a privacy improvement an operator is entitled to make.subagent-model-selection-settingsdefaults toenabled: false, so its absence is its
default, and the enabled state is the constraining one.- The
*-controllerandui-*rows are surfaces: disabling one deletes the wire namespace,
and the protections live inside verbs that then do not exist.
The rule behind the table is now stated rather than implied: a row is in when disabling it fails
open, or removes evidence silently. A row that fails closed on removal is out however
security-adjacent its name reads.
Excluded is not unreported. Disabling any of the thirteen is still an A3 finding — measured, not
asserted: A3 high for the four base rows, medium for the nine web-app rows, nothing at A2's
critical.
v0.8.0 — Catching the plugin that answers on your behalf
Four checks for the plugin that answers on your behalf, and the tables now track 0.1.2-rc.1
The capability the catalogue could not see
approval/request is a waterfall whose innermost next resolves to "unavailable", and the
surface that actually asks the user is a listener in that chain — not the built-in. A plugin
that returns from that listener without calling next() answers every approval itself, and nobody
is asked. The same shape vetoes tools/pre-execute before any guard is consulted, and replaces a
tool body outright at tools/execute.
Nothing in the catalogue detected it. Four new checks, each proven against a loadable hostile
fixture that the previous build passed clean:
B14 waterfall-veto |
a listener that never mentions its trailing next, or declares no parameter there |
B15 seam-internals-write |
assignment or delete on a member of a context seam |
B16 foreign-registration-teardown |
events._hooks, registry.delete, reflect.store |
A26 foreign-row-modified |
a patch row targeting an id this layer neither owns nor inserts |
Two shapes were deliberately designed out, because they fire on honest code: prepending on a
decision seam is how a security plugin gets ahead of the chain, and reading events._hooks is how
one decides whether an approval would reach a human. Zero findings across our four packages, all 224
installed harness packages, and the 40-package pinned corpus.
Breaking: verdicts move, because the tables now describe 0.1.2-rc.1
HARNESS_REFERENCE was 0.1.1-rc.2. sync now reports 147/147 rows, 68/68 seams, 14/14
waterfalls.
- 3 rows dropped (
api-gateway,client-runtime,tool-subagent-report): an override on one of
these was A3/A5/A19 medium and is now A26 high. - 13 rows added: an override on one of these was A26 high and is now A3/A5 — a de-escalation.
- 4 rows moved web-app → base (
storage,storage-json,storage-domain,
session-projection-cache): A3 goes medium → high.
If you gate CI on this tool, expect movement in both directions.
Four of eleven new seams are security-relevant
Decided from each package rather than its name. The strongest is webhookRuntime, which calls
ctx.permissionPresets.set(...) before admitting the prompt — it picks the sandbox and approval
posture for an agent started by a remote delivery with no user present. Also credentialsController,
settingsController, sessionController.
sessionSkillCatalog is excluded: its consumers are the session controller and the client
picker, while model-visible skill text travels the skills seam instead.
user-questions/request joins DECISION_EVENTS. Its inner callback is noAnswerer, rejecting
NO_PROVIDER — structurally identical to approval/request, so answering on the user's behalf
there grades critical rather than high.
v0.7.0 — provenance, and the bypasses that reported clean
It certified packages that had bypassed every capability check
Two fixtures with identical capabilities, written two ways:
ordinary style B1(critical) B8(high) B6 B7 B13
quiet style NONE
integrity: complete · negativesReliable: true · degradedBy: []
const {provide} = ctx, process.getBuiltinModule('node:fs') and a joined string defeated every
check — and the tool then asserted its negatives were reliable. A false clean bill of health is worse
than a missed finding, because the reader acts on it.
The published ceiling doc claimed a Tier B bypass costs the reader a Tier C degrade. For plain
JavaScript it cost nothing.
The real defect was narrower than the three shapes. C2 was built entirely around a hidden
name — computed members, assembled arguments, base64. But every Tier B check matches a member on
a receiver, so there were two ways to miss and only one was covered: detach the member and the name
sits in plain text while the thing the checks key on is gone.
Now: getBuiltinModule is a third way a module arrives, beside import and require. A member of a
known seam bound off a known receiver raises C2. A literal, a + chain, a template of literals and
[…].join(…) fold to a bound of 8; anything else is refused and degrades. The quiet fixture returns
degraded · negativesReliable: false · degradedBy: ['C2'].
The ceiling claim is now split rather than softened: a name that selects a target either folds or
raises C2, so there is no way to hide which service or module is reached while keeping negatives
reliable. But the heuristics that read content — path lists, injection phrasing — are missed with
no C2, because nothing was unreadable. A clean B6 or B10 is therefore not a claim, whatever
integrity says, and that is now written in those words.
Provenance
--from-npm now reports npm's build attestation: the source repository, commit, ref, workflow and
signer. Four checks run offline, with no dependency and no second host — the statement's subject
digest is the SHA-512 of the tarball this run downloaded and integrity-verified; the subject is
this package at this version; the DSSE signature verifies under the certificate in the bundle; and
the certificate's identity is the workflow the statement claims.
What is not checked is printed in the same block: that the certificate is Sigstore's, and the
Rekor inclusion proof. Both need a trust root this tool does not carry, so a registry serving a
doctored bundle passes every check above. The test fixture signs with a self-signed certificate and
passes all four — the gap demonstrated, not described.
Absence raises nothing: 12 of the 40 corpus packages publish an attestation and 28 do not, so a
finding on absence would fire on 70% of legitimate packages. Only a mismatch raises A25.
Provenance does not prove the repository is trustworthy, that the commit was reviewed, or that the
code does nothing harmful — stated in the catalogue, not implied away.
Knowledge tables re-verified against harness 0.1.1-rc.2
CORE_ROWS 131 → 137 and SEAM_KEYS 55 → 58, diffed mechanically in both directions; nothing was
removed and no module or bundle membership changed. authorization joined the security-seam set.
One bug found that was not drift: SANDBOX_DENIED_GLOBALS had listed five traps since 0.1 while
the sandbox has always trapped seven — clearTimeout and clearInterval were missed in the
original transcription, and the docs had been naming all seven the whole time.
A weekly job now diffs those tables against a published harness release, so the next drift is found
by a machine rather than by an audit.
Upgrading
A package that reported clean may now report degraded, and the exit code changes with it. That
is the point: the previous silence was the defect. Calibration is unmoved — 295 findings, 21 of 40
with a high or critical, byte-identical to the recorded baseline.