nexu-io/open-design · error · Error
--image path "${rel}" resolves outside the project directory
Error message
--image path "${rel}" resolves outside the project directory. What it means
Thrown by resolveProjectImage, a security guard in apps/daemon/src/media/index.ts. The resolved absolute path of an `--image` argument must equal or sit beneath the project directory; any path that escapes (via `..`, absolute paths, or symlink resolution) is rejected before stat/readFile. This blocks an agent or hallucinated arg from uploading arbitrary host files (e.g. /etc/passwd) to a paid model.
Source
Thrown at apps/daemon/src/media/index.ts:226
/**
* Resolve a project-relative `--image` path into a base64 data URL the
* upstream model APIs (Volcengine i2v, OpenAI image-edit, etc.) accept
* directly. Returns null when no path was supplied.
*
* Security: refuses anything that escapes the project directory.
* Without this guard, an agent (or a hallucinated arg) could ask the
* daemon to upload `/etc/passwd` to a paid model.
*/
async function resolveProjectImage(rel: unknown, projectDir: string): Promise<ImageRef | null> {
if (typeof rel !== 'string' || !rel.trim()) return null;
const projectRootResolved = path.resolve(projectDir);
const abs = path.resolve(projectRootResolved, rel.trim());
if (
abs !== projectRootResolved &&
!abs.startsWith(projectRootResolved + path.sep)
) {
throw new Error(
`--image path "${rel}" resolves outside the project directory.`,
);
}
let info;
try {
info = await stat(abs);
} catch {
throw new Error(`--image not found: ${rel}`);
}
if (!info.isFile()) {
throw new Error(`--image is not a regular file: ${rel}`);
}
// Cap at 16 MB. Beyond this, base64 inflation alone (≈4/3) starts
// hitting body-size limits at the upstream APIs and our own express
// 4mb body cap on inbound requests; bigger payloads should travel
// via the dedicated upload endpoint, not the dispatcher.
const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
if (info.size > MAX_IMAGE_BYTES) {View on GitHub (pinned to 5be4028344)
Solutions
- Reference images by a path relative to the project root and keep them inside the project directory.
- Copy the external image into the project's assets folder first, then reference it.
- Remove or relocate symlinks that escape the project root.
- Audit agent-supplied --image args against the project root before dispatch.
Example fix
// before --image /home/user/pic.png // after: place under project first --image assets/pic.png
Defensive patterns
Strategy: validation
Validate before calling
const abs = path.resolve(projectDir, rel.trim());
if (abs !== projectDir && !abs.startsWith(projectDir + path.sep)) {
throw new Error('image path escapes project dir');
} Type guard
function isWithinProject(rel: string, projectDir: string): boolean {
const abs = path.resolve(projectDir, rel.trim());
return abs === projectDir || abs.startsWith(projectDir + path.sep);
} Prevention
- Keep all image assets under the project directory.
- Reject agent-supplied absolute paths before dispatch.
- Resolve and audit symlinks inside asset folders.
When it happens
Trigger: Passing `--image ../../../etc/passwd` or `--image /etc/shadow`; a symlink inside the project that points outside; an absolute path that is not under the project root; path components that normalize above the project root.
Common situations: Agent hallucinates a system path as an image source; symlinked assets pointing outside the workspace; user pastes an absolute path from elsewhere on the machine.
Related errors
- invalid brand id: ${input.brandId}
- invalid design system id: ${designSystemId}
- invalid memory id
- path traversal detected in input_images for job {job.get('id
- path traversal detected in prompt_file for job {job_id}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/6a5c1d7c5aebebb1.
Report an issue: GitHub.