mirror of
https://github.com/usestrix/strix.git
synced 2026-11-09 13:31:41 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba5cab24c2 | ||
|
|
0b78002686 | ||
|
|
7c490203ab |
No files matched your search
@@ -136,8 +136,12 @@ fi
|
||||
- A finding with a call stack ⇒ `reachability=reachable_call_path`, put the
|
||||
call-path excerpt (entrypoint → vulnerable function) in
|
||||
`reachability_evidence`.
|
||||
- Listed as affecting a required module but with no reachable symbol ⇒ fall
|
||||
back to the import/symbol checks below (`imported` / `not_imported`).
|
||||
- Listed as affecting a required module but with no reachable symbol ⇒ the
|
||||
call graph found no path to the vulnerable function: `not_reached` when
|
||||
application code imports the module (cite the govulncheck result and the
|
||||
import), `not_imported` when nothing imports it. When govulncheck could not
|
||||
analyze the module (build errors, missing deps), fall back to the
|
||||
import/symbol checks below instead of claiming a level.
|
||||
|
||||
**All other ecosystems — import check, then symbol match:**
|
||||
|
||||
@@ -148,15 +152,25 @@ fi
|
||||
`PyYAML` → `yaml`). No hits ⇒ `not_imported`, with the search scope stated
|
||||
in `reachability_evidence`. For a **transitive** dependency, the check is
|
||||
whether application code imports it directly; if not, it is reachable only
|
||||
through the direct dependency — check whether the direct dep's usage can
|
||||
hit it (if unclear, use `imported` when the direct dep is used at all).
|
||||
through the packages that depend on it — walk every introducing chain
|
||||
from the lockfile (e.g. `express > body-parser > qs`) and inspect the code
|
||||
of the package one hop above the vulnerable one on each chain
|
||||
(`body-parser`, not just `express`; its installed sources, or the class
|
||||
references of its jar) for the affected API. No chain references it ⇒
|
||||
`not_reached` (below), citing every artifact you inspected; referenced ⇒
|
||||
that package's call is the usage site, continue the trace upward from
|
||||
there. If you could not inspect every chain, or one of them is unclear,
|
||||
the package is still loaded, so the level is `imported`, never
|
||||
`not_reached`.
|
||||
2. **Symbol match — per CVE, not per package.** Read each CVE's own advisory
|
||||
(GHSA/NVD/OSV `affected[].ecosystem_specific.imports` or the advisory
|
||||
text) for the affected functions/classes/APIs. Search application code for
|
||||
those symbols (`ast-grep` pattern or `rg -n`). Hits ⇒
|
||||
`vulnerable_symbol_used`, with repo-relative `file:line` of each hit (up
|
||||
to a handful) in `reachability_evidence`. Imported but no affected-symbol
|
||||
usage found (or the advisory names no symbols) ⇒ `imported`.
|
||||
to a handful) in `reachability_evidence`. Imported, search completed, and
|
||||
you can say why the vulnerable code does not run here ⇒ `not_reached`
|
||||
(below). Imported but the search was partial, the result is unclear, or
|
||||
the advisory names no symbols ⇒ `imported`.
|
||||
Different CVEs on the same package usually affect **different** symbols
|
||||
(one hits a parser, another a header check) — never copy one CVE's
|
||||
verdict/evidence onto its siblings; run the symbol search against each
|
||||
@@ -182,6 +196,23 @@ fi
|
||||
dynamic loading, unparsable sources) ⇒ `unknown` and say why in
|
||||
`assumptions`.
|
||||
|
||||
**`not_reached` — loaded, but this CVE's vulnerable code does not run here.**
|
||||
Use it when the package is imported (so `not_imported` is wrong) and you
|
||||
showed that the code path the advisory describes cannot execute in this
|
||||
codebase, and `reachability_evidence` says what you checked. The judgement is
|
||||
per CVE: it rests on what triggers *this* flaw (an affected function, a mode,
|
||||
an option, a kind of input) against what this code does. Situations that
|
||||
commonly support it, as inspiration rather than a checklist: the affected API
|
||||
has no caller in application code and the search was complete; the only call
|
||||
site is behind a condition that is false here (a polyfill for an older
|
||||
runtime, a flag that is off, a path only a dev tool runs); a transitive
|
||||
package's parent never references the affected API; the package is build or
|
||||
test tooling that the shipped artifact never executes; a library compiled into
|
||||
the runtime whose flaw sits in a mode the application never uses (a TLS server
|
||||
path in a client-only service). An inconclusive or skipped check stays
|
||||
`imported`. `not_reached` is still an evidence level, not proof of safety:
|
||||
dynamic dispatch, reflection and framework wiring can evade static search.
|
||||
|
||||
Cheap-first budgeting: the import check is one search per package — always do
|
||||
it. Do the per-CVE symbol match for every CVE whose advisory names affected
|
||||
symbols (they can be batched into one multi-pattern search per package);
|
||||
@@ -189,13 +220,18 @@ prioritize `critical`/`high`/KEV when the budget is tight; a CVE whose symbol
|
||||
search was skipped may still be reported as `imported` (the import check is
|
||||
real evidence), but its `reachability_evidence` must state that the
|
||||
affected-symbol check was not performed, so a skipped search is never
|
||||
mistaken for a completed one with no hits. Never let this analysis stall
|
||||
mistaken for a completed one with no hits, and is never filed as
|
||||
`not_reached`. Never let this analysis stall
|
||||
reporting — `unknown` with a reason beats an unverified claim.
|
||||
|
||||
Anti-overclaim rules:
|
||||
|
||||
- `not_imported` still does NOT mean safe (dynamic `import()`/reflection/
|
||||
framework wiring evade static search) — never phrase it as "not exploitable".
|
||||
- `not_reached` needs the check that shows the vulnerable code does not run,
|
||||
named in `reachability_evidence`; "no call found" after a partial search is
|
||||
`imported`, and one CVE's `not_reached` never carries over to a sibling CVE
|
||||
on the same package.
|
||||
- `reachable_call_path` is reserved for call-graph tools (govulncheck); a
|
||||
symbol grep hit is `vulnerable_symbol_used`, no matter how convinced you are.
|
||||
- The tool rejects any level other than `unknown` without
|
||||
|
||||
@@ -1693,6 +1693,7 @@ _VALID_REACHABILITY = frozenset(
|
||||
{
|
||||
"not_imported",
|
||||
"imported",
|
||||
"not_reached",
|
||||
"vulnerable_symbol_used",
|
||||
"reachable_call_path",
|
||||
"unknown",
|
||||
@@ -1840,6 +1841,9 @@ def _build_dependency_metadata(
|
||||
_REACHABILITY_EVIDENCE_LABELS = {
|
||||
"not_imported": "not imported by application code",
|
||||
"imported": "imported by application code; affected API usage unconfirmed",
|
||||
"not_reached": (
|
||||
"imported by application code; the vulnerable code of this advisory is not reached"
|
||||
),
|
||||
"vulnerable_symbol_used": "the advisory's affected API is used in application code",
|
||||
"reachable_call_path": (
|
||||
"a call path from application code to the vulnerable function was proven"
|
||||
@@ -2157,6 +2161,11 @@ async def create_dependency_report(
|
||||
can evade static search).
|
||||
- ``imported`` — application code imports the package, but usage of
|
||||
the advisory's affected API was not confirmed.
|
||||
- ``not_reached`` — application code imports the package, and the
|
||||
analysis showed that the vulnerable code of this advisory does not
|
||||
run here (the affected API has no caller, the only call site is
|
||||
behind a condition that is false in this codebase, the parent
|
||||
package never reaches it, ...). Per advisory, never per package.
|
||||
- ``vulnerable_symbol_used`` — the advisory's affected
|
||||
function/class/API appears in application code.
|
||||
- ``reachable_call_path`` — a call-graph tool (e.g. ``govulncheck``)
|
||||
@@ -2210,8 +2219,9 @@ async def create_dependency_report(
|
||||
repository root. This binds the finding to its exact file so
|
||||
remediation can target the right repository.
|
||||
reachability: Usage-evidence level from static analysis — one of
|
||||
``not_imported`` / ``imported`` / ``vulnerable_symbol_used`` /
|
||||
``reachable_call_path`` / ``unknown``. Claim only what the
|
||||
``not_imported`` / ``imported`` / ``not_reached`` /
|
||||
``vulnerable_symbol_used`` / ``reachable_call_path`` /
|
||||
``unknown``. Claim only what the
|
||||
evidence proves; when in doubt use ``unknown``.
|
||||
reachability_evidence: **Required.** The concrete proof for the
|
||||
claimed level, or, for ``unknown``, what you searched and why
|
||||
|
||||
@@ -668,6 +668,39 @@ async def test_dependency_report_records_reachability(report_state: ReportState)
|
||||
assert report["severity"] == "high"
|
||||
|
||||
|
||||
async def test_dependency_report_records_not_reached(report_state: ReportState) -> None:
|
||||
result = await _do_create_dependency(
|
||||
title="CVE-2021-23337 in lodash 4.17.20",
|
||||
description="Command injection via template.",
|
||||
target="repo/package.json",
|
||||
cve="CVE-2021-23337",
|
||||
package_name="lodash",
|
||||
installed_version="4.17.20",
|
||||
impact="Command injection where template is used.",
|
||||
remediation_steps="Upgrade to 4.17.21.",
|
||||
assumptions="Static search only; no dynamic dispatch into lodash was found.",
|
||||
package_ecosystem="npm",
|
||||
manifest_path="package-lock.json",
|
||||
fixed_version="4.17.21",
|
||||
cwe=None,
|
||||
advisory_cvss=7.2,
|
||||
technical_analysis=None,
|
||||
fix_effort="low",
|
||||
reachability="not_reached",
|
||||
reachability_evidence=(
|
||||
"src/util/strings.ts:3 imports lodash; a complete search of src/ finds no "
|
||||
"call to _.template, the only API the advisory affects."
|
||||
),
|
||||
contextual_cvss_breakdown=_DEP_CONTEXT,
|
||||
contextual_cvss_reasoning=_DEP_REASONING,
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
report = report_state.vulnerability_reports[0]
|
||||
assert report["dependency_metadata"]["reachability"] == "not_reached"
|
||||
assert "the vulnerable code of this advisory is not reached" in report["evidence"]
|
||||
|
||||
|
||||
async def test_dependency_report_rejects_reachability_without_evidence(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
|
||||
Reference in new issue
Block a user