@lannguyensi/harness 0.54.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/dist/cli/adopt/derive.js +1 -0
- package/dist/cli/adopt/derive.js.map +1 -1
- package/dist/cli/doctor/format.js +8 -0
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.js +16 -0
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/types.d.ts +16 -0
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/doctor/ug-inflight.d.ts +35 -0
- package/dist/cli/doctor/ug-inflight.js +57 -0
- package/dist/cli/doctor/ug-inflight.js.map +1 -0
- package/dist/cli/gc/index.d.ts +2 -1
- package/dist/cli/gc/index.js +196 -9
- package/dist/cli/gc/index.js.map +1 -1
- package/dist/cli/index.js +42 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pack/auto-approve-path.d.ts +11 -0
- package/dist/cli/pack/auto-approve-path.js +4 -2
- package/dist/cli/pack/auto-approve-path.js.map +1 -1
- package/dist/cli/pack/hook-bootstrap.d.ts +90 -0
- package/dist/cli/pack/hook-bootstrap.js +162 -0
- package/dist/cli/pack/hook-bootstrap.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +164 -10
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-subagent-start.d.ts +21 -0
- package/dist/cli/pack/hook-subagent-start.js +71 -0
- package/dist/cli/pack/hook-subagent-start.js.map +1 -0
- package/dist/cli/pack/hook-subagent-stop.d.ts +19 -0
- package/dist/cli/pack/hook-subagent-stop.js +41 -0
- package/dist/cli/pack/hook-subagent-stop.js.map +1 -0
- package/dist/io/read-regular-file.d.ts +33 -2
- package/dist/io/read-regular-file.js +41 -8
- package/dist/io/read-regular-file.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +34 -4
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +34 -3
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +2 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +9 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.d.ts +135 -0
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js +619 -0
- package/dist/policy-packs/builtin/understanding-before-execution/inflight-records.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js +25 -11
- package/dist/policy-packs/builtin/understanding-before-execution/lifecycle.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +4 -4
- package/dist/policy-packs/builtin/understanding-before-execution.js +42 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/runtime/read-only-bash.d.ts +33 -0
- package/dist/runtime/read-only-bash.js +291 -7
- package/dist/runtime/read-only-bash.js.map +1 -1
- package/dist/runtime/risk-classifier.js +16 -12
- package/dist/runtime/risk-classifier.js.map +1 -1
- package/dist/schema/hooks.d.ts +13 -13
- package/dist/schema/hooks.js +1 -0
- package/dist/schema/hooks.js.map +1 -1
- package/dist/schema/index.d.ts +24 -24
- package/dist/schema/policies.d.ts +19 -19
- package/package.json +1 -1
|
@@ -82,3 +82,93 @@ export declare function resolveToolInput(event: {
|
|
|
82
82
|
* hook then falls back to its legacy message shape.
|
|
83
83
|
*/
|
|
84
84
|
export declare function parseConfigUx(raw: unknown, stderr: NodeJS.WritableStream, hookLabel: string): PolicyUx | undefined;
|
|
85
|
+
export interface ResolveHookPackContextOptions extends LoaderOptions {
|
|
86
|
+
pack?: string;
|
|
87
|
+
generatedDir?: string;
|
|
88
|
+
manifest?: Manifest;
|
|
89
|
+
}
|
|
90
|
+
export interface ResolvedHookPackContext {
|
|
91
|
+
manifest: Manifest;
|
|
92
|
+
declared: Manifest["policy_packs"][number];
|
|
93
|
+
generatedDir: string;
|
|
94
|
+
}
|
|
95
|
+
export type ResolveHookPackContextResult = {
|
|
96
|
+
ok: true;
|
|
97
|
+
context: ResolvedHookPackContext;
|
|
98
|
+
} | {
|
|
99
|
+
ok: false;
|
|
100
|
+
diagnostic: string;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Load the manifest (or use injection), confirm `packName` is declared and
|
|
104
|
+
* enabled, and resolve `generatedDir` — the fixed sequence that follows
|
|
105
|
+
* `loadManifestOrInjected` in nearly every pack hook. Returns a single
|
|
106
|
+
* failure shape with a ready-to-emit `${hookLabel}: ...` diagnostic instead
|
|
107
|
+
* of three separate early-return blocks, so a caller wires it as:
|
|
108
|
+
*
|
|
109
|
+
* const ctx = resolveHookPackContext(hookLabel, packName, opts);
|
|
110
|
+
* if (!ctx.ok) return noop(ctx.diagnostic, stderr, ...);
|
|
111
|
+
*/
|
|
112
|
+
export declare function resolveHookPackContext(hookLabel: string, packName: string, opts: ResolveHookPackContextOptions): ResolveHookPackContextResult;
|
|
113
|
+
export interface ResolvedSessionAndAgentIds {
|
|
114
|
+
sessionId: string;
|
|
115
|
+
agentId: string;
|
|
116
|
+
}
|
|
117
|
+
export type ResolveSessionAndAgentIdsResult = {
|
|
118
|
+
ok: true;
|
|
119
|
+
ids: ResolvedSessionAndAgentIds;
|
|
120
|
+
} | {
|
|
121
|
+
ok: false;
|
|
122
|
+
diagnostic: string;
|
|
123
|
+
sessionId: string | null;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Parse and validate `session_id` + `agent_id` off an event body (the
|
|
127
|
+
* subagent-start/subagent-stop shared shape). `validateAgentId` is
|
|
128
|
+
* injected rather than imported here — the agent-id allowlist lives with
|
|
129
|
+
* the understanding-before-execution pack's in-flight records
|
|
130
|
+
* (`rejectMalformedAgentId`), and this module stays pack-agnostic on
|
|
131
|
+
* purpose (see the module header).
|
|
132
|
+
*/
|
|
133
|
+
export declare function resolveSessionAndAgentIds(hookLabel: string, event: {
|
|
134
|
+
session_id?: unknown;
|
|
135
|
+
agent_id?: unknown;
|
|
136
|
+
}, validateAgentId: (agentId: string) => void): ResolveSessionAndAgentIdsResult;
|
|
137
|
+
export interface ResolveSubagentHookContextOptions extends ResolveHookPackContextOptions {
|
|
138
|
+
stderr: NodeJS.WritableStream;
|
|
139
|
+
}
|
|
140
|
+
export interface ResolvedSubagentHookContext {
|
|
141
|
+
sessionId: string;
|
|
142
|
+
agentId: string;
|
|
143
|
+
declared: Manifest["policy_packs"][number];
|
|
144
|
+
generatedDir: string;
|
|
145
|
+
}
|
|
146
|
+
export type ResolveSubagentHookContextResult = {
|
|
147
|
+
ok: true;
|
|
148
|
+
context: ResolvedSubagentHookContext;
|
|
149
|
+
} | {
|
|
150
|
+
ok: false;
|
|
151
|
+
diagnostic: string;
|
|
152
|
+
sessionId: string | null;
|
|
153
|
+
agentId: string | null;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* The three-step preamble subagent-start and subagent-stop both ran
|
|
157
|
+
* verbatim before the point where their bodies diverge (write vs. clear):
|
|
158
|
+
* pause-sentinel check, session_id/agent_id resolution, then pack-context
|
|
159
|
+
* resolution. Extracted to close the residual clone between the two hook
|
|
160
|
+
* files (review finding, subagent-gate). `verb` names the hook for both
|
|
161
|
+
* the pause check's own stderr label and the caller-facing "paused,
|
|
162
|
+
* skipping" diagnostic (e.g. "subagent-start"); `hookLabel` is the fuller
|
|
163
|
+
* `harness pack hook: <verb>` prefix used in every other diagnostic.
|
|
164
|
+
*
|
|
165
|
+
* Each ok:false case reports a `sessionId`/`agentId` pair matching exactly
|
|
166
|
+
* what the pre-extraction call sites passed to their own `noop()`: both
|
|
167
|
+
* null on a pause, `sessionId` non-null (once past validation) on a
|
|
168
|
+
* missing/malformed agent_id, and both non-null once ids resolved but the
|
|
169
|
+
* pack context failed.
|
|
170
|
+
*/
|
|
171
|
+
export declare function resolveSubagentHookContext(hookLabel: string, verb: string, packName: string, event: {
|
|
172
|
+
session_id?: unknown;
|
|
173
|
+
agent_id?: unknown;
|
|
174
|
+
}, validateAgentId: (agentId: string) => void, opts: ResolveSubagentHookContextOptions): ResolveSubagentHookContextResult;
|
|
@@ -20,6 +20,24 @@
|
|
|
20
20
|
// Codex shim that sends `raw_input` instead — the exact shape
|
|
21
21
|
// `hook-codex-post-tool-use.ts`'s own private `resolveToolInput`
|
|
22
22
|
// already handles).
|
|
23
|
+
// 7. `resolveHookPackContext` — the declared-pack-lookup / enabled-check /
|
|
24
|
+
// generatedDir-resolution trio that follows manifest load in nearly
|
|
25
|
+
// every hook. Its shape is closely mirrored by hook-post-tool-use.ts,
|
|
26
|
+
// hook-track-active-claim.ts, hook-pre-tool-use.ts and the Codex
|
|
27
|
+
// siblings, which were deliberately left calling their own inline
|
|
28
|
+
// copies rather than switched over here (out of scope for the task
|
|
29
|
+
// that added this helper); it was extracted for the
|
|
30
|
+
// subagent-start/subagent-stop pair, which would otherwise have
|
|
31
|
+
// re-duplicated it a further two times.
|
|
32
|
+
// 8. `resolveSessionAndAgentIds` — session_id + agent_id parse and
|
|
33
|
+
// validation shared by subagent-start/subagent-stop. Pack-agnostic
|
|
34
|
+
// by construction: the agent-id validator is injected by the caller
|
|
35
|
+
// rather than imported here, so this module stays free of any
|
|
36
|
+
// understanding-before-execution-specific dependency.
|
|
37
|
+
// 9. `resolveSubagentHookContext` — the pause-check / id-resolution /
|
|
38
|
+
// pack-context preamble shared verbatim by subagent-start and
|
|
39
|
+
// subagent-stop, up to the point where their bodies diverge (write
|
|
40
|
+
// vs. clear). Composes 2, 7, and 8 above.
|
|
23
41
|
//
|
|
24
42
|
// Not used by:
|
|
25
43
|
// - hook-runtime-reality.ts: its stdin reader uses async iteration + an
|
|
@@ -37,6 +55,8 @@
|
|
|
37
55
|
// to each hook. This module covers structural boilerplate only, not semantics.
|
|
38
56
|
import { checkPauseFromLoader } from "../pause-check.js";
|
|
39
57
|
import { loadManifest } from "../loader.js";
|
|
58
|
+
import { resolveGeneratedDir } from "../../runtime/pending-approval.js";
|
|
59
|
+
import { rejectMalformedSessionId } from "../../runtime/reject-malformed-session-id.js";
|
|
40
60
|
import { PolicyUxSchema } from "../../schema/index.js";
|
|
41
61
|
// ---------------------------------------------------------------------------
|
|
42
62
|
// 1. Standard stdin reader
|
|
@@ -164,4 +184,146 @@ export function parseConfigUx(raw, stderr, hookLabel) {
|
|
|
164
184
|
}
|
|
165
185
|
return result.data;
|
|
166
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Load the manifest (or use injection), confirm `packName` is declared and
|
|
189
|
+
* enabled, and resolve `generatedDir` — the fixed sequence that follows
|
|
190
|
+
* `loadManifestOrInjected` in nearly every pack hook. Returns a single
|
|
191
|
+
* failure shape with a ready-to-emit `${hookLabel}: ...` diagnostic instead
|
|
192
|
+
* of three separate early-return blocks, so a caller wires it as:
|
|
193
|
+
*
|
|
194
|
+
* const ctx = resolveHookPackContext(hookLabel, packName, opts);
|
|
195
|
+
* if (!ctx.ok) return noop(ctx.diagnostic, stderr, ...);
|
|
196
|
+
*/
|
|
197
|
+
export function resolveHookPackContext(hookLabel, packName, opts) {
|
|
198
|
+
let manifest;
|
|
199
|
+
let manifestPath;
|
|
200
|
+
try {
|
|
201
|
+
({ manifest, manifestPath } = loadManifestOrInjected(opts, opts.manifest));
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
diagnostic: `${hookLabel}: manifest load failed (${err.message}), skipping`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const declared = manifest.policy_packs.find((p) => p.name === packName);
|
|
210
|
+
if (!declared) {
|
|
211
|
+
return {
|
|
212
|
+
ok: false,
|
|
213
|
+
diagnostic: `${hookLabel}: pack "${packName}" not declared in manifest, skipping`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (!declared.enabled) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
diagnostic: `${hookLabel}: pack "${packName}" is enabled:false, skipping`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const generatedDir = opts.generatedDir ??
|
|
223
|
+
(manifestPath !== undefined
|
|
224
|
+
? resolveGeneratedDir({
|
|
225
|
+
...(opts.homeDir !== undefined ? { homeDir: opts.homeDir } : {}),
|
|
226
|
+
manifestPath,
|
|
227
|
+
})
|
|
228
|
+
: undefined);
|
|
229
|
+
if (generatedDir === undefined) {
|
|
230
|
+
return {
|
|
231
|
+
ok: false,
|
|
232
|
+
diagnostic: `${hookLabel}: generatedDir unresolvable, skipping`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return { ok: true, context: { manifest, declared, generatedDir } };
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Parse and validate `session_id` + `agent_id` off an event body (the
|
|
239
|
+
* subagent-start/subagent-stop shared shape). `validateAgentId` is
|
|
240
|
+
* injected rather than imported here — the agent-id allowlist lives with
|
|
241
|
+
* the understanding-before-execution pack's in-flight records
|
|
242
|
+
* (`rejectMalformedAgentId`), and this module stays pack-agnostic on
|
|
243
|
+
* purpose (see the module header).
|
|
244
|
+
*/
|
|
245
|
+
export function resolveSessionAndAgentIds(hookLabel, event, validateAgentId) {
|
|
246
|
+
const sessionId = pickString(event.session_id) ?? "";
|
|
247
|
+
const agentId = pickString(event.agent_id) ?? "";
|
|
248
|
+
if (sessionId === "") {
|
|
249
|
+
return { ok: false, diagnostic: `${hookLabel}: missing session_id, skipping`, sessionId: null };
|
|
250
|
+
}
|
|
251
|
+
// Validate sessionId right after the emptiness check, before any other
|
|
252
|
+
// early return, so every ok:false path below carries an already-rejected
|
|
253
|
+
// (never a raw, unvalidated) sessionId — a caller that echoes `sessionId`
|
|
254
|
+
// into a diagnostic or a path.join can never see an unvalidated value.
|
|
255
|
+
try {
|
|
256
|
+
rejectMalformedSessionId(sessionId);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
diagnostic: `${hookLabel}: malformed session_id (${err.message}), skipping`,
|
|
262
|
+
sessionId: null,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
if (agentId === "") {
|
|
266
|
+
return { ok: false, diagnostic: `${hookLabel}: missing agent_id, skipping`, sessionId };
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
validateAgentId(agentId);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
diagnostic: `${hookLabel}: malformed agent_id (${err.message}), skipping`,
|
|
275
|
+
sessionId,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return { ok: true, ids: { sessionId, agentId } };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The three-step preamble subagent-start and subagent-stop both ran
|
|
282
|
+
* verbatim before the point where their bodies diverge (write vs. clear):
|
|
283
|
+
* pause-sentinel check, session_id/agent_id resolution, then pack-context
|
|
284
|
+
* resolution. Extracted to close the residual clone between the two hook
|
|
285
|
+
* files (review finding, subagent-gate). `verb` names the hook for both
|
|
286
|
+
* the pause check's own stderr label and the caller-facing "paused,
|
|
287
|
+
* skipping" diagnostic (e.g. "subagent-start"); `hookLabel` is the fuller
|
|
288
|
+
* `harness pack hook: <verb>` prefix used in every other diagnostic.
|
|
289
|
+
*
|
|
290
|
+
* Each ok:false case reports a `sessionId`/`agentId` pair matching exactly
|
|
291
|
+
* what the pre-extraction call sites passed to their own `noop()`: both
|
|
292
|
+
* null on a pause, `sessionId` non-null (once past validation) on a
|
|
293
|
+
* missing/malformed agent_id, and both non-null once ids resolved but the
|
|
294
|
+
* pack context failed.
|
|
295
|
+
*/
|
|
296
|
+
export function resolveSubagentHookContext(hookLabel, verb, packName, event, validateAgentId, opts) {
|
|
297
|
+
if (checkHookPause(verb, opts.stderr, opts, opts.generatedDir).paused) {
|
|
298
|
+
return {
|
|
299
|
+
ok: false,
|
|
300
|
+
diagnostic: `harness paused; ${verb} skipping without evaluating.`,
|
|
301
|
+
sessionId: null,
|
|
302
|
+
agentId: null,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const idsResult = resolveSessionAndAgentIds(hookLabel, event, validateAgentId);
|
|
306
|
+
if (!idsResult.ok) {
|
|
307
|
+
return {
|
|
308
|
+
ok: false,
|
|
309
|
+
diagnostic: idsResult.diagnostic,
|
|
310
|
+
sessionId: idsResult.sessionId,
|
|
311
|
+
agentId: null,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const { sessionId, agentId } = idsResult.ids;
|
|
315
|
+
const ctx = resolveHookPackContext(hookLabel, packName, opts);
|
|
316
|
+
if (!ctx.ok) {
|
|
317
|
+
return { ok: false, diagnostic: ctx.diagnostic, sessionId, agentId };
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
ok: true,
|
|
321
|
+
context: {
|
|
322
|
+
sessionId,
|
|
323
|
+
agentId,
|
|
324
|
+
declared: ctx.context.declared,
|
|
325
|
+
generatedDir: ctx.context.generatedDir,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
167
329
|
//# sourceMappingURL=hook-bootstrap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-bootstrap.js","sourceRoot":"","sources":["../../../src/cli/pack/hook-bootstrap.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,gEAAgE;AAChE,+BAA+B;AAC/B,EAAE;AACF,8DAA8D;AAC9D,6EAA6E;AAC7E,6DAA6D;AAC7D,2EAA2E;AAC3E,0EAA0E;AAC1E,iDAAiD;AACjD,oEAAoE;AACpE,8CAA8C;AAC9C,4EAA4E;AAC5E,wEAAwE;AACxE,sEAAsE;AACtE,wEAAwE;AACxE,6DAA6D;AAC7D,qEAAqE;AACrE,mEAAmE;AACnE,sEAAsE;AACtE,yBAAyB;AACzB,EAAE;AACF,eAAe;AACf,0EAA0E;AAC1E,+DAA+D;AAC/D,iEAAiE;AACjE,gDAAgD;AAChD,EAAE;AACF,wEAAwE;AACxE,4EAA4E;AAC5E,yEAAyE;AACzE,2EAA2E;AAC3E,wDAAwD;AACxD,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAE/E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAsB,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,cAAc,EAAgC,MAAM,uBAAuB,CAAC;AAErF,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAA6B;IAC3D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC3B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAClC,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,iCAAiC;AACjC,8EAA8E;AAE9E;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,MAA6B,EAC7B,UAA0B,EAC1B,YAAqB,EACrB,GAAU;IAEV,OAAO,oBAAoB,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;AACpF,CAAC;AAeD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAAyB,EACzB,QAA8B;IAE9B,4EAA4E;IAC5E,8EAA8E;IAC9E,sDAAsD;IACtD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;IACzD,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC3E,CAAC;AAED,8EAA8E;AAC9E,gDAAgD;AAChD,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,GAAG,UAAqB;IACjD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAGhC;IACC,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC,UAAU,CAAC;IAC5D,OAAO,KAAK,CAAC,SAAS,CAAC;AACzB,CAAC;AAED,8EAA8E;AAC9E,6BAA6B;AAC7B,8EAA8E;AAE9E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAC3B,GAAY,EACZ,MAA6B,EAC7B,SAAiB;IAEjB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACxC,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,KAAK,CACV,GAAG,SAAS,wBAAwB,MAAM,CAAC,KAAK,CAAC,MAAM;aACpD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,KAAK,CACnB,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
1
|
+
{"version":3,"file":"hook-bootstrap.js","sourceRoot":"","sources":["../../../src/cli/pack/hook-bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -56,7 +56,7 @@ import * as path from "node:path";
|
|
|
56
56
|
import { readRegularFileRejectingSymlink } from "../../io/read-regular-file.js";
|
|
57
57
|
import { queryLedgerByTag, } from "../../policies/index.js";
|
|
58
58
|
import { renderProducers } from "../../policies/producers.js";
|
|
59
|
-
import { ADOPTION_LEDGER_DIRNAME, CLAUDE_CODE_HARNESS, checkOperatorApprovalMarkers, checkPersistedReport, defaultReportsDir, delegationMarkerPathFor, delegationReportPathFor, harnessAllowed, listPersistedReports, matchLedgerEntries, parseAutoApprove, readActiveClaim, recordPermissionModeObservation, selectNewestStrictSessionReport, verifyDelegation, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
|
|
59
|
+
import { ADOPTION_LEDGER_DIRNAME, CLAUDE_CODE_HARNESS, checkOperatorApprovalMarkers, checkPersistedReport, defaultReportsDir, delegationMarkerPathFor, delegationReportPathFor, harnessAllowed, listPersistedReports, matchLedgerEntries, parseAutoApprove, readActiveClaim, recordPermissionModeObservation, sanitizeForDisplay, selectNewestStrictSessionReport, verifyDelegation, verifyInflightRecord, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
|
|
60
60
|
import { resolveMode, toPackageMode, } from "../../policy-packs/builtin/understanding-before-execution.js";
|
|
61
61
|
import { sha256Hex, signingKeyExists } from "../../runtime/approval-signing.js";
|
|
62
62
|
import { findLatestParseError, renderMalformedSectionsNotice } from "../approve/understanding.js";
|
|
@@ -523,6 +523,12 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
523
523
|
// distinct phrase from "no approval marker" so an operator/auditor can
|
|
524
524
|
// tell a forgery attempt apart from the routine "never approved" case.
|
|
525
525
|
let markerForged = false;
|
|
526
|
+
// `markers.detail` itself, hoisted alongside `markerForged` so the
|
|
527
|
+
// in-flight consult below can still surface it: a forged SESSION/TASK
|
|
528
|
+
// marker sitting next to a genuinely valid in-flight record (ADR "TTL,
|
|
529
|
+
// cwd, and subagents") is a forgery attempt the operator should see
|
|
530
|
+
// even though the in-flight record is what ends up opening the gate.
|
|
531
|
+
let markerForgedDetail;
|
|
526
532
|
if (generatedDir !== undefined) {
|
|
527
533
|
// Source 1a/1b: task-scoped marker for the currently-claimed task
|
|
528
534
|
// (harness/1ee26e77 + PR #198 correctness fix), then the
|
|
@@ -533,6 +539,20 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
533
539
|
const markers = checkOperatorApprovalMarkers(generatedDir, sessionId, declared.config, stderr);
|
|
534
540
|
markerExpired = markers.expired;
|
|
535
541
|
markerForged = markers.forged;
|
|
542
|
+
if (markerForged) {
|
|
543
|
+
// `markers.detail` is only ever the SESSION marker's detail on the
|
|
544
|
+
// unmatched path (see OperatorMarkerApproval.detail); when it is
|
|
545
|
+
// the TASK marker that failed verification, that fact is buried
|
|
546
|
+
// inside `taskCheckDetail`'s wrapping instead. Detect which one
|
|
547
|
+
// actually carries the forgery phrase so this line names the
|
|
548
|
+
// marker that was actually tampered with, not always the session
|
|
549
|
+
// one (review T-003 R3 L1: a forged task marker next to an intact
|
|
550
|
+
// session marker used to report the session's unrelated "no
|
|
551
|
+
// approval marker" detail here).
|
|
552
|
+
const taskMarkerIsForged = /forged\/unsigned marker rejected/.test(markers.taskCheckDetail);
|
|
553
|
+
const forgedMarkerDetail = taskMarkerIsForged ? markers.taskCheckDetail : markers.detail;
|
|
554
|
+
markerForgedDetail = `harness pack hook: forged/unsigned marker rejected for session ${sessionId}; ${forgedMarkerDetail}`;
|
|
555
|
+
}
|
|
536
556
|
if (markers.source !== "task") {
|
|
537
557
|
// Trace the task-marker miss to stderr so an operator chasing
|
|
538
558
|
// "why isn't my approval working?" sees the active-claim vs marker
|
|
@@ -550,6 +570,59 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
550
570
|
};
|
|
551
571
|
}
|
|
552
572
|
}
|
|
573
|
+
// Source 1c: in-flight subagent record (ADR "TTL, cwd, and subagents").
|
|
574
|
+
// An Agent-tool subagent shares its parent's
|
|
575
|
+
// `session_id`, so the marker check above cannot tell a subagent's call
|
|
576
|
+
// apart from the parent's own — the payload's `agent_id` is what does.
|
|
577
|
+
// Consulted ONLY here, ONLY when the marker check just missed AND the
|
|
578
|
+
// payload names a non-empty `agent_id`: a main-line call (no
|
|
579
|
+
// `agent_id`) never reaches `verifyInflightRecord` at all, so no record
|
|
580
|
+
// on disk for any agent can ever open the gate for a main-line call.
|
|
581
|
+
const agentId = typeof event.agent_id === "string" && event.agent_id.length > 0
|
|
582
|
+
? event.agent_id
|
|
583
|
+
: undefined;
|
|
584
|
+
// Sanitized copy for every surface a human or the agent itself reads
|
|
585
|
+
// (the block suffix, the forged phrase, this allow diagnostic): the raw
|
|
586
|
+
// `agentId` above is what verification and path-building use, but an
|
|
587
|
+
// attacker-controlled `agent_id` is otherwise interpolated verbatim
|
|
588
|
+
// into stderr and the agent-facing deny text (review finding: newline
|
|
589
|
+
// injection, unbounded length).
|
|
590
|
+
const displayAgentId = agentId !== undefined ? sanitizeForDisplay(agentId) : undefined;
|
|
591
|
+
// True SPECIFICALLY when a record FILE existed but failed verification
|
|
592
|
+
// (bad signature, or its body disagreed with its own path) — the same
|
|
593
|
+
// distinction `markerForged` draws for the operator marker. Folded into
|
|
594
|
+
// the auto-approval gating below so a forged record is declined the
|
|
595
|
+
// same way a forged marker is, without duplicating that enforcement.
|
|
596
|
+
let inflightForged = false;
|
|
597
|
+
// True SPECIFICALLY when the record verified and agreed with its own
|
|
598
|
+
// path but aged past the staleness window — distinct from `forged` and
|
|
599
|
+
// from a record simply never existing, so the subagent sentence below
|
|
600
|
+
// can name the actual cause instead of always saying "no record".
|
|
601
|
+
let inflightStale = false;
|
|
602
|
+
if (generatedDir !== undefined && agentId !== undefined) {
|
|
603
|
+
const record = verifyInflightRecord(generatedDir, sessionId, agentId, opts.now !== undefined ? { now: opts.now } : {});
|
|
604
|
+
inflightForged = record.forged;
|
|
605
|
+
inflightStale = record.stale;
|
|
606
|
+
if (record.matched) {
|
|
607
|
+
// A forged SESSION/TASK marker sitting next to this valid record is
|
|
608
|
+
// a distinct forgery attempt: surfaced to stderr BEFORE the allow
|
|
609
|
+
// diagnostic, but never gating it — the in-flight record is its
|
|
610
|
+
// own, independently-verified approval authority (ADR "TTL, cwd,
|
|
611
|
+
// and subagents"), so a forged copy of a DIFFERENT authority must
|
|
612
|
+
// not turn this allow into a block.
|
|
613
|
+
if (markerForgedDetail !== undefined) {
|
|
614
|
+
stderr.write(`${markerForgedDetail}\n`);
|
|
615
|
+
}
|
|
616
|
+
const diagnostic = `harness pack hook: in-flight subagent record for agent ${displayAgentId} (${record.detail}), allowing.`;
|
|
617
|
+
stderr.write(`${diagnostic}\n`);
|
|
618
|
+
return {
|
|
619
|
+
exitCode: 0,
|
|
620
|
+
blocked: false,
|
|
621
|
+
approvalCheck: { approved: true, source: "inflight", detail: record.detail },
|
|
622
|
+
diagnostic,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
553
626
|
// Persisted-report EVIDENCE probe (task 7402301d). This used to be
|
|
554
627
|
// "Source 2", an equal approval authority consulted right after a forged
|
|
555
628
|
// marker was rejected, and it was unsigned: under the threat model that
|
|
@@ -573,16 +646,44 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
573
646
|
// historic trail. The result intentionally does NOT influence the
|
|
574
647
|
// allow/block decision.
|
|
575
648
|
const ledger = await checkLedger(manifest, sessionId, opts);
|
|
649
|
+
// A subagent call (non-empty `agent_id`) that reached this point had no
|
|
650
|
+
// matching in-flight record — missing, for a different agent, forged,
|
|
651
|
+
// stale, or naming a malformed id (`verifyInflightRecord` never throws
|
|
652
|
+
// on any of those; it just reports `matched: false`). Branched on the
|
|
653
|
+
// actual verification outcome rather than always naming "no record": a
|
|
654
|
+
// forged or stale record is a materially different signal than one
|
|
655
|
+
// that never existed — the parent DID hold a valid approval at some
|
|
656
|
+
// point — and conflating them misdirects the agent's next action.
|
|
657
|
+
// Gated on `generatedDir !== undefined`: when it
|
|
658
|
+
// is not resolvable (test/injection path), `verifyInflightRecord` was
|
|
659
|
+
// never consulted at all (see `inflightForged`/`inflightStale` staying
|
|
660
|
+
// at their `false` initializers above), so there is no verification
|
|
661
|
+
// outcome to report. Named explicitly so an agent reading the deny
|
|
662
|
+
// text knows to stop rather than retry: unlike the operator-approval
|
|
663
|
+
// case, nothing this session can do mints a new record — only the
|
|
664
|
+
// parent's NEXT `subagent-start` call can.
|
|
665
|
+
const subagentRecordSentence = generatedDir !== undefined && displayAgentId !== undefined
|
|
666
|
+
? inflightForged
|
|
667
|
+
? ` subagent ${displayAgentId}: the in-flight record for this subagent failed verification; stop cleanly and report to the orchestrator instead of retrying.`
|
|
668
|
+
: inflightStale
|
|
669
|
+
? ` subagent ${displayAgentId}: the in-flight record for this subagent is older than the staleness window; stop cleanly and report to the orchestrator instead of retrying.`
|
|
670
|
+
: ` subagent ${displayAgentId}: no in-flight approval record (the parent session held no valid approval when this subagent started, or the record was removed); stop cleanly and report to the orchestrator instead of retrying.`
|
|
671
|
+
: "";
|
|
576
672
|
// Neither operator source approved. When a marker FILE existed but
|
|
577
673
|
// failed signature verification, use a distinct reason phrase
|
|
578
674
|
// ("forged/unsigned marker rejected") instead of the routine "no
|
|
579
675
|
// approval marker" — audit/operator surfaces can then tell an active
|
|
580
676
|
// forgery attempt (or a pre-signing legacy marker) apart from a session
|
|
581
|
-
// that simply never approved (harness/f9485cc7).
|
|
677
|
+
// that simply never approved (harness/f9485cc7). A forged in-flight
|
|
678
|
+
// record gets its own distinct phrase for the same reason, checked
|
|
679
|
+
// second so a forged SESSION/TASK marker (the higher-authority forgery)
|
|
680
|
+
// is never masked by a merely-forged copy of it.
|
|
582
681
|
const reason = generatedDir !== undefined
|
|
583
682
|
? markerForged
|
|
584
683
|
? `forged/unsigned marker rejected for session ${sessionId}; ${report.detail}; ${ledger.detail}`
|
|
585
|
-
:
|
|
684
|
+
: inflightForged
|
|
685
|
+
? `forged/unsigned in-flight record for agent ${displayAgentId} rejected for session ${sessionId}; ${report.detail}; ${ledger.detail}`
|
|
686
|
+
: `no approval marker for session ${sessionId}; ${report.detail}; ${ledger.detail}${subagentRecordSentence}`
|
|
586
687
|
: `generatedDir not resolvable (test/injection path); ${report.detail}; ${ledger.detail}`;
|
|
587
688
|
// Stage the session id so `harness approve`, run from the operator's
|
|
588
689
|
// shell where $CLAUDE_CODE_SESSION_ID / $CLAUDE_SESSION_ID is unset, can resolve it without
|
|
@@ -705,7 +806,12 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
705
806
|
// simply still in flight. That is the one case where the deny text asks
|
|
706
807
|
// for a retry, so it is the one case that sets this flag.
|
|
707
808
|
let reportScanTimedOut = false;
|
|
708
|
-
|
|
809
|
+
// `!inflightForged` added alongside `!markerForged`: a forged in-flight
|
|
810
|
+
// record is the same posture as a forged marker (ADR condition 6, "a
|
|
811
|
+
// forgery detected is never laundered into an approval") — no point
|
|
812
|
+
// spending a delegation resolution on a call already known to end in a
|
|
813
|
+
// decline.
|
|
814
|
+
if (generatedDir !== undefined && !markerForged && !inflightForged) {
|
|
709
815
|
// `null` stderr on purpose: `attemptAutoApproval` parses the same
|
|
710
816
|
// block a few lines below and writes the malformed-config line
|
|
711
817
|
// itself, so passing this hook's stderr here would double it.
|
|
@@ -761,11 +867,28 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
761
867
|
launcherReportPath,
|
|
762
868
|
});
|
|
763
869
|
if (!verified.ok) {
|
|
764
|
-
//
|
|
870
|
+
// An absent, moved, or content-modified launcher report surfaces
|
|
765
871
|
// here through the verifier's own distinct reasons
|
|
766
|
-
// (`
|
|
767
|
-
// every other refusal: no
|
|
768
|
-
//
|
|
872
|
+
// (`report_missing` / `report_path_mismatch` /
|
|
873
|
+
// `report_content_mismatch`), same as every other refusal: no
|
|
874
|
+
// special-casing needed now that this hook actually checks the
|
|
875
|
+
// fallback shape against a file. `verified.detail` names the
|
|
876
|
+
// expected path for the three file-state cases this hook can
|
|
877
|
+
// actually reach (absent / wrong path / wrong bytes). Two of
|
|
878
|
+
// `verifyDelegation`'s binding-shape branches are unreachable
|
|
879
|
+
// from here for different reasons, so their details are worth
|
|
880
|
+
// spelling out separately rather than lumping them together:
|
|
881
|
+
// "no report path offered" never fires from this call site,
|
|
882
|
+
// since this hook always offers the conventional path
|
|
883
|
+
// (`launcherReportPath` above is unconditional); "no
|
|
884
|
+
// reportContentHash to check against" IS reachable here, just
|
|
885
|
+
// not from a delegation this hook itself minted, since
|
|
886
|
+
// `writeDelegationMarker` refuses to produce that half-binding
|
|
887
|
+
// shape (a hand-signed marker can still carry it, as the
|
|
888
|
+
// half-binding test in
|
|
889
|
+
// understanding-before-execution-delegation.test.ts shows), and
|
|
890
|
+
// its detail names the delegation marker's own path, not the
|
|
891
|
+
// report path, since there is no report path yet to name.
|
|
769
892
|
stderr.write(`harness pack hook: delegation for ${childSessionId} refused: ${verified.reason}: ${verified.detail}\n`);
|
|
770
893
|
}
|
|
771
894
|
else {
|
|
@@ -1003,7 +1126,18 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
1003
1126
|
sessionConsistency: { kind: "env", variable: "CLAUDE_CODE_SESSION_ID" },
|
|
1004
1127
|
packConfig: declared.config,
|
|
1005
1128
|
reportsDir,
|
|
1006
|
-
|
|
1129
|
+
// Folded with `inflightForged`: `attemptAutoApproval`'s own guard on
|
|
1130
|
+
// this flag is the ACTUAL enforcement point for "a forgery is never
|
|
1131
|
+
// laundered into an approval" (the `!inflightForged` added to the
|
|
1132
|
+
// block above only skips a doomed delegation resolution; it does not
|
|
1133
|
+
// by itself stop this call).
|
|
1134
|
+
markerForged: markerForged || inflightForged,
|
|
1135
|
+
// Names which of the two the flag above actually folds together, so
|
|
1136
|
+
// the decline diagnostic says the right artifact. A real marker
|
|
1137
|
+
// forgery keeps the plain "marker" wording even when both happen to
|
|
1138
|
+
// be true, matching the higher-authority precedence `reason` above
|
|
1139
|
+
// already gives it.
|
|
1140
|
+
forgedKind: markerForged ? "marker" : inflightForged ? "inflight" : undefined,
|
|
1007
1141
|
// Slice 3: present only when the branch above verified a delegation
|
|
1008
1142
|
// for this child session. It supplies key ONE in place of a
|
|
1009
1143
|
// `when`-listed `permission_mode`; every other condition, key two
|
|
@@ -1046,7 +1180,27 @@ export async function runPackHookPreToolUseCli(opts = {}) {
|
|
|
1046
1180
|
const latestParseError = report.report === null
|
|
1047
1181
|
? findLatestParseError(path.join(path.dirname(reportsDir), "parse-errors"), sessionId)
|
|
1048
1182
|
: null;
|
|
1049
|
-
|
|
1183
|
+
// The subagent record sentence, delivered to the AGENT through the
|
|
1184
|
+
// same channel the slice-3 retry instruction uses (appended after both
|
|
1185
|
+
// the `ux:` and legacy envelopes, so it survives either one) rather
|
|
1186
|
+
// than staying stderr/audit-only, reaching only `reason` /
|
|
1187
|
+
// `approvalCheck.detail` and never the agent-facing stdout JSON. A
|
|
1188
|
+
// main-line block (`displayAgentId` undefined) never carries it.
|
|
1189
|
+
//
|
|
1190
|
+
// The retry instruction takes PRECEDENCE when both would apply: nothing
|
|
1191
|
+
// stops a delegated session (ADR "Report capture under `-p`") from also
|
|
1192
|
+
// being the parent of an in-flight subagent, but the two instructions
|
|
1193
|
+
// disagree on what the agent should do next — the subagent sentence
|
|
1194
|
+
// says stop, the retry instruction says retry — and the delegation
|
|
1195
|
+
// path can still succeed on the very next retry regardless of the
|
|
1196
|
+
// subagent record's own state, so telling the agent to stop here would
|
|
1197
|
+
// be wrong.
|
|
1198
|
+
const agentInstruction = reportScanTimedOut
|
|
1199
|
+
? DELEGATION_REPORT_RETRY_INSTRUCTION
|
|
1200
|
+
: displayAgentId !== undefined && subagentRecordSentence.length > 0
|
|
1201
|
+
? subagentRecordSentence.trim()
|
|
1202
|
+
: null;
|
|
1203
|
+
stdout.write(`${blockJson(toolName, "no approved Understanding Report for this session", configProducers, configUx, sessionId, escapeHint, latestParseError?.malformedSections, agentInstruction)}\n`);
|
|
1050
1204
|
return {
|
|
1051
1205
|
exitCode: 0,
|
|
1052
1206
|
blocked: true,
|