affaan-m/ECC · critical · CapsuleError

capsule.secret_canary

capsule.secret_canary

Error message

payload tripped secret canary ${findings[0].canary} at ${findings[0].path}

What it means

Before writing anything, append() scans the payload with the envelope's secret canary detector. If any value matches a canary pattern (API keys, tokens, key-like strings), it throws 'capsule.secret_canary' with the offending canary name and payload path in the message and the full findings array as detail. This is a deliberate security gate: the capsule journal is an append-only, potentially shared artifact, so secrets must never enter it.

Solutions

  1. Remove the secret from the payload; reference it by environment variable name or a redacted handle (e.g. 'key from SECRET_VAR') instead of its value.
  2. Inspect the findings detail array to see the exact canary and payload path that matched.
  3. Truncate or mask the offending value (e.g. last4 only) before appending.
  4. If it is a false positive (deliberately fake test value), restructure the string so it does not match the canary pattern rather than bypassing the check.
  5. If a real secret was nearly committed, rotate it — it reached your code path and logs.

Example fix

// before
await capsule.append('fix', 'note', { msg: `used key ${process.env.OPENAI_API_KEY}` }); // capsule.secret_canary

// after
await capsule.append('fix', 'note', { msg: 'used key from OPENAI_API_KEY env var' });
Defensive patterns

Strategy: validation

Validate before calling

const scan = envelope.redactPayload(payload, {});
if (scan.findings.length > 0) throw new Error(`secret-like value at ${scan.findings[0].path} (${scan.findings[0].canary}); remove before appending`);

Try / catch

try {
  await capsule.append(lineage, kind, payload);
} catch (e) {
  if (e instanceof CapsuleError && e.code === 'capsule.secret_canary') {
    console.error(`Refusing to journal secret: ${e.message}; scrub payload and rotate if real`);
    return; // never retry with the same payload
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling append() with a payload that contains a secret-shaped value — e.g. embedding an API key, token, or password (or a string that merely looks like one) in a note payload, or redacting that failed to exclude a credential field.

Common situations: Copying an error message that includes an Authorization header or key; logging environment-derived values like process.env.OPENAI_API_KEY into a capsule note; test fixtures containing fake-but-realistic keys that trip the pattern matcher.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/848cae7a194b881f. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/eval-harness/capsule.js:191

  /**
   * Serialize cooperating appenders and validate current disk state under lock.
   * A partial I/O failure is preserved for diagnosis, never silently rolled back.
   */
  append(lineage, kind, payload = {}, options = {}) {
    return withAppendLock(this.dir, () => {
      const state = readCapsule(this.dir);
      if (!state.ok) throw new CapsuleError(state.code, state.reason, { failed_at: state.failed_at });
      if (!envelope.LINEAGES.includes(lineage)) {
        throw new CapsuleError('capsule.bad_lineage', `unknown lineage ${lineage}`);
      }
      const effectClass = options.effect_class || 'SE0';
      const { payload: clean, dropped, findings, errors: payloadErrors } = envelope.redactPayload(payload, options);
      if (payloadErrors.length > 0) {
        throw new CapsuleError('capsule.payload_invalid', payloadErrors.join('; '));
      }
      if (findings.length > 0) {
        throw new CapsuleError('capsule.secret_canary', `payload tripped secret canary ${findings[0].canary} at ${findings[0].path}`, { findings });
      }
      if (dropped.length > 0 && options.strict !== false) {
        throw new CapsuleError('capsule.payload_denied', `payload keys not allowlisted: ${dropped.join(', ')}`, { dropped });
      }
      const body = {
        schema: envelope.SCHEMA_VERSION,
        run_id: state.meta.run_id,
        capsule_id: state.meta.capsule_id,
        seq: state.entries.length,
        ts: nowIso(this.clock),
        lineage,
        kind,
        effect_class: effectClass,
        harness_version: state.meta.harness_version,
        task_family: state.meta.task_family,
        parent_hash: state.root_hash,
        payload: clean,
      };

View on GitHub (pinned to 8321021c54)