Library
Detection rules
Rules published with the articles. Each card shows what has and has not been tested; test a rule on your own data before relying on it.
7 of 7 rules
From PolinRider: A Supply-Chain Loader in 40,136 GitHub Repositories and How the GitHub Investigation Was Verified
VS Code task that runs node on a disguised file when the folder opens· yaraDownload Matches .vscode/tasks.json files with the PolinRider trigger: a folderOpen task whose command runs node on a file with a font, image or made-up extension (.woff2, .ttf, .orc, .lkf and others). Proof additionally requires the target to be a non-empty JavaScript file.
- Status
- experimental
- Family
- PolinRider
- Platforms
- none recorded
- Engines tested
- YARA 4.5.0
- Known benign cases
- none recorded
- Testing
- Rewritten on 2 November: the first version matched none of 199 proven tasks, because it expected node and the target as separate JSON strings and knew only font extensions. The proven tasks run node inside one shell command string, and many target made-up extensions such as .orc. This version looks for node followed by a path with a font, image or made-up extension in any folderOpen task. It matched all 199 files it was written against, all 300 held-out files, and none of 88 benign tasks.json files. The proof pass chose the proven files with a similar check (a folderOpen task running node on a non-JavaScript file), so these runs show that the rule finds the proven set; they do not show that it finds variants without those markers.
Check results
Date Engine Corpus Result 2026-11-02 YARA 4.5.0 Proven PolinRider .vscode/tasks.json files the rule was written against (199) 199 of 199 matched 2026-11-02 YARA 4.5.0 Proven tasks.json files not used to write the rule, random sample (300) 300 of 300 matched 2026-11-02 YARA 4.5.0 The other proven sets of this investigation (1,194 files) 0 matches 2026-11-02 YARA 4.5.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (56,864 files, 88 of them tasks.json) 0 hits 2026-11-02 YARA 4.5.0 Benign files in the analysis VM: npm tools (19,474), Python standard library and packages (29,360), git sample hooks (14) 0 hits rule CMPC_PolinRider_VSCode_Task_Autorun { meta: author = "Cristian Chiriac / CMPC Research" description = "PolinRider trigger: VS Code folderOpen task that runs node on a file with a font, image or made-up extension" status = "experimental" date = "2026-11-02" scope = "Run on .vscode/tasks.json; confirm the target file is JavaScript before acting" targets = "woff2 woff ttf otf eot orc lkf lfk llf glb fml svg png jpeg sol: every extension in the proven tasks" strings: $open = "folderOpen" ascii $node = /\bnode(\.exe)?\s+(\\?["'])?(\.{0,2}[\/\\]{1,2})?[\w.\-\/\\\[\]()@]+\.(woff2?|ttf|otf|eot|orc|lkf|lfk|llf|glb|fml|sol|svg|png|jpe?g|gif|ico|bmp|webp)\b/ ascii condition: filesize < 200KB and $open and $node }From PolinRider: A Supply-Chain Loader in 40,136 GitHub Repositories and How the GitHub Investigation Was Verified
Obfuscated loader appended to a build configuration file· yaraDownload Matches files that carry the PolinRider loader on one long line: the global['_V'] or global['!'] marker, or an _$_xxxx obfuscator name, followed by at least 300 more characters on the same line. In build configurations the line is usually pushed off-screen by spaces or tabs; the rule does not depend on that padding.
- Status
- experimental
- Family
- PolinRider
- Platforms
- none recorded
- Engines tested
- YARA 4.5.0
- Known benign cases
- none recorded
- Testing
- Rewritten on 2 November: the first version found 8 of 200, because it required files over 20 KB and a 400-character run of spaces, and it hit 13 benign files. This version drops both conditions and requires the marker to be followed by at least 300 characters on the same line. It matched all 200 files it was written against, all 297 held-out files, and no benign file, 1,685 of them configuration files. The proof pass chose the proven files with the same markers plus a long line or a long run of spaces, so these runs show that the rule finds the proven set; they do not show that it finds variants without those markers.
Check results
Date Engine Corpus Result 2026-11-02 YARA 4.5.0 Proven PolinRider files carrying the appended loader, mostly build configurations (200; the files the rule was written against) 200 of 200 matched 2026-11-02 YARA 4.5.0 Proven files of the same set not used to write the rule, random sample (297; 3 no longer available) 297 of 297 matched 2026-11-02 YARA 4.5.0 The other proven sets of this investigation (1,196 files) 0 matches 2026-11-02 YARA 4.5.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (56,864 files, 1,673 of them *.config.* files) 0 hits 2026-11-02 YARA 4.5.0 Benign files in the analysis VM: npm tools (19,474), Python standard library and packages (29,360), git sample hooks (14) 0 hits rule CMPC_PolinRider_BuildConfig_Appended_Loader { meta: author = "Cristian Chiriac / CMPC Research" description = "PolinRider loader appended to a file: global['_V'] / global['!'] or an _$_xxxx name, then the rest of the loader on the same line" status = "experimental" date = "2026-11-02" scope = "Run on *.config.(js|ts|mjs|cjs), postcss/tailwind/vite/next/eslint configurations and font-named files that read as text" strings: $g = /global\[\s*['"](_V|!)['"]\s*\][^\n\r]{300}/ ascii $s = /_\$_[0-9a-f]{4}[^\n\r]{300}/ ascii condition: filesize < 5MB and any of them }From How the GitHub Investigation Was Verified
atob on an environment variable, evaluated with eval or Function· yaraDownload Matches server or build files that decode AUTH_API_KEY (or a sibling variable) from the environment and evaluate the result. The decoded value in .env is a URL; the article lists the hosts seen.
- Status
- experimental
- Family
- AUTH_API_KEY loader
- Platforms
- none recorded
- Engines tested
- YARA 4.5.0
- Known benign cases
- none recorded
- Testing
- Matched every proven file in both samples (200 and 172), no other proven set and no benign file. The proof pass chose the proven files with the same strings (atob on process.env plus eval or Function), so these runs show that the rule finds the proven set; they do not show that it finds variants without those markers.
Check results
Date Engine Corpus Result 2026-11-02 YARA 4.5.0 Proven AUTH_API_KEY loader files, random sample (200) 200 of 200 matched 2026-11-02 YARA 4.5.0 Proven AUTH_API_KEY loader files not in that sample (172) 172 of 172 matched 2026-11-02 YARA 4.5.0 The other proven sets of this investigation (1,321 files) 0 matches 2026-11-02 YARA 4.5.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (56,864 files) 0 hits 2026-11-02 YARA 4.5.0 Benign files in the analysis VM: npm tools (19,474), Python standard library and packages (29,360), git sample hooks (14) 0 hits rule CMPC_AuthApiKey_Env_Eval_Loader { meta: author = "Cristian Chiriac / CMPC Research" description = "AUTH_API_KEY loader: atob(process.env.<VAR>) passed to eval or Function" status = "experimental" date = "2026-11-01" scope = "Run on *.ts, *.js, *.mjs, *.cjs in application and build-config paths" strings: $decode = /atob\(\s*process\.env\.[A-Z_]*API_KEY[A-Z_]*\s*\)/ ascii $eval = /\beval\s*\(/ ascii $fn = /\bnew\s+Function\s*\(|\bFunction\s*\(/ ascii condition: filesize < 512KB and $decode and ($eval or $fn) }From How the GitHub Investigation Was Verified
Semgrep rule: code fetched from an environment-variable URL and evaluated (JavaScript/TypeScript)· semgrepDownload Taint rule: follows the value of atob(process.env.<VAR>) through the request that uses it and the response it returns, and reports when that data reaches eval, new Function or Function. For CI pipelines that scan source before build.
- Status
- experimental
- Family
- AUTH_API_KEY loader
- Platforms
- none recorded
- Engines tested
- Semgrep 1.183.0
- Known benign cases
- none recorded
- Testing
- Rewritten on 2 November: the first version matched none of 200, because the decoded value is a URL: it is fetched, and eval runs the response, not the decoded value. This version is a taint rule that follows the value through the request and its response to eval, new Function or Function. It matched 199 of the 200 files it was written against (the miss has the loader commented out, so not flagging it is right) and 166 of 172 held-out files (2 misses are Markdown files, 3 did not parse fully, 1 parsed file with the usual structure was missed for a reason not identified). No benign hits, but only 17,474 benign JavaScript and TypeScript files were scanned: Semgrep's default ignore list skips node_modules. The proof pass chose the proven files with atob on process.env plus eval or Function, so these runs show that the rule finds the proven set; they do not show that it finds variants without those markers.
Check results
Date Engine Corpus Result 2026-11-02 Semgrep 1.183.0 Proven AUTH_API_KEY loader files the rule was written against (200; 3 parsed only partially) 199 of 200 matched; the miss has the loader commented out 2026-11-02 Semgrep 1.183.0 Proven AUTH_API_KEY loader files not used to write the rule (172) 166 of 172 matched: 2 are Markdown files Semgrep does not scan, 3 did not parse fully, 1 missed for a reason not identified 2026-11-02 Semgrep 1.183.0 The other proven sets of this investigation (1,321 files; 560 of them JavaScript or TypeScript that Semgrep scanned) 0 matches 2026-11-02 Semgrep 1.183.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (Semgrep scanned 17,088 JavaScript/TypeScript files; 8 timed out) 0 hits 2026-11-02 Semgrep 1.183.0 Benign files in the analysis VM (Semgrep scanned 386: its default ignore list skips node_modules; 3 timed out or failed to parse) 0 hits rules: - id: cmpc-auth-api-key-env-fetch-eval message: Code fetched from a URL decoded out of an environment variable reaches eval or Function (AUTH_API_KEY loader pattern) severity: ERROR languages: [javascript, typescript] metadata: author: Cristian Chiriac / CMPC Research status: experimental date: 2026-11-02 mode: taint pattern-sources: - pattern: atob(process.env.$VAR) pattern-sinks: - pattern: eval(...) - pattern: new Function(...) - pattern: Function(...)From How the GitHub Investigation Was Verified
Repository-shipped post-checkout hook that pipes a download into a shell· yaraDownload Matches .githooks/post-checkout and .github/githooks/post-checkout files that fetch a script with curl or wget and run it. Git does not run hooks from these paths by itself; the repositories set core.hooksPath or ship a .git directory in an archive.
- Status
- experimental
- Family
- Contagious Interview
- Platforms
- none recorded
- Engines tested
- YARA 4.5.0
- Known benign cases
- none recorded
- Testing
- Matched every available proven hook and no benign file, including git's own sample hooks. Six files is a small set. YARA warns that $bg may slow scanning.
Check results
Date Engine Corpus Result 2026-11-02 YARA 4.5.0 Proven repository-shipped post-checkout hooks (6 of the 7 sampled; 1 no longer available) 6 of 6 matched 2026-11-02 YARA 4.5.0 The other proven sets of this investigation (917 files) 0 matches 2026-11-02 YARA 4.5.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (56,864 files) 0 hits (includes the clones' .git/hooks samples) 2026-11-02 YARA 4.5.0 Benign files in the analysis VM: npm tools (19,474), Python standard library and packages (29,360), git sample hooks (14) 0 hits rule CMPC_Git_Hook_PostCheckout_Pipe_To_Shell { meta: author = "Cristian Chiriac / CMPC Research" description = "Committed post-checkout hook downloading and executing a remote script" status = "experimental" date = "2026-11-01" scope = "Run on files named post-checkout under .githooks/ or .github/githooks/" strings: $fetch = /\b(curl|wget)\b[^\n]{0,200}(https?:\/\/|\/settings\/)/ ascii $pipe = /\|\s*(sh|bash|zsh)\b/ ascii $flag = "flag=" ascii $bg = /(\bnohup\b|&\s*$|setsid)/ ascii condition: filesize < 8KB and $fetch and $pipe and ($flag or $bg) }From How the GitHub Investigation Was Verified
Published Discord token stealer reading local storage and posting to a webhook· yaraDownload Matches source files that hold a complete Discord webhook URL and read the Local Storage leveldb folder of Discord or a browser, where Discord keeps its login token. These are published tools; the owner is not necessarily a victim or an operator.
- Status
- experimental
- Family
- Discord token stealer
- Platforms
- none recorded
- Engines tested
- YARA 4.5.0
- Known benign cases
- none recorded
- Testing
- Rewritten on 2 November: the first version found 2 of 196, because it required a literal token-shaped string; most of these tools hold the token pattern as regular-expression source instead. This version requires a complete webhook URL and a Local Storage or leveldb read. It matched all 196 files it was written against, the one remaining proven file, and no benign file. With one held-out file, this is close to a test on the development set only. The proof pass chose the proven files with the same strings (a webhook URL plus Local Storage or leveldb), so these runs show that the rule finds the proven set; they do not show that it finds variants without those markers.
Check results
Date Engine Corpus Result 2026-11-02 YARA 4.5.0 Proven Discord token stealer files the rule was written against (196) 196 of 196 matched 2026-11-02 YARA 4.5.0 The one other proven Discord token stealer file 1 of 1 matched 2026-11-02 YARA 4.5.0 The other proven sets of this investigation (1,496 files) 0 matches 2026-11-02 YARA 4.5.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (56,864 files) 0 hits 2026-11-02 YARA 4.5.0 Benign files in the analysis VM: npm tools (19,474), Python standard library and packages (29,360), git sample hooks (14) 0 hits rule CMPC_Discord_Token_Stealer_LevelDB_Webhook { meta: author = "Cristian Chiriac / CMPC Research" description = "Discord token stealer: hard-coded webhook URL plus a read of Discord or browser Local Storage (leveldb)" status = "experimental" date = "2026-11-02" scope = "Run on source files (py, js, ps1, cs, java, rs); expect hits in research corpora too" strings: $hook = /discord(app)?\.com\/api\/webhooks\/\d{15,}\/[\w\-]{30,}/ ascii $ls1 = "Local Storage" ascii $ls2 = "leveldb" ascii nocase condition: filesize < 5MB and $hook and any of ($ls*) }From How the GitHub Investigation Was Verified
Python file that decrypts an embedded Fernet stage and executes it· yaraDownload Matches Python sources that call exec on Fernet(key).decrypt(ciphertext) with the key in the same file. One proven repository; the same shape is common in sample collections, so check the repository is not a research corpus.
- Status
- experimental
- Family
- Python Fernet loader
- Platforms
- none recorded
- Engines tested
- YARA 4.5.0
- Known benign cases
- none recorded
- Testing
- Matched the only proven file and no benign file. With one proven file, recall cannot be estimated.
Check results
Date Engine Corpus Result 2026-11-02 YARA 4.5.0 The one proven Python Fernet loader file 1 of 1 matched 2026-11-02 YARA 4.5.0 The other proven sets of this investigation (922 files) 0 matches 2026-11-02 YARA 4.5.0 Benign source code: shallow clones of next.js, microsoft/vscode and vscode-extension-samples (56,864 files) 0 hits 2026-11-02 YARA 4.5.0 Benign files in the analysis VM: npm tools (19,474), Python standard library and packages (29,360), git sample hooks (14) 0 hits, including 29,360 Python library files rule CMPC_Python_Fernet_Exec_Stage { meta: author = "Cristian Chiriac / CMPC Research" description = "exec(Fernet(<literal key>).decrypt(b'<ciphertext>')) in a Python source file" status = "experimental" date = "2026-11-01" scope = "Run on *.py; exclude test, fixture, sample and dataset paths" strings: $import = "from cryptography.fernet import Fernet" ascii $exec = /exec\s*\(\s*Fernet\s*\(\s*b?['\"][A-Za-z0-9_\-=]{40,48}['\"]\s*\)\s*\.decrypt\s*\(\s*b?['\"][A-Za-z0-9_\-=]{200,}/ ascii condition: filesize < 4MB and $import and $exec }