Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions src/skillspector/yara_rules/malware.yar.b64
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,18 @@ ICBjb25maWRlbmNlID0gIjAuNzUiDQogICAgICAgIHJlZmVyZW5jZSA9ICJodHRwczovL2dpdGh1
Yi5jb20vTmVvMjN4MC9zaWduYXR1cmUtYmFzZSINCiAgICBzdHJpbmdzOg0KICAgICAgICAkY2hy
b21lX2xvZ2luICAgICA9IC9DaHJvbWUuKkxvZ2luXHMqRGF0YS8gbm9jYXNlDQogICAgICAgICRm
aXJlZm94X2xvZ2lucyAgID0gL2xvZ2luc1wuanNvbi4qZmlyZWZveC8gbm9jYXNlDQogICAgICAg
ICRicm93c2VyX2Nvb2tpZXMgID0gL0Nvb2tpZXMuKihjaHJvbWV8ZmlyZWZveHxlZGdlfG9wZXJh
KS8gbm9jYXNlDQogICAgICAgICR3YWxsZXRfc3RlYWwgICAgID0gL3dhbGxldFwuZGF0LyBub2Nh
c2UNCiAgICAgICAgJG1pbWlrYXR6ICAgICAgICAgPSAibWltaWthdHoiIG5vY2FzZQ0KICAgICAg
ICAkbGF6YWduZSAgICAgICAgICA9ICJsYXphZ25lIiBub2Nhc2UNCiAgICAgICAgJGNyZWRfZHVt
cF9zYW0gICAgPSAvcmVnXHMrc2F2ZVxzKy4qXFxzYW0vIG5vY2FzZQ0KICAgICAgICAkbnRkc19k
dW1wICAgICAgICA9IC9udGRzXC5kaXQvIG5vY2FzZQ0KICAgIGNvbmRpdGlvbjoNCiAgICAgICAg
YW55IG9mIHRoZW0NCn0NCg==
ICRicm93c2VyX2Nvb2tpZXMgID0gLyhDaHJvbWV8Q2hyb21pdW18RWRnZXxCcmF2ZS1Ccm93c2Vy
KVx3KltcXFwvJ1x4MjIsIF17MSw1fShVc2VyIERhdGFbXlxuXXswLDgwfXwoRGVmYXVsdHxQcm9m
aWxlIFxkKylbXFxcLydceDIyLCBdezEsNX0oTmV0d29ya1tcXFwvJ1x4MjIsIF17MSw1fSk/KUNv
b2tpZXMvIG5vY2FzZQ0KICAgICAgICAkY29va2llc19icm93c2VyICA9IC9Db29raWVzW15cbl17
MCw4MH0oW15hLXpdKGNocm9tZXxjaHJvbWl1bXxlZGdlfGJyYXZlfG9wZXJhfGZpcmVmb3gpKF98
XC5cd3xbKCpdKXxbXFxcL1wtLidceDIyXShjaHJvbWV8Y2hyb21pdW18ZWRnZXxicmF2ZXxvcGVy
YXxmaXJlZm94KVtcXFwvXXxbJ1x4MjJdKGNocm9tZXxjaHJvbWl1bXxlZGdlfGJyYXZlfG9wZXJh
fGZpcmVmb3gpWydceDIyXSkvIG5vY2FzZQ0KICAgICAgICAkb3BlcmFfY29va2llcyAgICA9IC9P
cGVyYSAoU3RhYmxlfEdYIFN0YWJsZSlbXlxuXXswLDgwfUNvb2tpZXMvIG5vY2FzZQ0KICAgICAg
ICAkZmlyZWZveF9jb29raWVzICA9IC9jb29raWVzXC5zcWxpdGUvIG5vY2FzZQ0KICAgICAgICAk
d2FsbGV0X3N0ZWFsICAgICA9IC93YWxsZXRcLmRhdC8gbm9jYXNlDQogICAgICAgICRtaW1pa2F0
eiAgICAgICAgID0gIm1pbWlrYXR6IiBub2Nhc2UNCiAgICAgICAgJGxhemFnbmUgICAgICAgICAg
PSAibGF6YWduZSIgbm9jYXNlDQogICAgICAgICRjcmVkX2R1bXBfc2FtICAgID0gL3JlZ1xzK3Nh
dmVccysuKlxcc2FtLyBub2Nhc2UNCiAgICAgICAgJG50ZHNfZHVtcCAgICAgICAgPSAvbnRkc1wu
ZGl0LyBub2Nhc2UNCiAgICBjb25kaXRpb246DQogICAgICAgIGFueSBvZiB0aGVtDQp9DQo=
82 changes: 82 additions & 0 deletions tests/nodes/analyzers/test_static_yara.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,53 @@ def _single_line_perl_socket_shell() -> str:
).decode()


# Browser cookie-store paths an information stealer reads (base64 so the test
# source itself carries no stealer indicator).
_STEALER_COOKIE_PATHS = {
"chrome_windows": "b3MucGF0aC5qb2luKG9zLmVudmlyb25bJ0xPQ0FMQVBQREFUQSddLCAnR29vZ2xlJywgJ0Nocm9tZScsICdVc2VyIERhdGEnLCAnRGVmYXVsdCcsICdOZXR3b3JrJywgJ0Nvb2tpZXMnKQ==",
"edge_windows": "Y29weSAlTE9DQUxBUFBEQVRBJVxNaWNyb3NvZnRcRWRnZVxVc2VyIERhdGFcRGVmYXVsdFxOZXR3b3JrXENvb2tpZXMgb3V0LmRi",
"brave_linux": "fi8uY29uZmlnL0JyYXZlU29mdHdhcmUvQnJhdmUtQnJvd3Nlci9Vc2VyIERhdGEvRGVmYXVsdC9Db29raWVz",
"chrome_macos": "cGF0aCA9IGhvbWUgKyAiL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9Hb29nbGUvQ2hyb21lL0RlZmF1bHQvQ29va2llcyI=",
"opera_windows": "JHAgPSAiJGVudjpBUFBEQVRBXE9wZXJhIFNvZnR3YXJlXE9wZXJhIFN0YWJsZVxOZXR3b3JrXENvb2tpZXMi",
"firefox_profile": "c3JjID0gcHJvZmlsZV9kaXIgLyAnY29va2llcy5zcWxpdGUn",
"chrome_pathlib_spaced": "UGF0aC5ob21lKCkgLyAiQXBwRGF0YSIgLyAiTG9jYWwiIC8gIkdvb2dsZSIgLyAiQ2hyb21lIiAvICJVc2VyIERhdGEiIC8gIkRlZmF1bHQiIC8gIk5ldHdvcmsiIC8gIkNvb2tpZXMi",
}


def _stealer_cookie_path(name: str) -> str:
return base64.b64decode(_STEALER_COOKIE_PATHS[name]).decode()


# Code that names the store before the browser, as the previous pattern did.
_STEALER_COOKIE_CODE = {
"sqlite_connect": "Y29va2llc19kYiA9IHNxbGl0ZTMuY29ubmVjdChjaHJvbWVfcHJvZmlsZSArICIvQ29va2llcyIp",
"copy_to_output": "c2h1dGlsLmNvcHkocHJvZmlsZSAvICJDb29raWVzIiwgY2hyb21lX291dCk=",
"browser_constant": "ZGIgPSBvcy5wYXRoLmpvaW4oQ0hST01FX0RJUiwgIkRlZmF1bHQiLCAiQ29va2llcyIp",
"browser_argument": "c3RlYWwoIkNvb2tpZXMiLCAiZmlyZWZveCIp",
"keyword_argument": "Z3JhYl9jb29raWVzKGJyb3dzZXI9ImVkZ2UiKQ==",
"attribute_access": "Y29va2llcyA9IHJlYWQob3BlcmEucHJvZmlsZSk=",
"find_glob": "ZmluZCB+IC1uYW1lIENvb2tpZXMgLXBhdGggJypjaHJvbWUqJw==",
"quoted_path_segment": "c2h1dGlsLmNvcHkoaG9tZSArICIvQ29va2llcyIsICIvdG1wLyIgKyAiY2hyb21pdW0vIiArIG5hbWUp",
"dashed_path_segment": "c3JjID0gIkNvb2tpZXMiOyBkc3QgPSBwcm9maWxlX3Jvb3QgKyAiLy5jb25maWcvZ29vZ2xlLWNocm9tZS9vdXQi",
}


# Documentation that names the browser store and, later on the same line, a
# browser or product name (issue #751: a Supabase skill description).
_COOKIE_PROSE = [
"description: Auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, "
"getClaims, RLS); Supabase CLI or MCP server; debugging timeouts, Edge Function crashes\n",
"Set cookies for Edge Functions with the SSR helper.\n",
"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.

"Third-party cookies are blocked by Chrome's tracking protection.\n",
"Session cookies are dropped by Firefox's Enhanced Tracking Protection.\n",
"Cookies: Chrome/Edge supported, Safari planned.\n",
]


def _has_rule(findings: list, rule_name: str) -> bool:
"""Return True when a finding message references a specific YARA rule.

Expand Down Expand Up @@ -2234,6 +2281,41 @@ def test_known_webshell_rule_matches_family_markers(self, fixture, filename):
findings = _run_builtin(_webshell_fixture(fixture), filename)
assert _has_rule(findings, "php_webshell_known")

@pytest.mark.parametrize(
"content",
_COOKIE_PROSE,
ids=[
"supabase_description",
"edge_functions",
"edge_middleware",
"chrome_headless",
"clear_in_chrome",
"shared_with_browsers",
"chrome_possessive",
"firefox_possessive",
"browser_list_with_slash",
],
)
def test_info_stealer_ignores_cookie_prose(self, content):
findings = _run_builtin(content, "SKILL.md")
assert not _has_rule(findings, "info_stealer")

@pytest.mark.parametrize("name", sorted(_STEALER_COOKIE_PATHS))
def test_info_stealer_matches_browser_cookie_store_paths(self, name):
findings = _run_builtin(_stealer_cookie_path(name) + "\n", "collect.py")
assert _has_rule(findings, "info_stealer")

@pytest.mark.parametrize("name", sorted(_STEALER_COOKIE_CODE))
def test_info_stealer_matches_code_naming_the_store_first(self, name):
code = base64.b64decode(_STEALER_COOKIE_CODE[name]).decode()
findings = _run_builtin(code + "\n", "collect.py")
assert _has_rule(findings, "info_stealer")

def test_info_stealer_matches_cookie_store_path_next_to_prose(self):
content = _COOKIE_PROSE[1] + _stealer_cookie_path("chrome_windows") + "\n"
findings = _run_builtin(content, "collect.py")
assert _has_rule(findings, "info_stealer")


# ── Rule caching ──────────────────────────────────────────────────────

Expand Down
Loading