spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / sec-default / README.md
108 lines · 7.0 KBPreviewCodeRaw
1# sec-default
2
3The security default for organizations. Function hooks give every plugin a
4say on every event, in chain order, and the plugins a person installs sit
5in the user tier, beneath the organization's prepend tier and above its
6append tier. Some of what an organization sets today (its classic hooks,
7its managed CLAUDE.md and rules, its settings, its MCP allowlist) was never
8within a person's reach before function hooks; seated outermost, this
9plugin keeps exactly those out of the user tier's reach and adds no policy
10of its own. Everything else passes through untouched.
11
12It has three moves and nothing else: continue past the user tier
13(`next.to(e, "append")`), refuse a user-tier caller or module by name
14(`{ deny }` when `next.origin.tier` is `user`, `{ refuse }` when a module's
15pinned `e.tier` is), or pass (`next(e)`). A subject's provenance is the
16event's pinned `e.provider`; policy is read through
17`$.settings.read({ source: "policy" })`, one read serving a burst of tool
18calls; both fail closed, so an unreadable policy counts as a policy in force.
19
20`hooks/register.ts` is the module; `hooks/policy/` reads the managed
21settings it decides by.
22
23## The rows
24
25| event | from the outermost seat |
26| --- | --- |
27| `classic.*` | Continue past the user tier: the organization's settings hooks see the engine's input and their answer stands. |
28| `prompt.section`, `prompt.context`, `skill.prompt`, `attribution.text` | Continue past the user tier: managed CLAUDE.md, rules and policy skills reach the model as written. A person's plugins keep `prompt.submit` and its additive context. |
29| `prompt.compose` | Continue past the user tier: the system prompt's list of sections is what the organization's tiers, the built-ins and the engine's own composition make it. A person's plugin neither drops, reorders nor rewrites a section, nor changes the facts the list is composed from, nor answers a list of its own in its place. The engine raises this event only when some loaded plugin hooks it, so where this plugin is seated every render of the system prompt runs the chain. |
30| `settings.read` | Continue past the user tier: no user hook rewrites what any caller reads as settings, this plugin's own policy reads included. |
31| `tool.describe`, `command.describe`, `agent.offer`, `agent.spawn` | When the subject's pinned `e.provider.tier` is `prepend` or `append` (a policy-installed plugin, the managed folder, a policy MCP server), continue past the user tier; a subject provided by `user`, `builtin` or `core` passes. |
32| `tool.register` | A caller in `prepend` or `append` continues past the user tier. A `user`-tier caller is refused by name while managed settings hold `allowedMcpServers` (set at all, empty included); otherwise it passes. |
33| `tool.list` | The tools of the organization's managed MCP servers are listed as the organization's tiers listed them; every other tool as the user tier left it. With no policy to read, or a refusal from either listing, the organization's listing stands whole. |
34| `plugin.register` | A hooks module in the `user` tier (one a person installed, named with `--plugin-dir`, or keeps in their mods folder) is refused while managed settings set this plugin's `allowManagedModsOnly` option; otherwise it passes. Modules in `prepend`, `append` and `builtin` are never asked about. |
35| everything else | Passes: `prompt.submit`, `turn.*`, `tool.call`, `tool.check`, `command.run`, `command.register`, `session.*`, `ui.*`, `fs.*`, `http.fetch`, `process.run`, `store.*`, `clock.*`, `model.*`, `mcp.call`, `audio.*`, `agent.list`, `engine.create`. |
36
37## Options an administrator sets
38
39One, in managed settings, under this plugin's own `pluginConfigs` entry,
40keyed by the id the CLI builds the plugin in under (only this spelling of the
41id is read):
42
43```json
44{
45 "pluginConfigs": {
46 "cc-plugin-sec-default@builtin": {
47 "options": { "allowManagedModsOnly": true }
48 }
49 }
50}
51```
52
53`allowManagedModsOnly`: only the mods the organization deploys through
54managed settings, and the ones built into Claude Code, load. A hooks module
55a person installed, named with `--plugin-dir` or keeps in their mods folder
56is refused whenever it loads or reloads (one already running when the option
57is set keeps running until then), with one line that names it: `mods are
58limited to your organization's by policy (allowManagedModsOnly); <plugin>
59was not loaded`
60(in the debug log, and on screen where the session hot-reloads the mod's
61folder). A plain `-p` run has it in the debug log alone; the mod is still
62not loaded. Settings hooks, status lines and `/goal` are not touched by it.
63
64- The decision reads the tier the CLI pins on the module and nothing the
65 module says of itself: a person's copy carrying an organization mod's name
66 is still in the `user` tier and is refused.
67- Refused means nothing of the module joins: no hook, no tool, no command.
68 Its top-level code has run once by then, in the closed context every hooks
69 module is evaluated in, with no call on `$` served.
70- Only managed settings are read (`$.settings.read({ source: "policy" })`):
71 the same entry in a person's, a project's or a `--settings` file neither
72 turns it on nor off. On unless absent or `false`, so a mistyped `"true"` or
73 `1` still locks. A value the settings schema rejects (`null`, an object),
74 here or in any other `pluginConfigs` entry, makes the CLI ignore the whole
75 `pluginConfigs` key with a settings warning, and the option reads as unset.
76- It fails closed: when the read of managed settings is refused (a hook beneath
77 denies it) or this plugin's hook fails, its `.catch` refuses the module,
78 with the same line, and names the failure in the debug log; so a policy
79 that cannot be read keeps every person's mod out at load. Where this plugin
80 is not seated there is no such rule, and mods load as they do without it.
81- Where managed settings define `prependPlugins`, that list must name this
82 plugin (next section) for the option to apply.
83- `claude plugin test` is not covered: it runs a mod's tests in an engine of
84 their own and loads nothing into a session.
85
86## What it hooks
87
88`classic.*`, `prompt.section`, `prompt.context`, `prompt.compose`, `skill.prompt`,
89`attribution.text`, `settings.read`, `tool.describe`, `command.describe`,
90`agent.offer`, `agent.spawn`, `tool.register`, `tool.list`,
91`plugin.register`.
92
93## What it calls on `$`
94
95`settings.read`, and `ui.log` to the debug log. It continues to the `append`
96tier with `next.to`, which only a plugin in a managed tier may do.
97
98## Where it is seated
99
100The CLI seats it first in the prepend tier wherever hooks modules load on a
101machine with managed settings or for a Team or Enterprise organization,
102unless managed settings define `prependPlugins`: then that list is the
103whole prepend tier, and the organization names `sec-default@builtin` in it
104at the position it wants, e.g. `"prependPlugins": ["acme-guard@acme-tools",
105"sec-default@builtin"]`, or leaves it out. It is a plugin folder like any
106other, but its one move that matters, `next.to`, is refused outside a
107managed tier, so loading it with `--plugin-dir` seats a plugin that can
108only pass.