nanocoai/nanoclaw · error · Error
cwd escapes the plugin root
Error message
cwd escapes the plugin root
What it means
parseCwd's containment check (container-config.ts:227): after matching the allowed prefix shape, the remaining path contains `..` segments, `${`, a backslash, or empty segments (e.g. "a//b") — i.e. it could escape the plugin root. This is a lexical security guard, not a filesystem check.
Source
Thrown at src/container-config.ts:227
}
return record;
}
/** Accept only the spec's fixed cwd shapes, lexically contained (no ".." segments). */
function parseCwd(value: unknown): string | undefined {
if (value === undefined) return undefined;
if (typeof value !== 'string' || !CWD_FORM_RE.test(value)) {
throw new Error('cwd must be ./path, ${PLUGIN_ROOT}[/path], or ${PLUGIN_DATA}[/path]');
}
// rest === '' is the bare form (`${PLUGIN_DATA}`, `./`); empty segments in a
// non-empty rest are rejected for symmetry with the command validator.
const rest = value.startsWith('./') ? value.slice(2) : value.replace(CWD_FORM_RE, '');
if (
rest.includes('${') ||
rest.includes('\\') ||
(rest !== '' && rest.split('/').some((s) => s === '..' || s === ''))
) {
throw new Error('cwd escapes the plugin root');
}
return value;
}
export interface AdditionalMountConfig {
hostPath: string;
containerPath: string;
readonly?: boolean;
}
/** Shape of the materialized `container.json` file read by the container runner. */
export interface ContainerConfig {
mcpServers: Record<string, McpServerConfig>;
packages: { apt: string[]; npm: string[] };
imageTag?: string;
additionalMounts: AdditionalMountConfig[];
skills: string[] | 'all';
provider?: string;View on GitHub (pinned to 294ef2aee8)
Solutions
- Remove `..` segments; stay within the plugin root/data dir
- Use forward slashes only (no backslashes)
- Build subpaths by appending clean segments, then sanity-check with a regex before saving
Example fix
// before
{"cwd":"${PLUGIN_ROOT}/../shared"}
// after
{"cwd":"${PLUGIN_ROOT}/shared-links"} Defensive patterns
Strategy: validation
Validate before calling
const rest = cwd.replace(/^\.\//, '').replace(/^\$\{PLUGIN_(ROOT|DATA)\}/, '');
if (rest.includes('..') || rest.includes('\\') || rest.includes('${') || rest.split('/').some(s => s === '')) throw new UserError('cwd escapes plugin root'); Type guard
const isContainedCwd = (v: string) => { const r = v.replace(/^\.\//, '').replace(/^\$\{PLUGIN_(ROOT|DATA)\}/, ''); return !r.includes('..') && !r.includes('\\') && !r.includes('${') && (r === '' || r.split('/').every(s => s !== '')); }; Try / catch
catch (err) { if (err.message.includes('escapes the plugin root')) rebuildPathWithoutTraversal(); else throw err; } Prevention
- Join paths programmatically instead of string concatenation with ../
- Reject any config containing '..' segments before persisting
When it happens
Trigger: cwd values like "./a/../../etc", "${PLUGIN_ROOT}/../x", "./a\\b", or "./a//b" in a stdio MCP entry.
Common situations: Path traversal attempts or careless ../ joins in generated configs; Windows-style separators; double slashes from naive path concatenation.
Related errors
- type "stdio" requires command
- type "http" requires url
- MCP instructions must be a string
- Provide exactly one of command or url
- args, env, and cwd are only valid with command
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/b6e05a4b59baa18d.
Report an issue: GitHub.