Skip to content

refactor: one source of truth for the detection rules - #6

Merged
fabrizzio-dotCMS merged 1 commit into
fix-sweep-silent-cleanfrom
rules/single-source
Aug 28, 2026
Merged

fabrizzio-dotCMS merged 1 commit into
fix-sweep-silent-cleanfrom
rules/single-source

Conversation

@fabrizzio-dotCMS

Copy link
Copy Markdown
Member

Stacked on #3 — review that one first; this diff is only the last commit (8436003).

Why

The detection rules lived in three places: polinrider_scan.sh, org_sweep.sh, and a third
hardcoded copy inside classify_config(). They had drifted. The sweep was still matching the
campaign tag as the literal A9-3727 long after the scanner generalised it, which left the
sweep blind to the wave-1 variant on every run it ever performed — and nobody noticed,
because a rule that has drifted still reports CLEAN.

rules.sh is now the only place a rule is written down. Both scripts source it and abort if
it is missing.

Not a flat list — three context sets

The two tools have different false-positive surfaces, and that difference was deliberate:

set contents scope
core rmcej%otb%, wuqktam… hard everywhere
repo-only 'Sec-V', helloipbot, AUTH_API_KEY, 9-3727-2 hard in a repo sweep
host-only atob(process.env.AUTH_API_KEY hard on a workstation

Flattening these would promote 'Sec-V' to a hard indicator on developer machines, where it
appears as quoted text in Claude Code transcripts — a recorded FP class that would produce
false COMPROMISED verdicts.

Surfaced while doing this

  • Neither tool carried the bare 9-3727-2 literal. Both relied on the marker pattern alone,
    which requires the global-assignment context to match. That literal is load-bearing; it is
    now in the repo set.
  • classify_config() held a third, inline copy of the signatures.
  • The comment claiming the payload line carries ~5,000 padding spaces was wrong. It is
    507, measured on all three obfuscated carriers. The conclusion the comment supported was
    right; the number was not. Anchor on the run length, never on a column.

Tests

test_rules_single_source.sh, 21 assertions: both sets non-empty, shared indicators present in
both, the deliberate split preserved in each direction, no script defining a rule inline, both
scripts sourcing rules.sh and aborting without it, and the marker matching every known build
plus a rotated one it has never seen.

All five suites pass. Verified against the four byte-exact carriers: all four detected, marker
fires on all three obfuscated builds including A9-3727-3.

Known gap — recorded, not solved

rules.sh is the single source for bash consumers. A non-bash consumer — the PR-check
service — cannot source it. That needs a canonical format both can read, decided once we know
what the service is written in. Do not close this gap by copying the strings; that
recreates exactly the drift this PR removes.

The rules lived in polinrider_scan.sh and org_sweep.sh separately, plus a third
hardcoded copy inside classify_config(). They drifted: the sweep matched the
campaign tag as the literal 'A9-3727' long after the scanner had generalised it,
which left the sweep blind to the wave-1 variant on every run it ever performed.
Nobody noticed, because a rule that has drifted still reports CLEAN.

rules.sh is now the only place a rule is written down. Both scripts source it and
abort if it is missing.

It is not a flat list. The two tools have different false-positive surfaces and
that difference was deliberate, so the file carries three sets:

  core        rmcej%otb%, wuqktam...        hard everywhere
  repo-only   'Sec-V', helloipbot,          hard in a repo sweep
              AUTH_API_KEY, 9-3727-2
  host-only   atob(process.env.AUTH_API_KEY hard on a workstation

Flattening these would have promoted 'Sec-V' to a hard indicator on developer
machines, where it appears as quoted text in Claude Code transcripts -- a
recorded FP class that would produce false COMPROMISED verdicts.

Three things surfaced while doing this:

- Neither tool carried the bare 9-3727-2 literal. Both relied on the marker
  pattern alone, which requires the global-assignment context to match. The
  handoff lists that literal as load-bearing. It is now in the repo set.
- classify_config() held a third copy of the signatures, inline.
- The comment claiming the payload line carries ~5,000 padding spaces was wrong.
  It is 507, measured on all three obfuscated carriers. The conclusion that
  comment supported was right; the number was not.

test_rules_single_source.sh, 21 assertions: both sets non-empty, the shared
indicators in both, the deliberate split preserved in each direction, no script
defining a rule inline, both scripts sourcing and aborting without rules.sh, and
the marker matching every known build plus a rotated one it has never seen.

All five suites pass. Verified against the four byte-exact carriers: all four
detected, marker fires on all three obfuscated builds including A9-3727-3.

Known gap, recorded rather than solved: rules.sh is the single source for BASH
consumers. A non-bash consumer -- the PR-check service -- cannot source it. That
needs a canonical format both can read, decided when we know what the service is
written in. Do not solve it by copying the strings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fabrizzio-dotCMS

Copy link
Copy Markdown
Member Author

Auto-closed as merged: its base branch fix-sweep-silent-clean was fast-forwarded to include this commit. The work lives on in #3, which is now the single review for the whole scanner/sweep correctness change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant