@lannguyensi/harness 0.61.0 → 0.63.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/README.md +1 -1
- package/dist/cli/apply/install-codex-config.d.ts +8 -5
- package/dist/cli/apply/install-codex-config.js +196 -25
- package/dist/cli/apply/install-codex-config.js.map +1 -1
- package/dist/cli/approve/understanding.d.ts +36 -2
- package/dist/cli/approve/understanding.js +191 -42
- package/dist/cli/approve/understanding.js.map +1 -1
- package/dist/cli/bounded-stdin.d.ts +15 -2
- package/dist/cli/bounded-stdin.js +32 -3
- package/dist/cli/bounded-stdin.js.map +1 -1
- package/dist/cli/explain-policy.d.ts +11 -0
- package/dist/cli/explain-policy.js +41 -3
- package/dist/cli/explain-policy.js.map +1 -1
- package/dist/cli/gc/index.d.ts +6 -2
- package/dist/cli/gc/index.js +22 -7
- package/dist/cli/gc/index.js.map +1 -1
- package/dist/cli/index.js +15 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pack/auto-approve-path.js +29 -3
- package/dist/cli/pack/auto-approve-path.js.map +1 -1
- package/dist/cli/pack/hook-bootstrap.d.ts +0 -12
- package/dist/cli/pack/hook-bootstrap.js +1 -27
- package/dist/cli/pack/hook-bootstrap.js.map +1 -1
- package/dist/cli/pack/hook-codex-pre-tool-use.js +4 -2
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.js +19 -5
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-runtime-reality.js +2 -2
- package/dist/cli/pack/hook-runtime-reality.js.map +1 -1
- package/dist/cli/policy/intercept.js +75 -103
- package/dist/cli/policy/intercept.js.map +1 -1
- package/dist/cli/policy/risk-envelope-enrichment.d.ts +40 -0
- package/dist/cli/policy/risk-envelope-enrichment.js +108 -0
- package/dist/cli/policy/risk-envelope-enrichment.js.map +1 -0
- package/dist/io/atomic-write.d.ts +37 -1
- package/dist/io/atomic-write.js +61 -8
- package/dist/io/atomic-write.js.map +1 -1
- package/dist/io/display-path.d.ts +11 -0
- package/dist/io/display-path.js +29 -0
- package/dist/io/display-path.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +152 -21
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +286 -48
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/io/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAY,aAAa,EAAE,MAAM,MAAM,CAAC;AAM/C,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,OAAe,EACf,UAA8B,EAAE;
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../../src/io/atomic-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAY,aAAa,EAAE,MAAM,MAAM,CAAC;AAM/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAClC,EAAE,CAAC,SAAS,CAAC,QAAQ;IACrB,EAAE,CAAC,SAAS,CAAC,OAAO;IACpB,EAAE,CAAC,SAAS,CAAC,MAAM;IACnB,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC;AAEjC,6FAA6F;AAC7F,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,OAAe,EACf,UAA8B,EAAE,EAChC,SAAuB,gBAAgB;IAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1E,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;IAChF,IAAI,CAAC;QACH,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YAC1B,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,gEAAgE;QAClE,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,UAAkB,EAClB,MAAsC;IAEtC,MAAM,GAAG,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IACtC,MAAM,CAAC,GAAG,CAAC,CAAC;IACZ,iFAAiF;IACjF,sEAAsE;IACtE,4EAA4E;IAC5E,uEAAuE;IACvE,8BAA8B;IAC9B,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `value` as one double-quoted, single-line literal that is safe to print:
|
|
3
|
+
* `JSON.stringify` escapes `"`, `\`, every C0 control character (ESC as
|
|
4
|
+
* `\u001b`, CR as `\r`, LF as `\n`) and lone surrogates, and DEL and the C1
|
|
5
|
+
* range, which JSON leaves raw (U+009B is a one-byte CSI on terminals that
|
|
6
|
+
* honour C1), are escaped here as `\uXXXX`. Unicode format characters
|
|
7
|
+
* (including bidi overrides) and line/paragraph separators are escaped too.
|
|
8
|
+
* Supplementary characters use two UTF-16 `\uXXXX` escapes so the result
|
|
9
|
+
* stays a JSON literal that parses back to the original string.
|
|
10
|
+
*/
|
|
11
|
+
export declare function escapeForDisplay(value: string): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Safe rendering of a path (or any other agent-chosen string) into text an
|
|
2
|
+
// operator reads on a terminal.
|
|
3
|
+
//
|
|
4
|
+
// A file name in a directory the gated agent can write is attacker-chosen:
|
|
5
|
+
// POSIX permits every byte but `/` and NUL in it, so it can carry an ESC
|
|
6
|
+
// (an OSC 52 clipboard write, a screen clear), a CR (overwrite the line it
|
|
7
|
+
// lands on) or a newline (forge an extra diagnostic-looking line). A path
|
|
8
|
+
// that reaches stderr therefore goes through this one helper; it ESCAPES
|
|
9
|
+
// rather than strips, so the operator can still see which name is meant and
|
|
10
|
+
// can copy its escaped form into a recovery command.
|
|
11
|
+
//
|
|
12
|
+
// Lives in `src/io/` for the same reason `project-name.ts` does: it is a
|
|
13
|
+
// leaf utility any layer may use (`io/` may not import from `runtime/`).
|
|
14
|
+
/** Controls JSON leaves raw, Unicode format characters and line separators. */
|
|
15
|
+
const UNSAFE_DISPLAY_CHARACTERS = /[\u007f-\u009f\p{Cf}\u2028\u2029]/gu;
|
|
16
|
+
/**
|
|
17
|
+
* `value` as one double-quoted, single-line literal that is safe to print:
|
|
18
|
+
* `JSON.stringify` escapes `"`, `\`, every C0 control character (ESC as
|
|
19
|
+
* `\u001b`, CR as `\r`, LF as `\n`) and lone surrogates, and DEL and the C1
|
|
20
|
+
* range, which JSON leaves raw (U+009B is a one-byte CSI on terminals that
|
|
21
|
+
* honour C1), are escaped here as `\uXXXX`. Unicode format characters
|
|
22
|
+
* (including bidi overrides) and line/paragraph separators are escaped too.
|
|
23
|
+
* Supplementary characters use two UTF-16 `\uXXXX` escapes so the result
|
|
24
|
+
* stays a JSON literal that parses back to the original string.
|
|
25
|
+
*/
|
|
26
|
+
export function escapeForDisplay(value) {
|
|
27
|
+
return JSON.stringify(value).replace(UNSAFE_DISPLAY_CHARACTERS, (ch) => ch.split("").map((unit) => `\\u${unit.charCodeAt(0).toString(16).padStart(4, "0")}`).join(""));
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=display-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-path.js","sourceRoot":"","sources":["../../src/io/display-path.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,gCAAgC;AAChC,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAC1E,yEAAyE;AACzE,4EAA4E;AAC5E,qDAAqD;AACrD,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AAEzE,+EAA+E;AAC/E,MAAM,yBAAyB,GAAG,qCAAqC,CAAC;AAExE;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAClC,yBAAyB,EACzB,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CACtG,CAAC;AACJ,CAAC"}
|
|
@@ -6,7 +6,7 @@ export interface ApprovalCheckResult {
|
|
|
6
6
|
}
|
|
7
7
|
export { APPROVED_LEDGER_TAG_PREFIX, approvedLedgerTagFor, isPolicyDecisionRow, matchLedgerEntries, } from "./ledger.js";
|
|
8
8
|
export { APPROVAL_MARKER_DIRNAME, approvalMarkerPathFor, type ApprovalMarker, writeApprovalMarker, type MarkerCheck, type CheckApprovalMarkerOptions, checkApprovalMarker, clearApprovalMarker, } from "./markers.js";
|
|
9
|
-
export { type PersistedReport, REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, type SkippedReportEntry, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, type FindReportOptions, type FindReportSelection, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, type PersistedReportEvidence, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, type ReportFileReadFailure, type BoundedReportRead, readReportFileBounded, type UnhashableReportReason, type ReportFileHash, hashReportFile, type ApprovalMarkerKind, type MarkerReportBinding, type ApprovedReportHashVerification, verifyApprovedReportHash, } from "./persisted-reports.js";
|
|
9
|
+
export { type PersistedReport, REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, type SkippedReportEntry, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, type FindReportOptions, type FindReportSelection, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, type PersistedReportEvidence, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, MAX_HOOK_LISTING_ENTRIES, type BoundedDirNames, listDirNamesBounded, type ReportFileReadFailure, type BoundedReportRead, type ReadBudget, readReportFileBounded, type UnhashableReportReason, type ReportFileHash, hashReportFile, type ApprovalMarkerKind, type MarkerReportBinding, type ApprovedReportHashVerification, verifyApprovedReportHash, } from "./persisted-reports.js";
|
|
10
10
|
export { type ApprovalLifecycle, parseApprovalLifecycle } from "./lifecycle.js";
|
|
11
11
|
export { AUTO_APPROVED_BY_PREFIX, CLAUDE_CODE_HARNESS, autoApprovedByFor, type ParsedAutoApprovedBy, parseAutoApprovedBy, autoApprovedLedgerTagFor, type AutoApproveConfig, parseAutoApprove, permissionModeAllowed, harnessAllowed, AUTO_APPROVE_HARNESS_VALUES, DEFAULT_AUTO_APPROVE_HARNESSES, DEFAULT_REPORT_SCAN_MAX_WAIT_MS, REPORT_SCAN_MAX_WAIT_CEILING_MS, parseReportScanMaxWait, } from "./auto-approve.js";
|
|
12
12
|
export { type AutoApproveDefaultConfig, defaultAutoApproveConfig, renderAutoApproveSnippet, } from "./auto-approve-default.js";
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
// harness gate; `harness approve understanding` is the approval path.
|
|
38
38
|
export { APPROVED_LEDGER_TAG_PREFIX, approvedLedgerTagFor, isPolicyDecisionRow, matchLedgerEntries, } from "./ledger.js";
|
|
39
39
|
export { APPROVAL_MARKER_DIRNAME, approvalMarkerPathFor, writeApprovalMarker, checkApprovalMarker, clearApprovalMarker, } from "./markers.js";
|
|
40
|
-
export { REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, readReportFileBounded, hashReportFile, verifyApprovedReportHash, } from "./persisted-reports.js";
|
|
40
|
+
export { REPORTS_DIR_ENV, defaultReportsDir, reportsDirForManifest, listPersistedReports, listPersistedReportsBoundedWithSkips, TOLERANT_FALLBACK_MAX_AGE_MS, TOLERANT_FALLBACK_FUTURE_SKEW_MS, selectReportForSession, selectNewestStrictSessionReport, findLatestReportForSession, expirePersistedReport, checkPersistedReport, canonicalReportHash, canonicalReportHashOfFile, MAX_HASHED_REPORT_BYTES, MAX_HOOK_LISTING_ENTRIES, listDirNamesBounded, readReportFileBounded, hashReportFile, verifyApprovedReportHash, } from "./persisted-reports.js";
|
|
41
41
|
export { parseApprovalLifecycle } from "./lifecycle.js";
|
|
42
42
|
export { AUTO_APPROVED_BY_PREFIX, CLAUDE_CODE_HARNESS, autoApprovedByFor, parseAutoApprovedBy, autoApprovedLedgerTagFor, parseAutoApprove, permissionModeAllowed, harnessAllowed, AUTO_APPROVE_HARNESS_VALUES, DEFAULT_AUTO_APPROVE_HARNESSES, DEFAULT_REPORT_SCAN_MAX_WAIT_MS, REPORT_SCAN_MAX_WAIT_CEILING_MS, parseReportScanMaxWait, } from "./auto-approve.js";
|
|
43
43
|
// D-004 shipped default (task 8f637efd): the one `auto_approve` snippet
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,mEAAmE;AACnE,sEAAsE;AACtE,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,uEAAuE;AACvE,oEAAoE;AACpE,qEAAqE;AACrE,yEAAyE;AACzE,uEAAuE;AACvE,oEAAoE;AACpE,qEAAqE;AACrE,sEAAsE;AACtE,uEAAuE;AACvE,sEAAsE;AACtE,oEAAoE;AACpE,yEAAyE;AACzE,gEAAgE;AAChE,0EAA0E;AAC1E,yEAAyE;AACzE,uEAAuE;AACvE,yEAAyE;AACzE,uEAAuE;AACvE,sEAAsE;AACtE,kEAAkE;AAClE,wEAAwE;AACxE,yDAAyD;AACzD,qEAAqE;AACrE,sEAAsE;AACtE,uEAAuE;AACvE,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,oEAAoE;AACpE,sEAAsE;AAqBtE,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EAErB,mBAAmB,EAGnB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAEL,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EAEpB,oCAAoC,EACpC,4BAA4B,EAC5B,gCAAgC,EAGhC,sBAAsB,EACtB,+BAA+B,EAC/B,0BAA0B,EAE1B,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,yBAAyB,EACzB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/policy-packs/builtin/understanding-before-execution/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -55,16 +55,16 @@ export declare function defaultReportsDir(cwd?: string): string;
|
|
|
55
55
|
export declare function reportsDirForManifest(manifestPath: string): string;
|
|
56
56
|
/**
|
|
57
57
|
* List persisted reports under `dir`, newest-first by creation time
|
|
58
|
-
* (JSON `createdAt`,
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
58
|
+
* (JSON `createdAt`, then the filename ISO prefix, then mtime). Missing
|
|
59
|
+
* directory returns []; an I/O error on a single file skips it. Reads each
|
|
60
|
+
* regular file IN FULL, so it must not list the agent-writable reports
|
|
61
|
+
* directory (use {@link listPersistedReportsBoundedWithSkips}); no `src`
|
|
62
|
+
* caller remains, it stays for its tests and the pack's export surface.
|
|
62
63
|
*
|
|
63
64
|
* Creation time, NOT mtime, is the sort key: `harness approve
|
|
64
65
|
* understanding` rewrites the report it flips, which bumps mtime and
|
|
65
66
|
* made an old just-approved report sort as the freshest
|
|
66
|
-
* (harness-discovery C1). mtime
|
|
67
|
-
* fallback for files that carry neither timestamp.
|
|
67
|
+
* (harness-discovery C1). mtime is the last-resort fallback.
|
|
68
68
|
*/
|
|
69
69
|
export declare function listPersistedReports(dir: string): PersistedReport[];
|
|
70
70
|
/**
|
|
@@ -276,6 +276,86 @@ export declare function describeBoundaryExpiry(report: Pick<PersistedReport, "ap
|
|
|
276
276
|
* producers refuse to approve such a report.
|
|
277
277
|
*/
|
|
278
278
|
export declare const MAX_HASHED_REPORT_BYTES: number;
|
|
279
|
+
/**
|
|
280
|
+
* Budget the gate-read hash scan spends before it gives up (32 MiB), charged
|
|
281
|
+
* per `*.json` entry as the larger of the bytes read and
|
|
282
|
+
* {@link MIN_SCAN_ENTRY_COST_BYTES}. The per-file cap bounds one file, not
|
|
283
|
+
* their number: 160 planted 1 MiB key-heavy files took 15.9 to 17.6 s of scan
|
|
284
|
+
* time, past the 15 s PreToolUse budget, and a hook past its budget is a
|
|
285
|
+
* non-blocking error for the runtime (an allow). Charging bytes alone left the
|
|
286
|
+
* entry count open: entries that cost no bytes (tiny files, directories named
|
|
287
|
+
* `*.json`, files over the cap, unreadable ones) still cost an open each, and
|
|
288
|
+
* 400k tiny files took about 22 s of CPU. A scan that spent the budget without
|
|
289
|
+
* finding every wanted hash stops and the check fails closed with the mismatch
|
|
290
|
+
* reason. With the floor the scan opens at most 8192 entries and reads about
|
|
291
|
+
* 32 MiB (the last file read may add up to the 1 MiB per-file cap), whichever
|
|
292
|
+
* the entries are. Real reports are a few kilobytes, so the budget covers
|
|
293
|
+
* thousands of them. The scan reads the newest file first, which is where the
|
|
294
|
+
* approved report usually is.
|
|
295
|
+
*
|
|
296
|
+
* The other full-directory reads on the PreToolUse path spend the same
|
|
297
|
+
* budget with the same floor, each its own (a fresh {@link ReadBudget} per
|
|
298
|
+
* listing, not one shared across the hook call): the evidence read, the
|
|
299
|
+
* auto-approval precondition listing, the subagent-delegation lookup and the
|
|
300
|
+
* parse-error log lookup each read at most about 32 MiB, and none opens an
|
|
301
|
+
* entry once the directory holds more than {@link MAX_HOOK_LISTING_ENTRIES}.
|
|
302
|
+
*/
|
|
303
|
+
export declare const MAX_HASH_SCAN_BYTES: number;
|
|
304
|
+
/**
|
|
305
|
+
* Least a `*.json` entry is charged against {@link MAX_HASH_SCAN_BYTES}
|
|
306
|
+
* whatever the read returned (4 KiB, about one real report), so entries that
|
|
307
|
+
* read nothing or little still spend the budget at a rate that bounds their
|
|
308
|
+
* count: 32 MiB / 4 KiB = 8192 entries.
|
|
309
|
+
*/
|
|
310
|
+
export declare const MIN_SCAN_ENTRY_COST_BYTES = 4096;
|
|
311
|
+
/**
|
|
312
|
+
* Most entries (8192, the hash scan's budget divided by its per-entry floor)
|
|
313
|
+
* a PreToolUse-path reader accepts in one agent-writable directory. The hash
|
|
314
|
+
* scan stops at its budget, but four more readers on the same hook path used
|
|
315
|
+
* to open every entry (the evidence read, the auto-approval precondition
|
|
316
|
+
* listing, the subagent-delegation lookup, the parse-error log lookup), so a
|
|
317
|
+
* directory of hundreds of thousands of planted entries took the hook past the
|
|
318
|
+
* 15 s PreToolUse budget, which the runtime treats as an allow (measured at
|
|
319
|
+
* 400k planted entries: 408195 opens and 28.9 s of CPU in the Claude Code
|
|
320
|
+
* hook). The count alone left each entry readable up to the 1 MiB per-file
|
|
321
|
+
* cap, so every one of those readers also charges the entries it reads against
|
|
322
|
+
* {@link MAX_HASH_SCAN_BYTES} (see {@link ReadBudget}).
|
|
323
|
+
*
|
|
324
|
+
* A reader does not take a newest window of such a directory: a partial view
|
|
325
|
+
* would let a hidden newer entry change a selection (an auto-approval would
|
|
326
|
+
* adopt an older pending report while a newer, hidden one is already
|
|
327
|
+
* approved). Past either bound (more entries, or more report data than the
|
|
328
|
+
* byte budget) it returns nothing and reports the directory as too large to
|
|
329
|
+
* read, and each reader treats that as no evidence: the evidence read
|
|
330
|
+
* reports no report, the auto-approval path declines, the delegation lookup
|
|
331
|
+
* captures nothing and the parse-error lookup finds nothing. None of them
|
|
332
|
+
* allows. The directory listing stops as soon as the bound is crossed. See
|
|
333
|
+
* {@link listDirNamesBounded}.
|
|
334
|
+
*/
|
|
335
|
+
export declare const MAX_HOOK_LISTING_ENTRIES: number;
|
|
336
|
+
/** Result of {@link listDirNamesBounded}. */
|
|
337
|
+
export interface BoundedDirNames {
|
|
338
|
+
/** Entry names that end with the suffix, in ascending byte order; empty when `truncated`. */
|
|
339
|
+
names: string[];
|
|
340
|
+
/** True when the directory holds more matching entries than `maxEntries`, or more entries in all than `maxScanned`. */
|
|
341
|
+
truncated: boolean;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Names under `dir` ending in `suffix`, or none and `truncated: true` when
|
|
345
|
+
* there are more than `maxEntries` of them. The one listing the bounded
|
|
346
|
+
* PreToolUse-path readers share, so the bound lives in one place; a missing or
|
|
347
|
+
* unreadable directory lists nothing and is not `truncated`.
|
|
348
|
+
*
|
|
349
|
+
* The listing reads the directory one entry at a time and stops as soon as
|
|
350
|
+
* `maxEntries + 1` matching names have been seen, or `maxScanned` entries of
|
|
351
|
+
* any name (default: {@link HOOK_LISTING_SCAN_FACTOR} times `maxEntries`),
|
|
352
|
+
* so millions of planted names, matching or not, cost a bounded number of
|
|
353
|
+
* directory reads and no allocation of their names. The directory handle is
|
|
354
|
+
* always closed. An unbounded `maxEntries` reads the whole directory. The
|
|
355
|
+
* names come back in ascending byte order (the `fs.readdirSync` order), not
|
|
356
|
+
* in the filesystem's iteration order.
|
|
357
|
+
*/
|
|
358
|
+
export declare function listDirNamesBounded(dir: string, suffix: string, maxEntries: number, maxScanned?: number): BoundedDirNames;
|
|
279
359
|
/** Result of {@link walkContainerDepth}. */
|
|
280
360
|
export interface ContainerDepthWalk {
|
|
281
361
|
/** True when some array/object nests deeper than the walk's `max` levels. */
|
|
@@ -313,7 +393,15 @@ export declare function walkContainerDepth(value: unknown, max: number): Contain
|
|
|
313
393
|
*/
|
|
314
394
|
export declare function canonicalReportHash(report: Record<string, unknown>): string | null;
|
|
315
395
|
/** Why {@link readReportFileBounded} returned no content. */
|
|
316
|
-
export type ReportFileReadFailure = "unreadable" | "not-regular" | "too-large" | "grew";
|
|
396
|
+
export type ReportFileReadFailure = "unreadable" | "not-regular" | "too-large" | "grew" | "over-budget";
|
|
397
|
+
/**
|
|
398
|
+
* Bytes a bounded multi-entry reader has spent against
|
|
399
|
+
* {@link MAX_HASH_SCAN_BYTES}; pass one object to every
|
|
400
|
+
* {@link readReportFileBounded} call of the same listing.
|
|
401
|
+
*/
|
|
402
|
+
export interface ReadBudget {
|
|
403
|
+
spent: number;
|
|
404
|
+
}
|
|
317
405
|
export type BoundedReportRead = {
|
|
318
406
|
ok: true;
|
|
319
407
|
raw: string;
|
|
@@ -337,8 +425,22 @@ export type BoundedReportRead = {
|
|
|
337
425
|
* (never more than the cap plus one byte); a file that grows into that byte
|
|
338
426
|
* while being read is refused too. The descriptor is always closed. Never
|
|
339
427
|
* throws.
|
|
428
|
+
*
|
|
429
|
+
* `opts.noFollow` adds `O_NOFOLLOW` to the open: a path that is a symbolic
|
|
430
|
+
* link (whatever it points at, a dangling link included) is refused as
|
|
431
|
+
* not-regular by the open itself (`ELOOP`), with no earlier `lstat` for a
|
|
432
|
+
* swap to race. Off by default, so every hook read keeps following links
|
|
433
|
+
* exactly as before; `harness approve understanding` turns it on.
|
|
434
|
+
*
|
|
435
|
+
* `opts.budget` charges the read against {@link MAX_HASH_SCAN_BYTES} the way
|
|
436
|
+
* the hash scan does (the larger of the `fstat` size and
|
|
437
|
+
* {@link MIN_SCAN_ENTRY_COST_BYTES} per entry, taken before the read); once
|
|
438
|
+
* the budget is spent the call opens nothing and returns `over-budget`.
|
|
340
439
|
*/
|
|
341
|
-
export declare function readReportFileBounded(filePath: string
|
|
440
|
+
export declare function readReportFileBounded(filePath: string, opts?: {
|
|
441
|
+
noFollow?: boolean;
|
|
442
|
+
budget?: ReadBudget;
|
|
443
|
+
}): BoundedReportRead;
|
|
342
444
|
/** Why a report file has no canonical hash. */
|
|
343
445
|
export type UnhashableReportReason = ReportFileReadFailure | "not-json-object" | "too-deep";
|
|
344
446
|
export type ReportFileHash = {
|
|
@@ -357,9 +459,13 @@ export type ReportFileHash = {
|
|
|
357
459
|
* any of these (the `detail` is its message); the gate-read scan counts such
|
|
358
460
|
* a file as one that matches nothing. Never throws.
|
|
359
461
|
*/
|
|
360
|
-
export declare function hashReportFile(filePath: string
|
|
462
|
+
export declare function hashReportFile(filePath: string, opts?: {
|
|
463
|
+
noFollow?: boolean;
|
|
464
|
+
}): ReportFileHash;
|
|
361
465
|
/** Canonical hash of the report file at `filePath` ({@link hashReportFile}); null when it has none. */
|
|
362
|
-
export declare function canonicalReportHashOfFile(filePath: string
|
|
466
|
+
export declare function canonicalReportHashOfFile(filePath: string, opts?: {
|
|
467
|
+
noFollow?: boolean;
|
|
468
|
+
}): string | null;
|
|
363
469
|
/** Marker kind whose signed report hash the gate checks. */
|
|
364
470
|
export type ApprovalMarkerKind = "task" | "session";
|
|
365
471
|
/** One matched marker's signed `reportContentHash`, tagged with its kind. */
|
|
@@ -412,11 +518,26 @@ export type ApprovedReportHashVerification = {
|
|
|
412
518
|
* the "no report file" allow, the same as before this check existed. The
|
|
413
519
|
* marker and the operator's approval itself are untouched in both cases;
|
|
414
520
|
* only the audit trail loses its claim to be the approved content. (3) The
|
|
415
|
-
* cap bounds each file, not their number
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
521
|
+
* cap bounds each file, not their number, so the scan charges every `*.json`
|
|
522
|
+
* entry at least `MIN_SCAN_ENTRY_COST_BYTES` against `MAX_HASH_SCAN_BYTES`
|
|
523
|
+
* (bytes read or the floor, whichever is larger, whatever the read returned):
|
|
524
|
+
* past the budget the check denies with the mismatch reason (fail closed).
|
|
525
|
+
* That bounds the entries opened (8192) and the bytes read (about 32 MiB, the
|
|
526
|
+
* last file read may add up to the 1 MiB per-file cap). Three costs remain.
|
|
527
|
+
* A legitimate approval whose report sits behind that much newer report data
|
|
528
|
+
* (by name, descending) is denied; re-approving writes a new, newest report
|
|
529
|
+
* and recovers only when the newer entries are real reports: planted `*.json`
|
|
530
|
+
* names that sort after the timestamped report names (for example names
|
|
531
|
+
* starting with a letter) stay newer than any new report and keep denying
|
|
532
|
+
* until removed. The `readdir` plus name sort of the directory itself are not
|
|
533
|
+
* charged, so a directory of millions of entries still costs that listing
|
|
534
|
+
* before this scan starts (the scan runs when a signed marker is present). The
|
|
535
|
+
* hash scan is not the whole hook: the other full-directory reads on it (the
|
|
536
|
+
* evidence read, the auto-approval listing, the delegation lookup and the
|
|
537
|
+
* parse-error lookup) list the directory entry by entry and stop once
|
|
538
|
+
* {@link MAX_HOOK_LISTING_ENTRIES} entries are crossed, and charge the entries
|
|
539
|
+
* they read against the same byte budget; past either bound they open or read
|
|
540
|
+
* nothing more and fail closed.
|
|
420
541
|
*
|
|
421
542
|
* Removing the approved report while other report files remain is a deny:
|
|
422
543
|
* nothing on disk carries the signed content any more. `harness gc --apply`
|
|
@@ -429,8 +550,8 @@ export type ApprovedReportHashVerification = {
|
|
|
429
550
|
* fallback is consulted only when the primary fails the check, so a task
|
|
430
551
|
* marker signed for another report does not hide a session marker that
|
|
431
552
|
* verifies. Unchanged behaviour: a null hash on the accepted marker (no
|
|
432
|
-
* report resolved, or `--force` over a report
|
|
433
|
-
*
|
|
553
|
+
* report resolved, or `--force` over a report nested too deeply to hash,
|
|
554
|
+
* never one over the size cap) and a reports directory with no
|
|
434
555
|
* `*.json` entry at all both allow. A forced approval of a hashable report
|
|
435
556
|
* signs its hash and is checked like any other.
|
|
436
557
|
*
|
|
@@ -465,9 +586,12 @@ export interface SkippedReportEntry {
|
|
|
465
586
|
*
|
|
466
587
|
* A caller that must not let the selection fall back to an older report when
|
|
467
588
|
* a newer one is unreadable (the auto-approval precondition) declines when
|
|
468
|
-
* `skipped` is not empty. The operator commands
|
|
469
|
-
*
|
|
470
|
-
*
|
|
589
|
+
* `skipped` is not empty. The operator commands read through this listing as
|
|
590
|
+
* well, so a planted oversized or non-regular entry cannot crash or hang
|
|
591
|
+
* them, and they still see it: `harness approve understanding` refuses
|
|
592
|
+
* while `skipped` is not empty (it passes `refuseSymlinks`, which also
|
|
593
|
+
* refuses a symbolic link at the open, whatever it points at), and
|
|
594
|
+
* `harness gc` reports each skipped entry and leaves it in place.
|
|
471
595
|
*
|
|
472
596
|
* Placement: this block sits at the end of the file, and
|
|
473
597
|
* {@link readPersistedReport} takes the already-read text (`boundedRaw`), so
|
|
@@ -475,9 +599,16 @@ export interface SkippedReportEntry {
|
|
|
475
599
|
* docs/decisions/2026-08-27-ug-auto-mode-approval.md cites in this file stay
|
|
476
600
|
* put (tests/decisions-citations-resolve.test.ts pins them).
|
|
477
601
|
*/
|
|
478
|
-
export declare function listPersistedReportsBoundedWithSkips(dir: string
|
|
602
|
+
export declare function listPersistedReportsBoundedWithSkips(dir: string, opts?: {
|
|
603
|
+
refuseSymlinks?: boolean;
|
|
604
|
+
maxEntries?: number;
|
|
605
|
+
}): {
|
|
479
606
|
reports: PersistedReport[];
|
|
480
607
|
skipped: SkippedReportEntry[];
|
|
608
|
+
/** True when `opts.maxEntries` was set and the directory holds more `*.json` entries, or more report data than the byte budget; no report is returned. */
|
|
609
|
+
truncated: boolean;
|
|
610
|
+
/** Why the listing was `truncated`, as a clause that follows the subject ("holds more than ..."); empty otherwise. */
|
|
611
|
+
truncatedDetail: string;
|
|
481
612
|
};
|
|
482
613
|
/**
|
|
483
614
|
* {@link listPersistedReportsBoundedWithSkips} without the skipped entries,
|