@kelceyp/caw-server 1.0.286 → 1.0.289
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/dist/claude-config-dir-guard.mjs +105 -0
- package/dist/main.js +410 -400
- package/dist/pty-non-interactive-setup.mjs +30 -18
- package/dist/pty-wrapper.mjs +23 -8
- package/dist/public_html/main.js +352 -352
- package/dist/public_html/styles.css +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLAUDE_CONFIG_DIR refusal guard (story As1065).
|
|
3
|
+
*
|
|
4
|
+
* Claude Code does no shell expansion on `CLAUDE_CONFIG_DIR`. Some values
|
|
5
|
+
* therefore name an unintended directory: the vendor builds a fresh config
|
|
6
|
+
* tree there (`.claude.json`, `projects/`, `sessions/`, `backups/`), usually
|
|
7
|
+
* inside the job's working directory, and quietly stops using the real one
|
|
8
|
+
* (upstream #78988). Probe evidence, Claude Code 2.1.281 (2026-09-25):
|
|
9
|
+
* `~/x`, `$HOME/x` and `rel-cfg` land under the cwd; `/abs/$USER/cfg` creates
|
|
10
|
+
* a literal `$USER` directory; `/abs/x/~/y` creates a literal `~` directory;
|
|
11
|
+
* an empty value makes the cwd itself the config dir.
|
|
12
|
+
*
|
|
13
|
+
* This module refuses such a value; it never repairs, expands or rewrites
|
|
14
|
+
* it. The predicate (ratified 2026-09-25, As1065 scope AC 1):
|
|
15
|
+
* - `undefined` is absent: nothing happens.
|
|
16
|
+
* - any other non-string (including `null`) throws `TypeError`; env values
|
|
17
|
+
* are always `string | undefined`, so this is a programming error.
|
|
18
|
+
* - a string is checked against `CLAUDE_CONFIG_DIR_REFUSAL_RULES` in order
|
|
19
|
+
* EMPTY, NOT_ABSOLUTE, CONTAINS_DOLLAR, TILDE_SEGMENT; the first match is
|
|
20
|
+
* reported. "Absolute" is Node's `path.isAbsolute`, as in
|
|
21
|
+
* `pty-non-interactive-setup.mjs`. A `~` counts only at the start of a
|
|
22
|
+
* `/`-separated segment, so `/a/b~c` passes.
|
|
23
|
+
*
|
|
24
|
+
* Dependency-free by design: the only import is the `path` builtin, there is
|
|
25
|
+
* no logger (an absent variable must produce no new log line) and nothing
|
|
26
|
+
* runs at load. `shared/` is not published from `server/dist/`, so this
|
|
27
|
+
* module carries its own copy of the prefix literal.
|
|
28
|
+
*
|
|
29
|
+
* `CLAUDE_CONFIG_DIR_REFUSED_PREFIX` is literal copy 2 of 2; copy 1 is
|
|
30
|
+
* `CLAUDE_CONFIG_DIR_REFUSED_PREFIX` in `shared/jobErrorPrefixes.js`. The
|
|
31
|
+
* prefix-drift block of
|
|
32
|
+
* `server/test/unit/core/agents/claude-config-dir-guard.test.js` asserts the
|
|
33
|
+
* two are equal.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { isAbsolute } from 'path';
|
|
37
|
+
|
|
38
|
+
export const CLAUDE_CONFIG_DIR_REFUSED_PREFIX = 'CLAUDE_CONFIG_DIR_REFUSED';
|
|
39
|
+
|
|
40
|
+
export const CLAUDE_CONFIG_DIR_REFUSAL_RULES = Object.freeze({
|
|
41
|
+
EMPTY: 'EMPTY',
|
|
42
|
+
NOT_ABSOLUTE: 'NOT_ABSOLUTE',
|
|
43
|
+
CONTAINS_DOLLAR: 'CONTAINS_DOLLAR',
|
|
44
|
+
TILDE_SEGMENT: 'TILDE_SEGMENT'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Per-rule `<rule reason>` and `<rule consequence>` of the message contract.
|
|
48
|
+
const RULE_TEXT = Object.freeze({
|
|
49
|
+
EMPTY: Object.freeze({
|
|
50
|
+
reason: 'is set but empty',
|
|
51
|
+
consequence: "use the job's working directory itself as its config directory"
|
|
52
|
+
}),
|
|
53
|
+
NOT_ABSOLUTE: Object.freeze({
|
|
54
|
+
reason: 'is not an absolute path',
|
|
55
|
+
consequence: "create a new config tree relative to the job's working directory"
|
|
56
|
+
}),
|
|
57
|
+
CONTAINS_DOLLAR: Object.freeze({
|
|
58
|
+
reason: 'contains "$"',
|
|
59
|
+
consequence: 'create a directory with a literal "$" in its name'
|
|
60
|
+
}),
|
|
61
|
+
TILDE_SEGMENT: Object.freeze({
|
|
62
|
+
reason: 'has a path segment starting with "~"',
|
|
63
|
+
consequence: 'create a directory whose name starts with a literal "~"'
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Classify a `CLAUDE_CONFIG_DIR` value.
|
|
69
|
+
*
|
|
70
|
+
* @param {string|undefined} value
|
|
71
|
+
* @returns {string|null} the first matching rule code, or `null` when the
|
|
72
|
+
* value is absent or passes
|
|
73
|
+
* @throws {TypeError} if `value` is neither a string nor `undefined`
|
|
74
|
+
*/
|
|
75
|
+
export const classifyClaudeConfigDir = (value) => {
|
|
76
|
+
if (value === undefined) return null;
|
|
77
|
+
if (typeof value !== 'string') {
|
|
78
|
+
throw new TypeError(`CLAUDE_CONFIG_DIR must be a string or undefined (got ${value === null ? 'null' : typeof value})`);
|
|
79
|
+
}
|
|
80
|
+
if (value === '') return CLAUDE_CONFIG_DIR_REFUSAL_RULES.EMPTY;
|
|
81
|
+
if (!isAbsolute(value)) return CLAUDE_CONFIG_DIR_REFUSAL_RULES.NOT_ABSOLUTE;
|
|
82
|
+
if (value.includes('$')) return CLAUDE_CONFIG_DIR_REFUSAL_RULES.CONTAINS_DOLLAR;
|
|
83
|
+
if (value.split('/').some((segment) => segment.startsWith('~'))) return CLAUDE_CONFIG_DIR_REFUSAL_RULES.TILDE_SEGMENT;
|
|
84
|
+
return null;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Throw if a `CLAUDE_CONFIG_DIR` value must not be forwarded to Claude Code.
|
|
89
|
+
* The refusal is a plain `Error`; its message prefix is the contract.
|
|
90
|
+
*
|
|
91
|
+
* @param {string|undefined} value
|
|
92
|
+
* @throws {Error} `CLAUDE_CONFIG_DIR_REFUSED (<RULE>): ...` for a refused value
|
|
93
|
+
* @throws {TypeError} if `value` is neither a string nor `undefined`
|
|
94
|
+
*/
|
|
95
|
+
export const assertClaudeConfigDirForwardable = (value) => {
|
|
96
|
+
const rule = classifyClaudeConfigDir(value);
|
|
97
|
+
if (rule === null) return;
|
|
98
|
+
const { reason, consequence } = RULE_TEXT[rule];
|
|
99
|
+
throw new Error(
|
|
100
|
+
`${CLAUDE_CONFIG_DIR_REFUSED_PREFIX} (${rule}): CLAUDE_CONFIG_DIR is ${JSON.stringify(value)}, which ${reason}. ` +
|
|
101
|
+
`Claude Code does not expand "~" or "$VARIABLES" in this value, so it would ${consequence} and stop using your real config directory (upstream #78988). ` +
|
|
102
|
+
'Fix: unset CLAUDE_CONFIG_DIR, or set it to an absolute path with no "$" and no path segment starting with "~", ' +
|
|
103
|
+
"in the environment that launches Claude Code (for direct jobs, the CAW server's environment - restart the server after changing it). Then retry."
|
|
104
|
+
);
|
|
105
|
+
};
|