n8n-io/n8n · error · Error
${label} "${candidate}" escapes ${root}
Error message
${label} "${candidate}" escapes ${root} What it means
Thrown by `resolveInside()` in computer-use/runner.ts when joining a scenario-declared path (fixture path or sandbox destination) onto a root directory would produce a resolved path outside that root. The check uses `node:path.resolve` then `isContained(root, full)`; the root itself is allowed (empty candidate is a no-op), but any `..` segment or absolute path that escapes the root is rejected. Purpose: keep scenario JSON authors honest so a malicious or careless fixture path can't read or write outside the sandbox.
Source
Thrown at packages/@n8n/instance-ai/evaluations/computer-use/runner.ts:186
function resolveFixture(fixturesDir: string, fixturePath: string): string {
return resolveInside(fixturesDir, fixturePath, 'fixture path');
}
/**
* Join `candidate` onto `root` and assert the result stays within `root`.
* Throws if the resolved path escapes (e.g. via `..`). Used to keep scenario
* authors honest when declaring fixture paths and sandbox destinations.
*
* Exported for unit testing — keep the import surface narrow.
*/
export function resolveInside(root: string, candidate: string, label: string): string {
const rootResolved = resolve(root);
const fullResolved = resolve(rootResolved, candidate);
// Allow the root itself (e.g. empty candidate) as a no-op destination;
// otherwise require strict containment.
if (fullResolved !== rootResolved && !isContained(rootResolved, fullResolved)) {
throw new Error(`${label} "${candidate}" escapes ${root}`);
}
return fullResolved;
}
// ---------------------------------------------------------------------------
// Optional pre-seeded workflow (for scenarios that say "look at my workflow X")
// ---------------------------------------------------------------------------
async function maybeSeedWorkflow(
client: N8nClient,
scenario: Scenario,
fixturesDir: string,
logger: EvalLogger,
): Promise<void> {
const path = scenario.setup?.seedWorkflow;
if (!path) return;
const fixturePath = resolveFixture(fixturesDir, path);View on GitHub (pinned to 5ac6606e81)
Solutions
- Open the scenario JSON cited by the run and find the field matching the `label` in the message ('fixture path' → check `seedWorkflow`/fixture refs; 'sandbox path' → check `setup.seeds[].to`).
- Replace the escaping path with one that resolves strictly under the root: use a relative path with no `..`, or copy the external file into the fixtures dir first.
- If you genuinely need a path outside the root, that's unsupported by design — restructure so the file lives under `fixtures/` or `sandboxDir`.
Example fix
// before (scenario.json)
{ "setup": { "seeds": [{ "from": "data.json", "to": "../../shared/out.json" }] } }
// after
{ "setup": { "seeds": [{ "from": "data.json", "to": "shared/out.json" }] } } Defensive patterns
Strategy: type-guard
Validate before calling
import { resolve, relative } from 'node:path';
function isContained(root: string, child: string): boolean {
const rel = relative(root, child);
return rel === '' || (!rel.startsWith('..') && !resolve(child).isAbsolute);
}
function safeResolveInside(root: string, candidate: string): string | null {
const r = resolve(root);
const f = resolve(r, candidate);
return f === r || isContained(r, f) ? f : null;
} Type guard
function isSafePath(root: string, candidate: string): boolean {
const r = resolve(root);
const f = resolve(r, candidate);
if (f === r) return true;
const rel = relative(r, f);
return rel.length > 0 && !rel.startsWith('..') && !path.isAbsolute(rel);
} Prevention
- When authoring scenario JSON, never use absolute paths or `..` in fixture/seed destinations — keep everything relative to the declared root.
- Validate scenario files against the case-file schema (which can encode path constraints) before committing.
- If you need to share fixtures across scenarios, copy them into the fixtures dir rather than reaching out.
When it happens
Trigger: A scenario JSON declares `seedWorkflow: '../../../etc/passwd'` or a fixture path like `'/etc/secrets.json'` (absolute, escapes). A sandbox `to: '../../../../tmp'` declared in a seed entry. Any fixture/destination resolved relative to `fixturesDir` or `sandboxDir` that does not stay underneath it. Called from `resolveFixture` (label 'fixture path') and from the seed loop (label 'sandbox path'), so the `label` in the message tells you which.
Common situations: Scenario author uses an absolute path by mistake. A copy-pasted fixture path from a different scenario layout where the relative root differs. A legitimate intent to share a fixture from outside the fixtures dir — the model forbids this; you must put the fixture inside the fixtures dir or symlink/copy it in.
Related errors
- ${label} path must stay within the workspace root: ${path}.
- Unsupported syntax: '${nodeType}' is not allowed in SDK code
- '${node.kind}' declarations are not allowed. Use 'const' onl
- Invalid skill at ${sourceDirectory}: ${errors.join('; ')}
- Webhook URL must use HTTPS. Got: ${url.protocol}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/f175ad8bfa5f0886.
Report an issue: GitHub.