Skip to content

fix(yara): require a browser cookie-store path in info_stealer - #752

Open
elliottwaves-20 wants to merge 3 commits into
NVIDIA:mainfrom
elliottwaves-20:fix/751-info-stealer-cookie-path
Open

elliottwaves-20 wants to merge 3 commits into
NVIDIA:mainfrom
elliottwaves-20:fix/751-info-stealer-cookie-path

Conversation

@elliottwaves-20

@elliottwaves-20 elliottwaves-20 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #751.

The built-in info_stealer rule matched $browser_cookies = /Cookies.*(chrome|firefox|edge|opera)/ nocase across a whole line. Because the condition is any of them, documentation that names cookies and, later on the same line, a browser or "Edge Functions" raised a HIGH YR1 finding. The supabase skill description in #751 is one example.

This change keeps the rule's intent and requires code context. It matches one of these:

  • a browser profile path that ends in the cookie store;
  • the store name followed on the line by a browser name in code: part of an identifier (chrome_profile, opera.profile), a path segment (/chromium/, google-chrome/), a quoted string ("edge"), or a glob ('*chrome*').

This way, code that names the store first is still caught. The change also adds the Opera (Opera Stable / Opera GX Stable) and Firefox (cookies.sqlite) store forms. All other strings of the rule are unchanged.

$browser_cookies  = /(Chrome|Chromium|Edge|Brave-Browser)\w*[\\\/'\x22, ]{1,5}(User Data[^\n]{0,80}|(Default|Profile \d+)[\\\/'\x22, ]{1,5}(Network[\\\/'\x22, ]{1,5})?)Cookies/ nocase
$cookies_browser  = /Cookies[^\n]{0,80}([^a-z](chrome|chromium|edge|brave|opera|firefox)(_|\.\w|[(*])|[\\\/\-.'\x22](chrome|chromium|edge|brave|opera|firefox)[\\\/]|['\x22](chrome|chromium|edge|brave|opera|firefox)['\x22])/ nocase
$opera_cookies    = /Opera (Stable|GX Stable)[^\n]{0,80}Cookies/ nocase
$firefox_cookies  = /cookies\.sqlite/ nocase

The previous pattern also needed Cookies before the browser name. The usual path forms put the browser first and Cookies last, so they did not match unless something else followed on the line. The new strings cover those forms.

A quote counts only when it encloses the browser name. A slash or backslash after the browser name counts only after a path character or a quote. So prose such as blocked by Chrome's tracking protection or Cookies: Chrome/Edge supported does not match.

Known trade-offs

  • Some store-first forms that the old pattern matched no longer match. These are a browser name followed by ) or ] (get_cookies(chrome)), a browser name at the end of the line, and a browser name more than 80 characters after Cookies. ) is left out on purpose, because (Chrome) is a common parenthetical in prose. A real stealer usually also names the store path, which $browser_cookies catches.
  • cookies.sqlite is a bare token, like wallet.dat and ntds.dit in this rule. A document that names the Firefox cookie store gets HIGH YR1.

Tests

tests/nodes/analyzers/test_static_yara.py, following the #489 pattern:

  • test_info_stealer_ignores_cookie_prose: the False positive: info_stealer $browser_cookies matches prose that mentions cookies and Edge on one line #751 description and eight short documentation sentences, including two possessives and a browser list with a slash.
  • test_info_stealer_matches_browser_cookie_store_paths: Chrome/Edge on Windows, Brave on Linux, Chrome on macOS, Opera, Firefox, and the spaced pathlib form. The fixtures are base64-encoded, as for the webshell families.
  • test_info_stealer_matches_code_naming_the_store_first: nine code forms that name the store first, then the browser.
  • test_info_stealer_matches_cookie_store_path_next_to_prose: a store path after benign prose in one file.

Validation

  • pytest tests/nodes/analyzers/test_static_yara.py: 379 passed.
  • Against the rule on main, the info_stealer tests give 18 failed, 8 passed. The 8 that pass are seven store-first code forms and the mixed case. The other two code forms (os.path.join(CHROME_DIR, "Default", "Cookies") and a "chromium/" path segment) are newly caught.
  • Over 14,324 local skill and plugin files, the final rule matches no file outside this rule's own test fixtures. The rule on main matched requests/cookies.py.
  • Full suite on Windows: the same failures as unchanged main on this machine, none in YARA.
  • ruff check and ruff format --check on the changed test file: clean.

🤖 Generated with Claude Code

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SkillSpector Review]

@elliottwaves-20 thank you for tracing the #751 false positive to the unbounded $browser_cookies string, and for testing benign prose, the real cookie-store paths and the store-first code forms.

Value and readiness

This fixes a real HIGH YR1 false positive that blocks documentation-only skills. It also closes the gap where the old pattern missed the usual browser-first store paths. The approach is right, but the PR isn't ready yet, for two reasons. The branch now conflicts with main. And the new $cookies_browser string still fires on a common kind of prose, which is the same false-positive class this PR fixes.

Material findings

  1. Blocker: src/skillspector/yara_rules/malware.yar.b64 conflicts with main (GitHub reports CONFLICTING). #608 changed the ransomware_behavior strings above info_stealer, so every base64 line after that point differs and git can't merge the two encodings. Please rebase, apply the info_stealer change to main's decoded rules, and re-encode. The test file merges cleanly.

  2. Blocker: the trailing class of $cookies_browser, [\\\/'\x22(], accepts an apostrophe, so an English possessive counts as code context. Both of these still match, so a cookie or auth doc still gets HIGH YR1 and a blocking verdict, as in #751:

    • Third-party cookies are blocked by Chrome's tracking protection.
    • Session cookies are dropped by Firefox's Enhanced Tracking Protection.

    One fix that keeps "edge", 'chrome' and every current fixture: count a quote only when it encloses the browser name, as in ['\x22](chrome|...|firefox)['\x22]. Keep [^a-z](...)(_|\.\w|[\\\/(]) for identifiers and paths. Please add both sentences to _COOKIE_PROSE (tests/nodes/analyzers/test_static_yara.py:633-641).

  3. Optional: $browser_cookies allows 1-4 separator characters, so the spaced pathlib form Path.home() / "AppData" / "Local" / "Google" / "Chrome" / "User Data" / "Default" / "Network" / "Cookies" matches neither new string. Here " / " is five characters, and Cookies comes last. main misses this too, so it isn't a regression. It is the common Python spelling, though, so {1,5} and a fixture would make the "browser first" coverage hold.

  4. Optional: the PR body says nothing main caught is lost. That is true for the six tested forms only. Store-first code where the browser is followed by ) or ], or sits more than 80 characters after Cookies, matched before and no longer does. Examples are get_cookies(chrome) and find ~ -name Cookies -path '*chrome*'. That may be a fair price for precision, but please say so in the body, or add ) to the follow set and recheck the prose corpus.

PIC tradeoffs

$firefox_cookies (cookies\.sqlite) is a bare-token string, like wallet.dat and ntds.dit. Any document that names Firefox's cookie store will now get HIGH YR1, for example a yt-dlp or browser-automation skill README. That is consistent with this rule's style, but the PIC should confirm it.

Verification and gaps

  • I decoded the merge-base, head and current-main copies of malware.yar.b64 with base64 -d and diffed them.
    • The PR changes only info_stealer, replacing one string with four.
    • main differs only in the ransomware strings. git merge-tree confirms the conflict is limited to the .b64 file.
  • I decoded the 12 base64 test fixtures. I traced them, the six prose lines and the examples above through the four regexes by hand.
  • Per instructions, I didn't run the rule, YARA or the tests. All 6 CI checks passed at 345d34a.

"Cookies set by the Edge middleware are httpOnly.\n",
"Forward cookies to Chrome headless through the Playwright context.\n",
"Clear cookies in Chrome.\n",
"Cookies are shared with Edge, Chrome and Firefox.\n",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker (fix together with the rule): $cookies_browser still matches English possessives because ' is in its trailing class [\\\/'\x22(]. For example, Third-party cookies are blocked by Chrome's tracking protection. and Session cookies are dropped by Firefox's Enhanced Tracking Protection. both still get HIGH YR1. Please add both sentences to this list, and count a quote only when it encloses the browser name, as in ['\x22](chrome|...)['\x22]. Keep (_|\.\w|[\\\/(]) for identifiers and paths.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in db2c857: a quote now counts only when it encloses the browser name, and both sentences are in _COOKIE_PROSE (plus Cookies: Chrome/Edge supported, which matched through the slash). Details in the PR comment above.

elliottwaves-20 and others added 3 commits October 13, 2026 11:45
$browser_cookies matched /Cookies.*(chrome|firefox|edge|opera)/ across a
whole line, so documentation naming cookies and, later on the line, a
browser or Edge Functions raised a HIGH info_stealer finding. Require a
browser profile path that ends in the cookie store instead, and add the
Opera and Firefox store forms. Every other string of the rule is unchanged.

Fixes NVIDIA#751

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: elliottwaves-20 <pail1217@web.de>
The first change only matched the browser-before-store order, so code such
as sqlite3.connect(chrome_profile + "/Cookies") that the previous pattern
caught no longer matched. Add $cookies_browser for the store-first order
with a browser name that is part of an identifier, path or string, and let
$browser_cookies accept an identifier suffix (CHROME_DIR). Regression
tests cover six such code forms and two more prose sentences.

Refs NVIDIA#751

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: elliottwaves-20 <pail1217@web.de>
$cookies_browser accepted an apostrophe after the browser name, so English
possessives such as "blocked by Chrome's tracking protection" still gave
HIGH YR1. A quote now counts only when it encloses the browser name
("edge", 'chrome'). A slash or backslash after the browser name counts
only when a path character or quote precedes it (/chromium/,
google-chrome/, "chromium/), so a browser list such as "Chrome/Edge"
in prose no longer matches. Identifiers keep the (_|\.\w|[(]) follow
set, which now also accepts a glob star (-path '*chrome*').

$browser_cookies allows up to five separator characters, so the spaced
pathlib form Path.home() / "Chrome" / "User Data" / ... / "Cookies"
matches.

Refs NVIDIA#751

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: elliottwaves-20 <pail1217@web.de>
@elliottwaves-20
elliottwaves-20 force-pushed the fix/751-info-stealer-cookie-path branch from 345d34a to db2c857 Compare October 13, 2026 10:10
@elliottwaves-20

Copy link
Copy Markdown
Contributor Author

@rng1995 I addressed all four points. The branch is rebased onto main (b07f181).

  1. Conflict. I decoded main's malware.yar.b64, applied only the info_stealer strings and re-encoded it in the same format (base64.encodebytes, 76 columns, LF). Decoded against main, the diff touches only the info_stealer strings: one line removed, four added. The ransomware changes from fix(yara): avoid ransomware findings for Bitcoin payment prose #608 are untouched.
  2. Possessives. A quote now counts only when it encloses the browser name: ['\x22](chrome|…|firefox)['\x22]. I applied the same idea to the slash: / or \ after the browser name counts only when a path character or a quote precedes it (/chromium/, google-chrome/, "chromium/). Without that, Cookies: Chrome/Edge supported in prose still matched, on main too. Both of your sentences and that one are now in _COOKIE_PROSE. Two code fixtures make sure quoted and dashed path segments still match.
  3. Spaced pathlib. $browser_cookies now allows {1,5} separators, with your Path.home() / … / "Cookies" example as a fixture.
  4. Store-first forms lost against main. I added the glob star to the follow set, so find ~ -name Cookies -path '*chrome*' matches again (fixture added). get_cookies(chrome), a browser name followed by ) or ], and a browser name more than 80 characters after Cookies still do not match. I left ) out because (Chrome) is a common parenthetical in prose. I updated the PR body to state this.

cookies.sqlite (PIC): I kept it as a bare token, in line with wallet.dat and ntds.dit. As you say, a document that names the Firefox cookie store gets HIGH YR1. Over 14,324 local skill and plugin files, the final rule matches no file outside this rule's own test fixtures. The rule on main matched requests/cookies.py.

Verification (Windows):

  • tests/nodes/analyzers/test_static_yara.py: 379 passed. Against main's rule, the info_stealer tests give 18 failed, 8 passed. The 8 that pass are seven store-first code forms and the mixed prose case, so main still matches those forms.
  • Full suite: the same failures as unchanged main on this machine, none in YARA. One timing test failed only under parallel load and passes alone.
  • ruff check and ruff format --check on the test file: clean.

🤖 Generated with Claude Code

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.

False positive: info_stealer $browser_cookies matches prose that mentions cookies and Edge on one line

2 participants