@sema-agent/core 7.37.1 → 7.38.1
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 +168 -0
- package/dist/agents/observer.js +1 -1
- package/dist/agents/peer-notices.js +4 -4
- package/dist/agents/subagent.js +1 -1
- package/dist/agents/team-role.js +2 -2
- package/dist/brain/anthropic.js +10 -9
- package/dist/brain/errors.d.ts +26 -0
- package/dist/brain/errors.js +34 -0
- package/dist/brain/input-too-long.d.ts +0 -31
- package/dist/brain/input-too-long.js +1 -18
- package/dist/brain/open-responses.js +10 -9
- package/dist/brain/openai.js +10 -9
- package/dist/brain/status-sink.d.ts +1 -1
- package/dist/brain/stream-engine.js +10 -2
- package/dist/brain/stream-shared.d.ts +11 -3
- package/dist/brain/stream-shared.js +4 -2
- package/dist/brain/tool-call-id.d.ts +29 -8
- package/dist/brain/tool-call-id.js +36 -6
- package/dist/brain/tool-call-repair.js +1 -1
- package/dist/core/a2a-task-state.js +2 -2
- package/dist/core/artifact-host.js +6 -6
- package/dist/core/ask-origin.js +6 -6
- package/dist/core/ask-unresolvable-notice.js +1 -1
- package/dist/core/auto-mode.js +4 -4
- package/dist/core/background-agent-store.js +2 -2
- package/dist/core/checkpoint-execution-record.js +1 -1
- package/dist/core/checkpoint-store.d.ts +2 -2
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/child-clamp-seats.js +3 -3
- package/dist/core/compliance.js +1 -1
- package/dist/core/context-edit.js +1 -1
- package/dist/core/context-guard.d.ts +0 -19
- package/dist/core/context-guard.js +0 -38
- package/dist/core/decision-receipt.d.ts +27 -1
- package/dist/core/decision-receipt.js +176 -1
- package/dist/core/engine-notice.d.ts +60 -8
- package/dist/core/engine-notice.js +2 -1
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-outcome.js +4 -4
- package/dist/core/governance-codes.d.ts +221 -13
- package/dist/core/governance-codes.js +104 -4
- package/dist/core/hook-seats.js +2 -2
- package/dist/core/hooks.d.ts +21 -2
- package/dist/core/hooks.js +4 -1
- package/dist/core/mailbox-store.js +3 -3
- package/dist/core/mcp-failure.js +4 -4
- package/dist/core/mcp-injection-drop.js +1 -1
- package/dist/core/memory-engine/consolidation.js +1 -1
- package/dist/core/memory-engine/delegation-settlement.d.ts +8 -0
- package/dist/core/memory-engine/delegation-settlement.js +7 -0
- package/dist/core/memory-engine/engine.d.ts +12 -2
- package/dist/core/memory-engine/engine.js +10 -6
- package/dist/core/memory-engine/export-bundle.js +1 -1
- package/dist/core/memory-engine/layout.d.ts +6 -6
- package/dist/core/memory-engine/layout.js +6 -8
- package/dist/core/memory-engine/session-write-home.d.ts +30 -0
- package/dist/core/memory-engine/session-write-home.js +34 -0
- package/dist/core/memory-engine/tools.js +1 -1
- package/dist/core/memory-engine/types.js +1 -1
- package/dist/core/memory.js +1 -1
- package/dist/core/mode-policy.d.ts +10 -1
- package/dist/core/mode-policy.js +15 -7
- package/dist/core/model-seat.js +1 -1
- package/dist/core/permission-rule-model.js +3 -3
- package/dist/core/permission-rule-store.js +2 -2
- package/dist/core/read-only-shell-table.js +8 -8
- package/dist/core/retention.js +2 -2
- package/dist/core/runner/assemble-result.js +15 -5
- package/dist/core/runner/classifier-ladder.js +1 -1
- package/dist/core/runner/denial-limit-arms.d.ts +2 -0
- package/dist/core/runner/denial-limit-arms.js +6 -2
- package/dist/core/runner/inherited-ask-grants.d.ts +6 -5
- package/dist/core/runner/inherited-ask-grants.js +3 -2
- package/dist/core/runner/prepare-artifact.d.ts +3 -2
- package/dist/core/runner/prepare-context-lane.d.ts +19 -15
- package/dist/core/runner/prepare-context-lane.js +11 -15
- package/dist/core/runner/prepare-gate-stations.js +5 -5
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -3
- package/dist/core/runner/prepare-hands-readface.js +1 -0
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +10 -10
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -2
- package/dist/core/runner/run-harness-handlers.js +3 -0
- package/dist/core/runner/run-identity-wiring.d.ts +1 -1
- package/dist/core/runner/run-identity-wiring.js +0 -2
- package/dist/core/runner/run-leg.js +2 -2
- package/dist/core/runner/run-stop-and-final-verify.js +1 -0
- package/dist/core/runner/run-turn-boundary.js +1 -1
- package/dist/core/runner/session-mode-seats.d.ts +11 -3
- package/dist/core/runner/session-mode-seats.js +6 -0
- package/dist/core/runner/session-rule-policy.js +2 -3
- package/dist/core/runner/stream-steer-verb.js +1 -1
- package/dist/core/runner/tool-call-id-fold-seat.js +16 -9
- package/dist/core/runner/tool-turn-repair-seat.d.ts +11 -1
- package/dist/core/runner/tool-turn-repair-seat.js +35 -0
- package/dist/core/runner/turn-attachments.js +1 -1
- package/dist/core/runtime-caps.js +1 -1
- package/dist/core/safety-axis-vocab.d.ts +5 -0
- package/dist/core/safety-axis-vocab.js +5 -0
- package/dist/core/session-reconcile.d.ts +9 -7
- package/dist/core/session-reconcile.js +21 -1
- package/dist/core/shared-memory/types.js +1 -1
- package/dist/core/side-query.d.ts +6 -1
- package/dist/core/store-contracts/read-back-shape-contract.js +1 -1
- package/dist/core/swappable-deps.js +1 -1
- package/dist/core/task-notification.js +4 -4
- package/dist/core/task-registry-shared.js +1 -1
- package/dist/core/terminal-cause.d.ts +19 -0
- package/dist/core/tool-face.js +6 -6
- package/dist/core/tool-policy.d.ts +17 -11
- package/dist/core/tool-policy.js +38 -72
- package/dist/core/tool-roster.js +1 -1
- package/dist/core/tool-search-setting.js +1 -1
- package/dist/core/trace.d.ts +5 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.js +2 -2
- package/dist/core/write-protect.js +1 -1
- package/dist/engine/harness/agent-harness.js +10 -6
- package/dist/engine/llm/tool-turns.d.ts +93 -4
- package/dist/engine/llm/tool-turns.js +63 -9
- package/dist/engine/llm/types.d.ts +11 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/internal/llm.d.ts +2 -2
- package/dist/internal/llm.js +1 -1
- package/dist/orchestration/workflow-governance.js +2 -2
- package/dist/orchestration/workflow-types.js +2 -2
- package/dist/shell/lex.js +7 -7
- package/dist/tools/fs/bash-program-position.js +2 -2
- package/dist/tools/fs/fs-shared.d.ts +2 -1
- package/dist/tools/fs/fs-shared.js +1 -1
- package/dist/tools/fs/index.d.ts +13 -6
- package/dist/tools/fs/index.js +12 -4
- package/dist/tools/fs/read-deny-home.d.ts +98 -45
- package/dist/tools/fs/read-deny-home.js +294 -117
- package/dist/tools/fs/read-deny.d.ts +2 -2
- package/dist/tools/fs/read-deny.js +2 -2
- package/dist/tools/fs/safety.d.ts +2 -0
- package/dist/tools/fs/safety.js +2 -2
- package/dist/tools/fs/search.d.ts +4 -3
- package/dist/tools/fs/search.js +3 -3
- package/dist/tools/fs/shell-read-check.d.ts +9 -0
- package/dist/tools/fs/shell-read-check.js +3 -2
- package/dist/tools/fs/shell-read-establish.d.ts +3 -0
- package/dist/tools/fs/shell-read-establish.js +1 -1
- package/dist/tools/fs/shell-read-identity.d.ts +4 -3
- package/dist/tools/fs/shell-read-identity.js +2 -2
- package/dist/tools/fs/shell-read-judge.js +2 -2
- package/dist/tools/fs/shell-read-plan.js +2 -2
- package/dist/tools/fs/shell-read-write-targets.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -1
- package/wire-changes/core@7.38.0.json +664 -0
- package/wire-changes/core@7.38.1.json +56 -0
|
@@ -1,23 +1,106 @@
|
|
|
1
|
+
import { userInfo } from "node:os";
|
|
1
2
|
import { canonicalizeTarget, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
|
|
2
3
|
import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "../../core/gate-bounded-await.js";
|
|
4
|
+
import { thrownText } from "../../core/thrown-value.js";
|
|
3
5
|
import { readDenyPatternRun, readDenyRunHead } from "./read-deny.js";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
export const AUXILIARY_HOME_PROBE_MS = 2_000;
|
|
7
|
+
const said = new Set();
|
|
8
|
+
function sayOnce(kind, line) {
|
|
9
|
+
if (said.has(kind))
|
|
10
|
+
return;
|
|
11
|
+
said.add(kind);
|
|
12
|
+
try {
|
|
13
|
+
console.warn(line);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
}
|
|
7
17
|
}
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
function isNoAccountRow(e) {
|
|
19
|
+
try {
|
|
20
|
+
const info = typeof e === "object" && e !== null ? e.info : undefined;
|
|
21
|
+
return typeof info === "object" && info !== null && info.code === "ENOENT";
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
10
26
|
}
|
|
11
|
-
|
|
12
|
-
|
|
27
|
+
function valueText(v) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.stringify(v) ?? String(v);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return typeof v;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function accountHomeOf() {
|
|
36
|
+
try {
|
|
37
|
+
const home = userInfo().homedir;
|
|
38
|
+
if (typeof home === "string" && isAbsolutePathForm(home))
|
|
39
|
+
return { state: "home", home };
|
|
40
|
+
sayOnce("bad_value", `read-deny homes: the account home lookup answered ${valueText(home)}, not an absolute path — home-anchored read-deny rows are placed under the environment's declared home only.`);
|
|
41
|
+
return { state: "none" };
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
if (isNoAccountRow(e))
|
|
45
|
+
return { state: "none" };
|
|
46
|
+
sayOnce("unanswered", `read-deny homes: the account home could not be read (${thrownText(e)}) — home-anchored read-deny rows hold their run at every location until the lookup answers.`);
|
|
47
|
+
return { state: "unanswered" };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function declaredHomeOf(env) {
|
|
51
|
+
const home = env.homeDir;
|
|
52
|
+
return home !== undefined && isAbsolutePathForm(home) ? home : undefined;
|
|
53
|
+
}
|
|
54
|
+
function declaredReadDenyHomesAnswered(env) {
|
|
55
|
+
const home = declaredHomeOf(env);
|
|
13
56
|
if (home === undefined)
|
|
14
57
|
return { homes: [], whole: true };
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
17
|
-
return { homes:
|
|
18
|
-
|
|
58
|
+
const account = accountHomeOf();
|
|
59
|
+
if (account.state === "unanswered")
|
|
60
|
+
return { homes: [], whole: false };
|
|
61
|
+
if (account.state === "none" || normalizeAbsPathLexically(account.home) === normalizeAbsPathLexically(home))
|
|
62
|
+
return { homes: [home], whole: true };
|
|
63
|
+
return { homes: [home, account.home], whole: true };
|
|
64
|
+
}
|
|
65
|
+
export function declaredReadDenyHomes(env) {
|
|
66
|
+
return declaredReadDenyHomesAnswered(env).homes;
|
|
67
|
+
}
|
|
68
|
+
function isDeclared(declared, home) {
|
|
69
|
+
return declared !== undefined && normalizeAbsPathLexically(home) === normalizeAbsPathLexically(declared);
|
|
19
70
|
}
|
|
20
|
-
|
|
71
|
+
async function homeSpellingsOf(env, home, signal) {
|
|
72
|
+
try {
|
|
73
|
+
const canonical = await env.canonicalPath(home, signal);
|
|
74
|
+
if (canonical.ok)
|
|
75
|
+
return { spellings: canonical.value !== home ? [home, canonical.value] : [home], canonical: canonical.value, answered: true };
|
|
76
|
+
return { spellings: [home], answered: canonical.error.code === "not_found" || canonical.error.code === "not_directory" };
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return { spellings: [home], answered: false };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export async function readDenyHomesOf(env, signal) {
|
|
83
|
+
const listed = declaredReadDenyHomesAnswered(env);
|
|
84
|
+
const [declared, ...auxiliary] = listed.homes;
|
|
85
|
+
if (!listed.whole || declared === undefined)
|
|
86
|
+
return [];
|
|
87
|
+
const [own, ...others] = await Promise.all([
|
|
88
|
+
homeSpellingsOf(env, declared, signal),
|
|
89
|
+
...auxiliary.map((home) => until(homeSpellingsOf(env, home, signal), Date.now() + AUXILIARY_HOME_PROBE_MS, signal).then((r) => r ?? { spellings: [home], answered: false })),
|
|
90
|
+
]);
|
|
91
|
+
if (!own.answered)
|
|
92
|
+
return [];
|
|
93
|
+
if (others.some((r) => !r.answered))
|
|
94
|
+
sayAuxiliary("its path probe did not answer");
|
|
95
|
+
return [...new Set([...own.spellings, ...others.flatMap((r) => r.spellings)])];
|
|
96
|
+
}
|
|
97
|
+
async function until(work, deadline, signal) {
|
|
98
|
+
return await withGateTimeout(work, Math.max(0, deadline - Date.now()), signal, "auxiliaryHome").catch(() => undefined);
|
|
99
|
+
}
|
|
100
|
+
function sayAuxiliary(what) {
|
|
101
|
+
sayOnce("auxiliary", `read-deny homes: a home beside the environment's declared home did not answer its probes within ${AUXILIARY_HOME_PROBE_MS} ms (${what}) — its home-anchored read-deny rows sit at its spelling only, and a path through an alias of it is judged as spelled, until the next placement.`);
|
|
102
|
+
}
|
|
103
|
+
export async function readDenyHomeLinksOf(env, entries, homes, signal, deadline) {
|
|
21
104
|
const rows = [];
|
|
22
105
|
const familyOf = new Map();
|
|
23
106
|
for (const { pattern, caseSensitive } of entries) {
|
|
@@ -35,7 +118,8 @@ export async function readDenyHomeLinksOf(env, entries, homes, signal) {
|
|
|
35
118
|
});
|
|
36
119
|
rows.push({ pattern, caseSensitive: caseSensitive === true, spelled });
|
|
37
120
|
}
|
|
38
|
-
const
|
|
121
|
+
const leadOf = async (at, family) => (await whereItLeads(env, at, family, signal)) ?? ((await holdsNothingBelow(env, at, signal)) ? at : undefined);
|
|
122
|
+
const resolved = new Map(await Promise.all([...familyOf].map(async ([at, family]) => [at, deadline === undefined ? await leadOf(at, family) : await until(leadOf(at, family), deadline, signal)])));
|
|
39
123
|
const out = new Map();
|
|
40
124
|
const whole = [...resolved.values()].every((location) => location !== undefined);
|
|
41
125
|
for (const { pattern, caseSensitive, spelled } of rows) {
|
|
@@ -52,6 +136,10 @@ export async function readDenyHomeLinksOf(env, entries, homes, signal) {
|
|
|
52
136
|
}
|
|
53
137
|
return { linked: out, whole };
|
|
54
138
|
}
|
|
139
|
+
async function holdsNothingBelow(env, at, signal) {
|
|
140
|
+
const probe = await withGateTimeout(Promise.resolve().then(() => env.canonicalPath(at, signal)), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "readDenyHead").catch(() => undefined);
|
|
141
|
+
return probe !== undefined && !probe.ok && probe.error.code === "not_directory";
|
|
142
|
+
}
|
|
55
143
|
export async function whereItLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
|
|
56
144
|
try {
|
|
57
145
|
const key = await canonicalizeTarget(env, at, signal, family);
|
|
@@ -77,17 +165,189 @@ export async function whereItsAnsweredPrefixLeads(env, at, family = pathFamilyOf
|
|
|
77
165
|
};
|
|
78
166
|
return await withGateTimeout(walk(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "ruleRowHead").catch(() => undefined);
|
|
79
167
|
}
|
|
168
|
+
const NO_CHAIN = { ancestors: [], whole: false };
|
|
169
|
+
async function chainAbove(env, identity, canonical, signal, deadline) {
|
|
170
|
+
let own;
|
|
171
|
+
const ancestors = [];
|
|
172
|
+
let whole = true;
|
|
173
|
+
for (let dir = normalizeAbsPathLexically(canonical), first = true;; first = false) {
|
|
174
|
+
const id = deadline === undefined ? await identityOf(env, identity, dir, signal) : await until(identityOf(env, identity, dir, signal), deadline, signal);
|
|
175
|
+
if (id === undefined && deadline !== undefined && Date.now() >= deadline) {
|
|
176
|
+
whole = false;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
if (id?.ok !== true)
|
|
180
|
+
whole = false;
|
|
181
|
+
else if (first)
|
|
182
|
+
own = { dir, id: id.value };
|
|
183
|
+
else
|
|
184
|
+
ancestors.push({ dir, id: id.value });
|
|
185
|
+
const parent = normalizeAbsPathLexically(`${dir}/..`);
|
|
186
|
+
if (parent === dir)
|
|
187
|
+
break;
|
|
188
|
+
dir = parent;
|
|
189
|
+
}
|
|
190
|
+
return { ...(own !== undefined ? { own } : {}), ancestors, whole };
|
|
191
|
+
}
|
|
192
|
+
async function identityOf(env, identity, path, signal) {
|
|
193
|
+
try {
|
|
194
|
+
return await identity.call(env, path, signal);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function readAuxiliaryHome(env, home, opts, signal) {
|
|
201
|
+
const deadline = Date.now() + AUXILIARY_HOME_PROBE_MS;
|
|
202
|
+
const spelled = await until(homeSpellingsOf(env, home, signal), deadline, signal);
|
|
203
|
+
let reading;
|
|
204
|
+
if (spelled?.canonical === undefined) {
|
|
205
|
+
const answered = spelled?.answered === true;
|
|
206
|
+
reading = { place: [home], chain: answered ? { ancestors: [], whole: true } : NO_CHAIN, linked: new Map(), answered };
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
const place = spelled.spellings;
|
|
210
|
+
const identity = env.fileIdentity;
|
|
211
|
+
const [chain, heads] = await Promise.all([
|
|
212
|
+
typeof identity === "function" ? chainAbove(env, identity, spelled.canonical, signal, deadline) : Promise.resolve({ ancestors: [], whole: true }),
|
|
213
|
+
opts.entries !== undefined ? readDenyHomeLinksOf(env, opts.entries, place, signal, deadline) : Promise.resolve({ linked: new Map(), whole: true }),
|
|
214
|
+
]);
|
|
215
|
+
reading = { place, chain, linked: heads.linked, answered: chain.whole && heads.whole };
|
|
216
|
+
}
|
|
217
|
+
if (!reading.answered)
|
|
218
|
+
sayAuxiliary("a probe did not answer");
|
|
219
|
+
return reading;
|
|
220
|
+
}
|
|
221
|
+
function homeViewSourceOver(env, auxiliary) {
|
|
222
|
+
const declared = declaredHomeOf(env);
|
|
223
|
+
const identity = env.fileIdentity;
|
|
224
|
+
let declaredChain;
|
|
225
|
+
const readDeclared = () => {
|
|
226
|
+
if (declared === undefined || typeof identity !== "function")
|
|
227
|
+
return Promise.resolve(NO_CHAIN);
|
|
228
|
+
if (declaredChain === undefined) {
|
|
229
|
+
const reading = withGateTimeout((async () => {
|
|
230
|
+
const spelled = await homeSpellingsOf(env, declared);
|
|
231
|
+
return spelled.canonical === undefined ? NO_CHAIN : await chainAbove(env, identity, spelled.canonical);
|
|
232
|
+
})(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, undefined, "homeChain").catch(() => NO_CHAIN);
|
|
233
|
+
declaredChain = reading;
|
|
234
|
+
void reading.then((c) => {
|
|
235
|
+
if (!c.whole && declaredChain === reading)
|
|
236
|
+
declaredChain = undefined;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return declaredChain;
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
views(signal) {
|
|
243
|
+
let index;
|
|
244
|
+
const indexOf = () => (index ??= (async () => {
|
|
245
|
+
const chains = [await readDeclared(), ...(await Promise.all(auxiliary)).map((a) => a.chain)];
|
|
246
|
+
const out = new Map();
|
|
247
|
+
for (const { own } of chains)
|
|
248
|
+
if (own !== undefined && !out.has(own.id))
|
|
249
|
+
out.set(own.id, own.dir);
|
|
250
|
+
for (const { ancestors } of chains)
|
|
251
|
+
for (const { dir, id } of ancestors)
|
|
252
|
+
if (!out.has(id))
|
|
253
|
+
out.set(id, dir);
|
|
254
|
+
return out.size > 0 ? out : undefined;
|
|
255
|
+
})());
|
|
256
|
+
const views = new Map();
|
|
257
|
+
const viewOf = (path, leafIsFile) => {
|
|
258
|
+
const key = `${leafIsFile ? "f" : "d"}:${path}`;
|
|
259
|
+
let v = views.get(key);
|
|
260
|
+
if (v === undefined) {
|
|
261
|
+
v = (async () => {
|
|
262
|
+
const index = await indexOf();
|
|
263
|
+
if (index === undefined || typeof identity !== "function")
|
|
264
|
+
return undefined;
|
|
265
|
+
if (!leafIsFile) {
|
|
266
|
+
const id = await identityOf(env, identity, path, signal);
|
|
267
|
+
if (id === undefined)
|
|
268
|
+
return undefined;
|
|
269
|
+
if (id.ok) {
|
|
270
|
+
const at = index.get(id.value);
|
|
271
|
+
if (at !== undefined)
|
|
272
|
+
return at;
|
|
273
|
+
}
|
|
274
|
+
else if (id.error.code !== "not_found" && id.error.code !== "not_directory")
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
const parent = normalizeAbsPathLexically(`${path}/..`);
|
|
278
|
+
if (parent === path)
|
|
279
|
+
return undefined;
|
|
280
|
+
const up = await viewOf(parent, false);
|
|
281
|
+
return up === undefined ? undefined : joinForFamily(pathFamilyOf({ root: path }), up, path.slice(parent.length).replace(/^[\\/]+/, ""));
|
|
282
|
+
})();
|
|
283
|
+
views.set(key, v);
|
|
284
|
+
}
|
|
285
|
+
return v;
|
|
286
|
+
};
|
|
287
|
+
return {
|
|
288
|
+
async of(path, opts) {
|
|
289
|
+
if (typeof identity !== "function" || (declared === undefined && auxiliary.length === 0) || !isAbsolutePathForm(path))
|
|
290
|
+
return undefined;
|
|
291
|
+
const spelled = normalizeAbsPathLexically(path);
|
|
292
|
+
const view = await viewOf(spelled, opts?.leaf === "file");
|
|
293
|
+
return view === undefined || view === spelled ? undefined : view;
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const PLACEMENT_VIEWS = new WeakMap();
|
|
300
|
+
function placementOf(matcher, homes, linked, whole) {
|
|
301
|
+
const list = Object.freeze([...homes]);
|
|
302
|
+
PLACEMENT_VIEWS.set(list, matcher.homeViews);
|
|
303
|
+
return { matcher, homes: list, linked, whole };
|
|
304
|
+
}
|
|
305
|
+
export function placementViewsOf(homes) {
|
|
306
|
+
return PLACEMENT_VIEWS.get(homes);
|
|
307
|
+
}
|
|
308
|
+
export function sayPlacementLost() {
|
|
309
|
+
try {
|
|
310
|
+
console.warn("read-deny homes: this tool band was handed a home list that is not the placement's own (an engine defect) — its home-anchored read-deny rows hold their run at every location, and only the declared home's views are read.");
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export function homeViewsAt(env, homes) {
|
|
316
|
+
const declared = declaredHomeOf(env);
|
|
317
|
+
const auxiliary = [...new Set(homes.filter((h) => isAbsolutePathForm(h) && !isDeclared(declared, h)))];
|
|
318
|
+
return homeViewSourceOver(env, auxiliary.map((home) => readAuxiliaryHome(env, home, {})));
|
|
319
|
+
}
|
|
320
|
+
export function homeViewsOf(env, judge, signal) {
|
|
321
|
+
return (judge?.homeViews ?? homeViewsAt(env, [])).views(signal);
|
|
322
|
+
}
|
|
323
|
+
export function carryHomeViews(judge, source) {
|
|
324
|
+
return { ...judge, homeViews: source };
|
|
325
|
+
}
|
|
80
326
|
export async function placeReadDenyAtHome(env, compile, homes, signal) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
327
|
+
const entries = compile([]).entries;
|
|
328
|
+
const declared = declaredHomeOf(env);
|
|
329
|
+
const heldAnywhere = (whole) => placementOf(carryHomeViews(compile([]), homeViewSourceOver(env, [])), [], new Map(), whole);
|
|
330
|
+
const listed = homes === undefined ? declaredReadDenyHomesAnswered(env) : { homes, whole: true };
|
|
331
|
+
if (!listed.whole)
|
|
332
|
+
return heldAnywhere(false);
|
|
333
|
+
const own = listed.homes.filter((h) => isDeclared(declared, h));
|
|
334
|
+
let declaredAt = [];
|
|
335
|
+
if (own.length > 0) {
|
|
336
|
+
const ownSpellings = await withGateTimeout(homeSpellingsOf(env, own[0], signal), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "readDenyHomes");
|
|
337
|
+
if (!ownSpellings.answered)
|
|
338
|
+
return heldAnywhere(false);
|
|
339
|
+
declaredAt = [...new Set([...own, ...ownSpellings.spellings])];
|
|
340
|
+
}
|
|
341
|
+
const auxiliary = listed.homes.filter((h) => !isDeclared(declared, h)).map((home) => readAuxiliaryHome(env, home, { entries }, signal));
|
|
342
|
+
const [heads, readings] = await Promise.all([readDenyHomeLinksOf(env, entries, declaredAt, signal), Promise.all(auxiliary)]);
|
|
343
|
+
const at = [...new Set([...declaredAt, ...readings.flatMap((r) => r.place)])];
|
|
344
|
+
const linked = new Map();
|
|
345
|
+
for (const map of [heads.linked, ...readings.map((r) => r.linked)]) {
|
|
346
|
+
for (const [pattern, locations] of map)
|
|
347
|
+
linked.set(pattern, [...new Set([...(linked.get(pattern) ?? []), ...locations])]);
|
|
87
348
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
return { matcher: linked.size === 0 ? atHomes : compile(at, linked), homes: at, linked, whole };
|
|
349
|
+
const matcher = linked.size === 0 ? compile(at) : compile(at, linked);
|
|
350
|
+
return placementOf(carryHomeViews(matcher, homeViewSourceOver(env, auxiliary)), at, linked, heads.whole);
|
|
91
351
|
}
|
|
92
352
|
export function readDenyPlacedLater(unplaced, place) {
|
|
93
353
|
let current = unplaced;
|
|
@@ -108,6 +368,17 @@ export function readDenyPlacedLater(unplaced, place) {
|
|
|
108
368
|
if (!whole)
|
|
109
369
|
await again();
|
|
110
370
|
};
|
|
371
|
+
const settled = async () => {
|
|
372
|
+
if (pending !== undefined)
|
|
373
|
+
await pending;
|
|
374
|
+
return current;
|
|
375
|
+
};
|
|
376
|
+
const homeViews = {
|
|
377
|
+
views(signal) {
|
|
378
|
+
let views;
|
|
379
|
+
return { of: async (path, opts) => await (await (views ??= settled().then((m) => m.homeViews.views(signal)))).of(path, opts) };
|
|
380
|
+
},
|
|
381
|
+
};
|
|
111
382
|
const matcher = {
|
|
112
383
|
get entries() {
|
|
113
384
|
return current.entries;
|
|
@@ -125,101 +396,7 @@ export function readDenyPlacedLater(unplaced, place) {
|
|
|
125
396
|
},
|
|
126
397
|
unexcludableUnder: (scope) => current.unexcludableUnder?.(scope) ?? false,
|
|
127
398
|
rowAtOrBelow: (path) => current.rowAtOrBelow?.(path) ?? null,
|
|
399
|
+
homeViews,
|
|
128
400
|
};
|
|
129
401
|
return { matcher, ready };
|
|
130
402
|
}
|
|
131
|
-
const HOME_CHAINS = new WeakMap();
|
|
132
|
-
function homeChainOf(env) {
|
|
133
|
-
const identity = env.fileIdentity;
|
|
134
|
-
const home = declaredReadDenyHomes(env)[0];
|
|
135
|
-
if (typeof identity !== "function" || home === undefined)
|
|
136
|
-
return undefined;
|
|
137
|
-
let perEnv = HOME_CHAINS.get(env);
|
|
138
|
-
if (perEnv === undefined)
|
|
139
|
-
HOME_CHAINS.set(env, (perEnv = new Map()));
|
|
140
|
-
const known = perEnv;
|
|
141
|
-
let chain = known.get(home);
|
|
142
|
-
if (chain === undefined) {
|
|
143
|
-
const reading = withGateTimeout((async () => {
|
|
144
|
-
const canonical = await env.canonicalPath(home);
|
|
145
|
-
if (!canonical.ok)
|
|
146
|
-
return { index: undefined, whole: false };
|
|
147
|
-
const out = new Map();
|
|
148
|
-
let whole = true;
|
|
149
|
-
for (let dir = normalizeAbsPathLexically(canonical.value);;) {
|
|
150
|
-
const id = await identityOf(env, identity, dir);
|
|
151
|
-
if (id?.ok === true) {
|
|
152
|
-
if (!out.has(id.value))
|
|
153
|
-
out.set(id.value, dir);
|
|
154
|
-
}
|
|
155
|
-
else
|
|
156
|
-
whole = false;
|
|
157
|
-
const parent = normalizeAbsPathLexically(`${dir}/..`);
|
|
158
|
-
if (parent === dir)
|
|
159
|
-
break;
|
|
160
|
-
dir = parent;
|
|
161
|
-
}
|
|
162
|
-
return { index: out.size > 0 ? out : undefined, whole };
|
|
163
|
-
})(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, undefined, "homeChain").catch(() => ({ index: undefined, whole: false }));
|
|
164
|
-
chain = reading;
|
|
165
|
-
known.set(home, reading);
|
|
166
|
-
const drop = () => {
|
|
167
|
-
if (known.get(home) === reading)
|
|
168
|
-
known.delete(home);
|
|
169
|
-
};
|
|
170
|
-
void reading.then((c) => (c.whole ? undefined : drop()));
|
|
171
|
-
}
|
|
172
|
-
return chain.then((c) => c.index);
|
|
173
|
-
}
|
|
174
|
-
async function identityOf(env, identity, path, signal) {
|
|
175
|
-
try {
|
|
176
|
-
return await identity.call(env, path, signal);
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
export function homeViewsOf(env, signal) {
|
|
183
|
-
const chain = homeChainOf(env);
|
|
184
|
-
const identity = env.fileIdentity;
|
|
185
|
-
const views = new Map();
|
|
186
|
-
const viewOf = (path, leafIsFile) => {
|
|
187
|
-
const key = `${leafIsFile ? "f" : "d"}:${path}`;
|
|
188
|
-
let v = views.get(key);
|
|
189
|
-
if (v === undefined) {
|
|
190
|
-
v = (async () => {
|
|
191
|
-
const index = await chain;
|
|
192
|
-
if (index === undefined || typeof identity !== "function")
|
|
193
|
-
return undefined;
|
|
194
|
-
if (!leafIsFile) {
|
|
195
|
-
const id = await identityOf(env, identity, path, signal);
|
|
196
|
-
if (id === undefined)
|
|
197
|
-
return undefined;
|
|
198
|
-
if (id.ok) {
|
|
199
|
-
const at = index.get(id.value);
|
|
200
|
-
if (at !== undefined)
|
|
201
|
-
return at;
|
|
202
|
-
}
|
|
203
|
-
else if (id.error.code !== "not_found" && id.error.code !== "not_directory")
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
const parent = normalizeAbsPathLexically(`${path}/..`);
|
|
207
|
-
if (parent === path)
|
|
208
|
-
return undefined;
|
|
209
|
-
const up = await viewOf(parent, false);
|
|
210
|
-
return up === undefined ? undefined : joinForFamily(pathFamilyOf({ root: path }), up, path.slice(parent.length).replace(/^[\\/]+/, ""));
|
|
211
|
-
})();
|
|
212
|
-
views.set(key, v);
|
|
213
|
-
}
|
|
214
|
-
return v;
|
|
215
|
-
};
|
|
216
|
-
return {
|
|
217
|
-
async of(path, opts) {
|
|
218
|
-
if (chain === undefined || !isAbsolutePathForm(path))
|
|
219
|
-
return undefined;
|
|
220
|
-
const spelled = normalizeAbsPathLexically(path);
|
|
221
|
-
const view = await viewOf(spelled, opts?.leaf === "file");
|
|
222
|
-
return view === undefined || view === spelled ? undefined : view;
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
}
|
|
@@ -392,8 +392,8 @@ export declare function checkedReadDenyHomes(homes: unknown, seat?: string): rea
|
|
|
392
392
|
* layer or knob.
|
|
393
393
|
*
|
|
394
394
|
* `homes` — the spellings of the execution environment's home directory a home-anchored entry
|
|
395
|
-
* ({@link READ_DENY_HOME_WORD}) is placed under: the declared home and, where the environment's
|
|
396
|
-
* spelling differs, that one too (the faces judge canonical keys beside the spelled views). Absolute
|
|
395
|
+
* ({@link READ_DENY_HOME_WORD}) is placed under: the declared home, the account home and, where the environment's
|
|
396
|
+
* canonical spelling of one differs, that one too (the faces judge canonical keys beside the spelled views). Absolute
|
|
397
397
|
* spellings only — any other shape refuses loudly, naming `readDenyHomes`. EMPTY = the environment declares no
|
|
398
398
|
* home: a home-anchored entry then matches its run anywhere — a row that cannot be placed holds wider, never narrower.
|
|
399
399
|
*
|
|
@@ -11,7 +11,7 @@ export function readDenyRunHead(run) {
|
|
|
11
11
|
const wild = segments.findIndex((seg) => seg.includes("*"));
|
|
12
12
|
return wild === -1 ? segments.length : wild;
|
|
13
13
|
}
|
|
14
|
-
export const DENY_FACES = ["read", "write"];
|
|
14
|
+
export const DENY_FACES = Object.freeze(["read", "write"]);
|
|
15
15
|
export async function withRuleRowsWhereTheyLead(rows, leadsTo) {
|
|
16
16
|
const leads = new Map();
|
|
17
17
|
const partsOf = (head) => {
|
|
@@ -144,7 +144,7 @@ export function elideMiddle(s, budget) {
|
|
|
144
144
|
const head = Math.ceil(keep / 2);
|
|
145
145
|
return `${sliceHeadSafe(s, head)}\u2026${sliceTailSafe(s, keep - head)}`;
|
|
146
146
|
}
|
|
147
|
-
export const READ_DENY_BUILTIN_TIERS = ["credentials", "shell-history", "browser", "wallet", "agent-config"];
|
|
147
|
+
export const READ_DENY_BUILTIN_TIERS = Object.freeze(["credentials", "shell-history", "browser", "wallet", "agent-config"]);
|
|
148
148
|
export const READ_FACE_BUILTIN_DENY_TABLE = [
|
|
149
149
|
{ pattern: ".ssh", tier: "credentials" },
|
|
150
150
|
{ pattern: "id_rsa*", tier: "credentials" },
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type HomeViewSource } from "./read-deny-home.js";
|
|
1
2
|
import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
|
|
2
3
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
3
4
|
import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-registry.js";
|
|
@@ -449,6 +450,7 @@ export type RootsSeat = readonly string[] | (() => readonly string[] | undefined
|
|
|
449
450
|
*/
|
|
450
451
|
export declare function resolveKey(env: ExecutionEnv, rootCanonical: string, path: string, signal?: AbortSignal, baseCwd?: string, additionalRoots?: RootsSeat, exactFileReadExemption?: (canonicalKey: string) => boolean, readDeny?: {
|
|
451
452
|
readonly face?: "write";
|
|
453
|
+
readonly homeViews?: HomeViewSource;
|
|
452
454
|
matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
|
|
453
455
|
readonly aliasResolved?: boolean;
|
|
454
456
|
}): {
|
package/dist/tools/fs/safety.js
CHANGED
|
@@ -312,7 +312,7 @@ export function isUncSpelling(p) {
|
|
|
312
312
|
export function isAbsoluteForFamily(family, p) {
|
|
313
313
|
return family === "win" ? /^[A-Za-z]:[\\/]/.test(p) || isUncSpelling(p) : p.startsWith("/");
|
|
314
314
|
}
|
|
315
|
-
export const WIN32_NAMESPACE_REFUSALS = ["device_namespace", "unmappable_namespace", "unc_on_posix_tree", "drive_relative"];
|
|
315
|
+
export const WIN32_NAMESPACE_REFUSALS = Object.freeze(["device_namespace", "unmappable_namespace", "unc_on_posix_tree", "drive_relative"]);
|
|
316
316
|
export const WIN32_NAMESPACE_REFUSAL_TEXT = {
|
|
317
317
|
device_namespace: "is a Win32 device-namespace path (\\\\.\\ names a raw device object, not a file)",
|
|
318
318
|
unmappable_namespace: "is a Win32 extended-length namespace path with no DOS-path equivalent (only \\\\?\\<drive>: and \\\\?\\UNC\\ forms name files)",
|
|
@@ -469,7 +469,7 @@ export async function resolveKey(env, rootCanonical, path, signal, baseCwd, addi
|
|
|
469
469
|
const judged = { aliasResolved: canon.aliasResolved === true };
|
|
470
470
|
let hit = readDeny.matchTarget(key, lexicalViewOf(path, baseCwd ?? rootCanonical), judged);
|
|
471
471
|
if (hit === null) {
|
|
472
|
-
const homeView = await homeViewsOf(env, signal).of(key);
|
|
472
|
+
const homeView = await homeViewsOf(env, readDeny, signal).of(key);
|
|
473
473
|
if (homeView !== undefined)
|
|
474
474
|
hit = readDeny.matchTarget(key, homeView, judged);
|
|
475
475
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
|
+
import { type HomeViewsCarrier } from "./read-deny-home.js";
|
|
2
3
|
/** The JS scanner's own output ceiling, in bytes of emitted content rows. Pinned to the
|
|
3
4
|
* ripgrep leg's figure ({@link MAX_EXEC_OUTPUT_BYTES}, the exec pipe's rolling-tail bound) so the two
|
|
4
5
|
* legs cannot disagree about how much one search may return; see the collection site in {@link jsGrep}
|
|
@@ -130,7 +131,7 @@ export declare function walk(env: ExecutionEnv, root: string, start: string, ign
|
|
|
130
131
|
matchPath(path: string): {
|
|
131
132
|
pattern: string;
|
|
132
133
|
} | null;
|
|
133
|
-
}): Promise<WalkResult>;
|
|
134
|
+
} & HomeViewsCarrier): Promise<WalkResult>;
|
|
134
135
|
/** The JS-leg withholding disclosure: an empty result must stay distinguishable from
|
|
135
136
|
* searched-and-empty, so this note rides EVERY output shape when something was pruned. Distinct from
|
|
136
137
|
* {@link walkCaveat}: pruning is deliberate withholding, not incompleteness.
|
|
@@ -157,8 +158,8 @@ export interface ReadDenyWithheld {
|
|
|
157
158
|
count?: number;
|
|
158
159
|
patterns?: readonly string[];
|
|
159
160
|
}
|
|
160
|
-
/** The deny judge seam every search leg shares (structural subset of ReadDenyMatcher). */
|
|
161
|
-
export interface ReadDenyJudge {
|
|
161
|
+
/** The deny judge seam every search leg shares (structural subset of ReadDenyMatcher), with its placement's home views. */
|
|
162
|
+
export interface ReadDenyJudge extends HomeViewsCarrier {
|
|
162
163
|
matchPath(path: string): {
|
|
163
164
|
pattern: string;
|
|
164
165
|
} | null;
|
package/dist/tools/fs/search.js
CHANGED
|
@@ -228,7 +228,7 @@ export async function walk(env, root, start, ignore, signal, deny) {
|
|
|
228
228
|
let aborted = false;
|
|
229
229
|
let denyPruned = 0;
|
|
230
230
|
const denyPatterns = [];
|
|
231
|
-
const views = deny !== undefined ? homeViewsOf(env, signal) : undefined;
|
|
231
|
+
const views = deny !== undefined ? homeViewsOf(env, deny, signal) : undefined;
|
|
232
232
|
const denyHit = async (path, leaf) => {
|
|
233
233
|
let hit = deny?.matchPath(path);
|
|
234
234
|
if (!hit && views !== undefined) {
|
|
@@ -1156,7 +1156,7 @@ function rgRecordAbsolute(base) {
|
|
|
1156
1156
|
return (p) => (base === undefined || family === undefined || isAbsoluteForFamily(family, p) ? p : joinForFamily(family, base, p.replace(/^\.[\\/]/, "")));
|
|
1157
1157
|
}
|
|
1158
1158
|
async function rgOutputDenyTripwireAt(env, stdout, mode, deny, opts, signal) {
|
|
1159
|
-
const views = homeViewsOf(env, signal);
|
|
1159
|
+
const views = homeViewsOf(env, deny, signal);
|
|
1160
1160
|
const absolute = rgRecordAbsolute(opts.base);
|
|
1161
1161
|
const placed = new Map();
|
|
1162
1162
|
for (const record of parseRgRecords(stdout, mode, opts.dropIncompleteTail === true)) {
|
|
@@ -1343,7 +1343,7 @@ export async function runGrepDetailed(env, root, p, signal, deny) {
|
|
|
1343
1343
|
return false;
|
|
1344
1344
|
if (deny.unexcludableUnder(scope))
|
|
1345
1345
|
return true;
|
|
1346
|
-
const view = await homeViewsOf(env, signal).of(scope);
|
|
1346
|
+
const view = await homeViewsOf(env, deny, signal).of(scope);
|
|
1347
1347
|
return view !== undefined && deny.unexcludableUnder(view);
|
|
1348
1348
|
};
|
|
1349
1349
|
if (!(await unexcludableUnder()) && (await detectRipgrep(env)))
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
21
21
|
import { violationDetails } from "./safety.js";
|
|
22
22
|
import { type ShellReadPlan } from "./shell-read-plan.js";
|
|
23
|
+
import { type HomeViewSource } from "./read-deny-home.js";
|
|
23
24
|
import { type EstablishedRead, type ShellReadBoundary, type ShellReadDisposition, type ShellReadFinding, type ShellReadSeat } from "./shell-read-judge.js";
|
|
24
25
|
import type { ReadRootGrantCandidate } from "../../core/tool-spec.js";
|
|
25
26
|
import type { ToolCallLike } from "../../core/tool-registry.js";
|
|
@@ -145,6 +146,9 @@ export interface ShellReadStationOptions {
|
|
|
145
146
|
/** `true` ⇔ every root `boundaryOf` returns is already a canonical identity (the Runner's): the station never
|
|
146
147
|
* re-resolves one, so a root swapped for a link after it was declared or granted cannot bring in the link's target. */
|
|
147
148
|
readonly rootsCanonical?: boolean;
|
|
149
|
+
/** The home views of the placement the boundary's deny seats were compiled at (read-deny-home.ts) — the Runner's. Absent,
|
|
150
|
+
* the declared home alone. */
|
|
151
|
+
readonly homeViews?: HomeViewSource;
|
|
148
152
|
}
|
|
149
153
|
/** What a consumer hands {@link judgeShellRead}: the command as written, the environment whose filesystem
|
|
150
154
|
* answers where its words land (symlinks resolved, patterns expanded — a `NodeExecutionEnv` over a real
|
|
@@ -191,6 +195,11 @@ export interface ShellReadJudgment {
|
|
|
191
195
|
* LAXER where only a filesystem sees the escape (an in-root symlink pointing out vouches) and answers a
|
|
192
196
|
* pattern on its spelling alone, so the two diverge in both directions.
|
|
193
197
|
*
|
|
198
|
+
* A read target is also judged at its HOME VIEW (a path spelled through a firmlink or a bind mount of a home, respelled
|
|
199
|
+
* at the home): this judgment is its own placement — the views read the environment's declared home and the account
|
|
200
|
+
* home the account database answers once, for this call (`readDenyHomesOf`'s homes); the account home's probes wait at
|
|
201
|
+
* most a short bound of their own.
|
|
202
|
+
*
|
|
194
203
|
* It carries NO deadline. Bounding an environment-facing wait and substituting a fail-closed verdict is the
|
|
195
204
|
* gate FOLD's policy on the gate's one deadline seat; the station has never held one on either entry, and a
|
|
196
205
|
* second default here would decide the same number twice and turn a slow-but-correct filesystem's true
|
|
@@ -6,6 +6,7 @@ import { NOT_AUTO_ALLOWED, programPositionRefusalSentence, readProgramPosition }
|
|
|
6
6
|
import { bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
|
|
7
7
|
import { planShellReads } from "./shell-read-plan.js";
|
|
8
8
|
import { createShellReadEstablisher, establishShellReadsLexically } from "./shell-read-establish.js";
|
|
9
|
+
import { declaredReadDenyHomes, homeViewsAt } from "./read-deny-home.js";
|
|
9
10
|
import { programArgs, spellsOption } from "./shell-read-write-targets.js";
|
|
10
11
|
import { allFindings, decideShellRead, denyRowAtOrBelow, foldShellReadFindings, judgeEstablishedPlan, readRootGrantCandidateOf, } from "./shell-read-judge.js";
|
|
11
12
|
import { seatOf } from "../../core/tool-name-readings.js";
|
|
@@ -326,7 +327,7 @@ async function runShellReadStation(tool, command, declared, seat, readers, estab
|
|
|
326
327
|
}
|
|
327
328
|
export async function judgeShellRead(input) {
|
|
328
329
|
const readers = input.readers ?? new Set(bashClassifyDefaultAllow());
|
|
329
|
-
const run = await runShellReadStation(input.tool ?? "Bash", input.command, input.boundary, input.seat, readers, createShellReadEstablisher(input.env, input.boundary.roots), input.env, input.signal);
|
|
330
|
+
const run = await runShellReadStation(input.tool ?? "Bash", input.command, input.boundary, input.seat, readers, createShellReadEstablisher(input.env, input.boundary.roots, { homeViews: homeViewsAt(input.env, declaredReadDenyHomes(input.env)) }), input.env, input.signal);
|
|
330
331
|
const offer = normalizeReadRootCandidate(run.verdict.readRootCandidate);
|
|
331
332
|
return {
|
|
332
333
|
plan: run.plan,
|
|
@@ -349,7 +350,7 @@ export function createShellReadStation(opts) {
|
|
|
349
350
|
const key = roots.join("\0");
|
|
350
351
|
let e = establishers.get(key);
|
|
351
352
|
if (e === undefined) {
|
|
352
|
-
e = createShellReadEstablisher(opts.env, roots, { rootsCanonical: opts.rootsCanonical === true });
|
|
353
|
+
e = createShellReadEstablisher(opts.env, roots, { rootsCanonical: opts.rootsCanonical === true, ...(opts.homeViews !== undefined ? { homeViews: opts.homeViews } : {}) });
|
|
353
354
|
establishers.set(key, e);
|
|
354
355
|
}
|
|
355
356
|
return e;
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
41
41
|
import type { ShellReadPlan, ShellReadSegment } from "./shell-read-plan.js";
|
|
42
42
|
import type { EstablishedRead, ShellReadBoundary } from "./shell-read-judge.js";
|
|
43
|
+
import type { HomeViewSource } from "./read-deny-home.js";
|
|
43
44
|
/**
|
|
44
45
|
* How many directories one glob operand (or one traversal root) may enumerate, and how many names it may
|
|
45
46
|
* match, before the leg gives up and answers `unestablishable`.
|
|
@@ -82,7 +83,9 @@ export declare function establishShellReadsLexically(plan: ShellReadPlan, bounda
|
|
|
82
83
|
* The filesystem-resolving establisher for one environment and one root set. `readRoots` are the roots
|
|
83
84
|
* as declared (canonical by contract); they are resolved once more per instance because the comparison is
|
|
84
85
|
* against `realpath` output on both sides — an aliased root would otherwise refuse every read under itself.
|
|
86
|
+
* `homeViews`: the home views of the placement the deny seats were compiled at (read-deny-home.ts); absent, the declared home's.
|
|
85
87
|
*/
|
|
86
88
|
export declare function createShellReadEstablisher(env: ExecutionEnv, readRoots: readonly string[], opts?: {
|
|
87
89
|
readonly rootsCanonical?: boolean;
|
|
90
|
+
readonly homeViews?: HomeViewSource;
|
|
88
91
|
}): ShellReadEstablisher;
|
|
@@ -534,7 +534,7 @@ export function createShellReadEstablisher(env, readRoots, opts) {
|
|
|
534
534
|
if (lexicalOnly)
|
|
535
535
|
return establishShellReadsLexically(plan, boundary);
|
|
536
536
|
const roots = await canonicalRoots(signal);
|
|
537
|
-
return await withHomeViews(env, await establishPlan(plan, { ...boundary, roots }, roots, signal), signal);
|
|
537
|
+
return await withHomeViews(env, opts, await establishPlan(plan, { ...boundary, roots }, roots, signal), signal);
|
|
538
538
|
},
|
|
539
539
|
reestablish: async (previous, boundary, signal) => {
|
|
540
540
|
if (lexicalOnly)
|