Skip to content

sec-default: a person's plugin may tighten, never loosen, what holds over it - #99137

Open
poteat wants to merge 2 commits into
mainfrom
poteat/sec-default-holds
Open

poteat wants to merge 2 commits into
mainfrom
poteat/sec-default-holds

Conversation

@poteat

@poteat poteat commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

TLDR: Where sec-default is seated, a person's plugin no longer lifts a deny or an ask rule, nor changes a pinned variable.

Needs no new engine: it reads nothing the published one does not carry.

Notes

What Detail
tool.check, any deny the verdict reached past the user tier is returned when it is a deny, whatever decided it. Before: only a deny naming a settings rule. A deny that names nothing can stand in front of a rule's ask: lifted, the call would run with nobody asked
tool.check, an ask rule an ask that names the settings rule behind it holds over an allow. The mode's own ask is still theirs to allow: that is what such a plugin is for
ui.log (new) a line a plugin in prepend or append logs, this one's included, continues past the user tier. A user hook that heard this plugin's own notice ran inside its tool.check hook, and the engine leaves a hook out of whatever is raised from inside it: the notice was a way round the hold, deny rules included
env.set (new) a variable managed settings set in env (its name in any case) is pinned: a user-tier caller is refused by name, setting or unsetting; any other caller continues past the user tier. With no policy to read every variable counts as pinned. Its .catch fails closed
The notice once for each plugin and kind: a deny rule, an ask rule, a refusal
The option allowModsToOverrideDenyRules covers all that holds on tool.check. It does not unpin env
Cost every $.ui.log and $.env.set of any plugin now runs a chain where this plugin is seated; an env.set waits on the policy read
Not reached an ask that names no rule is not held: the mode's own, a settings hook's, a check of the engine's own, an organization's plugin's. A held ask is still an ask: a classic PermissionRequest hook of the person's answers it (allowManagedHooksOnly is the control). A command hook decides only if its command runs: a variable its spawn depends on (PATH, CLAUDE_CODE_SHELL_PREFIX) that managed env does not set is a person's to change. The README says each
Typings none change

Test Plan

  • claude plugin test mods/sec-default on the published 2.1.288: 86 pass (57 before).
  • The same suite on the unpatched hooks, 2.1.288: 28 of 86 fail, among them the ask rule, the unnamed deny, the notice as a way round, the dropped notice, one notice a kind, and each env.set case.
  • 33 planted bugs on 2.1.288: 32 red. The one green is env.set's .catch: only a hook that overruns its budget reaches it, and the kit cannot shorten a budget.
  • tsc -p mods/tsconfig.json: clean. claude plugin validate: passes.

…over it

Where this plugin is seated, a plugin a person installs no longer answers more permissively than any deny on tool.check, an ask a settings rule or a classic hook decided, or a variable managed settings set in env. The lines an organization's plugins log, this one's included, continue past the user tier.
The hold of a classic hook's ask is read off a field the published engine does not carry yet, so it leaves this change and follows by itself. What stays needs no new engine: any deny, an ask a settings rule decided, the organization's own log lines, and the variables managed env sets.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant