nanocoai/nanoclaw · critical · deniedByPolicy
identity-material mount ${mount.hostPath} invalid on role ${
Error message
identity-material mount ${mount.hostPath} invalid on role ${container.role} What it means
A mount classified 'identity-material' (session private keys and similar credentials-by-reference) violates the no-credentials invariant: either its mode is not 'ro', or it is attached to the container with role 'agent'. Identity materials are ro-only and never enter the agent container — the agent is the untrusted party the material authenticates.
Source
Thrown at src/drivers/types.ts:472
if (required && mount.class !== required) {
// Where a file lives decides what it IS, so the class is not the
// composer's to choose for these roots. Without this the taxonomy is
// only as strong as whoever assigns the class, and two of the four
// classes carry safety properties that a demotion silently drops:
// `allowlisted-extra` is permitted unconditionally, so relabelling a
// session private key as one mounts it INTO THE AGENT — defeating the
// no-credentials invariant outright — and relabelling the runner source
// as one escapes the read-only rule on the code the agent executes.
// Neither is exotic: both are a single word in a mount literal.
throw deniedByPolicy(`mount ${mount.hostPath} must be classed ${required}, not ${mount.class}`);
}
if (mount.class === 'install-surface' && mount.mode !== 'ro') {
throw deniedByPolicy(`install-surface mount ${mount.hostPath} must be ro`);
}
if (mount.class === 'identity-material' && (mount.mode !== 'ro' || container.role === 'agent')) {
// The no-credentials invariant, as a checkable rule: identity materials
// are ro-only and never enter the agent container.
throw deniedByPolicy(`identity-material mount ${mount.hostPath} invalid on role ${container.role}`);
}
if (!mountAllowed(mount, spec, policy)) {
throw deniedByPolicy(`mount ${mount.hostPath} violates class ${mount.class} scope ${mount.groupScope}`);
}
}
for (const [key, value] of Object.entries(container.env)) {
if (isSecretShaped(key, value)) {
throw deniedByPolicy(`secret-shaped env '${key}' on ${container.role}`);
}
}
for (const [key, value] of Object.entries(container.contributedEnv ?? {})) {
// The sanctioned lane: credential-shaped NAMES are its purpose — a
// provider registering `ANTHROPIC_AUTH_TOKEN=placeholder` for the proxy
// to overwrite is the pattern working as intended, and the name check
// alone denies every such install. Credential VALUES have no sanctioned
// channel, from anyone: real material rides mounts by reference.
if (looksLikeCredential(value)) {
throw deniedByPolicy(`credential value in contributed env '${key}' on ${container.role}`);View on GitHub (pinned to 294ef2aee8)
Solutions
- Move the identity-material mount off the agent container — it belongs only on non-agent roles (e.g. the proxy) that need to present the credential.
- Ensure its mode is 'ro'.
- If the agent needs to authenticate, route it through the proxy that holds the material, never mount the material directly.
Example fix
// before (agent container)
agentMounts: [{ hostPath: mats + '/session-key.pem', containerPath: '/creds/key.pem', class: 'identity-material', mode: 'ro' }]
// after (only on the proxy container)
proxyMounts: [{ hostPath: mats + '/session-key.pem', containerPath: '/creds/key.pem', class: 'identity-material', mode: 'ro' }] Defensive patterns
Strategy: validation
Validate before calling
for (const c of spec.containers) {
if (c.role === 'agent')
for (const m of c.mounts)
if (m.class === 'identity-material') throw new Error(`identity-material on agent: ${m.hostPath}`);
for (const m of c.mounts)
if (m.class === 'identity-material' && m.mode !== 'ro') throw new Error(`identity-material must be ro: ${m.hostPath}`);
} Type guard
function identityMaterialOk(m: MountSpec, role: string): boolean {
return m.class !== 'identity-material' || (m.mode === 'ro' && role !== 'agent');
} Prevention
- Build agent-container mounts from a dedicated list; never share the proxy's mount array.
- Treat 'identity-material' as proxy-only by convention and lint for it on agent containers.
- Authenticate the agent via the proxy that holds the credential, not by direct mount.
When it happens
Trigger: validateSpec sees an 'identity-material' mount with mode 'rw', or any 'identity-material' mount on the container whose role is 'agent'. E.g. wiring a proxy credential mount onto the agent container because both containers were built from one shared mount list.
Common situations: Sharing a single mounts array between the agent container and a proxy/sidecar container; debugging by mounting credentials everywhere; refactors that changed container roles so a mount previously on the proxy now sits on the agent.
Related errors
- mount ${mount.hostPath} must be classed ${required}, not ${m
- install-surface mount ${mount.hostPath} must be ro
- mount ${mount.hostPath} violates class ${mount.class} scope
- mount ${mount.hostPath} must be a canonical absolute path (n
- secret-shaped env '${key}' on ${container.role}
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/297c802e91123e7d.
Report an issue: GitHub.