feat(config): name the declarations that will never do anything - #185
Merged
RatulMaharaj merged 1 commit intoAug 1, 2026
Merged
Conversation
RatulMaharaj
force-pushed
the
feat/174b-inert-declarations
branch
from
August 1, 2026 18:34
70e690c to
1e545d3
Compare
A config that loads, validates, and quietly does nothing is the worst state a security control can be in: no error, no warning, no behaviour, and an author who believes they configured something. Three projects in the research hit this from different angles, and the Claude Agent SDK ships a warning for exactly it. Adds inertDeclarations(config), reported at startup and by the CLI alongside the existing run-grant advisories. Four checks: - an http.auth credential whose host is not in permissions.net, so the request is denied before the credential is ever reached - a permissions.net entry carrying a scheme, path, port or userinfo, which is compared against a bare hostname and matches nothing. "https://api.github.com" looks right and denies everything - memory.compact_at_tokens without memory.scope: thread, which the schema's own docstring already calls inert without detecting it - limits.max_cost with no known price for the model, moved here from the ad-hoc warning added with the cost cap so there is one mechanism This is a different failure from runGrantAdvisories, which is about authority that is wider than it looks. These are statements the runtime will never act on. Nothing here refuses a config. An inert declaration is harmless in itself; it is the belief about it that costs something, and every line names both the location and the fix. Refs #174 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPftW3F8JTuivRhFmoY1wC
RatulMaharaj
force-pushed
the
feat/174b-inert-declarations
branch
from
August 1, 2026 18:44
1e545d3 to
cd8ea5d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #184. Fourth in the series; base moves down as each merges.
Refs #174, which #184 closes. This is the other half of that issue's lesson and small enough to review separately.
Why
The most transferable finding in the whole research, from three projects hitting it three ways:
CanUseToolShadowedWarningbecause a permission callback can be configured, correct, and never consulted.print.A control that fails by being quietly ignored is worse than one that fails loudly, because nothing surfaces and the operator's mental model stays wrong. Every declaration either takes effect or says out loud that it did not.
What it checks
inertDeclarations(config), reported at startup and by the CLI next to the existing run-grant advisories.A credential for a host the agent cannot reach.
http.authwithurl: https://api.stripe.comandapi.stripe.comabsent frompermissions.net. The request is denied before the credential is reached, so the credential does nothing. The schema documents the requirement; nothing checked it.A
permissions.netentry that is not a bare host. Entries are compared against a hostname, sohttps://api.github.commatches nothing and denies everything. Same for a path, a port, or userinfo. The advice names the host to use instead. This one is a plain footgun: it reads as more precise thanapi.github.comand is strictly worse.memory.compact_at_tokenswithoutmemory.scope: thread. The schema's own docstring says "without scope: thread there is no history, so the threshold is inert" - documented and undetected.compact_at_tokens: falseis an author declining the feature, not a mistake, so it is not flagged.limits.max_costwith no known price. Moved here from the ad-hoc warning I added in #183, so there is one mechanism rather than two.Not a refusal
None of these fail a config, which is a deliberate difference from the floor in #184. A floor violation is an operator's policy being breached; an inert declaration is harmless in itself, and it is the author's belief about it that costs something. Printing it fixes that.
Every line names the location (
http.auth[0].url) and the fix, and there is a test asserting the advice text actually contains one.Testing
491 pass,
deno task okclean. Eight new: a clean config producing nothing; the unreachable credential; wildcardnetcovering a credential host, and the apex case not covering it; each malformednetshape, with bare hosts,*and a bracketed IPv6 literal all accepted; the advice naming the right replacement host; compaction with and without thread scope and the explicitfalse; the spend cap fixed by eithermodel.pricingormax_cost: 0; and a check that every declaration carries a location and an actionable fix.What I left out
Candidates I considered and rejected, in case you want them later:
env:entries never referenced elsewhere in the file. Too many false positives: subprocesses and MCP servers read the environment without the file mentioning the name.permissions.read/writepaths that do not exist on disk. A path can be legitimately created later, and the check would fire in CI for every agent.skills:entries that nopurposetext plausibly triggers. Not decidable without the model.