Skip to content

security(ci): scan tracked files for known repository-malware markers (#480) - #615

Open
timo126 wants to merge 2 commits into
Sub-Rosa-Issue:mainfrom
timo126:security/480-malware-marker-scan
Open

timo126 wants to merge 2 commits into
Sub-Rosa-Issue:mainfrom
timo126:security/480-malware-marker-scan

Conversation

@timo126

@timo126 timo126 commented Oct 27, 2026

Copy link
Copy Markdown

Summary

Closes #480.

Known repository malware families keep stable marker strings even when they
rename files, but nothing in this repo looked for them. This PR adds a tiny,
dependency-free, text-only marker scanner over tracked source and
configuration files, runs it in CI on every push to main and every pull
request, and documents how to maintain the marker list.

No production code, contract, or dependency is touched.

Acceptance criteria

Criterion Where
Detect at least rmcej%otb%, Cot%3t=shtP, MicrosoftSystem64, .pcl-data, .pcl-state MARKERS in scripts/check-malware-markers.mjs, asserted by the marker list test group
Exclude .git, dependencies, build output, and the scanner test fixtures from production matches SKIP_DIRS + SKIP_PATHS, proven by tests and by the repository scanning clean
Include positive and negative tests scripts/check-malware-markers.test.mjs (19 tests) + scripts/fixtures/malware-markers/
CI reports the matched marker name and path, never the complete line path/to/file.ext:LINE: <marker name> on stdout, asserted by a test
Document how to update the marker list docs/MALWARE_MARKERS.md → Updating the marker list, plus a section in docs/CI.md
The scanner must never evaluate matched content Fixed-string includes only; tests assert no evaluation and no dynamic-evaluation primitives in the source

How it works

scripts/check-malware-markers.mjs walks the working tree, reads each file as
UTF-8, and does a fixed-string includes search per line against the MARKERS
array. It deliberately does not use regular expressions, so no untrusted
content is ever parsed as a pattern, and it never imports, evals, or executes
anything it reads. Scanning every file type (not an extension allowlist) means a
marker cannot hide in a file type the scanner has never heard of.

Report format — marker name, path, line number, and nothing else:

path/to/file.ext:LINE: <marker name>

Matches go to stdout, the summary to the structured log on stderr. The matched
line is never returned by the API or printed, so a marker cannot be read back
out of a CI log.

Exclusions

Excluded Reason
.git VCS metadata
node_modules, .pnpm-store Dependencies
dist, build, out, .next, .turbo, coverage, .nyc_output, target, artifacts, tmp-bindings, test_snapshots Build output
scripts/check-malware-markers.mjs, scripts/check-malware-markers.test.mjs, scripts/fixtures/malware-markers/ The scanner, its tests and fixtures necessarily contain the markers

The marker strings are intentionally not repeated in docs/CI.md or
docs/MALWARE_MARKERS.md, because those files are themselves scanned — the docs
explain where the list lives instead.

CI

.github/workflows/malware-markers.yml runs on push to main and on pull
requests: node --test scripts/check-malware-markers.test.mjs then
node scripts/check-malware-markers.mjs. There is no install step — the scanner
and the shared logger have no external dependencies — and permissions is
contents: read, matching the other read-only gates.

The job is named check, the same job name as the other lightweight gates
(Bindings check, Line lengths check), which places it inside the required
status-check context the branch ruleset already enforces.

Supply-chain review

  • No package scripts or dependencies changed. Changed: added two entries, markers:check and markers:test, which invoke node on first-party scripts. No dependency was added or removed and pnpm-lock.yaml is untouched.
  • No .vscode/, workflow, or other editor/config file changes. Changed: added .github/workflows/malware-markers.yml, a new read-only gate. No existing workflow, .vscode/ or editor config was modified.
  • No lockfile changes.
  • No binary or font asset changes.
  • I confirm no automatic task (such as preinstall/postinstall) was added.
  • I confirm no hidden executable payload (encoded blob, obfuscated command, or unexpected binary) was added.
  • Any changed executable or binary was reviewed by a maintainer. (None changed.)

Testing

All run locally on this branch:

$ node scripts/check-malware-markers.mjs
{"level":"info","component":"scripts.check-malware-markers","event":"pass",
 "message":"No known malware markers found in tracked source and configuration files."}
exit 0

$ node --test scripts/check-malware-markers.test.mjs
# tests 19
# pass 19
# fail 0

$ node scripts/check-links.js
OK  43 local link(s) checked, 0 allowlisted

$ node scripts/check-line-lengths.mjs   # package.json changed
All configuration files have lines ≤ 500 characters.

What the 19 tests prove:

  • every marker required by the issue is present in the list, and the list is unique and non-empty;
  • positive: each required marker is detected on the line where it appears, markers hidden inside configuration files are detected, and the positive fixture yields one hit per line;
  • negative: the lookalike fixture (one-character, case, spacing and missing-dot variants) produces zero matches;
  • .git, node_modules, dist, build, target, coverage, artifacts, .turbo and the scanner's own files are not reported, while an unrelated file in the same directory still is;
  • this repository scans clean end to end, with the fixtures present in the tree;
  • the CLI exits 1 on a match, prints path:line: <marker>, and never prints the surrounding sentinel text;
  • no evaluation: a fixture that would set a global if executed leaves it untouched, and the scanner source is asserted to contain no eval(, new Function(, vm.runIn, spawnSync/execSync, or dynamic import(.

Manual probe outside the test suite: a marker planted in probe/hit.js exited 1
and printed probe/hit.js:1: <marker>, while the same marker planted under
.git, node_modules, dist, build, scripts/fixtures/malware-markers/ and
the scanner source produced no match.

Docs

  • docs/MALWARE_MARKERS.md — guarantees, report format, exclusions, the step-by-step procedure for adding or removing a marker, test coverage, and the known limits (fixed-string only; split, encoded or compressed markers are out of scope).
  • docs/CI.md — new Malware marker scan section linking to it.

Closes #480

dedukpe and others added 2 commits October 27, 2026 16:36
…Sub-Rosa-Issue#480)

Known repository malware families keep stable marker strings even when they
rename files, but nothing in the repo looked for them. Add a dependency-free,
text-only scanner over tracked source and configuration files that reports the
marker name, path and line number and never evaluates what it reads, with
positive and negative tests, excluded fixtures, and a CI job that runs it on
every push and pull request.

Closes Sub-Rosa-Issue#480

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
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.

security(ci): scan tracked files for known repository-malware markers

1 participant