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
114 lines · 3.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 Policy from './policy'
8import { TOOL_REGISTER_REFUSAL } from './tool-register-refusal'
9
10/**
11 * The built-in's hooks, seated outermost: each keeps one control an
12 * organization has today out of reach of the plugins a person installs.
13 *
14 * Three moves: continue past the user tier (`next.to(e, "append")`), refuse
15 * a user-tier caller or module by name, or pass. Provenance is the event's
16 * pinned `provider` or `tier`; policy is `$.settings.read`; fail closed.
17 *
18 * @param on the engine's registrar
19 */
20export function register(on: On) {
21 const readPolicy = Policy.createPolicyMemo(Policy.POLICY_MEMO_MS)
22 const told = new Set<string>()
23
24 on('classic.*', ($, e, next) => next.to(e, 'append'))
25
26 on('prompt.section', ($, e, next) => next.to(e, 'append'))
27 on('prompt.context', ($, e, next) => next.to(e, 'append'))
28 on('skill.prompt', ($, e, next) => next.to(e, 'append'))
29 on('attribution.text', ($, e, next) => next.to(e, 'append'))
30
31 on('settings.read', ($, e, next) => next.to(e, 'append'))
32
33 on('tool.describe', ($, e, next) => pastUsers(e, next))
34 on('command.describe', ($, e, next) => pastUsers(e, next))
35 on('agent.offer', ($, e, next) => pastUsers(e, next))
36 on('agent.spawn', ($, e, next) => pastUsers(e, next))
37
38 on('tool.register', async ($, e, next) => {
39 const isOrgs =
40 next.origin.tier === 'prepend' || next.origin.tier === 'append'
41
42 if (isOrgs) {
43 return next.to(e, 'append')
44 }
45
46 const isRefused =
47 next.origin.tier === 'user' &&
48 (await Policy.decidedByPolicy(
49 readPolicy(() => $.settings.read(Policy.SOURCE)),
50 Policy.hasMcpAllowlist,
51 ))
52
53 return isRefused ? { deny: TOOL_REGISTER_REFUSAL } : next(e)
54 })
55
56 on('tool.list', async ($, e, next) =>
57 Policy.managedToolsRestored(
58 await readPolicy(() => $.settings.read(Policy.SOURCE)).catch(
59 () => undefined,
60 ),
61 await next.to(e, 'append'),
62 await next(e),
63 ),
64 )
65
66 on('tool.check', async ($, e, next) => {
67 const answer = await next(e)
68 const mods = HeldVerdict.loosenedByUsers(next.trace)
69
70 const shouldRecheck =
71 answer.decision !== 'deny' &&
72 mods.length > 0 &&
73 (await Policy.decidedByPolicy(
74 readPolicy(() => $.settings.read(Policy.SOURCE)),
75 Policy.denyRulesHold,
76 ))
77
78 if (!shouldRecheck) {
79 return answer
80 }
81
82 const held = await next.to(e, 'append')
83
84 if (!HeldVerdict.isRuleDeny(held)) {
85 return answer
86 }
87
88 for (const mod of mods.filter(name => !told.has(name))) {
89 told.add(mod)
90 $.ui.log(HeldVerdict.heldNotice(mod, e.tool, held.rule))
91 }
92
93 return held
94 }).catch(async ($, e, next) => {
95 const last = await next(e).catch(() => undefined)
96
97 const shouldVouch = await Policy.decidedByPolicy(
98 readPolicy(() => $.settings.read(Policy.SOURCE)),
99 Policy.denyRulesHold,
100 )
101
102 return shouldVouch ? HeldVerdict.caughtAnswer(last, next.trace) : last
103 })
104
105 on('plugin.register', { tier: 'user' }, async ($, e, next) =>
106 Policy.isManagedModsOnly(await $.settings.read(Policy.SOURCE))
107 ? { refuse: managedModsOnlyRefusal(e.name) }
108 : next(e),
109 ).catch(($, e, next) => {
110 $.ui.log(admissionFailure(e.name, next.error), { to: 'debug' })
111
112 return next.called ? next(e) : { refuse: managedModsOnlyRefusal(e.name) }
113 })
114}