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

  1. 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.
  2. Ensure its mode is 'ro'.
  3. 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

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


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