Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ import { PiHarnessAdapter } from "@noopolis/daimon/pi";

## Organization-runtime contract

For a strict Codex agent in an already prepared runtime, the public
`resolveOrganizationCodexSandboxProjection(config, agentId, { acceptanceStorePath })`
API returns `noopolis.daimon.codex-sandbox-projection.v1`: canonical workspace/home
paths, the resolved executable, and `sandboxArgs` containing the exact permission
profile rendered for production. Append only `--` and a mechanical command when
checking whether that policy can execute in the caller's container.

The resolver opens and verifies caller-owned path identities and executes the
Codex version probe. It does not import/read authentication, create an agent,
accept a wake or invoke a model. The acceptance-store path must be the same one
given to the control host. This API projects command permissions, not the complete
cognition invocation: Codex's `sandbox` subcommand lacks the strict configuration
flags accepted by `exec`. A caller must therefore use a fresh empty `HOME` and
`CODEX_HOME` and a restricted environment for its mechanical probe. Do not infer
successful tool use or completed agent work from this projection alone.

`@noopolis/daimon/runtime` exports a standard JSON Schema for structural
validation plus the strict, side-effect-free semantic
`validateOrganizationRuntimeConfig` / `parseOrganizationRuntimeConfig` API and
Expand Down
11 changes: 11 additions & 0 deletions src/pi/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Pi runtime implementation

This directory owns the Pi harness and native CLI adaptation. Keep public
runtime contracts independent of Pi types. Files stay below 400 lines and
tests live beside their implementations.

Codex permission rendering must preserve the effective path permissions while
removing redundant nested denies that cannot be mounted by its Linux sandbox.
An intervening readable path or workspace root makes a deeper deny necessary.
Verify changes with generated production arguments and real local sandbox
commands; a model call is neither required nor permitted for this check.
1 change: 1 addition & 0 deletions src/pi/CLAUDE.md
11 changes: 6 additions & 5 deletions src/pi/cliEngineSpawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { spawn, type ChildProcess } from "node:child_process";

import { trackCliChild } from "./cliProcess.js";
import { cliChildEnvironment } from "./cliEnvironment.js";
import { codexFilesystemRules } from "./codexFilesystemRules.js";
import type { CliEngineOptions, CliSessionInput } from "./cliSession.js";

export const GROK_STRICT_SANDBOX_PROFILE = "strict";
Expand Down Expand Up @@ -49,7 +50,7 @@ export const renderCodexArgs = (
"--strict-config",
"-c", "web_search=\"disabled\"",
"-c", `default_permissions=${JSON.stringify(profileName)}`,
"-c", renderCodexPermissionProfile(profileName, options.codexSandboxProtectedPaths ?? [], options.codexSandboxReadablePaths ?? []),
"-c", renderCodexPermissionProfile(profileName, options.codexSandboxProtectedPaths ?? [], options.codexSandboxReadablePaths ?? [], cwd),
"-c", "approval_policy=\"never\"",
"-c", `mcp_servers={daimon={url=\"${endpoint}\",enabled=true,default_tools_approval_mode=\"approve\"}}`
];
Expand All @@ -66,13 +67,13 @@ export const renderCodexArgs = (
export const renderCodexPermissionProfile = (
profileName: string,
protectedPaths: readonly string[],
readablePaths: readonly string[] = []
readablePaths: readonly string[] = [],
workspacePath?: string
): string => {
const filesystem: Record<string, "read" | "write" | "deny" | Record<string, "write">> = {
":workspace_roots": { ".": "write" }
":workspace_roots": { ".": "write" },
...codexFilesystemRules(protectedPaths, readablePaths, workspacePath)
};
for (const readablePath of readablePaths) filesystem[readablePath] = "read";
for (const protectedPath of protectedPaths) filesystem[protectedPath] = "deny";
return `permissions=${tomlInline({ [profileName]: {
extends: ":workspace",
filesystem,
Expand Down
68 changes: 68 additions & 0 deletions src/pi/codexFilesystemRules.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import assert from "node:assert/strict";
import test from "node:test";

import { renderCodexArgs } from "./cliEngineSpawn.js";
import { codexFilesystemRules } from "./codexFilesystemRules.js";

test("prunes redundant nested denies independently of declaration order", () => {
for (const denies of [["/run", "/run/control", "/run/control/acceptance"], ["/run/control/acceptance", "/run/control", "/run"]]) {
assert.deepEqual(codexFilesystemRules(denies, []), { "/run": "deny" });
}
assert.deepEqual(codexFilesystemRules(["/run", "/runner", "/other/control"], []), {
"/run": "deny", "/runner": "deny", "/other/control": "deny"
});
});

test("preserves a deny below an intervening readable exception", () => {
assert.deepEqual(codexFilesystemRules([
"/vault", "/vault/shared/auth", "/vault/shared/auth/deeper", "/vault/secret"
], ["/vault/shared"]), {
"/vault/shared": "read", "/vault": "deny", "/vault/shared/auth": "deny"
});
});

test("normalizes aliases before testing ancestry, including a shared path that escapes /run", () => {
assert.deepEqual(codexFilesystemRules(["/run/", "/run//control/", "/run/../var/lib/private/control/", "/var/lib/private/control"], []), {
"/run": "deny", "/var/lib/private/control": "deny"
});
assert.deepEqual(codexFilesystemRules(["/vault/", "/vault/open//secret/"], ["/vault/unused/../open/"]), {
"/vault/open": "read", "/vault": "deny", "/vault/open/secret": "deny"
});
assert.deepEqual(codexFilesystemRules(["/vault", "/vault/work/secret"], [], "/vault//work/"), {
"/vault": "deny", "/vault/work/secret": "deny"
});
});

test("same-path deny wins over a read and does not open a descendant", () => {
assert.deepEqual(codexFilesystemRules(["/vault", "/vault/auth"], ["/vault"]), { "/vault": "deny" });
});

test("implicit workspace write is an intervening exception, including in production argv", () => {
const denies = ["/vault", "/vault/workspace/secret", "/vault/workspace/secret/deeper"];
assert.deepEqual(codexFilesystemRules(denies, [], "/vault/workspace"), {
"/vault": "deny", "/vault/workspace/secret": "deny"
});
const args = renderCodexArgs({ codexSandbox: { mode: "workspace-write", networkAccess: false, webSearch: "disabled" },
codexSandboxProtectedPaths: denies }, "/vault/workspace", "http://127.0.0.1:1/mcp");
const config = args.find(arg => arg.startsWith("permissions="))!;
assert.ok(config.includes('"/vault/workspace/secret"="deny"'));
assert.ok(!config.includes('"/vault/workspace/secret/deeper"'));
});

test("root deny subsumes descendants and a same-path workspace deny remains authoritative", () => {
assert.deepEqual(codexFilesystemRules(["/", "/run"], []), { "/": "deny" });
assert.deepEqual(codexFilesystemRules(["/workspace", "/workspace/secret"], [], "/workspace"), { "/workspace": "deny" });
assert.deepEqual(codexFilesystemRules([], [], "/workspace"), {});
});

test("pruning preserves the most specific permission across alternating rules", () => {
const deny = ["/v", "/v/a", "/v/a/read/secret", "/v/a/read/secret/deeper", "/v/work/secret"];
const read = ["/v/a/read", "/v/a/read/secret/open"];
const before = { "/v/work": "write", ...Object.fromEntries(read.map(p => [p, "read"])), ...Object.fromEntries(deny.map(p => [p, "deny"])) };
const after = { "/v/work": "write", ...codexFilesystemRules(deny, read, "/v/work") };
const permission = (rules: Record<string, string>, target: string): string | undefined => Object.entries(rules)
.filter(([root]) => target === root || target.startsWith(`${root}/`)).sort(([a], [b]) => b.length - a.length)[0]?.[1];
for (const entry of [...deny, ...read, "/v/work", "/v/work/secret", "/v/ab", "/other"]) {
for (const target of [entry, `${entry}/file`]) assert.equal(permission(after, target), permission(before, target), target);
}
});
29 changes: 29 additions & 0 deletions src/pi/codexFilesystemRules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import path from "node:path";

type Permission = "read" | "write" | "deny";

/** Codex mounts deny roots read-only; a second deny below one cannot create parents. */
export function codexFilesystemRules(
protectedPaths: readonly string[], readablePaths: readonly string[], workspacePath?: string
): Record<string, "read" | "deny"> {
const explicit = new Map<string, "read" | "deny">();
// Agent roots are canonical, but shared acceptance paths may contain `..`
// or trailing separators. Compare the same absolute locations Codex uses.
for (const readablePath of readablePaths) explicit.set(path.resolve(readablePath), "read");
for (const protectedPath of protectedPaths) explicit.set(path.resolve(protectedPath), "deny");
const effective = new Map<string, Permission>([
...(workspacePath === undefined ? [] : [[path.resolve(workspacePath), "write"] as const]),
...explicit
]);
return Object.fromEntries([...explicit].filter(([target, permission]) => {
if (permission !== "deny") return true;
let ancestor = path.dirname(target);
while (ancestor !== target) {
const inherited = effective.get(ancestor);
if (inherited !== undefined) return inherited !== "deny";
target = ancestor;
ancestor = path.dirname(target);
}
return true;
}));
}
116 changes: 116 additions & 0 deletions src/runtime/codexSandbox.linux.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { readFile } from "node:fs/promises";
import path from "node:path";
import test from "node:test";
import { promisify } from "node:util";

import { renderCodexArgs } from "../pi/cliEngineSpawn.js";
import { codexSandboxProtectedPaths, codexSandboxReadablePaths } from "./engineDispatcher.js";
import type { OrganizationRuntimeAgentConfig } from "./organizationRuntime.js";

const image = process.env.DAIMON_CODEX_SANDBOX_TEST_IMAGE;
const script = `
const fs=require('node:fs'),path=require('node:path'),cp=require('node:child_process'),net=require('node:net');
const input=JSON.parse(process.argv[1]);
for(const p of [input.workspace,input.home,...input.denied.filter(p=>p!=='/proc'&&p!=='/run').map(p=>p.endsWith('/auth.json')?path.dirname(p):p),...input.readable])fs.mkdirSync(p,{recursive:true,mode:448});
const canaries=input.denied.filter(p=>p!=='/proc').map(p=>p.endsWith('/auth.json')?p:path.join(p,'canary'));
for(const p of canaries)fs.writeFileSync(p,'non-secret-denied-canary',{mode:384});
for(const p of canaries)if(fs.readFileSync(p,'utf8')!=='non-secret-denied-canary')throw Error('outside-sandbox canary missing');
for(const p of input.readable)fs.writeFileSync(path.join(p,'canary'),'readable-canary');
fs.writeFileSync(path.join(input.workspace,'input'),'workspace-read');
const server=net.createServer(s=>s.destroy());
server.listen(0,'127.0.0.1',()=>{
const command=input.workspaceDenied?\`const fs=require('node:fs');
for(const p of \${JSON.stringify([input.workspace+'/input',input.workspace+'/secret/canary'])}){let denied=false;try{fs.readFileSync(p);}catch{denied=true;}if(!denied)throw Error('workspace deny lost to implicit write');}
let denied=false;try{fs.writeFileSync(\${JSON.stringify(input.workspace+'/output')},'forbidden');}catch{denied=true;}if(!denied)throw Error('denied workspace writable');
process.stdout.write('EXACT_WORKSPACE_DENIED');\`:\`const fs=require('node:fs'),net=require('node:net');
if(fs.readFileSync('input','utf8')!=='workspace-read')throw Error('workspace read failed');
fs.writeFileSync('output','workspace-write');
for(const p of \${JSON.stringify(canaries)}){let denied=false;try{fs.readFileSync(p);}catch{denied=true;}if(!denied)throw Error('canary became readable: '+p);}
for(const p of \${JSON.stringify(input.readable)})if(fs.readFileSync(p+'/canary','utf8')!=='readable-canary')throw Error('readable exception missing');
const socket=net.connect({host:'127.0.0.1',port:\${server.address().port}});let done=false;
socket.once('connect',()=>{done=true;socket.destroy();throw Error('network reached outside sandbox');});
socket.once('error',()=>{if(!done){done=true;fs.writeFileSync('complete','CODEX_SANDBOX_ENFORCED');}});
socket.setTimeout(1500,()=>{socket.destroy();if(!done){done=true;throw Error('network probe timed out');}});\`;
const env={PATH:process.env.PATH,HOME:input.home,CODEX_HOME:input.home+'/.codex',TMPDIR:'/tmp',LANG:'C.UTF-8'};
const executable=input.workspaceDenied?['/bin/sh','-c','cd /tmp && exec /usr/local/bin/node -e "$1"','probe',command]:['/usr/local/bin/node','-e',command];
cp.execFile('codex',['sandbox','-P','daimon-strict','-C',input.workspace,'-c',input.profile,'--',...executable],{env,timeout:15000,maxBuffer:65536},(error,stdout,stderr)=>{
server.close();
if(error){process.stderr.write(stderr);process.exitCode=1;return;}
if(input.workspaceDenied){if(stdout!=='EXACT_WORKSPACE_DENIED'||fs.existsSync(path.join(input.workspace,'output')))throw Error('workspace deny not enforced: '+JSON.stringify({stdout,stderr,outputExists:fs.existsSync(path.join(input.workspace,'output'))}));process.stdout.write('CODEX_SANDBOX_ENFORCED');return;}
if(fs.readFileSync(path.join(input.workspace,'complete'),'utf8')!=='CODEX_SANDBOX_ENFORCED'||fs.readFileSync(path.join(input.workspace,'output'),'utf8')!=='workspace-write')throw Error('missing sandbox side effect');
process.stdout.write('CODEX_SANDBOX_ENFORCED');
});});`;

async function runSandbox(input: { workspace: string; home: string; denied: readonly string[]; readable: readonly string[]; profile: string; workspaceDenied?: boolean }): Promise<string> {
return (await promisify(execFile)("docker", [
"run", "--rm", "--network", "none", "--read-only", "--user", "501:20", "--cap-drop", "ALL",
"--security-opt", "no-new-privileges", "--security-opt", "seccomp=unconfined", "--security-opt", "apparmor=unconfined",
...["/tmp", "/run", "/var/lib/daimon", "/var/lib/spawnfile"].flatMap(target => ["--tmpfs", `${target}:rw,uid=501,gid=20,mode=0700`]),
"--entrypoint", "/usr/local/bin/node", image!, "-e", script, JSON.stringify(input)
], { timeout: 25000, maxBuffer: 131072 })).stdout;
}

test("actual Codex sandbox executes the production policy with overlapping control denies", { skip: !image, timeout: 60000 }, async () => {
const configFile = process.env.DAIMON_CODEX_SANDBOX_TEST_CONFIG;
const agent: OrganizationRuntimeAgentConfig = configFile
? (JSON.parse(await readFile(configFile, "utf8")) as { agents: OrganizationRuntimeAgentConfig[] }).agents[0]!
: { id: "agent:current", name: "Current", instructions: "Unused in this mechanical probe.",
workspacePath: "/var/lib/daimon/workspace", runtimeHomePath: "/var/lib/daimon/home",
engine: { kind: "codex", codexSandbox: { mode: "workspace-write", networkAccess: false, webSearch: "disabled" } } };
const control = "/run/paideia/control";
const denied = codexSandboxProtectedPaths(agent.id, agent, path.join(agent.runtimeHomePath, ".codex"), [agent], [control]);
const readable = codexSandboxReadablePaths(agent);
const profile = renderCodexArgs({ codexSandbox: agent.engine.codexSandbox,
codexSandboxProtectedPaths: denied, codexSandboxReadablePaths: readable }, agent.workspacePath, "http://127.0.0.1:1/mcp")
.find(arg => arg.startsWith("permissions="))!;
assert.ok(denied.includes("/run") && denied.includes(control));
assert.ok(!profile.includes(`"${control}"="deny"`));
const run = (permissionConfig: string): Promise<string> => runSandbox({ workspace: agent.workspacePath,
home: agent.runtimeHomePath, denied, readable, profile: permissionConfig });
assert.equal(await run(profile), "CODEX_SANDBOX_ENFORCED");
// Restore the exact redundant deny emitted before the fix. The real sandbox
// must reproduce the setup failure instead of merely executing an empty shell.
await assert.rejects(run(profile.replace('"/run"="deny"', `"/run"="deny","${control}"="deny"`)), /Can't mkdir parents.*Read-only file system/u);
});

test("a denied workspace fails readiness when Codex exits zero without executing the command", { skip: !image, timeout: 30000 }, async () => {
const workspace = "/var/lib/daimon/workspace", home = "/var/lib/daimon/home";
const denied = [workspace, `${workspace}/secret`, "/run", "/proc", `${home}/.codex/auth.json`];
const profile = renderCodexArgs({ codexSandbox: { mode: "workspace-write", networkAccess: false, webSearch: "disabled" },
codexSandboxProtectedPaths: denied }, workspace, "http://127.0.0.1:1/mcp").find(arg => arg.startsWith("permissions="))!;
assert.ok(!profile.includes(`"${workspace}/secret"="deny"`));
// Codex 0.142.3 executes neither Node nor the shell wrapper in this geometry.
// Exit zero is not readiness: the missing sentinel must reject it.
await assert.rejects(runSandbox({ workspace, home, denied, readable: [], profile, workspaceDenied: true }),
/workspace deny not enforced: \{"stdout":"","stderr":"","outputExists":false\}/u);
await assert.rejects(runSandbox({ workspace, home, denied, readable: [], workspaceDenied: true,
profile: profile.replace(`,"${workspace}"="deny"`, "") }), /workspace deny lost to implicit write/u);
});

test("a shared protected path containing .. cannot inherit the wrong ancestor deny", { skip: !image, timeout: 30000 }, async () => {
const workspace = "/var/lib/daimon/workspace", home = "/var/lib/daimon/home";
const denied = ["/run", "/run/../var/lib/daimon/control/", "/proc", `${home}/.codex/auth.json`];
const profile = renderCodexArgs({ codexSandbox: { mode: "workspace-write", networkAccess: false, webSearch: "disabled" },
codexSandboxProtectedPaths: denied }, workspace, "http://127.0.0.1:1/mcp").find(arg => arg.startsWith("permissions="))!;
assert.ok(profile.includes('"/var/lib/daimon/control"="deny"'));
assert.equal(await runSandbox({ workspace, home, denied, readable: [], profile }), "CODEX_SANDBOX_ENFORCED");
await assert.rejects(runSandbox({ workspace, home, denied, readable: [],
profile: profile.replace(',"/var/lib/daimon/control"="deny"', "") }), /canary became readable/u);
});

test("unsupported nested exception geometry fails closed instead of dropping its protected descendant", { skip: !image, timeout: 30000 }, async () => {
const workspace = "/var/lib/daimon/workspace", home = "/var/lib/daimon/home", vault = "/var/lib/daimon/vault";
const denied = ["/run", "/proc", `${home}/.codex/auth.json`, `${home}/.daimon-inbound`, vault, `${vault}/open/secret`];
const readable = [`${vault}/open`];
const profile = renderCodexArgs({ codexSandbox: { mode: "workspace-write", networkAccess: false, webSearch: "disabled" },
codexSandboxProtectedPaths: denied, codexSandboxReadablePaths: readable }, workspace, "http://127.0.0.1:1/mcp")
.find(arg => arg.startsWith("permissions="))!;
assert.ok(profile.includes(`"${vault}/open/secret"="deny"`));
// Codex 0.142.3 cannot mount this nonredundant geometry. Keep the deny and
// require the actual-profile readiness probe to refuse it before cognition.
await assert.rejects(runSandbox({ workspace, home, denied, readable, profile }), /Can't mkdir parents.*Read-only file system/u);
await assert.rejects(runSandbox({ workspace, home, denied, readable,
profile: profile.replace(`,"${vault}/open/secret"="deny"`, "") }), /canary became readable/u);
});
Loading
Loading