nanocoai/nanoclaw · critical · deniedByPolicy

credential value in contributed env '${key}' on ${container.

Error message

credential value in contributed env '${key}' on ${container.role}

What it means

A contributedEnv entry's VALUE looks like a real credential. contributedEnv is the sanctioned lane where credential-shaped NAMES are fine (a provider registering ANTHROPIC_AUTH_TOKEN=placeholder for the proxy to overwrite is the intended pattern), so only the name is exempt — the value check still applies, because credential values have no sanctioned channel from anyone; real material rides mounts by reference.

Source

Thrown at src/drivers/types.ts:490

        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}`);
      }
    }
  }
}

/**
 * The canonical absolute form the mount rules require: rooted, and free of
 * empty, '.' and '..' segments — so the string a prefix rule judges is the
 * path the runtime mounts, and a trusted root cannot be escaped lexically.
 */
function hostPathCanonical(hostPath: string): boolean {
  if (!hostPath.startsWith('/')) return false;
  const segments = hostPath.split('/').slice(1);
  return segments.every((segment) => segment !== '' && segment !== '.' && segment !== '..');
}

/**
 * The invariant is that no credential VALUE rides in the environment — not that

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Use an unmistakable placeholder value (e.g. 'placeholder', '__PROXY_FILLED__') — the pattern is that the proxy overwrites it at runtime, so the literal bytes never matter.
  2. If a real credential must reach the container, mount it by reference with the right mount class instead of contributing it as a value.
  3. Adjust test fixture generators to emit short, obviously-fake strings rather than token-shaped randomness.

Example fix

// before
contributedEnv: { ANTHROPIC_AUTH_TOKEN: 'sk-ant-api03-a1b2c3d4e5f6...' }

// after
contributedEnv: { ANTHROPIC_AUTH_TOKEN: 'placeholder' }
Defensive patterns

Strategy: validation

Validate before calling

for (const c of spec.containers)
  for (const [k, v] of Object.entries(c.contributedEnv ?? {}))
    if (looksLikeCredential(v)) throw new Error(`contributedEnv '${k}' carries a credential-shaped value; use a placeholder`);

Type guard

function isSafePlaceholder(v: string): boolean {
  return !looksLikeCredential(v); // short, non-token-shaped literals

Prevention

When it happens

Trigger: A plugin/provider contribution includes contributedEnv like { ANTHROPIC_AUTH_TOKEN: 'sk-ant-...' } — a plausible-looking placeholder that actually matches the credential-value heuristics (looksLikeCredential). Also any contributed value that happens to be a long token-shaped string.

Common situations: Writing a provider integration and putting a real or realistic token in the 'placeholder' slot; test fixtures generating random hex strings long enough to trip the credential heuristic; copying an env block from a working shell session into the contribution.

Related errors


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