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 / hooks / register.ts
152 lines · 4.5 KBRaw
1import type { On } from 'claude-code'
2
3import { admissionFailure } from './admission-failure'
4import HeldVerdict from './held-verdict'
5import { managedModsOnlyRefusal } from './managed-mods-only-refusal'
6import { pastUsers } from './past-users'
7import { pinnedVariableRefusal } from './pinned-variable-refusal'
8import Policy from './policy'
9import { TOOL_REGISTER_REFUSAL } from './tool-register-refusal'
10
11/**
12 * The built-in's hooks, seated outermost: each keeps one control an
13 * organization has today out of reach of the plugins a person installs.
14 *
15 * Three moves: continue past the user tier (`next.to(e, "append")`), refuse
16 * a user-tier caller or module by name, or pass. Provenance is the event's
17 * pinned `provider` or `tier`; policy is `$.settings.read`; fail closed.
18 *
19 * @param on the engine's registrar
20 */
21export function register(on: On) {
22 const readPolicy = Policy.createPolicyMemo(Policy.POLICY_MEMO_MS)
23 const told = new Set<string>()
24
25 on('classic.*', ($, e, next) => next.to(e, 'append'))
26
27 on('prompt.section', ($, e, next) => next.to(e, 'append'))
28 on('prompt.context', ($, e, next) => next.to(e, 'append'))
29 on('prompt.compose', ($, e, next) => next.to(e, 'append'))
30 on('skill.prompt', ($, e, next) => next.to(e, 'append'))
31 on('attribution.text', ($, e, next) => next.to(e, 'append'))
32
33 on('settings.read', ($, e, next) => next.to(e, 'append'))
34
35 on('tool.describe', ($, e, next) => pastUsers(e, next))
36 on('command.describe', ($, e, next) => pastUsers(e, next))
37 on('agent.offer', ($, e, next) => pastUsers(e, next))
38 on('agent.spawn', ($, e, next) => pastUsers(e, next))
39
40 on('tool.register', async ($, e, next) => {
41 const isOrgs =
42 next.origin.tier === 'prepend' || next.origin.tier === 'append'
43
44 if (isOrgs) {
45 return next.to(e, 'append')
46 }
47
48 const isRefused =
49 next.origin.tier === 'user' &&
50 (await Policy.decidedByPolicy(
51 readPolicy(() => $.settings.read(Policy.SOURCE)),
52 Policy.hasMcpAllowlist,
53 ))
54
55 return isRefused ? { deny: TOOL_REGISTER_REFUSAL } : next(e)
56 })
57
58 on('tool.list', async ($, e, next) =>
59 Policy.managedToolsRestored(
60 await readPolicy(() => $.settings.read(Policy.SOURCE)).catch(
61 () => undefined,
62 ),
63 await next.to(e, 'append'),
64 await next(e),
65 ),
66 )
67
68 on('tool.check', async ($, e, next) => {
69 const answer = await next(e)
70 const mods = HeldVerdict.loosenedByUsers(next.trace)
71
72 const shouldRecheck =
73 answer.decision !== 'deny' &&
74 mods.length > 0 &&
75 (await Policy.decidedByPolicy(
76 readPolicy(() => $.settings.read(Policy.SOURCE)),
77 Policy.denyRulesHold,
78 ))
79
80 if (!shouldRecheck) {
81 return answer
82 }
83
84 const held = await next.to(e, 'append')
85
86 if (!HeldVerdict.holdsOver(held, answer)) {
87 return answer
88 }
89
90 const kind = HeldVerdict.heldKind(held)
91
92 for (const mod of mods.filter(name => !told.has(`${name} ${kind}`))) {
93 told.add(`${mod} ${kind}`)
94 $.ui.log(HeldVerdict.heldNotice(mod, e.tool, held))
95 }
96
97 return held
98 }).catch(async ($, e, next) => {
99 const last = await next(e).catch(() => undefined)
100
101 const shouldVouch = await Policy.decidedByPolicy(
102 readPolicy(() => $.settings.read(Policy.SOURCE)),
103 Policy.denyRulesHold,
104 )
105
106 return shouldVouch ? HeldVerdict.caughtAnswer(last, next.trace) : last
107 })
108
109 on('ui.log', ($, e, next) => {
110 const isOrgs =
111 next.origin.tier === 'prepend' || next.origin.tier === 'append'
112
113 return isOrgs ? next.to(e, 'append') : next(e)
114 })
115
116 on('env.set', async ($, e, next) => {
117 const isPinned = await Policy.decidedByPolicy(
118 readPolicy(() => $.settings.read(Policy.SOURCE)),
119 policy => Policy.isPinnedVariable(policy, e.name),
120 )
121
122 if (!isPinned) {
123 return next(e)
124 }
125
126 const isTheirs = next.origin.tier === 'user'
127
128 return isTheirs
129 ? { deny: pinnedVariableRefusal(e.name) }
130 : next.to(e, 'append')
131 }).catch(($, e, next) => {
132 if (next.called) {
133 return next(e)
134 }
135
136 const isTheirs = next.origin.tier === 'user'
137
138 return isTheirs
139 ? { deny: pinnedVariableRefusal(e.name) }
140 : next.to(e, 'append')
141 })
142
143 on('plugin.register', { tier: 'user' }, async ($, e, next) =>
144 Policy.isManagedModsOnly(await $.settings.read(Policy.SOURCE))
145 ? { refuse: managedModsOnlyRefusal(e.name) }
146 : next(e),
147 ).catch(($, e, next) => {
148 $.ui.log(admissionFailure(e.name, next.error), { to: 'debug' })
149
150 return next.called ? next(e) : { refuse: managedModsOnlyRefusal(e.name) }
151 })
152}