Add mount security allowlist for external directory access (#14)

* Add secure mount allowlist validation

Addresses arbitrary host mount vulnerability by validating additional
mounts against an external allowlist stored at ~/.config/nanoclaw/.
This location is never mounted into containers, making it tamper-proof.

Security measures:
- Allowlist cached in memory (edits require process restart)
- Real path resolution (blocks symlink and .. traversal attacks)
- Blocked patterns for sensitive paths (.ssh, .gnupg, .aws, etc.)
- Non-main groups forced to read-only when nonMainReadOnly is true
- Container path validation prevents /workspace/extra escape

https://claude.ai/code/session_01BPqdNy4EAHHJcdtZ27TXkh

* Add mount allowlist setup to /setup skill

Interactive walkthrough that asks users:
- Whether they want agents to access external directories
- Which directories to allow (with paths)
- Read-write vs read-only for each
- Whether non-main groups should be restricted to read-only

Creates ~/.config/nanoclaw/mount-allowlist.json based on answers.

https://claude.ai/code/session_01BPqdNy4EAHHJcdtZ27TXkh

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-01 22:55:08 +02:00
committed by GitHub
parent 5760b75fa9
commit 48822ff67d
6 changed files with 554 additions and 18 deletions

View File

@@ -15,6 +15,7 @@ import {
DATA_DIR
} from './config.js';
import { RegisteredGroup } from './types.js';
import { validateAdditionalMounts } from './mount-security.js';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
@@ -151,22 +152,14 @@ function buildVolumeMounts(group: RegisteredGroup, isMain: boolean): VolumeMount
}
}
// Additional mounts validated against external allowlist (tamper-proof from containers)
if (group.containerConfig?.additionalMounts) {
for (const mount of group.containerConfig.additionalMounts) {
const hostPath = mount.hostPath.startsWith('~')
? path.join(homeDir, mount.hostPath.slice(1))
: mount.hostPath;
if (fs.existsSync(hostPath)) {
mounts.push({
hostPath,
containerPath: `/workspace/extra/${mount.containerPath}`,
readonly: mount.readonly !== false // Default to readonly for safety
});
} else {
logger.warn({ hostPath }, 'Additional mount path does not exist, skipping');
}
}
const validatedMounts = validateAdditionalMounts(
group.containerConfig.additionalMounts,
group.name,
isMain
);
mounts.push(...validatedMounts);
}
return mounts;