Repository navigation
fix(yara): require a browser cookie-store path in info_stealer - #752
elliottwaves-20 wants to merge 3 commits into
Conversation
rng1995
left a comment
There was a problem hiding this comment.
[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
-
Blocker:
src/skillspector/yara_rules/malware.yar.b64conflicts withmain(GitHub reportsCONFLICTING). #608 changed theransomware_behaviorstrings aboveinfo_stealer, so every base64 line after that point differs and git can't merge the two encodings. Please rebase, apply theinfo_stealerchange tomain's decoded rules, and re-encode. The test file merges cleanly. -
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 HIGHYR1and 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). -
Optional:
$browser_cookiesallows 1-4 separator characters, so the spaced pathlib formPath.home() / "AppData" / "Local" / "Google" / "Chrome" / "User Data" / "Default" / "Network" / "Cookies"matches neither new string. Here" / "is five characters, andCookiescomes last.mainmisses 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. -
Optional: the PR body says nothing
maincaught 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 afterCookies, matched before and no longer does. Examples areget_cookies(chrome)andfind ~ -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-
maincopies ofmalware.yar.b64withbase64 -dand diffed them.- The PR changes only
info_stealer, replacing one string with four. maindiffers only in the ransomware strings.git merge-treeconfirms the conflict is limited to the.b64file.
- The PR changes only
- 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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
$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>
345d34a to
db2c857
Compare
|
@rng1995 I addressed all four points. The branch is rebased onto main (b07f181).
Verification (Windows):
🤖 Generated with Claude Code |
Fixes #751.
The built-in
info_stealerrule matched$browser_cookies = /Cookies.*(chrome|firefox|edge|opera)/ nocaseacross a whole line. Because the condition isany of them, documentation that names cookies and, later on the same line, a browser or "Edge Functions" raised a HIGHYR1finding. 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:
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.The previous pattern also needed
Cookiesbefore the browser name. The usual path forms put the browser first andCookieslast, 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 protectionorCookies: Chrome/Edge supporteddoes not match.Known trade-offs
)or](get_cookies(chrome)), a browser name at the end of the line, and a browser name more than 80 characters afterCookies.)is left out on purpose, because(Chrome)is a common parenthetical in prose. A real stealer usually also names the store path, which$browser_cookiescatches.cookies.sqliteis a bare token, likewallet.datandntds.ditin this rule. A document that names the Firefox cookie store gets HIGHYR1.Tests
tests/nodes/analyzers/test_static_yara.py, following the #489 pattern:test_info_stealer_ignores_cookie_prose: the False positive: info_stealer$browser_cookiesmatches 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.main, theinfo_stealertests 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.mainmatchedrequests/cookies.py.mainon this machine, none in YARA.ruff checkandruff format --checkon the changed test file: clean.🤖 Generated with Claude Code