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
- 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.
- Inspect the findings detail array to see the exact canary and payload path that matched.
- Truncate or mask the offending value (e.g. last4 only) before appending.
- 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.
- 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
- Never interpolate env-var secret values into payload strings; reference the var name instead.
- Mask credentials to last4/prefix form before logging or journaling.
- Run the canary scan in CI on any code that builds capsule payloads.
- Rotate any real secret that tripped the canary, even if it was never written.
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
- Refusing to save memory containing a suspected secret
- artifact must be a resident regular file
- artifact path escapes output directory
- artifact path must be canonical and absolute
- artifact permits provider execution
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)