@sagentlab/navarch-runtime 0.1.17 → 0.1.18

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/README.md CHANGED
@@ -264,7 +264,7 @@ unchanged across the deployment.
264
264
  | `NAVARCH_MAX_SESSIONS` | `5` | Local concurrent-session capacity cap — see `src/capacity.cts`. |
265
265
  | `NAVARCH_CAPABILITIES` | `shell,browser-use` (`docker-sandbox,shell` in Docker mode) | Comma list reported at heartbeat/claim time. Docker mode does not advertise browser use until the configured image provides it. |
266
266
  | `NAVARCH_OWNER_ZONE` | `sagentlab` | `sagentlab` or `customer-<slug>-premises` (project-plan.md §3.11). |
267
- | `NAVARCH_POLL_INTERVAL_MS` | `5000` | Claim-loop poll interval. |
267
+ | `NAVARCH_POLL_INTERVAL_MS` | `30000` | Claim-loop poll interval. |
268
268
  | `NAVARCH_HEARTBEAT_INTERVAL_MS` | `60000` | Machine-level heartbeat interval. |
269
269
  | `NAVARCH_LEASE_HEARTBEAT_INTERVAL_MS` | `300000` | Per-lease heartbeat interval; must stay well under the 15-minute lease TTL (schema-design.md §4). |
270
270
  | `NAVARCH_SESSION_TIMEOUT_MS` | `2700000` (45 min) | Hard kill timeout for a single session. |
@@ -305,7 +305,12 @@ supported coding agents, using each CLI's native enforcement point:
305
305
  - **Codex:** the runtime passes a one-off native permission profile with
306
306
  `approval_policy="on-request"` and `approvals_reviewer="auto_review"`.
307
307
  Codex's OS sandbox grants read/write access only to the allowed roots and
308
- denies the surrounding multi-session workspace, while eligible escalations
308
+ denies the surrounding multi-session workspace. Its network policy allows
309
+ GitHub and GitHub-hosted content so authenticated `gh` and git operations
310
+ required by the task stay inside the sandbox. `gh` reads from an empty,
311
+ session-owned `GH_CONFIG_DIR` and authenticates with the lease-scoped token,
312
+ so the operator's GitHub configuration and unrelated credentials remain
313
+ inaccessible. Other destinations remain blocked and eligible escalations
309
314
  are decided by the automatic reviewer rather than waiting for human input.
310
315
  `--ignore-user-config` and an untrusted project-config override prevent a
311
316
  user or checked-in legacy `sandbox_mode` from silently disabling the
@@ -330,8 +335,10 @@ The resulting boundary is:
330
335
  system prefixes.
331
336
  - The **rest of the workspace root** — sibling sessions' worktrees, other
332
337
  projects' bare repos, and the session's own metadata dir (lease-scoped MCP
333
- config, the guard files themselves) — is denied outright, so an agent can
334
- neither read another agent's checkout nor rewrite its own guard policy.
338
+ config, the guard files themselves) — is denied outright. The only metadata
339
+ exception is the empty, read-only `gh` config directory described above, so
340
+ an agent can neither read another agent's checkout nor rewrite its own guard
341
+ policy.
335
342
 
336
343
  The Claude hook is a strong guardrail rather than a hard security boundary
337
344
  because shell paths are screened lexically. Codex's permission profile is
package/dist/config.cjs CHANGED
@@ -61,7 +61,7 @@ function loadRuntimeConfig(env = process.env) {
61
61
  maxSessions: envInt(env, "NAVARCH_MAX_SESSIONS", 5),
62
62
  capabilities: envList(env, "NAVARCH_CAPABILITIES", defaultCapabilities),
63
63
  ownerZone: env.NAVARCH_OWNER_ZONE ?? "sagentlab",
64
- pollIntervalMs: envInt(env, "NAVARCH_POLL_INTERVAL_MS", 5000),
64
+ pollIntervalMs: envInt(env, "NAVARCH_POLL_INTERVAL_MS", 30_000),
65
65
  machineHeartbeatIntervalMs: envInt(env, "NAVARCH_HEARTBEAT_INTERVAL_MS", 60_000),
66
66
  // leases.expires_at = claimed_at + 15 min (schema-design.md §4) — default renewal
67
67
  // interval must stay comfortably under that TTL.
package/dist/session.cjs CHANGED
@@ -95,6 +95,7 @@ async function runSession(deps, claimed, sessionId) {
95
95
  leaseId,
96
96
  };
97
97
  }
98
+ const sessionEnv = toEnvMap(secrets, gitCredentialRefresh, config.gitAuthorName, config.gitAuthorEmail);
98
99
  const cloneUrl = bundle.repository?.clone_url ??
99
100
  (task.repo ? `https://github.com/${task.repo.replace(/\.git$/, "")}.git` : null);
100
101
  if (!cloneUrl) {
@@ -241,6 +242,9 @@ async function runSession(deps, claimed, sessionId) {
241
242
  }
242
243
  }
243
244
  else if (runtime === "codex") {
245
+ const ghConfigDir = (0, worktree_guard_cjs_1.codexGhConfigDir)(workDir);
246
+ await node_fs_1.promises.mkdir(ghConfigDir, { recursive: true, mode: 0o700 });
247
+ sessionEnv.GH_CONFIG_DIR = ghConfigDir;
244
248
  codexGuardArgs = (0, worktree_guard_cjs_1.codexWorktreeGuardArgs)({
245
249
  workDir,
246
250
  worktreePath: gitWorktree.worktreePath,
@@ -266,7 +270,7 @@ async function runSession(deps, claimed, sessionId) {
266
270
  await gitWorktree.prepare();
267
271
  if (sandbox) {
268
272
  await sandbox.create();
269
- await sandbox.injectEnv(toEnvMap(secrets, gitCredentialRefresh, config.gitAuthorName, config.gitAuthorEmail));
273
+ await sandbox.injectEnv(sessionEnv);
270
274
  }
271
275
  // The control plane returns the agent type selected for this worker.
272
276
  // Any agent type may run any task; capabilities remain the task-level gate.
@@ -300,7 +304,7 @@ async function runSession(deps, claimed, sessionId) {
300
304
  model: execution.model,
301
305
  reasoningEffort: execution.reasoning_effort,
302
306
  timeoutMs: config.sessionTimeoutMs,
303
- env: toEnvMap(secrets, gitCredentialRefresh, config.gitAuthorName, config.gitAuthorEmail),
307
+ env: sessionEnv,
304
308
  settingsPath: claudeSettingsPath,
305
309
  codexGuardArgs,
306
310
  geminiSandboxMounts: geminiGuardMounts,
@@ -468,7 +472,10 @@ async function runSession(deps, claimed, sessionId) {
468
472
  // every other body we surface) so a 4xx/5xx completion stays diagnosable.
469
473
  const crashDetail = (0, redact_cjs_1.redactText)(describeCompletionError(err), knownSecrets);
470
474
  log.error(`session ${leaseId} threw before completing: ${crashDetail}`);
471
- const failureSummary = (0, redact_cjs_1.redactText)(`Session crashed: ${String(err)}`, knownSecrets);
475
+ // Send the same bounded, redacted detail to the control plane that we log
476
+ // locally. NavarchApiError.message contains only the HTTP status line; its
477
+ // response body carries the actionable server error shown in session UI.
478
+ const failureSummary = `Session crashed: ${crashDetail}`;
472
479
  await api
473
480
  .completeLease(leaseId, {
474
481
  status: "failed",
@@ -7,12 +7,18 @@ exports.guardHookScriptPath = guardHookScriptPath;
7
7
  exports.readClaudeApiKeyHelper = readClaudeApiKeyHelper;
8
8
  exports.prepareWorktreeGuard = prepareWorktreeGuard;
9
9
  exports.codexToolReadRoots = codexToolReadRoots;
10
+ exports.codexGhConfigDir = codexGhConfigDir;
10
11
  exports.codexWorktreeGuardArgs = codexWorktreeGuardArgs;
11
12
  exports.geminiSandboxMounts = geminiSandboxMounts;
12
13
  const node_path_1 = __importDefault(require("node:path"));
13
14
  const node_fs_1 = require("node:fs");
14
15
  const node_os_1 = __importDefault(require("node:os"));
15
16
  const CODEX_GUARD_PROFILE = "navarch-worktree";
17
+ const CODEX_GH_CONFIG_DIRNAME = "gh-config";
18
+ const CODEX_GITHUB_NETWORK_DOMAINS = {
19
+ "**.github.com": "allow",
20
+ "**.githubusercontent.com": "allow",
21
+ };
16
22
  const CLAUDE_USER_SETTINGS_FILENAME = "settings.json";
17
23
  /**
18
24
  * Tools the hook screens. Everything else — the lease-scoped Navarch MCP
@@ -122,6 +128,17 @@ function codexToolReadRoots(env = process.env) {
122
128
  node_path_1.default.join(homeDir, ".cache", "ms-playwright"),
123
129
  ];
124
130
  }
131
+ /**
132
+ * Isolated gh configuration root for one Codex session.
133
+ *
134
+ * `gh` reads its config file before it considers `GITHUB_TOKEN`. Pointing it
135
+ * at this empty, session-owned directory lets the CLI use the lease token
136
+ * without granting the agent read access to the operator's ~/.config/gh,
137
+ * which may contain unrelated account credentials.
138
+ */
139
+ function codexGhConfigDir(workDir) {
140
+ return node_path_1.default.join(workDir, CODEX_GH_CONFIG_DIRNAME);
141
+ }
125
142
  /**
126
143
  * Builds one-off Codex permission-profile arguments for a host session.
127
144
  *
@@ -144,6 +161,7 @@ function codexWorktreeGuardArgs(options) {
144
161
  [node_path_1.default.resolve(options.workspaceRoot)]: "deny",
145
162
  [node_path_1.default.resolve(options.worktreePath)]: "write",
146
163
  [node_path_1.default.resolve(options.repositoryPath)]: "write",
164
+ [codexGhConfigDir(options.workDir)]: "read",
147
165
  };
148
166
  for (const root of codexToolReadRoots()) {
149
167
  const resolved = node_path_1.default.resolve(root);
@@ -173,10 +191,15 @@ function codexWorktreeGuardArgs(options) {
173
191
  `default_permissions=${tomlString(CODEX_GUARD_PROFILE)}`,
174
192
  "-c",
175
193
  `permissions.${CODEX_GUARD_PROFILE}.filesystem=${tomlInlineTable(filesystem)}`,
176
- // Navarch coding tasks must be able to fetch dependencies and push their
177
- // branch. The profile still constrains filesystem access independently.
194
+ // A network-enabled permission profile still blocks every destination
195
+ // until it has at least one allow rule. Keep routine authenticated GitHub
196
+ // CLI and git traffic inside the sandbox so it does not become an
197
+ // Auto-review escalation, while leaving every non-GitHub destination
198
+ // blocked. The profile still constrains filesystem access independently.
178
199
  "-c",
179
200
  `permissions.${CODEX_GUARD_PROFILE}.network.enabled=true`,
201
+ "-c",
202
+ `permissions.${CODEX_GUARD_PROFILE}.network.domains=${tomlInlineTable(CODEX_GITHUB_NETWORK_DOMAINS)}`,
180
203
  ];
181
204
  }
182
205
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "Navarch machine-side session manager: claims delivery tasks and runs them through Claude Code, Codex, or Gemini CLI.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",