Duplicated Security Policy Is the Real Defect

architecture diagnostics secret detection security policy shared ownership Jul 17, 2026

When a secret scanner rejects a symbolic environment-variable name instead of a credential value, the visible bug looks like a false positive. It usually is not. The durable defect is duplicated security policy—the same rule implemented independently in several paths—creating inconsistent behaviour, shadow-path risk, and expensive maintenance. The fix is one shared scanner, clear classification boundaries, and diagnostics that improve without exposing secrets.

The problem

The surface complaint was familiar: a context-assembly path refused work because it matched strings that looked like secret names—symbolic references such as an API key environment-variable name—rather than actual credential values. Literal substring matching cannot tell those apart.

The first instinct is to relax the local matcher. That would have treated the symptom. The deeper problem was architectural: the same secret-detection policy had been copied into multiple assembler paths. Each copy used simple literal matching. That duplication created false positives, inconsistent future maintenance, and weak diagnostics.

What looked like a small bug was architecture-material because:

  • several independent assembler families had to converge on one behaviour
  • shared and controller boundaries had to stay intact
  • security behaviour could not be weakened
  • diagnostics had to improve without leaking matched secrets
  • every caller had to preserve the existing refusal contract

A local patch in one path would have left the other paths as shadow implementations of the same policy.

What actually happened

The work identified several discoveries before implementation:

  • literal substring matching cannot distinguish symbolic names from credential values
  • duplicated policy creates shadow-path risk—future fixes land in one copy and miss the others
  • without a repository-wide scanning standard, the change itself had to define scanner ownership and behaviour explicitly
  • crossing a shared boundary made the item architecture-material, not a quick matcher tweak

The eventual fix was structural rather than local:

  • create one shared scanner as the sole policy owner
  • migrate every assembler family onto that owner
  • remove the duplicated implementations
  • preserve existing refusal codes so callers keep a stable contract
  • add structured, source-attributed diagnostics that name where a match came from without printing the secret
  • expand automated verification around scanner ownership and behaviour

Residual risks remained bounded: the per-family non-weakening replay matrix was narrower than originally envisioned, and one documented fail-closed edge case stayed acceptable but imperfect. Those were treated as explicit residual risks, not silent acceptances or release blockers.

The lesson

When security behaviour fails inconsistently across paths, look for duplicated policy before you tune one matcher.

For security-policy and shared-library work:

  1. Centralise policy before modifying behaviour. If the same rule exists in several places, the first defect is ownership, not the local false positive.
  2. Treat duplicated implementations as architectural defects. Shadow paths diverge over time. Synchronising copies is a temporary illusion of control.
  3. Define classification boundaries with concrete examples. Security-sensitive parsing needs explicit cases for symbolic names versus credential values, assignment shapes, and fail-closed edges—before code lands.
  4. Improve diagnostics without exposing sensitive data. Source attribution and structured reasons help operators; printing matched secrets does not.
  5. Require behavioural tests when replacing shared infrastructure. Cover previous false positives, true positives, refusal-code stability, and static proof that one module owns the policy.

The broader principle

Security behaviour is not an implementation detail to be patched in place. It is shared policy with ownership, invariants, interfaces, verification, and a migration strategy.

Architecture-material items benefit from deliberate specification and planning before build. Specification should define scanner ownership, classification rules, security boundaries, diagnostic behaviour, and verification obligations—rather than leaving those decisions implicit in the first implementation pass. Planning should prove that every family converges on the same owner and that shadow paths are removed, not merely patched.

Audits earn their keep on this class of work. Spec review can catch taxonomy inconsistencies before code exists. Plan review can catch regex and classification ambiguity before build. Build review can catch unexpected boundary issues that require a second pass before acceptance. The cost of iteration on security-sensitive parsing is real; paying it early is cheaper than shipping divergent scanners.

Acceptance criteria should state invariants—non-weakening detection, no secret leakage in diagnostics, single ownership—not only happy-path behaviour. Static ownership tests are valuable whenever a canonical shared module is introduced: they make "one owner" an enforceable property rather than a README claim. Bounded residual risks should be documented explicitly rather than eliminated by over-promising completeness.

How to apply it

Before changing similar security behaviour, answer these questions:

  • Where else is this policy implemented, and who owns the canonical version?
  • Can the current matcher distinguish symbolic names from credential values with concrete examples?
  • Will the change leave any shadow path that can diverge later?
  • Do diagnostics name the source and reason without exposing matched secrets?
  • Which refusal codes must remain stable for callers?
  • What static and behavioural tests prove single ownership and non-weakening detection?

For similar future items: look first for duplicated implementations; create one owner rather than synchronising copies; define behavioural boundaries with edge cases before implementation; include regression coverage for previous false positives and true positives.

The successful outcome in this case came from replacing duplicated security policy with a single canonical implementation—not from relaxing one scanner to silence a false positive.