approval-md 0.4.5 → 0.4.7
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/SPEC.md +24 -3
- package/dist/src/channels/contract.d.ts +15 -0
- package/dist/src/channels/contract.js +23 -1
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/telegram.d.ts +180 -33
- package/dist/src/channels/telegram.js +256 -87
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/channel-telegram.d.ts +114 -35
- package/dist/src/cli/channel-telegram.js +259 -120
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/doctor.js +33 -13
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/help.d.ts +2 -0
- package/dist/src/cli/help.js +39 -3
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-exec-code.d.ts +150 -0
- package/dist/src/cli/hook-exec-code.js +357 -0
- package/dist/src/cli/hook-exec-code.js.map +1 -0
- package/dist/src/cli/hook.d.ts +154 -1
- package/dist/src/cli/hook.js +2344 -111
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/instructions.js +9 -6
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/main.js +9 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/study.d.ts +22 -0
- package/dist/src/cli/study.js +153 -0
- package/dist/src/cli/study.js.map +1 -0
- package/dist/src/cli/verb-registry.d.ts +14 -0
- package/dist/src/cli/verb-registry.js +37 -0
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/core/command-class.d.ts +349 -1
- package/dist/src/core/command-class.js +2347 -87
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/exec-code.d.ts +77 -0
- package/dist/src/core/exec-code.js +60 -0
- package/dist/src/core/exec-code.js.map +1 -0
- package/dist/src/core/gate.d.ts +38 -0
- package/dist/src/core/gate.js +26 -0
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/git-push-argv.d.ts +111 -0
- package/dist/src/core/git-push-argv.js +375 -0
- package/dist/src/core/git-push-argv.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +71 -30
- package/dist/src/core/harness-version.js +101 -34
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +8 -0
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/sender-identity.d.ts +13 -1
- package/dist/src/core/sender-identity.js +15 -0
- package/dist/src/core/sender-identity.js.map +1 -1
- package/dist/src/core/study-withdraw.d.ts +137 -0
- package/dist/src/core/study-withdraw.js +280 -0
- package/dist/src/core/study-withdraw.js.map +1 -0
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/README-extended.md +3 -2
- package/docs/cli-reference.md +83 -7
- package/package.json +2 -2
- package/schema/event.schema.json +213 -1
- package/schema/fixtures/event/invalid/approval-granted-study.json +23 -0
- package/schema/fixtures/event/invalid/approval-rejected-looks-wrong-cli-channel.json +23 -0
- package/schema/fixtures/event/invalid/approval-rejected-looks-wrong-operator.json +24 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-extra-key.json +24 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-unknown-reason.json +23 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-email-operator.json +20 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-no-operator.json +19 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-no-ref.json +19 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-wrong-actor.json +20 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-wrong-channel.json +20 -0
- package/schema/fixtures/event/invalid/approval-revoked-study.json +20 -0
- package/schema/fixtures/event/valid/approval-rejected-looks-wrong.json +23 -0
- package/schema/fixtures/event/valid/approval-rejected-study-withdrawn.json +20 -0
- package/schema/fixtures/policy/invalid/exec-code-custody-boolean.json +11 -0
- package/schema/fixtures/policy/invalid/exec-code-execute-code-value.json +11 -0
- package/schema/fixtures/policy/invalid/exec-code-unknown-key.json +12 -0
- package/schema/fixtures/policy/valid/exec-code-both.json +17 -0
- package/schema/fixtures/policy/valid/exec-code-custody.json +11 -0
- package/schema/policy.schema.json +23 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The disk half of `exec.code` (APRV-510): what a harness hook binds for an
|
|
3
|
+
* opaque program once the policy has opened the class.
|
|
4
|
+
*
|
|
5
|
+
* The classifier (`core/command-class.ts`) reads the TEXT and says which word
|
|
6
|
+
* of which segment carries a program. This module turns that into the bytes a
|
|
7
|
+
* record binds, and it is impure on purpose: a file program is read from disk
|
|
8
|
+
* here, at hook time, and hashed.
|
|
9
|
+
*
|
|
10
|
+
* ## What is bound, and what never is
|
|
11
|
+
*
|
|
12
|
+
* The payload carries, per program, `{interpreter, mode, code_sha256,
|
|
13
|
+
* code_bytes, argv, workdir}` (plus the `path` of a file program), and for a
|
|
14
|
+
* shell call the SHA-256 and length of the whole command line beside the
|
|
15
|
+
* segments that are not programs. The program TEXT is never in it: the
|
|
16
|
+
* payload's hash is what a log record carries and the payload itself is what
|
|
17
|
+
* the payload store and a phone card hold, and a program written by an agent
|
|
18
|
+
* is exactly where a secret it was handed would sit (SPEC.md §11.1 invariant
|
|
19
|
+
* 3). The word of the argv that held the program is replaced by a marker
|
|
20
|
+
* naming its hash. The command line's own hash still binds every byte, so a
|
|
21
|
+
* grant over this payload is a grant over that exact command.
|
|
22
|
+
*
|
|
23
|
+
* ## A file that cannot be read is refused, never guessed
|
|
24
|
+
*
|
|
25
|
+
* `hook-exec-code-unreadable`: the file is absent, unreadable by this process
|
|
26
|
+
* (EACCES under co-location, where the daemon's user cannot list the
|
|
27
|
+
* resident's 0700 home), not a regular file, larger than
|
|
28
|
+
* {@link EXEC_CODE_MAX_FILE_BYTES}, or relative after a `cd` this module
|
|
29
|
+
* cannot place. The same code refuses a file this module cannot place by the
|
|
30
|
+
* kernel's reading rather than its own (fix round 2): a path with a `..`
|
|
31
|
+
* component, a `.` or empty component after the first, or a directory whose
|
|
32
|
+
* realpath is not the directory as written (refuter M1: the hook folds `..`
|
|
33
|
+
* lexically, the kernel follows a symlink first); a file program on a line
|
|
34
|
+
* with any other segment (refuter M2: an earlier `echo > x.py`, `cp`, `mv`,
|
|
35
|
+
* `tee` or `ln -sf` rewrites the file after the hash); and a harness that
|
|
36
|
+
* states no working directory (refuter N8: the hook's own cwd is a guess).
|
|
37
|
+
* That is the OPPOSITE of the skill-script pass (APRV-502),
|
|
38
|
+
* which lets the text stand where the disk is unreadable, and deliberately:
|
|
39
|
+
* a skill script's path is the thing that pass checks, while here the file's
|
|
40
|
+
* bytes ARE the binding, and a record with no hash would record nothing.
|
|
41
|
+
*
|
|
42
|
+
* A file that resolves onto a credential or a protected path is refused
|
|
43
|
+
* `hook-unclassified`, naming the landing, exactly as a skill script that
|
|
44
|
+
* lands there is.
|
|
45
|
+
*
|
|
46
|
+
* ## What the hash does not close
|
|
47
|
+
*
|
|
48
|
+
* A file program is hashed when the hook runs and opened by the interpreter
|
|
49
|
+
* afterwards. The program itself is not running in that window, and the
|
|
50
|
+
* sole-segment rule closes every writer on the same line, so what remains is
|
|
51
|
+
* a writer OTHER than the hooked call: a background job an earlier hooked
|
|
52
|
+
* call started, another session, cron. One that rewrites the file in between
|
|
53
|
+
* makes the interpreter run bytes the record never saw (fix round 3, refuter
|
|
54
|
+
* S2). The record binds the bytes the hook saw; a writer the agent controls is
|
|
55
|
+
* itself an earlier hooked, logged call, so the trail that explains a mismatch
|
|
56
|
+
* is in the log. The record says which program the agent ASKED to run, which
|
|
57
|
+
* is what the class is for; it is not a sandbox, and hash-then-exec cannot
|
|
58
|
+
* close this without handing the interpreter the descriptor that was hashed.
|
|
59
|
+
*/
|
|
60
|
+
import { type ClassifiedSegment, type ExecCodeSource, type ProtectedPathEntry } from "../core/command-class.js";
|
|
61
|
+
/** The largest file program this hook will read and hash. */
|
|
62
|
+
export declare const EXEC_CODE_MAX_FILE_BYTES: number;
|
|
63
|
+
/** How a bound program reached the hook. */
|
|
64
|
+
export type ExecCodeBindingMode = "inline" | "file" | "execute_code";
|
|
65
|
+
/** One program, as a payload binds it. Never its text. */
|
|
66
|
+
export interface ExecCodeBinding {
|
|
67
|
+
interpreter: string;
|
|
68
|
+
mode: ExecCodeBindingMode;
|
|
69
|
+
code_sha256: string;
|
|
70
|
+
code_bytes: number;
|
|
71
|
+
/** The argv from the binary on, the program word replaced by a hash marker. */
|
|
72
|
+
argv: string[];
|
|
73
|
+
/** The absolute directory the program runs in, or `null` when none is stated. */
|
|
74
|
+
workdir: string | null;
|
|
75
|
+
/** A file program's path as written. */
|
|
76
|
+
path?: string;
|
|
77
|
+
}
|
|
78
|
+
/** A shell segment in an `exec.code` payload: its text, or the program it runs. */
|
|
79
|
+
export type ExecCodeSegment = {
|
|
80
|
+
text: string;
|
|
81
|
+
} | {
|
|
82
|
+
exec_code: number;
|
|
83
|
+
};
|
|
84
|
+
/** The payload a shell call carrying `exec.code` segments binds. */
|
|
85
|
+
export interface ExecCodeShellPayload {
|
|
86
|
+
exec_code: ExecCodeBinding[];
|
|
87
|
+
command_sha256: string;
|
|
88
|
+
command_bytes: number;
|
|
89
|
+
segments: ExecCodeSegment[];
|
|
90
|
+
cwd: string;
|
|
91
|
+
}
|
|
92
|
+
export type ExecCodeBindResult = {
|
|
93
|
+
ok: true;
|
|
94
|
+
payload: ExecCodeShellPayload;
|
|
95
|
+
headline: string;
|
|
96
|
+
notes: string[];
|
|
97
|
+
} | {
|
|
98
|
+
ok: false;
|
|
99
|
+
code: "hook-exec-code-unreadable" | "hook-unclassified";
|
|
100
|
+
detail: string;
|
|
101
|
+
};
|
|
102
|
+
/** SHA-256, lowercase hex. */
|
|
103
|
+
export declare function sha256Hex(bytes: string | Buffer): string;
|
|
104
|
+
/** The marker that stands in an argv where the program text was. */
|
|
105
|
+
export declare function programMarker(sha: string, bytes: number): string;
|
|
106
|
+
/**
|
|
107
|
+
* A program named WITHOUT its text, for a refusal or a note (refuter S1, S2):
|
|
108
|
+
* the interpreter, the mode, and the inline program's hash and length or the
|
|
109
|
+
* file's path as written. A deny string reaches the harness and the agent's
|
|
110
|
+
* transcript, and a program is where a secret it was handed sits.
|
|
111
|
+
*/
|
|
112
|
+
export declare function describeProgram(source: ExecCodeSource): string;
|
|
113
|
+
/**
|
|
114
|
+
* Is `text` well-formed UTF-16, with no lone surrogate? (Refuter S5.) A lone
|
|
115
|
+
* surrogate encodes to U+FFFD in UTF-8, so two programs that differ only there
|
|
116
|
+
* would hash alike while a Python harness passes them on as different bytes.
|
|
117
|
+
* Such a program or command is refused rather than bound by a lossy hash.
|
|
118
|
+
*/
|
|
119
|
+
export declare function isWellFormedText(text: string): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Bind every `exec.code` segment of a classified shell command, or refuse.
|
|
122
|
+
*
|
|
123
|
+
* `null` when no segment carries a program: the caller binds the command the
|
|
124
|
+
* way it always has. `workdir` is the directory the command runs in (the
|
|
125
|
+
* per-call one where the harness states it), and `cwd` is the value the
|
|
126
|
+
* ordinary payload would have carried as its `cwd`. `fileWorkdir` is the
|
|
127
|
+
* directory a file program is resolved against: the one the HARNESS states
|
|
128
|
+
* (the per-call key, else the envelope's `cwd`), or `null` when it states
|
|
129
|
+
* none, which refuses file mode rather than guess with the hook's own cwd
|
|
130
|
+
* (fix round 2, refuter N8).
|
|
131
|
+
*/
|
|
132
|
+
export declare function bindExecCodeCommand(raw: string, segments: readonly ClassifiedSegment[], workdir: string, cwd: string, protectedPaths: readonly ProtectedPathEntry[], fileWorkdir: string | null): ExecCodeBindResult | null;
|
|
133
|
+
/** The payload a Hermes `execute_code` call binds. */
|
|
134
|
+
export interface ExecuteCodePayload {
|
|
135
|
+
tool: "execute_code";
|
|
136
|
+
exec_code: ExecCodeBinding;
|
|
137
|
+
/** The SHA-256 of the whole `tool_input`, canonical JSON, so every key is bound. */
|
|
138
|
+
input_sha256: string;
|
|
139
|
+
}
|
|
140
|
+
/** Bind a Hermes `execute_code` call: the `code` string, by hash and length. */
|
|
141
|
+
export declare function bindExecuteCode(toolInput: Record<string, unknown>, inputHash: string): {
|
|
142
|
+
ok: true;
|
|
143
|
+
payload: ExecuteCodePayload;
|
|
144
|
+
headline: string;
|
|
145
|
+
notes: string[];
|
|
146
|
+
} | {
|
|
147
|
+
ok: false;
|
|
148
|
+
code: "hook-io" | "hook-exec-code-unreadable";
|
|
149
|
+
detail: string;
|
|
150
|
+
};
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The disk half of `exec.code` (APRV-510): what a harness hook binds for an
|
|
3
|
+
* opaque program once the policy has opened the class.
|
|
4
|
+
*
|
|
5
|
+
* The classifier (`core/command-class.ts`) reads the TEXT and says which word
|
|
6
|
+
* of which segment carries a program. This module turns that into the bytes a
|
|
7
|
+
* record binds, and it is impure on purpose: a file program is read from disk
|
|
8
|
+
* here, at hook time, and hashed.
|
|
9
|
+
*
|
|
10
|
+
* ## What is bound, and what never is
|
|
11
|
+
*
|
|
12
|
+
* The payload carries, per program, `{interpreter, mode, code_sha256,
|
|
13
|
+
* code_bytes, argv, workdir}` (plus the `path` of a file program), and for a
|
|
14
|
+
* shell call the SHA-256 and length of the whole command line beside the
|
|
15
|
+
* segments that are not programs. The program TEXT is never in it: the
|
|
16
|
+
* payload's hash is what a log record carries and the payload itself is what
|
|
17
|
+
* the payload store and a phone card hold, and a program written by an agent
|
|
18
|
+
* is exactly where a secret it was handed would sit (SPEC.md §11.1 invariant
|
|
19
|
+
* 3). The word of the argv that held the program is replaced by a marker
|
|
20
|
+
* naming its hash. The command line's own hash still binds every byte, so a
|
|
21
|
+
* grant over this payload is a grant over that exact command.
|
|
22
|
+
*
|
|
23
|
+
* ## A file that cannot be read is refused, never guessed
|
|
24
|
+
*
|
|
25
|
+
* `hook-exec-code-unreadable`: the file is absent, unreadable by this process
|
|
26
|
+
* (EACCES under co-location, where the daemon's user cannot list the
|
|
27
|
+
* resident's 0700 home), not a regular file, larger than
|
|
28
|
+
* {@link EXEC_CODE_MAX_FILE_BYTES}, or relative after a `cd` this module
|
|
29
|
+
* cannot place. The same code refuses a file this module cannot place by the
|
|
30
|
+
* kernel's reading rather than its own (fix round 2): a path with a `..`
|
|
31
|
+
* component, a `.` or empty component after the first, or a directory whose
|
|
32
|
+
* realpath is not the directory as written (refuter M1: the hook folds `..`
|
|
33
|
+
* lexically, the kernel follows a symlink first); a file program on a line
|
|
34
|
+
* with any other segment (refuter M2: an earlier `echo > x.py`, `cp`, `mv`,
|
|
35
|
+
* `tee` or `ln -sf` rewrites the file after the hash); and a harness that
|
|
36
|
+
* states no working directory (refuter N8: the hook's own cwd is a guess).
|
|
37
|
+
* That is the OPPOSITE of the skill-script pass (APRV-502),
|
|
38
|
+
* which lets the text stand where the disk is unreadable, and deliberately:
|
|
39
|
+
* a skill script's path is the thing that pass checks, while here the file's
|
|
40
|
+
* bytes ARE the binding, and a record with no hash would record nothing.
|
|
41
|
+
*
|
|
42
|
+
* A file that resolves onto a credential or a protected path is refused
|
|
43
|
+
* `hook-unclassified`, naming the landing, exactly as a skill script that
|
|
44
|
+
* lands there is.
|
|
45
|
+
*
|
|
46
|
+
* ## What the hash does not close
|
|
47
|
+
*
|
|
48
|
+
* A file program is hashed when the hook runs and opened by the interpreter
|
|
49
|
+
* afterwards. The program itself is not running in that window, and the
|
|
50
|
+
* sole-segment rule closes every writer on the same line, so what remains is
|
|
51
|
+
* a writer OTHER than the hooked call: a background job an earlier hooked
|
|
52
|
+
* call started, another session, cron. One that rewrites the file in between
|
|
53
|
+
* makes the interpreter run bytes the record never saw (fix round 3, refuter
|
|
54
|
+
* S2). The record binds the bytes the hook saw; a writer the agent controls is
|
|
55
|
+
* itself an earlier hooked, logged call, so the trail that explains a mismatch
|
|
56
|
+
* is in the log. The record says which program the agent ASKED to run, which
|
|
57
|
+
* is what the class is for; it is not a sandbox, and hash-then-exec cannot
|
|
58
|
+
* close this without handing the interpreter the descriptor that was hashed.
|
|
59
|
+
*/
|
|
60
|
+
import { createHash } from "node:crypto";
|
|
61
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
62
|
+
import { basename, dirname, isAbsolute, join, resolve as resolvePathSegments } from "node:path";
|
|
63
|
+
import { commandSegmentWords, isCredentialPath, protectedPathClass, } from "../core/command-class.js";
|
|
64
|
+
/** The largest file program this hook will read and hash. */
|
|
65
|
+
export const EXEC_CODE_MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
66
|
+
/** SHA-256, lowercase hex. */
|
|
67
|
+
export function sha256Hex(bytes) {
|
|
68
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
69
|
+
}
|
|
70
|
+
/** The marker that stands in an argv where the program text was. */
|
|
71
|
+
export function programMarker(sha, bytes) {
|
|
72
|
+
return `<program sha256:${sha} ${String(bytes)} bytes>`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A program named WITHOUT its text, for a refusal or a note (refuter S1, S2):
|
|
76
|
+
* the interpreter, the mode, and the inline program's hash and length or the
|
|
77
|
+
* file's path as written. A deny string reaches the harness and the agent's
|
|
78
|
+
* transcript, and a program is where a secret it was handed sits.
|
|
79
|
+
*/
|
|
80
|
+
export function describeProgram(source) {
|
|
81
|
+
if (source.mode === "file")
|
|
82
|
+
return `${source.interpreter} file ${source.source}`;
|
|
83
|
+
const bytes = Buffer.from(source.source, "utf8");
|
|
84
|
+
return `${source.interpreter} inline program sha256:${sha256Hex(bytes)} (${String(bytes.length)} bytes)`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Is `text` well-formed UTF-16, with no lone surrogate? (Refuter S5.) A lone
|
|
88
|
+
* surrogate encodes to U+FFFD in UTF-8, so two programs that differ only there
|
|
89
|
+
* would hash alike while a Python harness passes them on as different bytes.
|
|
90
|
+
* Such a program or command is refused rather than bound by a lossy hash.
|
|
91
|
+
*/
|
|
92
|
+
export function isWellFormedText(text) {
|
|
93
|
+
return !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(text);
|
|
94
|
+
}
|
|
95
|
+
/** A short form of a binding for a headline: `python3 -c sha256:0123456789ab (42 bytes)`. */
|
|
96
|
+
function headlineOf(binding) {
|
|
97
|
+
const how = binding.mode === "inline"
|
|
98
|
+
? `${binding.interpreter} inline`
|
|
99
|
+
: binding.mode === "file"
|
|
100
|
+
? `${binding.interpreter} ${binding.path ?? "file"}`
|
|
101
|
+
: "execute_code";
|
|
102
|
+
return `exec.code ${how} sha256:${binding.code_sha256.slice(0, 12)} (${String(binding.code_bytes)} bytes)`;
|
|
103
|
+
}
|
|
104
|
+
/** Does an earlier segment move the working directory (`cd`, `pushd`, `popd`)? */
|
|
105
|
+
function directoryMovedBefore(segments, index) {
|
|
106
|
+
for (const earlier of segments.slice(0, index)) {
|
|
107
|
+
const parsed = commandSegmentWords(earlier.text)?.[0];
|
|
108
|
+
if (parsed === undefined)
|
|
109
|
+
continue;
|
|
110
|
+
const bin = parsed.bin.split("/").pop() ?? parsed.bin;
|
|
111
|
+
if (bin === "cd" || bin === "pushd" || bin === "popd")
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/** The `code` of a filesystem error, or `null`. */
|
|
117
|
+
function fsCode(error) {
|
|
118
|
+
if (typeof error !== "object" || error === null || !("code" in error))
|
|
119
|
+
return null;
|
|
120
|
+
const code = error.code;
|
|
121
|
+
return typeof code === "string" ? code : null;
|
|
122
|
+
}
|
|
123
|
+
/** Read one file program, refusing everything this module cannot vouch for. */
|
|
124
|
+
function readProgramFile(source, spelled, protectedPaths) {
|
|
125
|
+
const unreadable = (why) => ({
|
|
126
|
+
ok: false,
|
|
127
|
+
code: "hook-exec-code-unreadable",
|
|
128
|
+
detail: `the program file ${JSON.stringify(source)} ${why}, so its bytes cannot be hashed and nothing could bind what would run. Nothing was appended. Run it as an inline program, or from a file this hook can read`,
|
|
129
|
+
});
|
|
130
|
+
let real;
|
|
131
|
+
try {
|
|
132
|
+
real = realpathSync(spelled);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
return unreadable(`cannot be resolved at ${spelled} (${fsCode(error) ?? "unknown error"})`);
|
|
136
|
+
}
|
|
137
|
+
if (isCredentialPath(real)) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
code: "hook-unclassified",
|
|
141
|
+
detail: `the program file ${JSON.stringify(source)} resolves to ${real}, a credential path (account.credential); a program read from credential material is not run as exec.code`,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const protectedClass = protectedPathClass(real, protectedPaths);
|
|
145
|
+
if (protectedClass !== null) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
code: "hook-unclassified",
|
|
149
|
+
detail: `the program file ${JSON.stringify(source)} resolves to ${real}, a protected path (${protectedClass}); a program read from the gate's own files is not run as exec.code`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const stat = statSync(real);
|
|
154
|
+
if (!stat.isFile())
|
|
155
|
+
return unreadable(`resolves to ${real}, which is not a regular file`);
|
|
156
|
+
if (stat.size > EXEC_CODE_MAX_FILE_BYTES) {
|
|
157
|
+
return unreadable(`is ${String(stat.size)} bytes, above the ${String(EXEC_CODE_MAX_FILE_BYTES)}-byte limit this hook hashes`);
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, bytes: readFileSync(real) };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return unreadable(`cannot be read at ${real} (${fsCode(error) ?? "unknown error"})`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Does a file program's path carry a component the kernel reads differently
|
|
167
|
+
* from a lexical join? (Fix round 2, refuter M1.) A `..` anywhere (the kernel
|
|
168
|
+
* follows a symlink before it climbs, `path.resolve` folds it first), and a
|
|
169
|
+
* `.` or empty component after the first (`./x.py` and an absolute path's
|
|
170
|
+
* leading `/` stay). Stricter than the kernel needs, on purpose: such a path
|
|
171
|
+
* is refused, never folded.
|
|
172
|
+
*/
|
|
173
|
+
function indirectPathComponent(path) {
|
|
174
|
+
const parts = path.split("/");
|
|
175
|
+
if (parts.includes(".."))
|
|
176
|
+
return "a `..` component";
|
|
177
|
+
if (parts.slice(1).includes("."))
|
|
178
|
+
return "a `.` component after the first";
|
|
179
|
+
if (parts.slice(1).includes(""))
|
|
180
|
+
return "an empty component (`//` or a trailing `/`)";
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/** One segment's program, bound. */
|
|
184
|
+
function bindSegment(source, segments, index, workdir, fileWorkdir, protectedPaths) {
|
|
185
|
+
let bytes;
|
|
186
|
+
if (source.mode === "inline") {
|
|
187
|
+
bytes = Buffer.from(source.source, "utf8");
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const refuse = (why) => ({
|
|
191
|
+
ok: false,
|
|
192
|
+
code: "hook-exec-code-unreadable",
|
|
193
|
+
detail: `the program file ${JSON.stringify(source.source)} ${why}, so this hook cannot tell which file would run. Nothing was appended. Run it as an inline program, or as the only command of the line, by a path with no \`..\` and no symlinked directory`,
|
|
194
|
+
});
|
|
195
|
+
if (!isAbsolute(source.source) && directoryMovedBefore(segments, index)) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
code: "hook-exec-code-unreadable",
|
|
199
|
+
detail: `the program file ${JSON.stringify(source.source)} is relative and an earlier segment of the command moves the working directory, so this hook cannot tell which file would run. Nothing was appended. Name the file by its absolute path`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Refuter M2: the hash is taken now and every other segment of the line
|
|
203
|
+
// runs before or beside the interpreter, so any of them can rewrite,
|
|
204
|
+
// move or relink the file (`echo … > x.py; python3 x.py`). File mode
|
|
205
|
+
// opens only for a program that is the line's sole segment.
|
|
206
|
+
if (segments.length !== 1) {
|
|
207
|
+
return refuse("shares the line with another segment, which may write, move or relink it after this hook hashes it (file mode is open only when the program is the line's sole command)");
|
|
208
|
+
}
|
|
209
|
+
// Refuter N8: the directory the harness says the command runs in, or
|
|
210
|
+
// nothing. The hook's own cwd is not evidence of where a relative path
|
|
211
|
+
// lands, so a harness that states none gets no file mode.
|
|
212
|
+
if (fileWorkdir === null || !isAbsolute(fileWorkdir)) {
|
|
213
|
+
return refuse("is named on a call whose harness states no absolute working directory");
|
|
214
|
+
}
|
|
215
|
+
// Refuter M1: no `..` to fold, and the directory resolved by the kernel's
|
|
216
|
+
// reading (realpath) must be the directory as written, so a symlinked
|
|
217
|
+
// directory, a symlinked workdir included, is refused rather than folded.
|
|
218
|
+
const indirect = indirectPathComponent(source.source);
|
|
219
|
+
if (indirect !== null)
|
|
220
|
+
return refuse(`has ${indirect}`);
|
|
221
|
+
const directory = resolvePathSegments(fileWorkdir, dirname(source.source));
|
|
222
|
+
let realDirectory;
|
|
223
|
+
try {
|
|
224
|
+
realDirectory = realpathSync(directory);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
return refuse(`sits in ${directory}, which cannot be resolved (${fsCode(error) ?? "unknown error"})`);
|
|
228
|
+
}
|
|
229
|
+
if (realDirectory !== directory) {
|
|
230
|
+
return refuse(`sits in ${directory}, which resolves through a symlink to ${realDirectory}`);
|
|
231
|
+
}
|
|
232
|
+
const read = readProgramFile(source.source, join(realDirectory, basename(source.source)), protectedPaths);
|
|
233
|
+
if (!read.ok)
|
|
234
|
+
return read;
|
|
235
|
+
bytes = read.bytes;
|
|
236
|
+
}
|
|
237
|
+
const sha = sha256Hex(bytes);
|
|
238
|
+
const argv = [...source.argv];
|
|
239
|
+
if (source.mode === "inline")
|
|
240
|
+
argv[source.sourceIndex] = programMarker(sha, bytes.length);
|
|
241
|
+
return {
|
|
242
|
+
ok: true,
|
|
243
|
+
binding: {
|
|
244
|
+
interpreter: source.interpreter,
|
|
245
|
+
mode: source.mode,
|
|
246
|
+
code_sha256: sha,
|
|
247
|
+
code_bytes: bytes.length,
|
|
248
|
+
argv,
|
|
249
|
+
workdir: source.mode === "file" ? fileWorkdir : workdir,
|
|
250
|
+
...(source.mode === "file" ? { path: source.source } : {}),
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Bind every `exec.code` segment of a classified shell command, or refuse.
|
|
256
|
+
*
|
|
257
|
+
* `null` when no segment carries a program: the caller binds the command the
|
|
258
|
+
* way it always has. `workdir` is the directory the command runs in (the
|
|
259
|
+
* per-call one where the harness states it), and `cwd` is the value the
|
|
260
|
+
* ordinary payload would have carried as its `cwd`. `fileWorkdir` is the
|
|
261
|
+
* directory a file program is resolved against: the one the HARNESS states
|
|
262
|
+
* (the per-call key, else the envelope's `cwd`), or `null` when it states
|
|
263
|
+
* none, which refuses file mode rather than guess with the hook's own cwd
|
|
264
|
+
* (fix round 2, refuter N8).
|
|
265
|
+
*/
|
|
266
|
+
export function bindExecCodeCommand(raw, segments, workdir, cwd, protectedPaths, fileWorkdir) {
|
|
267
|
+
if (!segments.some((segment) => segment.execCode !== undefined))
|
|
268
|
+
return null;
|
|
269
|
+
if (!isWellFormedText(raw)) {
|
|
270
|
+
return {
|
|
271
|
+
ok: false,
|
|
272
|
+
code: "hook-exec-code-unreadable",
|
|
273
|
+
detail: "the command carries a lone UTF-16 surrogate, which has no exact byte encoding, so the program's hash could not bind what the harness hands the interpreter. Nothing was appended. Send the command as well-formed text",
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
// Fix round 3 (refuter N-b): an embedded NUL is not text a command line can
|
|
277
|
+
// carry. No argv word holds one (Node `spawn` and Python `subprocess` refuse
|
|
278
|
+
// it), and a harness that writes the line to a script file hands bash bytes
|
|
279
|
+
// it then drops, so the hash would bind `print(1)\0junk` while the
|
|
280
|
+
// interpreter got `print(1)junk`, or nothing at all. Refused as not
|
|
281
|
+
// well-formed text, the lone surrogate's code, rather than bound.
|
|
282
|
+
if (raw.includes("\u0000")) {
|
|
283
|
+
return {
|
|
284
|
+
ok: false,
|
|
285
|
+
code: "hook-exec-code-unreadable",
|
|
286
|
+
detail: "the command carries an embedded NUL (U+0000), which no argv word can hold, so the program's hash could not bind what the harness hands the interpreter. Nothing was appended. Send the command as well-formed text",
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
const bindings = [];
|
|
290
|
+
const shown = [];
|
|
291
|
+
for (const [index, segment] of segments.entries()) {
|
|
292
|
+
if (segment.execCode === undefined) {
|
|
293
|
+
shown.push({ text: segment.text });
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const bound = bindSegment(segment.execCode, segments, index, workdir, fileWorkdir, protectedPaths);
|
|
297
|
+
if (!bound.ok)
|
|
298
|
+
return bound;
|
|
299
|
+
shown.push({ exec_code: bindings.length });
|
|
300
|
+
bindings.push(bound.binding);
|
|
301
|
+
}
|
|
302
|
+
const others = shown.filter((entry) => "text" in entry);
|
|
303
|
+
const headline = [
|
|
304
|
+
...bindings.map(headlineOf),
|
|
305
|
+
...(others.length === 0 ? [] : [`with ${others.map((entry) => entry.text).join(" ; ")}`]),
|
|
306
|
+
].join("; ");
|
|
307
|
+
return {
|
|
308
|
+
ok: true,
|
|
309
|
+
payload: {
|
|
310
|
+
exec_code: bindings,
|
|
311
|
+
command_sha256: sha256Hex(raw),
|
|
312
|
+
command_bytes: Buffer.byteLength(raw, "utf8"),
|
|
313
|
+
segments: shown,
|
|
314
|
+
cwd,
|
|
315
|
+
},
|
|
316
|
+
headline,
|
|
317
|
+
notes: [
|
|
318
|
+
`exec.code (APRV-510): ${bindings
|
|
319
|
+
.map((binding) => `${binding.interpreter} ${binding.mode} program sha256:${binding.code_sha256} (${String(binding.code_bytes)} bytes)`)
|
|
320
|
+
.join(", ")} bound by hash and length, never by text; the record says the program existed, and each tool call it makes is its own hook event`,
|
|
321
|
+
],
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/** Bind a Hermes `execute_code` call: the `code` string, by hash and length. */
|
|
325
|
+
export function bindExecuteCode(toolInput, inputHash) {
|
|
326
|
+
const code = toolInput["code"];
|
|
327
|
+
if (typeof code !== "string") {
|
|
328
|
+
return { ok: false, code: "hook-io", detail: "execute_code tool_input carries no code string" };
|
|
329
|
+
}
|
|
330
|
+
if (!isWellFormedText(code)) {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
code: "hook-exec-code-unreadable",
|
|
334
|
+
detail: "the execute_code program carries a lone UTF-16 surrogate, which has no exact byte encoding, so its hash could not bind what the kernel would run. Nothing was appended",
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const bytes = Buffer.from(code, "utf8");
|
|
338
|
+
const sha = sha256Hex(bytes);
|
|
339
|
+
const declared = toolInput["workdir"];
|
|
340
|
+
const binding = {
|
|
341
|
+
interpreter: "execute_code",
|
|
342
|
+
mode: "execute_code",
|
|
343
|
+
code_sha256: sha,
|
|
344
|
+
code_bytes: bytes.length,
|
|
345
|
+
argv: [],
|
|
346
|
+
workdir: typeof declared === "string" && isAbsolute(declared) ? declared : null,
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
ok: true,
|
|
350
|
+
payload: { tool: "execute_code", exec_code: binding, input_sha256: inputHash },
|
|
351
|
+
headline: headlineOf(binding),
|
|
352
|
+
notes: [
|
|
353
|
+
`exec.code (APRV-510): execute_code program sha256:${sha} (${String(bytes.length)} bytes) bound by hash and length, never by text; each tool call made from inside the kernel is its own hook event (the policy declares exec.code.execute_code: hooked)`,
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=hook-exec-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-exec-code.js","sourceRoot":"","sources":["../../../src/cli/hook-exec-code.ts"],"names":[],"mappings":"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"}
|