@sema-agent/core 7.17.4 → 7.17.6
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 +77 -0
- package/dist/core/memory-engine/delegation-settlement.d.ts +27 -9
- package/dist/core/memory-engine/delegation-settlement.js +155 -46
- package/dist/core/memory-engine/engine.js +12 -6
- package/dist/core/remote-env.d.ts +23 -1
- package/dist/core/remote-env.js +4 -1
- package/dist/core/runner/prepare-hands-readface.js +10 -8
- package/dist/core/tool-catalog-entries.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +46 -5
- package/dist/tools/fs/bash-readonly-classifier.js +572 -80
- package/dist/tools/fs/fs-bash.d.ts +28 -0
- package/dist/tools/fs/fs-bash.js +135 -27
- package/dist/tools/fs/index.d.ts +13 -0
- package/dist/tools/fs/index.js +1 -0
- package/package.json +3 -2
- package/wire-changes/core@7.10.0.json +739 -0
- package/wire-changes/core@7.11.0.json +685 -0
- package/wire-changes/core@7.11.1.json +112 -0
- package/wire-changes/core@7.11.2.json +73 -0
- package/wire-changes/core@7.12.0.json +356 -0
- package/wire-changes/core@7.13.0.json +199 -0
- package/wire-changes/core@7.14.0.json +578 -0
- package/wire-changes/core@7.15.0.json +174 -0
- package/wire-changes/core@7.16.0.json +132 -0
- package/wire-changes/core@7.17.0.json +1621 -0
- package/wire-changes/core@7.17.1.json +139 -0
- package/wire-changes/core@7.17.2.json +65 -0
- package/wire-changes/core@7.17.3.json +44 -0
- package/wire-changes/core@7.17.4.json +52 -0
- package/wire-changes/core@7.17.5.json +67 -0
- package/wire-changes/core@7.5.0.json +1063 -0
- package/wire-changes/core@7.5.1.json +252 -0
- package/wire-changes/core@7.5.2.json +418 -0
- package/wire-changes/core@7.6.0.json +1243 -0
- package/wire-changes/core@7.6.1.json +1130 -0
- package/wire-changes/core@7.7.0.json +509 -0
- package/wire-changes/core@7.8.0.json +1293 -0
- package/wire-changes/core@7.9.0.json +691 -0
- package/wire-changes/core@7.9.1.json +518 -0
- package/wire-changes/core@7.9.2.json +46 -0
package/dist/core/remote-env.js
CHANGED
|
@@ -40,5 +40,8 @@ export function isSuspendable(env) {
|
|
|
40
40
|
return isRemoteExecutionEnv(env) && env.capabilities.suspendable && missingRestoreSurface(env).length === 0;
|
|
41
41
|
}
|
|
42
42
|
export function isIsolated(env) {
|
|
43
|
-
return isRemoteExecutionEnv(env) ? env.capabilities.isolation : false;
|
|
43
|
+
return isRemoteExecutionEnv(env) ? env.capabilities.isolation === true : false;
|
|
44
|
+
}
|
|
45
|
+
export function boundaryIsTheSandbox(env) {
|
|
46
|
+
return isIsolated(env);
|
|
44
47
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { emitTrace } from "../trace.js";
|
|
2
2
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
3
3
|
import { hasBackgroundShell } from "../background-shell.js";
|
|
4
|
+
import { boundaryIsTheSandbox } from "../remote-env.js";
|
|
4
5
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
5
6
|
import { bashReadBoundaryProbe, bashReversibilityProbe, compileReadDeny, createHandsToolkit, deploymentReadFaceClampNotice, pdfModelCapabilitiesOf, resolveReadDenyBuiltins, inheritSessionReadFileState, resolveReadFace, seedReadFileStateFromContext, seedReadFileStateFromTranscript, } from "../../tools/fs/index.js";
|
|
6
7
|
import { resolveKey, sha256 } from "../../tools/fs/safety.js";
|
|
@@ -293,6 +294,13 @@ export async function prepareHandsMount(input) {
|
|
|
293
294
|
sessionSeats.reanchor(sessionId, readFileState, ownCommittedTailEntryId);
|
|
294
295
|
}
|
|
295
296
|
: undefined;
|
|
297
|
+
const shellReadBoundary = () => ({
|
|
298
|
+
roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
|
|
299
|
+
...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
|
|
300
|
+
...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
|
|
301
|
+
denyMatch: (p) => readDenyMatcher?.matchPath(p)?.pattern ?? null,
|
|
302
|
+
...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
|
|
303
|
+
});
|
|
296
304
|
const band = createHandsToolkit(executionEnv, readFileState, rootCanonical, {
|
|
297
305
|
...(additionalRootsCanonical.length > 0 ? { additionalRoots: additionalRootsCanonical } : {}),
|
|
298
306
|
...(additionalReadRootsCanonical.length > 0 ? { additionalReadRoots: additionalReadRootsCanonical } : {}),
|
|
@@ -306,6 +314,7 @@ export async function prepareHandsMount(input) {
|
|
|
306
314
|
...(input.onFileEdited !== undefined ? { onFileEdited: input.onFileEdited } : {}),
|
|
307
315
|
includeShell: handsIncludeShell,
|
|
308
316
|
readOnly: handsReadOnly,
|
|
317
|
+
shellReadBoundary,
|
|
309
318
|
...(handsCwdRef ? { cwdRef: handsCwdRef } : {}),
|
|
310
319
|
taskRegistry: defaultTaskRegistry,
|
|
311
320
|
taskOwner: hostTaskId,
|
|
@@ -346,18 +355,11 @@ export async function prepareHandsMount(input) {
|
|
|
346
355
|
if (!(executionEnv instanceof StubExecutionEnv) && spec.handsReadOnly !== true) {
|
|
347
356
|
if (shellGate === "off") {
|
|
348
357
|
deps.onError?.(new Error(`shell gate doctrine is "off" while a real writable shell (Bash) is mounted — the read boundary is ` +
|
|
349
|
-
`still judged on every command (a deny-listed or out-of-root read asks; the ask is mandated), but no ` +
|
|
358
|
+
`still judged on every command (a deny-listed or out-of-root read asks; the ask is mandated${boundaryIsTheSandbox(executionEnv) ? "" : "; a read that only leaves the roots after symlink resolution is refused before it runs"}), but no ` +
|
|
350
359
|
`residual shell-risk classifier is installed: writes, egress and unknown commands are adjudicated by ` +
|
|
351
360
|
`the ordinary policy/hook chain only. Set spec.shellGate to "classify" or "always" if this deployment ` +
|
|
352
361
|
`expects doctrine-gated shell behavior.`), { phase: "config", sessionId, classification: "shell-gate-off" });
|
|
353
362
|
}
|
|
354
|
-
const shellReadBoundary = () => ({
|
|
355
|
-
roots: [rootCanonical, ...additionalRootsCanonical, ...additionalReadRootsCanonical],
|
|
356
|
-
...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}),
|
|
357
|
-
...(executionEnv.homeDir !== undefined ? { homeDir: executionEnv.homeDir } : {}),
|
|
358
|
-
denyMatch: (p) => readDenyMatcher?.matchPath(p)?.pattern ?? null,
|
|
359
|
-
...(resolvedReadFace !== undefined ? { face: resolvedReadFace } : {}),
|
|
360
|
-
});
|
|
361
363
|
const installShellSeat = (name) => {
|
|
362
364
|
const tierBefore = irreversibilityTier.get(name);
|
|
363
365
|
const seatOwned = !egressTools.has(name) && tierBefore !== "always" && tierBefore !== "maybe";
|
|
@@ -56,7 +56,7 @@ export const TOOL_CATALOG_ENTRIES = [
|
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
id: "bash", name: "Bash", source: "builtin", definedIn: "src/tools/fs/fs-bash.ts", mountedBy: ["hands"],
|
|
59
|
-
cards: ["bash", "bash_invalid_timeout"],
|
|
59
|
+
cards: ["bash", "readonly_out_of_root", "bash_invalid_timeout"],
|
|
60
60
|
face: {
|
|
61
61
|
family: "shell", effect: "write", contentOrigin: "execution", contract: c(FULL_SHELL_CONTRACT_ID),
|
|
62
62
|
ruleFace: SHELL_RULE_FACE,
|
|
@@ -140,10 +140,22 @@ export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<
|
|
|
140
140
|
export declare function carriesShellRedirection(text: string): boolean;
|
|
141
141
|
/** What {@link splitShellCompoundSegments} produces: the connector-delimited segments, plus which of
|
|
142
142
|
* them a PIPE fed (the read-only classifier's stdin arm needs that distinction; a rule lane does not). */
|
|
143
|
+
/** The connector bash ran a segment through — `"start"` for the first one. A closed set, because the read
|
|
144
|
+
* boundary distinguishes all four: `&&` is the only one under which the previous segment is known to have
|
|
145
|
+
* exited ZERO, `||` is the only one under which the previous segment may have been SKIPPED ENTIRELY, `|`
|
|
146
|
+
* puts the segment in a subshell whose directory changes never reach the parent, and `;` is plain sequence. */
|
|
147
|
+
export type ShellSegmentConnector = "start" | ";" | "&&" | "||" | "|";
|
|
143
148
|
export interface ShellCompoundSegments {
|
|
144
149
|
segments: string[];
|
|
145
|
-
/** Parallel to {@link segments}: was this segment preceded by `|` (rather than `;`/`&&`/`||`/nothing)?
|
|
150
|
+
/** Parallel to {@link segments}: was this segment preceded by `|` (rather than `;`/`&&`/`||`/nothing)?
|
|
151
|
+
* A VIEW of {@link connectors}, derived at the one place both are minted — two rows that could drift are
|
|
152
|
+
* two rows that will. */
|
|
146
153
|
pipeFed: boolean[];
|
|
154
|
+
/** Parallel to {@link segments}: the connector bash reached this segment through. Read by the read
|
|
155
|
+
* boundary, where it is the difference between a directory change the shell itself proved happened and
|
|
156
|
+
* one this walk cannot verify. A consumer that does not read it gets the unverified reading, which is
|
|
157
|
+
* the fail-closed one. */
|
|
158
|
+
connectors: ShellSegmentConnector[];
|
|
147
159
|
}
|
|
148
160
|
/**
|
|
149
161
|
* Split a compound command into the segments bash would run, or refuse the whole string.
|
|
@@ -228,10 +240,10 @@ export interface BashReadonlyRootBoundary {
|
|
|
228
240
|
* allowlists classifies by its ordinary form and can auto-run a traversal unjudged);
|
|
229
241
|
* ② LEXICAL only — an in-root symlink whose target is a
|
|
230
242
|
* guarded path reads as its innocent spelling here, exactly as it does for the containment half
|
|
231
|
-
* (the enforcing/canonicalizing recheck is
|
|
243
|
+
* (the enforcing/canonicalizing recheck is both shell faces' execute leg, via checkedPaths; the
|
|
232
244
|
* classify auto-allow lane has no I/O seat by contract). The structured read faces judge BOTH
|
|
233
|
-
* views (canonical + lexical)
|
|
234
|
-
*
|
|
245
|
+
* views (canonical + lexical); what this seat is and is not is stated once, at
|
|
246
|
+
* `shell.read_boundary.friction_floor`. The `bash_readonly` face deliberately does NOT wire this seat (its
|
|
235
247
|
* roots containment + command allowlist double gate is the deployment's own read-safety
|
|
236
248
|
* declaration — v1 ruling). */
|
|
237
249
|
denyMatch?: (resolvedPath: string) => string | null;
|
|
@@ -315,6 +327,20 @@ export interface CompoundReadonlyVerdict {
|
|
|
315
327
|
* and miss the other.
|
|
316
328
|
*/
|
|
317
329
|
undecidedPaths?: readonly string[];
|
|
330
|
+
/** The KERNEL spellings of the {@link checkedPaths} / {@link undecidedPaths} entries whose operand
|
|
331
|
+
* traverses `..`: the same absolute path, `..` UNCOLLAPSED ({@link OperandSpellings.spelled}). A fold
|
|
332
|
+
* names a different file whenever a parent step follows a symlink — `<root>/link/../x` is
|
|
333
|
+
* `dirname(realpath(link))/x` to the kernel, `<root>/x` to the text. Absent when nothing traverses
|
|
334
|
+
* `..`, and never a replacement: a resolving caller judges these BESIDE the folded entries, list for
|
|
335
|
+
* list. Containment, deny judgment and every `reason` here stay decided on the FOLDED form — this file
|
|
336
|
+
* is zero-I/O, and only a caller holding the filesystem can tell the two apart. */
|
|
337
|
+
checkedSpellings?: readonly string[];
|
|
338
|
+
/** The kernel spellings of {@link undecidedPaths} — see {@link checkedSpellings}. */
|
|
339
|
+
undecidedSpellings?: readonly string[];
|
|
340
|
+
/** The kernel spellings of {@link outOfRootPaths} — see {@link checkedSpellings}. A caller GRANTING
|
|
341
|
+
* anything off a resolved out-of-root path judges this beside it: the two can disagree about whether
|
|
342
|
+
* the target is back inside. */
|
|
343
|
+
outOfRootSpellings?: readonly string[];
|
|
318
344
|
/**
|
|
319
345
|
* Operands (written spellings) the walk could NOT resolve at all — a `~user` form, a `~` under no declared
|
|
320
346
|
* home, an argument the shell rewrites (brace/variable/command expansion), a `cd` target it cannot follow
|
|
@@ -389,6 +415,20 @@ export declare function formatOutOfRootReadApprovalOption(directory: string): st
|
|
|
389
415
|
* has no filesystem and stays lexical, and a REMOTE env keeps the lexical behaviour (its
|
|
390
416
|
* `canonicalPath` is an RPC per candidate). */
|
|
391
417
|
export declare function resolveOperandLexically(base: string | undefined, operand: string, homeDir: string | undefined): string | undefined;
|
|
418
|
+
/** One operand's two absolute spellings. `folded` has its `..` collapsed in the TEXT and is the
|
|
419
|
+
* containment answer — every root comparison, deny judgment and verdict field here is built from it.
|
|
420
|
+
* `spelled` keeps those segments, the walk the KERNEL performs: each prefix resolves (symlinks included)
|
|
421
|
+
* BEFORE a parent step applies, so `<root>/link/../x` names `dirname(realpath(link))/x`. Present ONLY
|
|
422
|
+
* when the operand traverses `..`; a filesystem-resolving caller judges it BESIDE the folded form. */
|
|
423
|
+
export interface OperandSpellings {
|
|
424
|
+
/** The `..`-collapsed absolute path — what containment is decided on. */
|
|
425
|
+
folded: string;
|
|
426
|
+
/** The uncollapsed absolute path, when the operand traverses `..`. */
|
|
427
|
+
spelled?: string;
|
|
428
|
+
}
|
|
429
|
+
/** Resolve one operand into {@link OperandSpellings}, or `undefined` on the same unresolvable operands
|
|
430
|
+
* {@link resolveOperandLexically} reports (it is this function's folded half). */
|
|
431
|
+
export declare function resolveOperandSpellings(base: string | undefined, operand: string, homeDir: string | undefined): OperandSpellings | undefined;
|
|
392
432
|
/** Whether a command the grammar gate REFUSED WHOLE may READ a file the boundary never judged. The answer is the
|
|
393
433
|
* complement of a WHITELIST: the command is exempt only when every part of it is a shape the scan fully understands
|
|
394
434
|
* and none of those shapes reads a file outside the walk's reach — everything else is read evidence, so a token the
|
|
@@ -459,7 +499,8 @@ opts?: {
|
|
|
459
499
|
* so this face bounds where a command may NAME a file, not where the kernel will ultimately take it.
|
|
460
500
|
* A deployment that needs filesystem-true containment resolves the operands itself, and this
|
|
461
501
|
* verdict hands it the list to resolve ({@link CompoundReadonlyVerdict.checkedPaths}, on both arms).
|
|
462
|
-
*
|
|
502
|
+
* The engine's own shell legs are that caller on a local env — the read-only face before it runs an
|
|
503
|
+
* allowlisted command, the full face after its gate allowed one.
|
|
463
504
|
*/
|
|
464
505
|
export declare function classifySimpleCommandReadBoundary(command: string, boundary: BashReadonlyRootBoundary): CompoundReadonlyVerdict;
|
|
465
506
|
/**
|