nanocoai/nanoclaw · warning

inbox-safety: inbox dir escaped inbox root

Error message

inbox-safety: inbox dir escaped inbox root

What it means

After mkdir, realpath of the created inbox dir does not sit inside realpath of the inbox root — meaning the resolved location escaped the sandbox. The function returns null and attachment extraction is refused for that message.

Solutions

  1. Compare realpath of the inbox root from host vs inside container; align mounts so both resolve identically
  2. Avoid symlinked components in the session-data mount path
  3. On macOS, use /private/var-prefixed or Users-prefixed absolute paths consistently
  4. Reproduce with realpath on both sides, then adjust the mount source/target
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
import path from 'node:path';
function resolvesInside(root: string, child: string): boolean {
  const r = fs.realpathSync(root);
  const c = fs.realpathSync(child);
  return c === r || c.startsWith(r + path.sep);
}

Prevention

When it happens

Trigger: ensureContainedInboxDir: realpathSync(inboxDir) resolves outside realpathSync(inboxRoot), e.g. because inboxRoot itself traverses a symlinked parent that realpaths elsewhere while inboxDir was created on a different bind mount.

Common situations: Docker bind mounts where host and container resolve the same logical path to different real paths; macOS /var vs /private/var symlink divergence between the two realpath calls; overlapping mounts shadowing the inbox root.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/8c9e0c5141e4461d. Report an issue: GitHub.

Appendix: source

Thrown at src/inbox-safety.ts:75

  for (const dir of [inboxRoot, inboxDir]) {
    try {
      const st = fs.lstatSync(dir);
      if (st.isSymbolicLink() || !st.isDirectory()) {
        log.warn('inbox-safety: rejecting unsafe inbox path', { ...context, dir });
        return null;
      }
    } catch {
      // Does not exist yet — fine, mkdir below creates it.
    }
  }

  fs.mkdirSync(inboxDir, { recursive: true });

  try {
    const realInboxDir = fs.realpathSync(inboxDir);
    const realInboxRoot = fs.realpathSync(inboxRoot);
    if (!isPathInside(realInboxRoot, realInboxDir)) {
      log.warn('inbox-safety: inbox dir escaped inbox root', { ...context, inboxDir });
      return null;
    }
    return realInboxDir;
  } catch (err) {
    log.warn('inbox-safety: failed to resolve inbox dir', { ...context, inboxDir, err });
    return null;
  }
}

View on GitHub (pinned to 294ef2aee8)