mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 22:19:42 +02:00
feat(sessions): allow per-session CLAUDE_CONFIG_DIR env override (#255)
Adds an exact-key tier (ALLOWED_ENV_KEYS) beside ALLOWED_ENV_PREFIXES in schemas.ts, admitting CLAUDE_CONFIG_DIR so a case can run on a separate Claude subscription (client-billed accounts). Exact match only: other CLAUDE_* keys and near-misses like CLAUDE_CONFIG_DIR_EXTRA stay rejected, blocked keys stay blocked. The key also survives getEnvOverridesForPersist() (a path, not a secret; dropping it would silently switch a rebuilt session back to the default account after a reboot). Docs cover the transcript caveat: a relocated config dir writes transcripts outside ~/.claude/projects, so response viewer / subagent windows / ultracode / Read My Mind go blind for that session unless projects is symlinked back into the shared tree. Design and spec contributed by @jordan8037310 in #255. Closes #255. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -1219,7 +1219,9 @@ export class Session extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Returns a subset of env overrides safe for disk persistence (state.json).
|
||||
* Only non-sensitive `CLAUDE_CODE_*` keys are included. `OPENCODE_*` keys are
|
||||
* Only non-sensitive `CLAUDE_CODE_*` keys plus CLAUDE_CONFIG_DIR (a path, not
|
||||
* a secret — and losing it across a restart would silently move a session back
|
||||
* to the default Claude account, #255) are included. `OPENCODE_*` keys are
|
||||
* filtered out because the schema permits them and they can carry secrets
|
||||
* (e.g., OPENCODE_API_KEY); secrets must not land in `~/.codeman/state.json`.
|
||||
* Must NOT be included in any API-bound serializer — see toState() comment.
|
||||
@@ -1228,7 +1230,7 @@ export class Session extends EventEmitter {
|
||||
if (!this._envOverrides) return undefined;
|
||||
const safe: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(this._envOverrides)) {
|
||||
if (key.startsWith('CLAUDE_CODE_')) safe[key] = value;
|
||||
if (key.startsWith('CLAUDE_CODE_') || key === 'CLAUDE_CONFIG_DIR') safe[key] = value;
|
||||
}
|
||||
return Object.keys(safe).length > 0 ? safe : undefined;
|
||||
}
|
||||
|
||||
+10
-1
@@ -124,6 +124,14 @@ export const FileWriteSchema = z
|
||||
/** Allowlisted env var key prefixes */
|
||||
const ALLOWED_ENV_PREFIXES = ['CLAUDE_CODE_', 'OPENCODE_', 'CODEX_', 'GEMINI_', 'GOOGLE_', 'ANTIGRAVITY_'];
|
||||
|
||||
/**
|
||||
* Allowlisted exact env var keys (checked alongside the prefixes).
|
||||
* CLAUDE_CONFIG_DIR relocates the Claude CLI's user config (credentials,
|
||||
* settings, stats) so a case can run on a separate Claude subscription (#255).
|
||||
* Exact match only — CLAUDE_CONFIG_DIR_EXTRA etc. stay rejected.
|
||||
*/
|
||||
const ALLOWED_ENV_KEYS = new Set(['CLAUDE_CONFIG_DIR']);
|
||||
|
||||
/** Env var keys that are always blocked (security-sensitive) */
|
||||
const BLOCKED_ENV_KEYS = new Set([
|
||||
'PATH',
|
||||
@@ -138,6 +146,7 @@ const BLOCKED_ENV_KEYS = new Set([
|
||||
/** Validate that an env var key is allowed */
|
||||
function isAllowedEnvKey(key: string): boolean {
|
||||
if (BLOCKED_ENV_KEYS.has(key)) return false;
|
||||
if (ALLOWED_ENV_KEYS.has(key)) return true;
|
||||
return ALLOWED_ENV_PREFIXES.some((prefix) => key.startsWith(prefix));
|
||||
}
|
||||
|
||||
@@ -152,7 +161,7 @@ const safeEnvOverridesSchema = z
|
||||
},
|
||||
{
|
||||
message:
|
||||
'envOverrides contains blocked or disallowed env var keys. Only CLAUDE_CODE_*, OPENCODE_*, CODEX_*, GEMINI_*, GOOGLE_*, and ANTIGRAVITY_* keys are allowed.',
|
||||
'envOverrides contains blocked or disallowed env var keys. Only CLAUDE_CODE_*, OPENCODE_*, CODEX_*, GEMINI_*, GOOGLE_*, ANTIGRAVITY_* keys and CLAUDE_CONFIG_DIR are allowed.',
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user