nanocoai/nanoclaw · warning
inbox-safety: rejecting unsafe inbox path
Error message
inbox-safety: rejecting unsafe inbox path
What it means
inbox-safety rejected an inbox path because lstat found the inbox root or per-message inbox dir is a symlink or not a directory. This is path-traversal/symlink hardening for attachment extraction: a null return means the caller refuses to write attachments.
Source
Thrown at src/inbox-safety.ts:61
*
* Returns the resolved, contained subdir path (write into it with an exclusive
* flag — `COPYFILE_EXCL` / `wx` — so a pre-existing symlinked *file* can't be
* followed either), or `null` if any guard tripped. On `null` the caller logs
* its own context and skips; `context` is merged into the warn logs here so
* each call site stays diagnosable.
*/
export function ensureContainedInboxDir(
inboxRoot: string,
messageId: string,
context: Record<string, unknown>,
): string | null {
const inboxDir = path.join(inboxRoot, messageId);
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) {View on GitHub (pinned to 294ef2aee8)
Solutions
- ls -l the reported dir path and remove the offending symlink
- Recreate it as a real directory with correct ownership
- Audit what created the symlink (migration script, backup restore, manual tweak)
- Never point the inbox at another volume via symlink — mount the volume at the inbox root instead
Example fix
# before inbox -> /mnt/bigdisk/inbox (symlink) # after rm inbox mkdir inbox # or mount /mnt/bigdisk at the inbox root in the container config
Defensive patterns
Strategy: validation
Validate before calling
import fs from 'node:fs';
function isInboxDirSafe(p: string): boolean {
try {
const st = fs.lstatSync(p);
return st.isDirectory(); // lstat: false for symlinks
} catch { return true; } // absent is fine
} Type guard
function isRealDirectory(p: string): boolean {
try { return fs.lstatSync(p).isDirectory() && !fs.lstatSync(p).isSymbolicLink(); }
catch { return false; }
} Prevention
- Never symlink inbox dirs; use mounts
- Audit session data trees for symlinks after restores/migrations
- Run the inbox-safety checks in a startup self-test for the data dir
When it happens
Trigger: ensureContainedInboxDir lstats inboxRoot or inboxRoot/<messageId> and finds S_ISLNK or non-directory; returns null so targetInboxDir/extractAttachmentFiles skip the files.
Common situations: Someone symlinked data/.../inbox to another location (or /tmp) for debugging; a compromised or buggy writer replaced the dir; restoring from archive converted the dir to a symlink.
Related errors
- inbox-safety: inbox dir escaped inbox root
- engage_mode '${w.engage_mode}' can never engage on channel '
- MissingChannelAdapterError(channelType, instance)
- chat-sdk bridge instance ${JSON.stringify(config.instance)}
- inbox-safety: failed to resolve inbox dir
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/d450f41207394513.
Report an issue: GitHub.