@lannguyensi/harness 0.32.0 → 0.33.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 +20 -0
- package/dist/cli/approve/branch-protection.d.ts +69 -0
- package/dist/cli/approve/branch-protection.js +157 -0
- package/dist/cli/approve/branch-protection.js.map +1 -0
- package/dist/cli/index.js +55 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/composer.js +11 -5
- package/dist/cli/init/composer.js.map +1 -1
- package/dist/cli/init/profiles.d.ts +2 -2
- package/dist/cli/init/profiles.js +2 -2
- package/dist/cli/init/templates.d.ts +1 -1
- package/dist/cli/init/templates.js +8 -4
- package/dist/cli/init/templates.js.map +1 -1
- package/dist/cli/pack/hook-branch-protection.d.ts +8 -0
- package/dist/cli/pack/hook-branch-protection.js +59 -15
- package/dist/cli/pack/hook-branch-protection.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +31 -2
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-solution-acceptance.d.ts +2 -0
- package/dist/cli/pack/hook-solution-acceptance.js +24 -10
- package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
- package/dist/cli/pack/read-only-bash.js +127 -4
- package/dist/cli/pack/read-only-bash.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +47 -6
- package/dist/policy-packs/builtin/branch-protection-runtime.js +53 -6
- package/dist/policy-packs/builtin/branch-protection-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/branch-protection.js +21 -11
- package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
- package/dist/policy-packs/builtin/solution-acceptance-runtime.d.ts +18 -0
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js +32 -0
- package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +11 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js +15 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/package.json +3 -3
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
// HEAD.
|
|
9
9
|
//
|
|
10
10
|
// The verdict id is the active-claim task id (the same `active-claim` file
|
|
11
|
-
// `harness approve understanding` consumes).
|
|
12
|
-
//
|
|
11
|
+
// `harness approve understanding` consumes). For solo / non-agent-tasks
|
|
12
|
+
// sessions that never call `task_start`, the `SOLUTION_VERDICT_ID` env knob
|
|
13
|
+
// supplies the id instead; it is consulted only when no active claim is
|
|
14
|
+
// present, so a claimed session's id stays authoritative (an env var cannot
|
|
15
|
+
// redirect a claimed task's verdict). With neither source the gate fails
|
|
16
|
+
// CLOSED — a sessionId fallback would reopen the wrong-scope bug class
|
|
13
17
|
// understanding-gate already fixed.
|
|
14
18
|
//
|
|
15
19
|
// Failure mode: any error in load / parse / HEAD-resolution / verdict-read
|
|
@@ -20,7 +24,7 @@
|
|
|
20
24
|
// `solution_evaluate` as the recovery path so the operator is never wedged;
|
|
21
25
|
// `harness pause` (honored first) is the operator's hard override.
|
|
22
26
|
import { readActiveClaim, } from "../../policy-packs/builtin/understanding-before-execution-runtime.js";
|
|
23
|
-
import { DEFAULT_PUSH_BASH_RE, evaluateGate, PACK_NAME, readVerdict, resolveProtectedCompletionTools, verdictDir as resolveVerdictDir, } from "../../policy-packs/builtin/solution-acceptance-runtime.js";
|
|
27
|
+
import { DEFAULT_PUSH_BASH_RE, evaluateGate, PACK_NAME, readVerdict, resolveExplicitVerdictId, resolveProtectedCompletionTools, VERDICT_ID_ENV, verdictDir as resolveVerdictDir, } from "../../policy-packs/builtin/solution-acceptance-runtime.js";
|
|
24
28
|
import { resolveGeneratedDir } from "../../io/generated-dir.js";
|
|
25
29
|
import { resolveGitContext } from "../../runtime/git-context.js";
|
|
26
30
|
import { renderAgentFacing } from "../../runtime/agent-facing.js";
|
|
@@ -114,6 +118,7 @@ export async function runPackHookSolutionAcceptanceCli(opts = {}) {
|
|
|
114
118
|
const note = (msg) => {
|
|
115
119
|
stderr.write(`harness pack hook solution-acceptance: ${msg}\n`);
|
|
116
120
|
};
|
|
121
|
+
const env = opts.env ?? process.env;
|
|
117
122
|
const raw = await readStdin(stdin);
|
|
118
123
|
let event = {};
|
|
119
124
|
try {
|
|
@@ -128,8 +133,8 @@ export async function runPackHookSolutionAcceptanceCli(opts = {}) {
|
|
|
128
133
|
return { exitCode: 0, blocked: false, diagnostic };
|
|
129
134
|
}
|
|
130
135
|
const sessionId = (typeof event.session_id === "string" ? event.session_id : undefined) ??
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
env["CLAUDE_CODE_SESSION_ID"] ??
|
|
137
|
+
env["CLAUDE_SESSION_ID"] ??
|
|
133
138
|
"";
|
|
134
139
|
const toolName = typeof event.tool_name === "string" ? event.tool_name : "(unknown)";
|
|
135
140
|
const cwd = typeof opts.cwd === "string" && opts.cwd.length > 0
|
|
@@ -196,8 +201,11 @@ export async function runPackHookSolutionAcceptanceCli(opts = {}) {
|
|
|
196
201
|
return { exitCode: 0, blocked: false, diagnostic };
|
|
197
202
|
}
|
|
198
203
|
const configUx = parseConfigUx(pack.config["ux"], stderr);
|
|
199
|
-
// Resolve the verdict id
|
|
200
|
-
//
|
|
204
|
+
// Resolve the verdict id. Precedence: the agent-tasks active-claim task id
|
|
205
|
+
// first (authoritative for claimed sessions — an env var must not redirect a
|
|
206
|
+
// claimed task's verdict), then the SOLUTION_VERDICT_ID env knob for solo /
|
|
207
|
+
// non-agent-tasks sessions, then fail CLOSED. A sessionId fallback is
|
|
208
|
+
// intentionally NOT a source (it would reopen the wrong-scope bug class).
|
|
201
209
|
const generatedDir = opts.generatedDir ??
|
|
202
210
|
(manifestPath !== undefined
|
|
203
211
|
? resolveGeneratedDir({
|
|
@@ -205,21 +213,27 @@ export async function runPackHookSolutionAcceptanceCli(opts = {}) {
|
|
|
205
213
|
manifestPath,
|
|
206
214
|
})
|
|
207
215
|
: undefined);
|
|
208
|
-
const
|
|
216
|
+
const activeClaim = opts.activeClaim !== undefined
|
|
209
217
|
? opts.activeClaim
|
|
210
218
|
: generatedDir !== undefined
|
|
211
219
|
? readActiveClaim(generatedDir)
|
|
212
220
|
: null;
|
|
221
|
+
const taskId = activeClaim ?? resolveExplicitVerdictId(env);
|
|
213
222
|
if (!taskId) {
|
|
214
223
|
const detail = opts.activeClaim === undefined && generatedDir === undefined
|
|
215
224
|
? " (could not resolve harness.generated/; pass --config)"
|
|
216
225
|
: "";
|
|
217
|
-
const reason = `no active-claim task id recorded${detail}
|
|
226
|
+
const reason = `no verdict id: no active-claim task id recorded${detail} and ${VERDICT_ID_ENV} is unset or invalid. ` +
|
|
227
|
+
`Call mcp__agent-tasks__task_start first (agent-tasks workflow; the verdict id is the active task), ` +
|
|
228
|
+
`or set ${VERDICT_ID_ENV} to the verdict id for a solo / non-agent-tasks session.`;
|
|
218
229
|
const diagnostic = `BLOCK — ${reason}`;
|
|
219
230
|
note(diagnostic);
|
|
220
|
-
stdout.write(`${blockJson(actionLabel, toolName, "<no-
|
|
231
|
+
stdout.write(`${blockJson(actionLabel, toolName, "<no-verdict-id>", reason, configUx, sessionId)}\n`);
|
|
221
232
|
return { exitCode: 0, blocked: true, diagnostic };
|
|
222
233
|
}
|
|
234
|
+
// The verdict DIR still resolves SOLUTION_VERDICT_DIR from process.env (the
|
|
235
|
+
// `env` seam above covers the verdict id + sessionId, not the dir); in
|
|
236
|
+
// production both see the same process.env, and tests inject opts.verdictDir.
|
|
223
237
|
const dir = opts.verdictDir ?? resolveVerdictDir();
|
|
224
238
|
const currentHead = resolveGitContext(cwd).sha || null;
|
|
225
239
|
const verdict = readVerdict(dir, taskId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-solution-acceptance.js","sourceRoot":"","sources":["../../../src/cli/pack/hook-solution-acceptance.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yCAAyC;AACzC,EAAE;AACF,oEAAoE;AACpE,4EAA4E;AAC5E,qEAAqE;AACrE,0EAA0E;AAC1E,QAAQ;AACR,EAAE;AACF,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"hook-solution-acceptance.js","sourceRoot":"","sources":["../../../src/cli/pack/hook-solution-acceptance.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
* changing classification: `ls -la /tmp` is still read-only.
|
|
36
36
|
*/
|
|
37
37
|
const SIMPLE_READ_ONLY_BINS = new Set([
|
|
38
|
-
"ls", "cat", "pwd", "which", "type",
|
|
38
|
+
"ls", "cat", "pwd", "which", "type",
|
|
39
39
|
"grep", "rg", "wc",
|
|
40
40
|
"head", "tail", "file", "stat", "tree", "du", "df",
|
|
41
|
-
"ps", "whoami", "id", "date", "echo", "
|
|
41
|
+
"ps", "whoami", "id", "date", "echo", "printenv",
|
|
42
42
|
"true", "false", "uptime", "hostname", "uname", "tty",
|
|
43
43
|
"basename", "dirname", "realpath", "readlink",
|
|
44
44
|
"less", "more", "cmp", "diff", "comm",
|
|
@@ -60,6 +60,42 @@ const FIND_WRITE_FLAGS = new Set([
|
|
|
60
60
|
"-exec", "-execdir", "-ok", "-okdir",
|
|
61
61
|
"-fprint", "-fprintf", "-fprint0", "-fls",
|
|
62
62
|
]);
|
|
63
|
+
/**
|
|
64
|
+
* Command-runner binaries: their argv is itself a nested command to
|
|
65
|
+
* execute, so the "each accepts arguments without changing
|
|
66
|
+
* classification" rule does NOT hold for them. `command rm -rf /tmp/x`
|
|
67
|
+
* runs `rm`, and `env FOO=bar rm -rf /tmp/x` runs `rm` too. Including
|
|
68
|
+
* them in `SIMPLE_READ_ONLY_BINS` would classify the WRAPPER as
|
|
69
|
+
* read-only while the wrapped command does the write, a hard gate
|
|
70
|
+
* bypass. Each runner gets a `find`-style special case below that
|
|
71
|
+
* strips the runner's own leading flags/assignments and recurse-
|
|
72
|
+
* classifies the residual underlying command. Bare `env` /
|
|
73
|
+
* `command` (no underlying command) stay read-only: they only print
|
|
74
|
+
* the environment or resolve a name.
|
|
75
|
+
*
|
|
76
|
+
* `env` leading flags that take no command and do not change the fact
|
|
77
|
+
* that what follows is still a command to run. `-i` / `--ignore-
|
|
78
|
+
* environment` and `-u NAME` / `--unset=NAME` scrub the environment
|
|
79
|
+
* but still execute the residual command; `-` is the historical
|
|
80
|
+
* synonym for `-i`. `--` ends option parsing. We skip these (and any
|
|
81
|
+
* `NAME=VALUE` assignment tokens) to find the real underlying command.
|
|
82
|
+
*/
|
|
83
|
+
const ENV_LEADING_FLAGS = new Set([
|
|
84
|
+
"-i", "--ignore-environment", "-", "--",
|
|
85
|
+
]);
|
|
86
|
+
/** `env` flags that consume the following token as their value. */
|
|
87
|
+
const ENV_VALUE_FLAGS = new Set([
|
|
88
|
+
"-u", "--unset",
|
|
89
|
+
"-C", "--chdir",
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* `env -S` / `--split-string` re-parses its single string argument
|
|
93
|
+
* into a fresh argv, which defeats our whitespace tokenization: the
|
|
94
|
+
* write would live inside one quoted token. Any appearance of the
|
|
95
|
+
* split-string flag (bare, glued, or long-form) forfeits the
|
|
96
|
+
* read-only classification. Fail closed.
|
|
97
|
+
*/
|
|
98
|
+
const ENV_SPLIT_STRING_FLAGS = /^(-S.*|--split-string(=.*)?)$/;
|
|
63
99
|
/**
|
|
64
100
|
* `less` and `more` can shell out via interactive `!cmd`. The agent
|
|
65
101
|
* shell is non-interactive, so the escape is not reachable in
|
|
@@ -131,7 +167,10 @@ export function isReadOnlyBashCommand(command) {
|
|
|
131
167
|
return false;
|
|
132
168
|
// Reject any shell chaining, redirection, or command substitution.
|
|
133
169
|
// These make the command unclassifiable even when every visible
|
|
134
|
-
// piece would otherwise be read-only.
|
|
170
|
+
// piece would otherwise be read-only. Applied once to the whole
|
|
171
|
+
// string, so it also covers any residual command a runner
|
|
172
|
+
// (`env` / `command`) wraps: those are classified from the same
|
|
173
|
+
// token slice, never from a re-read of the shell.
|
|
135
174
|
if (/[;&|<>]/.test(trimmed))
|
|
136
175
|
return false;
|
|
137
176
|
if (trimmed.includes("\n"))
|
|
@@ -140,11 +179,95 @@ export function isReadOnlyBashCommand(command) {
|
|
|
140
179
|
return false;
|
|
141
180
|
if (trimmed.includes("$("))
|
|
142
181
|
return false;
|
|
143
|
-
|
|
182
|
+
return classifyTokens(trimmed.split(/\s+/));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Classify an already-tokenized, metachar-cleared argv. Factored out
|
|
186
|
+
* of `isReadOnlyBashCommand` so the command-runner special cases
|
|
187
|
+
* (`command` / `env`) can recurse on the residual underlying command
|
|
188
|
+
* without re-parsing a reconstructed string.
|
|
189
|
+
*/
|
|
190
|
+
function classifyTokens(tokens) {
|
|
144
191
|
const bin = tokens[0] ?? "";
|
|
145
192
|
const sub = tokens[1] ?? "";
|
|
146
193
|
if (SIMPLE_READ_ONLY_BINS.has(bin))
|
|
147
194
|
return true;
|
|
195
|
+
// `command <cmd> ...` runs <cmd>, bypassing shell functions/aliases.
|
|
196
|
+
// It is read-only ONLY if the command it wraps is read-only. Strip
|
|
197
|
+
// `command`'s own option flags (`-p`, `-v`, `-V`, and any combined
|
|
198
|
+
// short flags like `-pv`), then recurse-classify the residual argv.
|
|
199
|
+
// Bare `command` (no residual) and the lookup-only forms `command
|
|
200
|
+
// -v <name>` / `command -V <name>` (which print where a name
|
|
201
|
+
// resolves without executing it) stay read-only.
|
|
202
|
+
if (bin === "command") {
|
|
203
|
+
let i = 1;
|
|
204
|
+
let lookupOnly = false;
|
|
205
|
+
for (; i < tokens.length; i += 1) {
|
|
206
|
+
const t = tokens[i];
|
|
207
|
+
if (t === undefined || !t.startsWith("-") || t === "--")
|
|
208
|
+
break;
|
|
209
|
+
if (/[vV]/.test(t))
|
|
210
|
+
lookupOnly = true;
|
|
211
|
+
}
|
|
212
|
+
if (i < tokens.length && tokens[i] === "--")
|
|
213
|
+
i += 1;
|
|
214
|
+
if (lookupOnly)
|
|
215
|
+
return true;
|
|
216
|
+
if (i >= tokens.length)
|
|
217
|
+
return true; // bare `command`
|
|
218
|
+
return classifyTokens(tokens.slice(i));
|
|
219
|
+
}
|
|
220
|
+
// `env [NAME=VALUE...] [flags] <cmd> ...` runs <cmd> in a modified
|
|
221
|
+
// environment. It is read-only ONLY if the command it wraps is
|
|
222
|
+
// read-only. Skip leading env-assignment tokens (`FOO=bar`) and
|
|
223
|
+
// env's own flags, then recurse-classify the residual command. Bare
|
|
224
|
+
// `env`, `env -u X`, `env FOO=bar` (no residual command, just prints
|
|
225
|
+
// the environment) stay read-only.
|
|
226
|
+
if (bin === "env") {
|
|
227
|
+
let i = 1;
|
|
228
|
+
while (i < tokens.length) {
|
|
229
|
+
const t = tokens[i];
|
|
230
|
+
if (t === undefined)
|
|
231
|
+
break;
|
|
232
|
+
// `env -S` / `--split-string` re-parses a string into a command:
|
|
233
|
+
// forfeit read-only classification (fail closed).
|
|
234
|
+
if (ENV_SPLIT_STRING_FLAGS.test(t))
|
|
235
|
+
return false;
|
|
236
|
+
if (t === "--") {
|
|
237
|
+
i += 1;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
if (ENV_VALUE_FLAGS.has(t)) {
|
|
241
|
+
i += 2;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (ENV_LEADING_FLAGS.has(t)) {
|
|
245
|
+
i += 1;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
// Long flag with a glued value (`--unset=NAME`, `--chdir=DIR`):
|
|
249
|
+
// single token, skip it.
|
|
250
|
+
if (t.startsWith("--") && t.includes("=")) {
|
|
251
|
+
i += 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
// Short flag with a glued value (`-uNAME`, `-CDIR`): single
|
|
255
|
+
// token, skip it.
|
|
256
|
+
if (/^-[uC]./.test(t)) {
|
|
257
|
+
i += 1;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
// `NAME=VALUE` environment assignment (no leading dash): skip.
|
|
261
|
+
if (!t.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) {
|
|
262
|
+
i += 1;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
if (i >= tokens.length)
|
|
268
|
+
return true; // bare `env` / only assignments
|
|
269
|
+
return classifyTokens(tokens.slice(i));
|
|
270
|
+
}
|
|
148
271
|
// `find` is read-only ONLY when none of its argv tokens are write
|
|
149
272
|
// flags. Scan the whole argv: `-delete` / `-exec` / `-execdir` /
|
|
150
273
|
// `-ok` / `-okdir` mutate the filesystem; `-fprint*` and `-fls`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-only-bash.js","sourceRoot":"","sources":["../../../src/cli/pack/read-only-bash.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,sBAAsB;AACtB,EAAE;AACF,qEAAqE;AACrE,sEAAsE;AACtE,sEAAsE;AACtE,oEAAoE;AACpE,qEAAqE;AACrE,mEAAmE;AACnE,QAAQ;AACR,EAAE;AACF,mBAAmB;AACnB,oEAAoE;AACpE,kEAAkE;AAClE,sEAAsE;AACtE,iEAAiE;AACjE,qEAAqE;AACrE,qEAAqE;AACrE,kEAAkE;AAClE,4DAA4D;AAC5D,sEAAsE;AACtE,mEAAmE;AACnE,sEAAsE;AACtE,mEAAmE;AACnE,oEAAoE;AACpE,sBAAsB;AACtB,EAAE;AACF,gEAAgE;AAChE,qEAAqE;AACrE,gEAAgE;AAChE,oEAAoE;AACpE,yDAAyD;AAEzD;;;GAGG;AACH,MAAM,qBAAqB,GAAwB,IAAI,GAAG,CAAC;IACzD,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"read-only-bash.js","sourceRoot":"","sources":["../../../src/cli/pack/read-only-bash.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PolicyPack } from "../../schema/index.js";
|
|
2
|
+
import { type ApprovalMarker, type CheckApprovalMarkerOptions, type MarkerCheck } from "./understanding-before-execution-runtime.js";
|
|
2
3
|
export declare const PACK_NAME = "branch-protection";
|
|
3
4
|
/**
|
|
4
5
|
* Ledger tag written by the producer when the current branch is NOT in
|
|
@@ -8,14 +9,54 @@ export declare const PACK_NAME = "branch-protection";
|
|
|
8
9
|
*/
|
|
9
10
|
export declare const NON_PROTECTED_TAG_PREFIX = "branch:non-protected";
|
|
10
11
|
/**
|
|
11
|
-
* Operator escape-hatch tag
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Operator escape-hatch tag, kept as a best-effort AUDIT echo only.
|
|
13
|
+
*
|
|
14
|
+
* SECURITY (audit finding #39): this tag is NO LONGER a trusted override
|
|
15
|
+
* signal. The agent has direct `mcp__agent-grounding__ledger_add` access,
|
|
16
|
+
* so it could self-write `branch-protection-ack:<anything>` and bless its
|
|
17
|
+
* own protected-branch edit — exactly the self-approval backdoor the
|
|
18
|
+
* understanding gate closed in agent-tasks/88ca4bb3 by moving the
|
|
19
|
+
* canonical approval to a filesystem marker. The blocker now consults the
|
|
20
|
+
* operator-only marker file (see `checkBranchProtectionMarker` below); the
|
|
21
|
+
* `harness approve branch-protection` verb still records this ledger tag
|
|
22
|
+
* so `harness audit` / forensics keep a trail, but its presence alone
|
|
23
|
+
* never satisfies the gate. The trailing `:<reason>` stays free-form.
|
|
17
24
|
*/
|
|
18
25
|
export declare const ACK_TAG_PREFIX = "branch-protection-ack";
|
|
26
|
+
/**
|
|
27
|
+
* Marker-name namespace for an operator-written branch-protection
|
|
28
|
+
* override. The marker lives in the shared `.approvals/` directory under
|
|
29
|
+
* `harness.generated/` (the same directory the understanding gate uses),
|
|
30
|
+
* prefixed so it can never be confused with an understanding-gate session
|
|
31
|
+
* marker (`.approvals/<sessionId>`) or a task marker (`.approvals/task-<id>`):
|
|
32
|
+
* Claude Code / Codex session ids are UUIDs and never start with this
|
|
33
|
+
* literal, so the three namespaces stay disjoint.
|
|
34
|
+
*
|
|
35
|
+
* Why a marker and not the `branch-protection-ack` ledger tag: only a
|
|
36
|
+
* process the operator launched (their `!`-shell or any un-hooked
|
|
37
|
+
* terminal) can write under `harness.generated/` — Edit / Write / Bash
|
|
38
|
+
* are all gated, and the configured MCP servers expose no filesystem
|
|
39
|
+
* write. So the marker is the canonical override signal; the ledger row
|
|
40
|
+
* is a best-effort audit echo only.
|
|
41
|
+
*/
|
|
42
|
+
export declare const BRANCH_PROTECTION_MARKER_PREFIX = "branch-protection-";
|
|
43
|
+
/** Marker filename (inside `.approvals/`) for a session's branch-protection override. */
|
|
44
|
+
export declare function branchProtectionMarkerName(sessionId: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Operator-side: write the canonical branch-protection override marker for
|
|
47
|
+
* `sessionId`. Atomic (delegates to `writeApprovalMarker`). Caller is
|
|
48
|
+
* `harness approve branch-protection`, run from the operator's un-hooked
|
|
49
|
+
* shell; if the agent could reach this path the gate's value would
|
|
50
|
+
* collapse, so it lives behind the approve CLI.
|
|
51
|
+
*/
|
|
52
|
+
export declare function writeBranchProtectionMarker(generatedDir: string, sessionId: string, marker: ApprovalMarker): string;
|
|
53
|
+
/**
|
|
54
|
+
* Gate-side: is the operator's branch-protection override marker present
|
|
55
|
+
* for `sessionId`? Inherits `checkApprovalMarker`'s contract
|
|
56
|
+
* (existence-is-enough, symlink rejection, optional freshness via
|
|
57
|
+
* `maxAgeMs`); only the namespaced filename differs.
|
|
58
|
+
*/
|
|
59
|
+
export declare function checkBranchProtectionMarker(generatedDir: string, sessionId: string, opts?: CheckApprovalMarkerOptions): MarkerCheck;
|
|
19
60
|
/**
|
|
20
61
|
* Freshness window for the producer tag. Five minutes lets a single
|
|
21
62
|
* branch-check satisfy a whole edit batch without re-running for every
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// hook branch-protection` (blocker) — both under `src/cli/`. This module
|
|
7
7
|
// is the small shared surface they pull from: tag formats, default
|
|
8
8
|
// protected list, config parsing.
|
|
9
|
+
import { checkApprovalMarker, writeApprovalMarker, } from "./understanding-before-execution-runtime.js";
|
|
9
10
|
export const PACK_NAME = "branch-protection";
|
|
10
11
|
/**
|
|
11
12
|
* Ledger tag written by the producer when the current branch is NOT in
|
|
@@ -15,14 +16,60 @@ export const PACK_NAME = "branch-protection";
|
|
|
15
16
|
*/
|
|
16
17
|
export const NON_PROTECTED_TAG_PREFIX = "branch:non-protected";
|
|
17
18
|
/**
|
|
18
|
-
* Operator escape-hatch tag
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Operator escape-hatch tag, kept as a best-effort AUDIT echo only.
|
|
20
|
+
*
|
|
21
|
+
* SECURITY (audit finding #39): this tag is NO LONGER a trusted override
|
|
22
|
+
* signal. The agent has direct `mcp__agent-grounding__ledger_add` access,
|
|
23
|
+
* so it could self-write `branch-protection-ack:<anything>` and bless its
|
|
24
|
+
* own protected-branch edit — exactly the self-approval backdoor the
|
|
25
|
+
* understanding gate closed in agent-tasks/88ca4bb3 by moving the
|
|
26
|
+
* canonical approval to a filesystem marker. The blocker now consults the
|
|
27
|
+
* operator-only marker file (see `checkBranchProtectionMarker` below); the
|
|
28
|
+
* `harness approve branch-protection` verb still records this ledger tag
|
|
29
|
+
* so `harness audit` / forensics keep a trail, but its presence alone
|
|
30
|
+
* never satisfies the gate. The trailing `:<reason>` stays free-form.
|
|
24
31
|
*/
|
|
25
32
|
export const ACK_TAG_PREFIX = "branch-protection-ack";
|
|
33
|
+
/**
|
|
34
|
+
* Marker-name namespace for an operator-written branch-protection
|
|
35
|
+
* override. The marker lives in the shared `.approvals/` directory under
|
|
36
|
+
* `harness.generated/` (the same directory the understanding gate uses),
|
|
37
|
+
* prefixed so it can never be confused with an understanding-gate session
|
|
38
|
+
* marker (`.approvals/<sessionId>`) or a task marker (`.approvals/task-<id>`):
|
|
39
|
+
* Claude Code / Codex session ids are UUIDs and never start with this
|
|
40
|
+
* literal, so the three namespaces stay disjoint.
|
|
41
|
+
*
|
|
42
|
+
* Why a marker and not the `branch-protection-ack` ledger tag: only a
|
|
43
|
+
* process the operator launched (their `!`-shell or any un-hooked
|
|
44
|
+
* terminal) can write under `harness.generated/` — Edit / Write / Bash
|
|
45
|
+
* are all gated, and the configured MCP servers expose no filesystem
|
|
46
|
+
* write. So the marker is the canonical override signal; the ledger row
|
|
47
|
+
* is a best-effort audit echo only.
|
|
48
|
+
*/
|
|
49
|
+
export const BRANCH_PROTECTION_MARKER_PREFIX = "branch-protection-";
|
|
50
|
+
/** Marker filename (inside `.approvals/`) for a session's branch-protection override. */
|
|
51
|
+
export function branchProtectionMarkerName(sessionId) {
|
|
52
|
+
return `${BRANCH_PROTECTION_MARKER_PREFIX}${sessionId}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Operator-side: write the canonical branch-protection override marker for
|
|
56
|
+
* `sessionId`. Atomic (delegates to `writeApprovalMarker`). Caller is
|
|
57
|
+
* `harness approve branch-protection`, run from the operator's un-hooked
|
|
58
|
+
* shell; if the agent could reach this path the gate's value would
|
|
59
|
+
* collapse, so it lives behind the approve CLI.
|
|
60
|
+
*/
|
|
61
|
+
export function writeBranchProtectionMarker(generatedDir, sessionId, marker) {
|
|
62
|
+
return writeApprovalMarker(generatedDir, branchProtectionMarkerName(sessionId), marker);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Gate-side: is the operator's branch-protection override marker present
|
|
66
|
+
* for `sessionId`? Inherits `checkApprovalMarker`'s contract
|
|
67
|
+
* (existence-is-enough, symlink rejection, optional freshness via
|
|
68
|
+
* `maxAgeMs`); only the namespaced filename differs.
|
|
69
|
+
*/
|
|
70
|
+
export function checkBranchProtectionMarker(generatedDir, sessionId, opts = {}) {
|
|
71
|
+
return checkApprovalMarker(generatedDir, branchProtectionMarkerName(sessionId), opts);
|
|
72
|
+
}
|
|
26
73
|
/**
|
|
27
74
|
* Freshness window for the producer tag. Five minutes lets a single
|
|
28
75
|
* branch-check satisfy a whole edit batch without re-running for every
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-protection-runtime.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/branch-protection-runtime.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,EAAE;AACF,kEAAkE;AAClE,yEAAyE;AACzE,sEAAsE;AACtE,yEAAyE;AACzE,mEAAmE;AACnE,kCAAkC;
|
|
1
|
+
{"version":3,"file":"branch-protection-runtime.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/branch-protection-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,9 +16,14 @@
|
|
|
16
16
|
// consults the ledger on every Write/Edit (or `apply_patch`) and
|
|
17
17
|
// emits a Claude Code deny envelope unless either:
|
|
18
18
|
// - a fresh (<5m) `branch:non-protected` tag exists, OR
|
|
19
|
-
// -
|
|
20
|
-
//
|
|
21
|
-
//
|
|
19
|
+
// - the operator-only override marker exists at
|
|
20
|
+
// `harness.generated/.approvals/branch-protection-<sessionId>`,
|
|
21
|
+
// written by `harness approve branch-protection`. The legacy
|
|
22
|
+
// `branch-protection-ack:` ledger tag is no longer trusted as an
|
|
23
|
+
// override (audit finding #39): it is agent-writable via
|
|
24
|
+
// `mcp__agent-grounding__ledger_add`, so it could self-bless an
|
|
25
|
+
// edit. The marker lives under `harness.generated/`, which Edit /
|
|
26
|
+
// Write / Bash are all gated from writing.
|
|
22
27
|
//
|
|
23
28
|
// The producer is also runnable on-demand from the operator's `!` shell
|
|
24
29
|
// — same CLI verb, no SessionStart event piped on stdin — so an agent
|
|
@@ -72,7 +77,7 @@ function buildHooks(runtime) {
|
|
|
72
77
|
command: BLOCKER_COMMAND,
|
|
73
78
|
blocking: "hard",
|
|
74
79
|
budget_ms: 5000,
|
|
75
|
-
description: `Blocker: deny ${blockerMatch} on protected branches unless a fresh branch:non-protected tag or
|
|
80
|
+
description: `Blocker: deny ${blockerMatch} on protected branches unless a fresh branch:non-protected tag exists in the ledger or the operator-only override marker (harness approve branch-protection) is present.`,
|
|
76
81
|
},
|
|
77
82
|
];
|
|
78
83
|
}
|
|
@@ -113,7 +118,8 @@ While this pack is enabled, hooks are wired into the ${settingsArtefact}:
|
|
|
113
118
|
\`${blockerMatch}\`: refuses the tool call unless EITHER
|
|
114
119
|
- a \`${NON_PROTECTED_TAG_PREFIX}\` tag exists in the ledger from
|
|
115
120
|
within the last ${minutes} minutes, OR
|
|
116
|
-
-
|
|
121
|
+
- the operator-only override marker exists at
|
|
122
|
+
\`harness.generated/.approvals/branch-protection-<sessionId>\`.
|
|
117
123
|
|
|
118
124
|
## Escape hatches
|
|
119
125
|
|
|
@@ -122,12 +128,16 @@ While this pack is enabled, hooks are wired into the ${settingsArtefact}:
|
|
|
122
128
|
is gated by the Understanding Gate but the producer command is itself
|
|
123
129
|
a \`harness ...\` invocation that the gate's allowlist accepts.
|
|
124
130
|
|
|
125
|
-
- **Explicit override** (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
deliberate reason to edit a protected branch
|
|
129
|
-
workflow patches,
|
|
130
|
-
|
|
131
|
+
- **Explicit override** (operator only): from an un-hooked shell run
|
|
132
|
+
\`harness approve branch-protection --session <sessionId>\`. This writes
|
|
133
|
+
the canonical approval marker the blocker consults. Use it when you have
|
|
134
|
+
a deliberate reason to edit a protected branch (version bumps, CI
|
|
135
|
+
workflow patches, hotfixes). SECURITY (audit finding #39): a
|
|
136
|
+
\`${ACK_TAG_PREFIX}:<reason>\` ledger tag is NO LONGER sufficient on its
|
|
137
|
+
own — it is agent-writable via \`mcp__agent-grounding__ledger_add\`, so
|
|
138
|
+
the gate would otherwise be self-approvable. The approve verb still
|
|
139
|
+
records that ledger tag for audit, but only the marker file (which the
|
|
140
|
+
agent cannot write) opens the gate.
|
|
131
141
|
|
|
132
142
|
## Out of scope (v1)
|
|
133
143
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch-protection.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/branch-protection.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,sEAAsE;AACtE,uEAAuE;AACvE,sEAAsE;AACtE,wEAAwE;AACxE,EAAE;AACF,yDAAyD;AACzD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,qEAAqE;AACrE,uCAAuC;AACvC,EAAE;AACF,kEAAkE;AAClE,sEAAsE;AACtE,wDAAwD;AACxD,+DAA+D;AAC/D,
|
|
1
|
+
{"version":3,"file":"branch-protection.js","sourceRoot":"","sources":["../../../src/policy-packs/builtin/branch-protection.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,6 +36,15 @@ export interface Verdict {
|
|
|
36
36
|
}
|
|
37
37
|
/** Env knob that overrides the verdict directory (mirrors the producer). */
|
|
38
38
|
export declare const VERDICT_DIR_ENV = "SOLUTION_VERDICT_DIR";
|
|
39
|
+
/**
|
|
40
|
+
* Env knob that supplies the verdict id for SOLO / non-agent-tasks sessions.
|
|
41
|
+
* The completion-gate consults it ONLY when no agent-tasks `active-claim` is
|
|
42
|
+
* recorded (resolution order: active-claim first, then this env, then
|
|
43
|
+
* fail-closed), so a claimed session's id stays authoritative and cannot be
|
|
44
|
+
* redirected by an env var. A sessionId fallback is intentionally still NOT a
|
|
45
|
+
* source (the wrong-scope bug class understanding-gate closed).
|
|
46
|
+
*/
|
|
47
|
+
export declare const VERDICT_ID_ENV = "SOLUTION_VERDICT_ID";
|
|
39
48
|
/**
|
|
40
49
|
* Stable tail of the default verdict dir. The write-guard's reference
|
|
41
50
|
* detection matches on this so ANY spelling of the home prefix is caught
|
|
@@ -61,6 +70,15 @@ export declare function verdictDir(env?: NodeJS.ProcessEnv, homedir?: () => stri
|
|
|
61
70
|
*/
|
|
62
71
|
export declare function sanitizeVerdictId(id: string): string;
|
|
63
72
|
export declare function verdictPathFor(dir: string, id: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the explicit verdict id from `SOLUTION_VERDICT_ID`, or null when it
|
|
75
|
+
* is unset, blank, or not a safe single path segment. Validated through
|
|
76
|
+
* `sanitizeVerdictId` so a traversal-y or empty value fails closed here
|
|
77
|
+
* (returns null -> the gate denies) rather than reaching the marker read. This
|
|
78
|
+
* is the solo / non-agent-tasks fallback the completion-gate uses only when no
|
|
79
|
+
* active-claim is present.
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveExplicitVerdictId(env?: NodeJS.ProcessEnv): string | null;
|
|
64
82
|
/**
|
|
65
83
|
* Read + validate the verdict marker for `id`, or null when it is absent,
|
|
66
84
|
* unparseable, a symlink, or not a regular file. The lstat + symlink reject
|
|
@@ -75,6 +75,15 @@ export function resolveProtectedCompletionTools(pack) {
|
|
|
75
75
|
}
|
|
76
76
|
/** Env knob that overrides the verdict directory (mirrors the producer). */
|
|
77
77
|
export const VERDICT_DIR_ENV = "SOLUTION_VERDICT_DIR";
|
|
78
|
+
/**
|
|
79
|
+
* Env knob that supplies the verdict id for SOLO / non-agent-tasks sessions.
|
|
80
|
+
* The completion-gate consults it ONLY when no agent-tasks `active-claim` is
|
|
81
|
+
* recorded (resolution order: active-claim first, then this env, then
|
|
82
|
+
* fail-closed), so a claimed session's id stays authoritative and cannot be
|
|
83
|
+
* redirected by an env var. A sessionId fallback is intentionally still NOT a
|
|
84
|
+
* source (the wrong-scope bug class understanding-gate closed).
|
|
85
|
+
*/
|
|
86
|
+
export const VERDICT_ID_ENV = "SOLUTION_VERDICT_ID";
|
|
78
87
|
/**
|
|
79
88
|
* Stable tail of the default verdict dir. The write-guard's reference
|
|
80
89
|
* detection matches on this so ANY spelling of the home prefix is caught
|
|
@@ -116,6 +125,29 @@ export function sanitizeVerdictId(id) {
|
|
|
116
125
|
export function verdictPathFor(dir, id) {
|
|
117
126
|
return path.join(dir, `${sanitizeVerdictId(id)}.json`);
|
|
118
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolve the explicit verdict id from `SOLUTION_VERDICT_ID`, or null when it
|
|
130
|
+
* is unset, blank, or not a safe single path segment. Validated through
|
|
131
|
+
* `sanitizeVerdictId` so a traversal-y or empty value fails closed here
|
|
132
|
+
* (returns null -> the gate denies) rather than reaching the marker read. This
|
|
133
|
+
* is the solo / non-agent-tasks fallback the completion-gate uses only when no
|
|
134
|
+
* active-claim is present.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveExplicitVerdictId(env = process.env) {
|
|
137
|
+
const raw = env[VERDICT_ID_ENV];
|
|
138
|
+
if (typeof raw !== "string")
|
|
139
|
+
return null;
|
|
140
|
+
const trimmed = raw.trim();
|
|
141
|
+
if (trimmed.length === 0)
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
sanitizeVerdictId(trimmed);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return trimmed;
|
|
150
|
+
}
|
|
119
151
|
/**
|
|
120
152
|
* Read + validate the verdict marker for `id`, or null when it is absent,
|
|
121
153
|
* unparseable, a symlink, or not a regular file. The lstat + symlink reject
|