nexu-io/open-design · error · Error

${RESOURCE_ROOT_ENV} must be under the workspace root or app

Error message

${RESOURCE_ROOT_ENV} must be under the workspace root or app resources path

What it means

Thrown by resolveDaemonResourceRoot() when OD_RESOURCE_ROOT is set but the resolved path is not inside any of the supplied safeBases (workspace root or app resources path). It is a path-traversal / escape guard: the daemon only serves resources from sanctioned roots, so a configured resource root outside them is rejected hard. Returns null when the env var is unset (the legitimate 'no override' case).

Source

Thrown at apps/daemon/src/daemon-paths.ts:83

export interface ResolveDaemonResourceRootOptions {
  configured?: string;
  safeBases?: Array<string | null | undefined>;
}

export function resolveDaemonResourceRoot({
  configured = process.env[RESOURCE_ROOT_ENV],
  safeBases,
}: ResolveDaemonResourceRootOptions = {}): string | null {
  if (!configured || configured.length === 0) return null;

  const resolved = path.resolve(configured);
  const normalizedSafeBases = (safeBases ?? [])
    .filter((base): base is string => typeof base === 'string' && base.length > 0)
    .map((base) => path.resolve(base));

  if (!normalizedSafeBases.some((base) => isPathWithin(base, resolved))) {
    throw new Error(
      `${RESOURCE_ROOT_ENV} must be under the workspace root or app resources path`,
    );
  }

  return resolved;
}

export function resolveDaemonResourceDir(
  resourceRoot: string | null,
  segment: string,
  fallback: string,
): string {
  return resourceRoot ? path.join(resourceRoot, segment) : fallback;
}

export interface ResolveDaemonPluginPreviewsDirOptions {
  env?: NodeJS.ProcessEnv;
  resourceRoot: string | null | undefined;

View on GitHub (pinned to 5be4028344)

Solutions

  1. Set OD_RESOURCE_ROOT to a directory inside the workspace root or the app resources path.
  2. Unset OD_RESOURCE_ROOT to let the daemon derive resources from the workspace / packaged resources path.
  3. If a symlink is involved, resolve it to its real target and ensure that target is inside a safe base.

Example fix

# before: pointing outside the workspace
export OD_RESOURCE_ROOT=/opt/my-resources

# after: keep it inside the workspace (or unset)
export OD_RESOURCE_ROOT=$PWD/resources
# or simply: unset OD_RESOURCE_ROOT
Defensive patterns

Strategy: validation

Validate before calling

function assertResourceRootInSafeBase(configured: string | undefined, safeBases: string[]) {
  if (!configured) return null;
  const resolved = path.resolve(configured);
  if (!safeBases.some(b => isPathWithin(b, resolved))) {
    throw new Error('OD_RESOURCE_ROOT must be under workspace or app resources path');
  }
  return resolved;
}

Type guard

function isWithinAnyBase(target: string, bases: string[]): boolean {
  return bases.some(b => isPathWithin(b, target));
}

Try / catch

try { resolveDaemonResourceRoot({ safeBases }); }
catch (e) {
  if (e instanceof Error && /OD_RESOURCE_ROOT/.test(e.message)) {
    // unset OD_RESOURCE_ROOT and fall back to derived resources path
  } else throw e;
}

Prevention

When it happens

Trigger: Setting OD_RESOURCE_ROOT to an absolute path outside the workspace root or the packaged app resources path; using a relative path that resolves outside the safe bases via '..'; pointing at a symlink whose target escapes the bases.

Common situations: Operator pointed OD_RESOURCE_ROOT at /etc or an arbitrary absolute dir; a packaged run inherited a dev-time OD_RESOURCE_ROOT pointing outside Contents/Resources; a symlinked resources dir resolves outside the safe base.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/84a9ac2515f9c1d2. Report an issue: GitHub.