Merged
Size
S
Change Breakdown
Bug Fix45%
Security35%
Testing20%
#66030fix(config): redact sourceConfig and runtimeConfig alias fields in redactConfigSnapshot [AI]

Config redaction now covers all secret fields

A security fix ensures configuration snapshots no longer expose plaintext secrets through two alias fields that were inadvertently skipping redaction.

The redactConfigSnapshot function contained a gap in its redaction logic. When processing configuration snapshots, the function spread the original object and then overwrote only the config and resolved fields with redacted versions. However, two newer canonical alias fields—sourceConfig and runtimeConfig—were copied by reference from the original snapshot, bypassing redaction entirely.

This mattered because the config.get gateway RPC serializes full snapshot output to authenticated clients. Anyone with operator.read scope could potentially extract stored secrets including API keys, authentication tokens, and channel credentials through these unredacted fields.

The fix explicitly overwrites both sourceConfig and runtimeConfig with their corresponding redacted values. In the valid-snapshot path, sourceConfig receives redactedResolved and runtimeConfig receives redactedConfig. The invalid-snapshot path now uses named constants for the empty redacted objects, then assigns them consistently across all four fields.

Referential equality is preserved—sourceConfig remains identical to resolved, and runtimeConfig remains identical to config—so downstream code relying on reference checks continues to work correctly.

Tests now verify both alias fields contain REDACTED_SENTINEL and maintain their expected identity relationships.

View Original GitHub Description

Summary

  • Problem: redactConfigSnapshot() spread ...snapshot then only overwrote config and resolved with redacted copies, leaving sourceConfig and runtimeConfig (the canonical alias fields) pointing to the original unredacted objects. Any caller reading those fields received plaintext secrets.
  • Why it matters: The config.get gateway RPC serializes the full snapshot output to authenticated clients. A session with operator.read scope could extract all stored secrets (API keys, auth tokens, channel credentials) via the unredacted alias fields.
  • What changed: redactConfigSnapshot now explicitly overwrites sourceConfig (set to redactedResolved) and runtimeConfig (set to redactedConfig) in both the valid-snapshot and invalid-snapshot return paths. Test coverage added for both new fields.
  • What did NOT change: Redaction logic for config, resolved, raw, and parsed is unchanged. No gateway RPC surface, scope model, or schema changed.

AI-assisted: This fix was generated by OpenAI Codex and reviewed prior to submission.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • This PR fixes a bug or regression

Root Cause (if applicable)

  • Root cause: redactObject() returns new objects rather than mutating in place. The object spread copies the original sourceConfig and runtimeConfig references, and the subsequent explicit overrides only covered the deprecated aliases (config, resolved), not the canonical fields.
  • Missing detection / guardrail: No test asserted result.sourceConfig or result.runtimeConfig were redacted — only the deprecated alias fields were checked.
  • Contributing context: sourceConfig/runtimeConfig were introduced as canonical replacements for resolved/config but the redaction function was not updated in lockstep.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
  • Target test or file: src/config/redact-snapshot.test.ts
  • Scenario the test should lock in: Both result.sourceConfig and result.runtimeConfig contain REDACTED_SENTINEL for known-sensitive values; both alias pairs are referentially equal (sourceConfig === resolved, runtimeConfig === config).
  • Why this is the smallest reliable guardrail: Directly exercises the output shape of redactConfigSnapshot without any gateway layer.
  • Existing test that already covered this: None — only result.config and result.resolved were previously asserted.

User-visible / Behavior Changes

None. sourceConfig and runtimeConfig were already supposed to be redacted; this makes the behavior match the documented contract.

Diagram (if applicable)

Before:
redactConfigSnapshot() -> { ...snapshot,   // sourceConfig/runtimeConfig: UNREDACTED
                            config: redacted,
                            resolved: redacted }

After:
redactConfigSnapshot() -> { ...snapshot,
                            sourceConfig: redacted,  // now explicit
                            runtimeConfig: redacted, // now explicit
                            config: redacted,
                            resolved: redacted }

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? Yes — sourceConfig and runtimeConfig fields are now properly redacted before being returned by redactConfigSnapshot.
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • Risk + mitigation: The change narrows data exposure. The only risk is if a caller depended on receiving unredacted values via these fields, which would be an incorrect usage against the documented contract.

Repro + Verification

Environment

  • OS: Linux (CI)
  • Runtime/container: Node 22 / Bun
  • Model/provider: N/A
  • Integration/channel: N/A
  • Relevant config: Any config with gateway.auth.token or channels.discord.token set

Steps

  1. Run pnpm test src/config/redact-snapshot.test.ts
  2. Observe new assertions pass: result.sourceConfig and result.runtimeConfig both contain REDACTED_SENTINEL
  3. Observe identity assertions pass: result.sourceConfig === result.resolved, result.runtimeConfig === result.config

Expected

  • All redaction tests pass including the new alias-field assertions

Actual

  • All tests pass

Evidence

  • Failing test/log before + passing after

New test cases added to redact-snapshot.test.ts directly assert the previously-unredacted fields now contain REDACTED_SENTINEL.

Human Verification (required)

  • Verified scenarios: Valid-snapshot redaction path (both alias fields redacted); invalid-snapshot branch (both alias fields return {}); referential equality maintained between alias pairs.
  • Edge cases checked: sourceConfig === resolved and runtimeConfig === config identity preserved so downstream code relying on reference equality is unaffected.
  • What you did not verify: Live gateway RPC round-trip with a real scoped session.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: A caller that expected sourceConfig/runtimeConfig to be unredacted (incorrect usage) would now receive redacted values.
    • Mitigation: No such caller exists in the codebase; these fields are exclusively read-only outputs of redactConfigSnapshot and no code path depends on receiving unredacted aliases from this function.
© 2026 · via Gitpulse