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
15 changes: 14 additions & 1 deletion cli/__tests__/adapters.claude.environment.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,12 @@ describe('claude adapter — ctx.environment', () => {
await claude.spawn('hi', {
sessionId: null,
cwd,
env: { PATH: process.env.PATH },
env: {
PATH: process.env.PATH,
COMMONLY_HOST_SECRET: 'must-not-reach-public-seat',
AWS_SECRET_ACCESS_KEY: 'must-not-reach-public-seat',
},
commitAttributionEnv: { COMMONLY_AGENT_SEAT_NAME: 'Public Linux Seat' },
environment: { sandbox: { trust: 'public' } },
_spawnImpl: impl,
_detectBwrap: () => ({ available: true, path: 'bwrap' }),
Expand All @@ -730,6 +735,14 @@ describe('claude adapter — ctx.environment', () => {
expect(calls[0].cmd).toBe('bwrap');
expect(calls[0].args).toContain('--unshare-all');
expect(calls[0].args).toContain('--die-with-parent');
// The adapter passes its filtered environment to bwrap as the process
// environment; values are not serialized into bwrap argv.
expect(calls[0].args).not.toContain('--clearenv');
expect(calls[0].args).not.toContain('must-not-reach-public-seat');
expect(calls[0].opts.env).toEqual({
PATH: process.env.PATH,
COMMONLY_AGENT_SEAT_NAME: 'Public Linux Seat',
});
// The inner claude argv rides after `--`, inside the namespace.
const inner = calls[0].args.slice(calls[0].args.indexOf('--') + 1);
expect(inner[0]).toMatch(/claude$/);
Expand Down
4 changes: 2 additions & 2 deletions cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@commonlyai/cli",
"version": "0.1.137",
"version": "0.1.138",
"license": "Apache-2.0",
"description": "The Commonly CLI — connect agents, manage pods, iterate fast",
"type": "module",
Expand Down
23 changes: 16 additions & 7 deletions cli/src/lib/adapters/claude.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,13 +200,15 @@ const preparePublicClaudeState = async (ctx) => {
return { statePath, tmpPath, configPath };
};

const buildClaudeEnv = (input, state, expansionEnv = {}, credentialFile = null) => {
const buildClaudeEnv = (input, state, expansionEnv = {}, credentialFile = null, publicSeat = false) => {
const source = input || process.env;
const output = state ? {} : { ...source };
if (state) {
const output = state || publicSeat ? {} : { ...source };
if (state || publicSeat) {
for (const [key, value] of Object.entries(source)) {
if (PUBLIC_SAFE_ENV.test(key)) output[key] = value;
}
}
if (state) {
output.HOME = state.statePath;
output.TMPDIR = state.tmpPath;
// Claude keeps its temp root at /private/tmp/claude-<uid> unless told
Expand Down Expand Up @@ -825,14 +827,16 @@ export default {
let mcpConfig = null;
let publicClaudeState = null;
let publicSkillArgs = [];
const publicTrust = ctx.environment?.sandbox?.trust === 'public';
const publicNativeSandbox = publicTrust
&& PUBLIC_SANDBOX_MODES.has(resolvePublicSandboxMode(ctx.environment?.sandbox));
const publicBwrapSandbox = publicTrust
&& resolvePublicSandboxMode(ctx.environment?.sandbox) === 'bwrap';
try {
// Trust is normalized at entry (legacy `internal` reads as public). The
// MCP policy follows trust alone, whatever confines the seat: a public
// bwrap seat needs a host-prepared build exactly as a Seatbelt one does
// (Rhea on #2247).
const publicTrust = ctx.environment?.sandbox?.trust === 'public';
const publicNativeSandbox = publicTrust
&& PUBLIC_SANDBOX_MODES.has(resolvePublicSandboxMode(ctx.environment?.sandbox));
if (publicNativeSandbox) {
publicClaudeState = await preparePublicClaudeState(ctx);
}
Expand Down Expand Up @@ -863,8 +867,9 @@ export default {
publicClaudeState,
mcpConfig?.expansionEnv,
mcpConfig?.credential?.path || null,
publicTrust,
);
if (publicNativeSandbox && ctx.commitAttributionEnv) {
if ((publicNativeSandbox || publicBwrapSandbox) && ctx.commitAttributionEnv) {
Object.assign(claudeEnv, ctx.commitAttributionEnv);
}
const spawnCtx = {
Expand Down Expand Up @@ -920,8 +925,12 @@ export default {
publicClaudeState,
mcpConfig?.expansionEnv,
mcpConfig?.credential?.path || null,
publicTrust,
),
};
if ((publicNativeSandbox || publicBwrapSandbox) && ctx.commitAttributionEnv) {
Object.assign(retryCtx.claudeEnv, ctx.commitAttributionEnv);
}
const retry = await prepareArgv(retryBase, retryCtx);
const stdout = await runClaude({
cmd: retry.cmd,
Expand Down
6 changes: 5 additions & 1 deletion cli/src/lib/adapters/qwen.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,11 @@ export default {
],
},
},
}, { workspacePath: ctx.cwd, homePath: home, readOnlyPaths: [tempDir, executable, packageDir, process.execPath, ...mcpReadDirectories].filter(Boolean) });
}, {
workspacePath: ctx.cwd,
homePath: home,
readOnlyPaths: [tempDir, executable, packageDir, process.execPath, ...mcpReadDirectories].filter(Boolean),
});
} else if (isPublic) {
if ((ctx._platform || process.platform) !== 'darwin') {
throw new Error(`public Qwen sandbox.mode=${mode} requires Seatbelt on macOS or bwrap on Linux`);
Expand Down
6 changes: 4 additions & 2 deletions cli/src/lib/sandbox/bwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,10 @@ export const detectBwrap = ({ spawn = spawnSync, platform = process.platform } =
* user sees the gap before they think they're protected.
*
* The expansion order matches `bwrap`'s left-to-right argument application:
* isolation flags first, then binds (later binds win on collision), then
* --setenv, then `--` and the inner argv.
* isolation flags first, then binds (later binds win on collision), then the
* isolated HOME, then `--` and the inner argv. The caller passes the filtered
* child environment as the bwrap process environment; values must not be
* serialized into argv, where they would be visible in the process list.
*/
export const wrapArgvWithBwrap = (innerArgv, env, opts = {}) => {
if (process.platform !== 'linux') {
Expand Down
Loading