@sweny-ai/core 0.1.177 → 0.1.179

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.
@@ -713,6 +713,8 @@ export class AcpHarness {
713
713
  const extraDegraded = [];
714
714
  const tag = (r) => ({
715
715
  ...r,
716
+ // Only the process wrapper keeps the agent out of the sweny state dir.
717
+ contained: wrapper !== undefined,
716
718
  harness: this.info(),
717
719
  degraded: [...degraded, ...extraDegraded],
718
720
  });
@@ -51,6 +51,24 @@ export declare const CLAUDE_TOOLS_BY_CLASS: Readonly<Record<ToolClass, readonly
51
51
  * `denyRead` (merged with these rules) covers Bash.
52
52
  */
53
53
  export declare function claudeReadDenyRule(absPath: string): string;
54
+ /**
55
+ * Deny rules keeping every built-in file tool out of the run journals' state
56
+ * dir ({@link runStateRoot}): an agent that could read a run key could forge
57
+ * journal records, and one that could write there could cut a journal or
58
+ * plant a run.
59
+ *
60
+ * Mechanism: scoped deny rules on `disallowedTools`. Per the Agent SDK's
61
+ * permission evaluation order (code.claude.com/docs/en/agent-sdk/permissions,
62
+ * "Deny rules"), a matching deny rule from `disallowed_tools` blocks the tool
63
+ * "even in `bypassPermissions` mode", which every node runs under (#365);
64
+ * `canUseTool` and allow rules are never reached in that mode. `Read(path)`
65
+ * rules govern the built-in file readers (Read, and Grep/Glob), and
66
+ * `Edit(path)` rules "govern all built-in tools that write files, including
67
+ * `Write` and `NotebookEdit`" (a `Write(path)` rule is never matched, so none
68
+ * is emitted). `//` anchors an absolute filesystem path. Both the path as
69
+ * configured and its real path are denied (macOS `/var` is `/private/var`).
70
+ */
71
+ export declare function claudeStateDirDenyRules(root?: string): string[];
54
72
  /** Native `disallowedTools` for a policy: legacy names, compiled classes, and the read-only set. */
55
73
  export declare function compileClaudeCodeDeny(policy: NodePolicy, legacy?: readonly string[]): string[];
56
74
  /** How sweny resolves which credentials reach the Claude Code subprocess. */
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { query, createSdkMcpServer, tool as sdkTool } from "@anthropic-ai/claude-agent-sdk";
12
12
  import { createRequire } from "node:module";
13
+ import { realpathSync } from "node:fs";
13
14
  import { consoleLogger } from "../types.js";
14
15
  import { AGENT_AUTH_VARS, finishAgentEnv, reportWithheldEnv, parseList, resolveEnvScope, scopeAgentEnv, withPushBlocked, mcpWithheld, resolveAgentSandbox, } from "../agent-env.js";
15
16
  import { gitCredentialGap, policyGate } from "./policy.js";
@@ -20,6 +21,7 @@ import { jsonSchemaToZodShape, toolErrorToMcpResult, toolOutputToMcpResult } fro
20
21
  import { startToolBridge } from "./tool-bridge/server.js";
21
22
  import { parseToolResultContent, summarizeToolError, tryParseJSON } from "./parse.js";
22
23
  import { makeAbort } from "./abort.js";
24
+ import { runStateRoot } from "../journal.js";
23
25
  import { claudeCodeAuth } from "./auth.js";
24
26
  export { buildEvaluatePrompt, CLAUDE_CODE_CAPABILITIES };
25
27
  export { parseToolResultContent, summarizeToolError, makeAbort };
@@ -96,6 +98,39 @@ export const CLAUDE_TOOLS_BY_CLASS = {
96
98
  export function claudeReadDenyRule(absPath) {
97
99
  return `Read(/${absPath.startsWith("/") ? absPath : `/${absPath}`})`;
98
100
  }
101
+ /**
102
+ * Deny rules keeping every built-in file tool out of the run journals' state
103
+ * dir ({@link runStateRoot}): an agent that could read a run key could forge
104
+ * journal records, and one that could write there could cut a journal or
105
+ * plant a run.
106
+ *
107
+ * Mechanism: scoped deny rules on `disallowedTools`. Per the Agent SDK's
108
+ * permission evaluation order (code.claude.com/docs/en/agent-sdk/permissions,
109
+ * "Deny rules"), a matching deny rule from `disallowed_tools` blocks the tool
110
+ * "even in `bypassPermissions` mode", which every node runs under (#365);
111
+ * `canUseTool` and allow rules are never reached in that mode. `Read(path)`
112
+ * rules govern the built-in file readers (Read, and Grep/Glob), and
113
+ * `Edit(path)` rules "govern all built-in tools that write files, including
114
+ * `Write` and `NotebookEdit`" (a `Write(path)` rule is never matched, so none
115
+ * is emitted). `//` anchors an absolute filesystem path. Both the path as
116
+ * configured and its real path are denied (macOS `/var` is `/private/var`).
117
+ */
118
+ export function claudeStateDirDenyRules(root = runStateRoot()) {
119
+ let real = root;
120
+ try {
121
+ real = realpathSync(root);
122
+ }
123
+ catch {
124
+ // not created yet: the configured path is the one tools would use
125
+ }
126
+ const abs = (p) => (p.startsWith("/") ? p : `/${p}`);
127
+ return [...new Set([root, real])].flatMap((dir) => [
128
+ `Read(/${abs(dir)})`,
129
+ `Read(/${abs(dir)}/**)`,
130
+ `Edit(/${abs(dir)})`,
131
+ `Edit(/${abs(dir)}/**)`,
132
+ ]);
133
+ }
99
134
  /** Native `disallowedTools` for a policy: legacy names, compiled classes, and the read-only set. */
100
135
  export function compileClaudeCodeDeny(policy, legacy = []) {
101
136
  return [
@@ -344,6 +379,8 @@ export class ClaudeCodeHarness {
344
379
  harness: this.info(),
345
380
  degraded,
346
381
  ...(this.lastPolicyFacts ? { policy: this.lastPolicyFacts } : {}),
382
+ // The state dir deny rules hold for the file tools either way; Bash is held only by the sandbox.
383
+ contained: this.lastPolicyFacts?.sandboxStarted === true,
347
384
  };
348
385
  }
349
386
  async runQuery(opts) {
@@ -354,6 +391,9 @@ export class ClaudeCodeHarness {
354
391
  let disallowedTools = readOnly
355
392
  ? [...new Set([...(opts.disallowedTools ?? []), ...READ_ONLY_DISALLOWED_TOOLS])]
356
393
  : opts.disallowedTools;
394
+ // Run journals, their keys and locks live in the sweny state dir: no built-in
395
+ // file tool may read or write there (Bash is held by the sandbox's filesystem deny).
396
+ disallowedTools = [...new Set([...(disallowedTools ?? []), ...claudeStateDirDenyRules()])];
357
397
  const effectiveModel = model ?? this.model;
358
398
  // #360: run agent commands in the SDK sandbox. `auto` (default in CI) falls
359
399
  // back to unsandboxed with one loud warning when the host cannot sandbox;
@@ -517,12 +557,17 @@ export class ClaudeCodeHarness {
517
557
  ...(abort ? { abortController: abort.controller } : {}),
518
558
  ...(effectiveModel ? { model: effectiveModel } : {}),
519
559
  ...(Object.keys(allMcpServers).length > 0 ? { mcpServers: allMcpServers } : {}),
520
- // Dry run (#380): settingSources is omitted, so the SDK loads user,
521
- // project and local settings, including their MCP servers (and
522
- // project .mcp.json, plugins). strictMcpConfig limits MCP to the
523
- // servers passed above, which under readOnly is only sweny-core.
524
- // A strict policy (#365) makes it exclusive for write nodes too.
525
- ...(mcpWithheld(!!readOnly, agentAccess) || opts.strictMcp ? { strictMcpConfig: true } : {}),
560
+ // Only what sweny passes runs: an ambient MCP server or plugin (user
561
+ // settings, a repo's .mcp.json or .claude/settings.json, which are
562
+ // repo content) could read the run journals' state dir around the
563
+ // sandbox and the deny rules. SDK Options (sdk.d.ts):
564
+ // `strictMcpConfig` "Only use MCP servers passed via the `mcpServers`
565
+ // option ... ignoring ... project `.mcp.json`, user settings,
566
+ // plugins"; `settingSources` "Pass `[]` to disable filesystem
567
+ // settings (SDK isolation mode)", so no user/project/local
568
+ // settings, hooks or settings-enabled plugins load either.
569
+ strictMcpConfig: true,
570
+ settingSources: [],
526
571
  ...(disallowedTools && disallowedTools.length > 0 ? { disallowedTools } : {}),
527
572
  // CC-08: ask the SDK to produce validated structured output when the
528
573
  // node declares an output schema. The SDK then returns the parsed
@@ -758,6 +758,8 @@ export class CodexHarness {
758
758
  let degraded = gate.degraded;
759
759
  const tag = (r, extra = []) => ({
760
760
  ...r,
761
+ // Only the process wrapper keeps Codex out of the sweny state dir (its own sandbox allows reads).
762
+ contained: wrapper !== undefined,
761
763
  harness: this.info(),
762
764
  degraded: [...degraded, ...extra],
763
765
  });
@@ -756,6 +756,8 @@ export class PiHarness {
756
756
  const extraDegraded = [];
757
757
  const tag = (r) => ({
758
758
  ...r,
759
+ // Only the process wrapper keeps the agent out of the sweny state dir.
760
+ contained: wrapper !== undefined,
759
761
  harness: this.info(),
760
762
  degraded: [...degraded, ...extraDegraded],
761
763
  });
@@ -48,7 +48,7 @@ import { lstat, mkdir, mkdtemp, realpath, rm, writeFile } from "node:fs/promises
48
48
  import { homedir, tmpdir } from "node:os";
49
49
  import * as path from "node:path";
50
50
  import { DEFAULT_SANDBOX_DOMAINS, parseList, resolveSandboxMode } from "../agent-env.js";
51
- import { JOURNAL_DIR, runKeyDir } from "../journal.js";
51
+ import { runStateRoot } from "../journal.js";
52
52
  import { policyGate } from "./policy.js";
53
53
  // ─── srt settings ────────────────────────────────────────────────
54
54
  /**
@@ -132,13 +132,13 @@ export function buildSrtSettings(req, opts) {
132
132
  // cannot rename one out from under its read deny.
133
133
  const masked = [...new Set((req.denyRead ?? []).map(real))].filter((p) => exists(p));
134
134
  denyRead.push(...masked.filter((p) => !denyRead.includes(p)));
135
- // Run journal keys (outside the workspace) are never readable by the agent,
136
- // and the journals themselves are not writable.
137
- const keyDir = runKeyDir();
138
- if (exists(keyDir) && !denyRead.includes(keyDir))
139
- denyRead.push(keyDir);
140
- const journals = path.join(real(req.cwd), JOURNAL_DIR);
141
- const denyWrite = !req.readOnly && exists(journals) ? [...masked, journals] : masked;
135
+ // Run journals, their keys and locks (the sweny state dir, outside the
136
+ // workspace) are neither readable nor writable by the agent.
137
+ const journals = real(runStateRoot());
138
+ const journalsExist = exists(journals);
139
+ if (journalsExist && !denyRead.includes(journals))
140
+ denyRead.push(journals);
141
+ const denyWrite = journalsExist ? [...masked, journals] : masked;
142
142
  const allowWrite = req.readOnly ? [opts.home] : [real(req.cwd), opts.home];
143
143
  return {
144
144
  network: { allowedDomains, deniedDomains: [], strictAllowlist: true, allowLocalBinding: false },
package/dist/index.d.ts CHANGED
@@ -27,8 +27,8 @@ export { EVALUATOR_KINDS, EVAL_POLICIES, REQUIRES_ON_FAIL, NODE_ON_FAIL, MCP_TRA
27
27
  export { consoleLogger } from "./types.js";
28
28
  export { execute, RouteEvaluationError } from "./executor.js";
29
29
  export type { ExecuteOptions } from "./executor.js";
30
- export { systemOneProvider, DecideError, gateVerdict, DECIDER_MIN_CONFIDENCE, DECIDER_MIN_MARGIN } from "./decider.js";
31
- export type { DecisionProvider, DeciderConfig, DeciderMode, DeciderRecord, SystemOneOptions } from "./decider.js";
30
+ export { systemOneProvider } from "./decider.js";
31
+ export type { DeciderConfig, DeciderOperatorConfig } from "./decider.js";
32
32
  export { BudgetGuard, describeOverrun, minLimits, toLimits } from "./budget.js";
33
33
  export type { Budget, BudgetOverrun, BudgetUnit, SpendLimits } from "./budget.js";
34
34
  export { resolveNodePermissions, buildNodePolicy } from "./node-policy.js";
package/dist/index.js CHANGED
@@ -27,8 +27,8 @@ export { EVALUATOR_KINDS, EVAL_POLICIES, REQUIRES_ON_FAIL, NODE_ON_FAIL, MCP_TRA
27
27
  export { consoleLogger } from "./types.js";
28
28
  // Executor
29
29
  export { execute, RouteEvaluationError } from "./executor.js";
30
- // Decision models, shadow mode (#357)
31
- export { systemOneProvider, DecideError, gateVerdict, DECIDER_MIN_CONFIDENCE, DECIDER_MIN_MARGIN } from "./decider.js";
30
+ // Decision models (#357): config types and the provider adapter only
31
+ export { systemOneProvider } from "./decider.js";
32
32
  // Least-privilege nodes and safe outputs (#365)
33
33
  export { BudgetGuard, describeOverrun, minLimits, toLimits } from "./budget.js";
34
34
  export { resolveNodePermissions, buildNodePolicy } from "./node-policy.js";