@sema-agent/core 7.34.0 → 7.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +123 -3
  2. package/dist/brain/errors.d.ts +23 -1
  3. package/dist/brain/errors.js +19 -2
  4. package/dist/brain/status-sink.d.ts +7 -1
  5. package/dist/brain/stream-shared.d.ts +3 -1
  6. package/dist/brain/stream-shared.js +16 -4
  7. package/dist/core/ask-origin.d.ts +34 -6
  8. package/dist/core/ask-origin.js +24 -23
  9. package/dist/core/ask-question.js +2 -1
  10. package/dist/core/checkpoint-store.d.ts +4 -0
  11. package/dist/core/gate-fold.js +2 -2
  12. package/dist/core/gate-lanes.js +5 -4
  13. package/dist/core/governance-codes.d.ts +17 -1
  14. package/dist/core/governance-codes.js +11 -1
  15. package/dist/core/hooks.d.ts +3 -0
  16. package/dist/core/message-utils.d.ts +2 -1
  17. package/dist/core/message-utils.js +2 -1
  18. package/dist/core/mode-policy.d.ts +3 -0
  19. package/dist/core/mode-policy.js +5 -0
  20. package/dist/core/permission-rule-model.d.ts +4 -0
  21. package/dist/core/permission-rule-model.js +4 -0
  22. package/dist/core/permission-rules.js +21 -1
  23. package/dist/core/persisted-rule-arms.d.ts +4 -1
  24. package/dist/core/persisted-rule-arms.js +4 -0
  25. package/dist/core/retired-keys.js +1 -1
  26. package/dist/core/runner/active-skill-scope.js +5 -4
  27. package/dist/core/runner/assemble-result.js +2 -2
  28. package/dist/core/runner/contracts.d.ts +25 -3
  29. package/dist/core/runner/denial-limit-arms.d.ts +6 -1
  30. package/dist/core/runner/denial-limit-arms.js +4 -4
  31. package/dist/core/runner/file-history-request.d.ts +26 -0
  32. package/dist/core/runner/file-history-request.js +90 -0
  33. package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
  34. package/dist/core/runner/permission-rule-lanes.js +14 -11
  35. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  36. package/dist/core/runner/prepare-ask-lane.js +2 -2
  37. package/dist/core/runner/prepare-file-history.d.ts +9 -3
  38. package/dist/core/runner/prepare-file-history.js +4 -90
  39. package/dist/core/runner/prepare-gate-stations.d.ts +4 -3
  40. package/dist/core/runner/prepare-gate-stations.js +15 -11
  41. package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
  42. package/dist/core/runner/prepare-inherited-gate.js +10 -4
  43. package/dist/core/runner/prepare-park-ask.d.ts +4 -2
  44. package/dist/core/runner/prepare-park-ask.js +5 -4
  45. package/dist/core/runner/prepare-policy-chain.d.ts +3 -2
  46. package/dist/core/runner/prepare-policy-chain.js +171 -175
  47. package/dist/core/runner/prepare-task.js +7 -4
  48. package/dist/core/runner/run-brain-sinks.js +3 -1
  49. package/dist/core/runner/run-leg.js +5 -3
  50. package/dist/core/runner/run-notification-lane.d.ts +3 -0
  51. package/dist/core/runner/run-notification-lane.js +2 -1
  52. package/dist/core/runner/runtask.d.ts +1 -0
  53. package/dist/core/runner/runtask.js +5 -11
  54. package/dist/core/runner/session-mode-seats.d.ts +48 -0
  55. package/dist/core/runner/session-mode-seats.js +73 -0
  56. package/dist/core/runner/session-rule-policy.js +3 -2
  57. package/dist/core/secret-env.d.ts +30 -58
  58. package/dist/core/secret-env.js +149 -586
  59. package/dist/core/sensitive-path-policy.js +2 -1
  60. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  61. package/dist/core/tool-policy.d.ts +31 -6
  62. package/dist/core/tool-policy.js +35 -16
  63. package/dist/core/tool-registry.d.ts +36 -0
  64. package/dist/core/tool-registry.js +22 -1
  65. package/dist/core/tool-roster.d.ts +17 -2
  66. package/dist/core/tool-roster.js +11 -1
  67. package/dist/core/trace.d.ts +11 -0
  68. package/dist/engine/execution-env/node-execution-env.d.ts +3 -1
  69. package/dist/engine/execution-env/node-execution-env.js +5 -2
  70. package/dist/engine/harness/agent-harness.js +2 -1
  71. package/dist/engine/lsp/node-lsp-manager.js +1 -1
  72. package/dist/index.d.ts +4 -4
  73. package/dist/index.js +4 -4
  74. package/dist/stores/file/index.d.ts +5 -3
  75. package/dist/stores/file/session-store.d.ts +8 -2
  76. package/dist/stores/file/session-store.js +5 -5
  77. package/dist/tools/fs/file-read-check.d.ts +2 -1
  78. package/dist/tools/fs/file-read-check.js +15 -7
  79. package/dist/tools/fs/shell-read-check.d.ts +10 -2
  80. package/dist/tools/fs/shell-read-check.js +12 -8
  81. package/package.json +1 -1
  82. package/test/export-surface.snapshot.json +15 -1
  83. package/wire-changes/core@7.35.0.json +584 -0
@@ -513,6 +513,7 @@ export async function runLeg(input) {
513
513
  }
514
514
  prepared.abortController.abort();
515
515
  prepared.releaseSignal();
516
+ prepared.endModeCeiling();
516
517
  strandedHumanAnswers = prepared.settleContentAskBindings();
517
518
  try {
518
519
  prepared.harness.recoverUndrainedEngineNotes();
@@ -633,7 +634,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
633
634
  }
634
635
  if (prepared.permissionRuleOrg !== undefined) {
635
636
  const orgVerdict = prepared.permissionRuleOrg
636
- .adjudicate({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId })
637
+ .adjudicate({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId, ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)) })
637
638
  .catch(() => ({ status: "unavailable", disclosures: ["the org adjudication face threw on resume"] }));
638
639
  let orgWaitCancelled = false;
639
640
  const org = await settleOrgVerdictWithin(orgVerdict, { status: "unavailable", disclosures: [`the org adjudication face did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms (or the task ended first)`] }, {
@@ -716,7 +717,8 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
716
717
  }
717
718
  if (prepared.shellReadCheck !== undefined) {
718
719
  const edited = outcome.updatedInput !== undefined && !deepJsonEqual(outcome.updatedInput, pendingAction.args);
719
- const judged = prepared.shellReadCheck.judge(pendingAction.toolName, resolvedArgs, prepared.cwdRef?.current, prepared.abortController.signal, {
720
+ const readCall = { toolName: pendingAction.toolName, ...callFaceSeat(toolCallFaceOf(prepared.toolRosterDeltas.current, pendingAction.toolName)) };
721
+ const judged = prepared.shellReadCheck.judge(readCall, resolvedArgs, prepared.cwdRef?.current, prepared.abortController.signal, {
720
722
  toolCallId: pendingAction.toolCallId,
721
723
  ...(edited ? { seat: "rejudge" } : {}),
722
724
  });
@@ -728,7 +730,7 @@ async function resolvePendingCall(runner, prepared, resume, emit, emitCommitted,
728
730
  }
729
731
  catch {
730
732
  answered = false;
731
- verdict = prepared.shellReadCheck.unanswered(pendingAction.toolName, "the read station could not answer on the resumed leg");
733
+ verdict = prepared.shellReadCheck.unanswered(readCall, "the read station could not answer on the resumed leg");
732
734
  }
733
735
  if (!answered || verdict.disposition === "refuse" || (edited && verdict.disposition !== "vouch")) {
734
736
  const readDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" was not executed: ${verdict.sentence ?? "the read boundary does not vouch for what it reads on this leg"} This approval is spent — the call has to be re-issued and approved again.`, prepared.reminderMark);
@@ -4,6 +4,7 @@ import type { SessionStore } from "../session.js";
4
4
  import { type NotificationInjectionOutcome, type PendingSessionNotifications, type SystemInjectionPriority, type TaskNotificationPayload } from "../task-notification.js";
5
5
  import type { RunnerDeps, TaskEvent, TaskSpec } from "../types.js";
6
6
  import type { SessionReadFileStates } from "./session-read-file-states.js";
7
+ import type { SessionModeSeats } from "./session-mode-seats.js";
7
8
  import type { NotificationLaneBindings, Prepared, PrepareTaskFn, ResumeRun, RunInternals, RunnerDepsSeat, RunnerSelfSeat, TaskIdRef } from "./contracts.js";
8
9
  export interface RunNotificationLaneInput<R> {
9
10
  /** borrowed-readonly — the task spec: prepare's input, and the principal the peer scope is minted from. */
@@ -26,6 +27,8 @@ export interface RunNotificationLaneInput<R> {
26
27
  sessions: SessionStore;
27
28
  /** borrowed-mutable — the Runner's own per-session read-state seat, ALWAYS this Runner's (a caller-supplied one is overridden). */
28
29
  sessionReadStates: SessionReadFileStates;
30
+ /** borrowed-mutable — the Runner's per-session permission-mode seats, ALWAYS this Runner's (a caller-supplied one is overridden). */
31
+ sessionModeSeats: SessionModeSeats;
29
32
  /** borrowed-mutable — the Runner's per-session parked-notification store: the torn-down lane and the escalation pend here. */
30
33
  pendingSessionNotifications: PendingSessionNotifications;
31
34
  /** borrowed-readonly — the orchestrator's entry (a lane may not name the orchestrator; the driver hands it in), as the ONE
@@ -8,7 +8,7 @@ export function runNotificationLane(input) {
8
8
  return obligations.run(() => runNotificationLaneAs(input, obligations));
9
9
  }
10
10
  async function runNotificationLaneAs(input, obligations) {
11
- const { spec, queue, resume, internals, taskIdRef, runner, self, sessions, sessionReadStates, pendingSessionNotifications, prepareTask, next } = input;
11
+ const { spec, queue, resume, internals, taskIdRef, runner, self, sessions, sessionReadStates, sessionModeSeats, pendingSessionNotifications, prepareTask, next } = input;
12
12
  const prepareResume = resume
13
13
  ? {
14
14
  leafId: resume.cp.leafId,
@@ -118,6 +118,7 @@ async function runNotificationLaneAs(input, obligations) {
118
118
  peerInboundChainRef,
119
119
  detachHub,
120
120
  sessionReadStates,
121
+ sessionModeSeats,
121
122
  onTaskNotification: (notification, opts) => {
122
123
  try {
123
124
  upstreamTaskNotification?.(notification, opts);
@@ -22,6 +22,7 @@ export declare class Runner implements RunnerSelfSeat {
22
22
  * authority: a durable resume's checkpoint seed wins on its leg, and a miss (cross-process,
23
23
  * evicted) falls back to the transcript-replay rung. See {@link SessionReadFileStates}. */
24
24
  private readonly sessionReadStates;
25
+ private readonly sessionModeSeats;
25
26
  /** task notifications born BETWEEN turns (run torn down / harness already idle), parked
26
27
  * per session and drained into the session's next run at its first boundary. Runner-lived (outlives any
27
28
  * single run, like the registry handles that produce into it); bounded + drop-disclosing, see
@@ -16,9 +16,10 @@ import { TtlSessionStore } from "../session-store.js";
16
16
  import { errorCodeOf } from "./assemble-result.js";
17
17
  import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
18
18
  import { SessionReadFileStates } from "./session-read-file-states.js";
19
+ import { SessionModeSeats } from "./session-mode-seats.js";
19
20
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
20
21
  import { PAUSE_REGISTRY } from "../pause-registry.js";
21
- import { toolPolicyNameSets } from "../tool-policy.js";
22
+ import { wrapToolPolicy } from "../tool-policy.js";
22
23
  import { PendingSessionNotifications } from "../task-notification.js";
23
24
  import { ToolDetachHub } from "../tool-detach.js";
24
25
  import { assertContextSettingsValue, assertContextSettingsWords, contextOverlayFor } from "../context-settings.js";
@@ -65,6 +66,7 @@ export class Runner {
65
66
  sessions;
66
67
  sessionLocks = new Map();
67
68
  sessionReadStates = new SessionReadFileStates();
69
+ sessionModeSeats = new SessionModeSeats();
68
70
  pendingSessionNotifications = new PendingSessionNotifications();
69
71
  parentConstraintRegistry = new Map();
70
72
  suspendedEnvReaps = new Map();
@@ -174,15 +176,7 @@ export class Runner {
174
176
  return {
175
177
  ...(deps.toolPolicy !== undefined
176
178
  ? {
177
- toolPolicy: (() => {
178
- const sets = toolPolicyNameSets(deps.toolPolicy);
179
- const wrapped = {
180
- check: (req, signal) => deps.toolPolicy.check(req, signal),
181
- };
182
- if (sets.length > 0)
183
- wrapped.nameSets = sets;
184
- return wrapped;
185
- })(),
179
+ toolPolicy: wrapToolPolicy(deps.toolPolicy, (req, signal) => deps.toolPolicy.check(req, signal)),
186
180
  }
187
181
  : {}),
188
182
  ...(hooks !== undefined ? { hooks } : {}),
@@ -445,7 +439,7 @@ export class Runner {
445
439
  const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
446
440
  return runNotificationLane({
447
441
  spec, queue, resume, internals, taskIdRef,
448
- runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, pendingSessionNotifications: this.pendingSessionNotifications,
442
+ runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, sessionModeSeats: this.sessionModeSeats, pendingSessionNotifications: this.pendingSessionNotifications,
449
443
  prepareTask,
450
444
  next: ({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }) => {
451
445
  const { runSourceTaskId, parentToolCallId, ident, emitDelegationLifecycle, loopLatch, stats, internalsNotifier, undrainedUserAtEnd, userInputLostAtEnd } = runIdentityWiring({
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The Runner's per-session PERMISSION-MODE seats: the mode a session's CURRENT leg runs under, as the object a
3
+ * child's ceiling falls back to once the leg that spawned it has ended. A child's ceiling used to be the controller
4
+ * of the leg that spawned it for the child's whole life, so a session-scoped background child kept that leg's mode
5
+ * after the leg ended — the next leg of the same session, started in `plan`, did not reach it. The chain now carries
6
+ * a LEG ceiling: the spawning leg's live mode while that leg runs, and the session's seat after it. Each leg binds
7
+ * itself to its session's seat when its prepare commits, so the seat answers the most recently committed leg; a leg
8
+ * that is still running keeps answering its own children, whatever another leg of the session commits meanwhile.
9
+ */
10
+ import type { PermissionMode } from "../ask-origin.js";
11
+ import type { PermissionModeReceipt } from "../mode-policy.js";
12
+ /** A child leg whose ceiling is a leg ceiling: its receipt is read before a ceiling move and re-judged after it. */
13
+ export interface ModeCeilingHolder {
14
+ receipt(): PermissionModeReceipt;
15
+ /** The ceiling's source moved; `before` is this holder's receipt under the previous source. */
16
+ ceilingRebound(before: PermissionModeReceipt): void;
17
+ }
18
+ /** One leg's ceiling, as the chain carries it (`InheritedGate.permissionModeCeiling`). */
19
+ export interface LegModeCeiling {
20
+ /** The spawning leg's live mode while it runs; the session's current leg's after it (`default` before any leg of
21
+ * the session committed — no child can read it then). */
22
+ readonly current: PermissionMode;
23
+ readonly sessionId: string;
24
+ /** A child leg holds the ceiling until `signal` aborts (its own leg's end): each move of the ceiling re-judges it. */
25
+ hold(holder: ModeCeilingHolder, signal: AbortSignal): void;
26
+ }
27
+ /**
28
+ * The seats of one Runner, keyed by session id and held WEAKLY: a seat lives while a leg ceiling holds it, and the
29
+ * next leg of the session finds the same object as long as any child still reads it. In-process only — a session
30
+ * continued on another Runner instance or in another process binds that Runner's seat.
31
+ */
32
+ export declare class SessionModeSeats {
33
+ private readonly seats;
34
+ private readonly reaper;
35
+ private seatOf;
36
+ /**
37
+ * A leg's ceiling over its session's seat, the leg's commit (once its prepare succeeded: the leg becomes the
38
+ * session's current leg) and its end (once the leg has STOPPED — its loop settled, not merely cancelled: a cancelled
39
+ * leg whose tool is still pending keeps answering its own children).
40
+ */
41
+ legCeiling(sessionId: string, leg: {
42
+ readonly current: PermissionMode;
43
+ }): {
44
+ ceiling: LegModeCeiling;
45
+ commit: () => void;
46
+ end: () => void;
47
+ };
48
+ }
@@ -0,0 +1,73 @@
1
+ class Seat {
2
+ sessionId;
3
+ leg;
4
+ holders = new Set();
5
+ constructor(sessionId) {
6
+ this.sessionId = sessionId;
7
+ }
8
+ get current() {
9
+ return this.leg?.current ?? "default";
10
+ }
11
+ addHolder(holder, via, signal) {
12
+ if (signal.aborted)
13
+ return;
14
+ const entry = { holder, via };
15
+ this.holders.add(entry);
16
+ signal.addEventListener("abort", () => this.holders.delete(entry), { once: true });
17
+ }
18
+ moveCeiling(affected, move) {
19
+ const before = [...this.holders].filter((e) => affected(e.via)).map((e) => [e.holder, e.holder.receipt()]);
20
+ move();
21
+ for (const [holder, receipt] of before)
22
+ holder.ceilingRebound(receipt);
23
+ }
24
+ }
25
+ class LegCeiling {
26
+ leg;
27
+ seat;
28
+ live = true;
29
+ constructor(leg, seat) {
30
+ this.leg = leg;
31
+ this.seat = seat;
32
+ }
33
+ get sessionId() {
34
+ return this.seat.sessionId;
35
+ }
36
+ get current() {
37
+ return this.live ? this.leg.current : this.seat.current;
38
+ }
39
+ hold(holder, signal) {
40
+ this.seat.addHolder(holder, this, signal);
41
+ }
42
+ commit() {
43
+ if (this.seat.leg === this.leg)
44
+ return;
45
+ this.seat.moveCeiling((via) => !via.live, () => (this.seat.leg = this.leg));
46
+ }
47
+ end() {
48
+ if (!this.live)
49
+ return;
50
+ this.seat.moveCeiling((via) => via === this, () => (this.live = false));
51
+ }
52
+ }
53
+ export class SessionModeSeats {
54
+ seats = new Map();
55
+ reaper = new FinalizationRegistry(({ sessionId, ref }) => {
56
+ if (this.seats.get(sessionId) === ref)
57
+ this.seats.delete(sessionId);
58
+ });
59
+ seatOf(sessionId) {
60
+ const live = this.seats.get(sessionId)?.deref();
61
+ if (live !== undefined)
62
+ return live;
63
+ const seat = new Seat(sessionId);
64
+ const ref = new WeakRef(seat);
65
+ this.seats.set(sessionId, ref);
66
+ this.reaper.register(seat, { sessionId, ref });
67
+ return seat;
68
+ }
69
+ legCeiling(sessionId, leg) {
70
+ const ceiling = new LegCeiling(leg, this.seatOf(sessionId));
71
+ return { ceiling, commit: () => ceiling.commit(), end: () => ceiling.end() };
72
+ }
73
+ }
@@ -2,6 +2,7 @@ import { canonicalizeTarget, isWinFormPath, pathFamilyOf, writeTargetPath } from
2
2
  import { createCoarseCommandNamePolicy, namespacedCoveringEntries, namespacedCoveringHit } from "../tool-policy.js";
3
3
  import { continuesLineage } from "../session-policy-store.js";
4
4
  import { thrownText } from "../thrown-value.js";
5
+ import { toolNameSetHas } from "../tool-roster.js";
5
6
  import { ruleDenialSentence } from "../rule-denial-sentence.js";
6
7
  import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
7
8
  import { declaredPathTargetOf, isProtectedWrite, pathConfinableWriteToolNames, skillScopeWriteToolNames } from "../tool-registry.js";
@@ -136,9 +137,9 @@ export function createSessionRulePolicy(rules, opts) {
136
137
  async check(req, signal) {
137
138
  const { toolDeny, toolAllow, toolDenyCovering, toolAllowCovering, cmdPolicy, allowDirs } = compile(await readRules(req.toolCallId, signal));
138
139
  const toolName = req.toolName;
139
- if (toolDeny.has(toolName) || namespacedCoveringHit(toolDenyCovering, toolName))
140
+ if (toolNameSetHas(toolDeny, req) || namespacedCoveringHit(toolDenyCovering, toolName))
140
141
  return deny(req.toolName);
141
- if (toolAllow && !toolAllow.has(toolName) && !namespacedCoveringHit(toolAllowCovering, toolName)) {
142
+ if (toolAllow && !toolNameSetHas(toolAllow, req) && !namespacedCoveringHit(toolAllowCovering, toolName)) {
142
143
  return deny(req.toolName, "it is not in the session-rule allowlist");
143
144
  }
144
145
  if (cmdPolicy) {
@@ -1,77 +1,50 @@
1
1
  /**
2
- * Canonical "local-spawn shell secret-env protection" (sema D1) — FIRST-PARTY, vendor-independent.
2
+ * The environment a spawned child inherits: which of this process's variables it must not see.
3
3
  *
4
- * When the engine (or a downstream host/exec adapter) spawns a local shell whose commands the MODEL chooses (no human at the keyboard), the child's
5
- * inherited env MUST be fail-closed: a model-chosen `printenv DEEPSEEK_API_KEY` via the `bash` tool would
6
- * otherwise exfiltrate the orchestrator's brain key / git token / DB creds (the gate authorizes WHICH tool runs,
7
- * NOT isolation). The `scrub` policy drops secret-pattern keys (keeps PATH/build/locale → low breakage).
8
- *
9
- * This lives in `src/core/` — NOT `src/vendor/` — on purpose. It is OUR security invariant (D1 is a first-party
10
- * edit, not openclaw upstream), shared by the (currently-vendored) {@link NodeExecutionEnv} AND a downstream
11
- * adapter (service host adapter). Keeping the single source of the secret PATTERN here means:
12
- * 1. A consumer imports it instead of mirroring a regex that would silently DRIFT when the pattern is extended.
13
- * 2. The public export points at a STABLE first-party path that SURVIVES the planned de-vendoring refactor —
14
- * when NodeExecutionEnv is naturalized out of `src/vendor/`, this module does not move and nothing rebinds.
15
- *
16
- * The `inheritEnv` THREE-STATE policy (`"all"` opt-out / `"scrub"` default / `string[]` allowlist) is read ONCE, by
17
- * {@link inheritedHostEnv}, for every engine spawn site that takes it (the tool shell, a `runSpec` oracle gate). A
18
- * downstream adapter composes its own child env; only the secret PATTERN is shared with it.
4
+ * A child the engine spawns may run what the MODEL chose (a tool shell, a gate over model-written code, a language
5
+ * server). Under the `"scrub"` policy (the default) it inherits every variable but those the judgment below names —
6
+ * deleted whole, never rewritten; `"all"` and a `string[]` allow list are the caller's explicit alternatives
7
+ * ({@link inheritedHostEnv}). One judgment per variable ({@link classifySecretEnvEntry}), the first rule that answers:
8
+ * 1. exempt — git's environment config and the proxy variables, handed on as written ({@link isExemptEnvName});
9
+ * 2. `exact-name` — a known credential name ({@link CREDENTIAL_ENV_NAMES});
10
+ * 3. `prefix-rule` — the OpenTelemetry settings ({@link OTEL_ENV_RE});
11
+ * 4. `namespace-rule` — a credential word as any segment of a name in the engine's own `SEMA_` namespace;
12
+ * 5. `name-rule` — a credential-shaped name ({@link nameIsCredentialShaped}) off {@link NON_SECRET_ENV_EXACT_NAMES};
13
+ * 6. `value-rule` — a value that carries a credential ({@link valueCarriesCredential}).
14
+ * {@link isSecretEnvKey} and {@link scrubSecretEnv} both answer with this one judgment. The exempt families, the
15
+ * credential names, the name shape and the value predicate follow the reference implementation's subprocess-env scrub
16
+ * as written, three ways stricter: plural `TOKENS` is a credential word unless a count word touches it, the GitHub
17
+ * token names are not held back from the name shape, and the name shape always runs.
19
18
  */
20
19
  import type { RedactionConfidence, RedactionSource } from "./arg-summary.js";
21
20
  import { type EngineNotice } from "./engine-notice.js";
22
- /** One git config entry as git reads it from the environment. A half is undefined when its source lacks it (a member
23
- * missing from its index group). */
24
- interface GitConfigEntry {
25
- readonly key?: string;
26
- readonly value?: string;
27
- }
28
- export declare function readGitConfigParameters(text: string): GitConfigEntry[] | undefined;
29
- /** Which rule classified a key as secret-shaped — the env arm's `kind` vocabulary. */
30
- export type SecretEnvFindingKind = "suffix-rule" | "namespace-rule" | "exact-name" | "family-rule";
31
- /** One env entry the `scrub` policy withheld — dropped, or, for a member of git's config record, handed on holding a
32
- * placeholder half: the key (the VALUE is never carried — this record is meant to be loggable), the rule that
33
- * classified it, how strong that claim is, and the minting module. */
21
+ /** Which rule withheld an entry. */
22
+ export type SecretEnvFindingKind = "exact-name" | "name-rule" | "namespace-rule" | "prefix-rule" | "value-rule";
23
+ /** One env entry the `"scrub"` policy withheld (deleted): its name — never its value, so the record is loggable —, the
24
+ * rule that withheld it, how strong that claim is, and the minting module. */
34
25
  export interface SecretEnvFinding {
35
26
  key: string;
36
27
  kind: SecretEnvFindingKind;
37
28
  confidence: RedactionConfidence;
38
29
  source: RedactionSource;
39
30
  }
40
- /** What the two exact-name lists say about a name, before any shape rule is consulted. */
41
- export type ExactEnvNameVerdict = "credential" | "exempt" | "unknown";
42
- /** The exact-name arm of the classification, taking both lists as arguments. The credential list is read
43
- * FIRST: an exemption can never take a name away from it, so a name landing on both lists resolves to the
44
- * credential reading — the fail-closed direction (a wrongly-exempted name leaks; a wrongly-dropped one
45
- * does not). Module-level export (not part of the package surface): the two real lists are disjoint, so
46
- * only a controlled collision can tell this precedence from an inverted one. */
47
- export declare function classifyExactEnvName(key: string, credentialNames: ReadonlySet<string>, exemptNames: ReadonlySet<string>): ExactEnvNameVerdict;
48
- /** Is `key` a secret-pattern env var (one the fail-closed `scrub` policy drops before spawning a shell)? Pass the
49
- * entry's `value` — and the environment it sits in as `env`, and the child's `platform` — to get exactly the scrub's
50
- * answer for that entry: a git config member is judged with its index group, which only the environment holds; without
51
- * a value the name alone decides (a `GIT_CONFIG_KEY_<i>` then reads as secret — the name cannot clear itself).
52
- * `platform` is as for {@link scrubSecretEnv}. */
53
- export declare function isSecretEnvKey(key: string, value?: string, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
54
- /** Drop every secret-pattern key (see {@link isSecretEnvKey}) from an env object — the fail-closed `scrub`
55
- * policy. git's indexed record is withheld entry by entry and never renumbered ({@link readGitEnvConfigRecord}): a
56
- * withheld entry git reads keeps its names, each holding its half of the placeholder entry `sema.withheld` (key) / an
57
- * empty string (value), so `GIT_CONFIG_COUNT` and every other index reach the child as the host wrote them. `findings`
58
- * is an OPTIONAL caller-owned collector: pass an array to learn which entries were withheld (by name) and why, read it
59
- * back after the call. A finding's name is absent from the result unless it is such a record member, which is present
60
- * holding the placeholder half. `platform` is the platform the child runs on — where git reads this environment:
61
- * Windows reads env names case-insensitively, so there any spelling of a record member is that member; elsewhere git
62
- * looks up the capitalised names only and every other spelling is judged on its own, apart from the record. It defaults
63
- * to this process's own platform, the answer for a child spawned on this host; a host whose child runs elsewhere (a
64
- * remote or sandboxed execution environment) passes that environment's platform. */
65
- export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[], platform?: NodeJS.Platform): NodeJS.ProcessEnv;
31
+ /** Does the `"scrub"` policy withhold `key`? With `value`, exactly the answer {@link scrubSecretEnv} gives for that
32
+ * entry; without it, the answer for its name alone (the value rule then has nothing to read). */
33
+ export declare function isSecretEnvKey(key: string, value?: string): boolean;
34
+ /** The `"scrub"` policy: `env` without every entry {@link isSecretEnvKey} withholds, the rest as written. `findings` is
35
+ * an optional caller-owned collector: each withheld entry is pushed onto it (name, rule, confidence), none of them
36
+ * present in the result. `env` itself is not modified. */
37
+ export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
66
38
  /** How much of this process's environment a child spawned on this host inherits — the `inheritEnv` value. */
67
39
  export type InheritEnvPolicy = "all" | "scrub" | string[];
68
40
  /**
69
41
  * The environment a child spawned ON THIS HOST inherits, by an {@link InheritEnvPolicy}: `"scrub"` (and absent) is
70
- * this process's environment through {@link scrubSecretEnv} for this platform; `"all"` is this process's environment
42
+ * this process's environment through {@link scrubSecretEnv}; `"all"` is this process's environment
71
43
  * verbatim (the explicit opt-out); a `string[]` is exactly the named variables that are set, verbatim — a strict allow
72
44
  * list, so a name it leaves out (`PATH` included) is not inherited. `findings` collects what the scrub withheld, and
73
- * only the `"scrub"` arm adds to it. Any other value reads as `"scrub"`: a spawn site that must refuse a malformed
74
- * policy asks {@link inheritEnvPolicyProblem} first.
45
+ * only the `"scrub"` arm adds to it. Any other value reads as `"scrub"`; both spawn sites that take a policy — the
46
+ * tool shell's `NodeExecutionEnv` (at construction) and the oracle gate (per gate) — refuse a malformed one through
47
+ * {@link inheritEnvPolicyProblem} before it gets here.
75
48
  */
76
49
  export declare function inheritedHostEnv(policy: InheritEnvPolicy | undefined, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
77
50
  /** Why `value` is not an {@link InheritEnvPolicy} (absent counts as one — the scrub), or `undefined` when it is. */
@@ -86,4 +59,3 @@ export declare function __resetSecretEnvScrubNoticeAnnouncement(): void;
86
59
  * findings array is a no-op.
87
60
  */
88
61
  export declare function announceSecretEnvScrub(onNotice: ((notice: EngineNotice) => void) | undefined, findings: readonly SecretEnvFinding[]): void;
89
- export {};