Reachable Risk sample reports

Redash v26.3.0

272 open dependency alerts on Redash v26.3.0, scored in the four places its code runs. 29 need attention.

Run date
2026-08-16
Scale
272 alerts, 664 context-specific verdicts, 4 execution contexts (server, worker, browser-bundle, build)
Rubric
2026.08.13-v26
Workflow
2026.08.12-v33
Scored by
claude-opus-5
Risk context
2026-07-30+87af4dc
SLA policy
SafeCo Vulnerability Management Policy (ISP-07) v1.0 (fictional stand-in)

Report (Markdown)risk-context.yaml (the context we gather together)

The SLA policy is a fictional stand-in so the samples stay comparable; deadlines in a real run come from your own vulnerability management policy. The published risk-context.yaml is a copy of the run file with prose comments edited for publication; every declared context, path, asset context, actor, capability and bound is identical.

Dependency Alert Triage Report - redash - 2026-08-16

Executive Summary

Evidence is complete on 269 of 272 open alerts: nothing to fix now, 26 recommended for a scheduled fix, and 243 recommended for deferral, 89% of everything scored, with the evidence below. Evidence is incomplete on 3: cryptography GHSA-537c-gmf6-5ccf, sqlparse GHSA-27jp-wm6q-gp25, and markdown GHSA-wx77-rp39-c6vg need a person to decide fix-or-defer; resolve those first.

The supplied policy resolves a 60-calendar-day remediation window for all 26 scheduled alerts and a 30-day window for the 3 under review. No expiry date can be computed: SafeCo ISP-07 starts its clock on the date each alert was opened in the tracking system, and the alert list carries no opened date. Every SLA cell in this report reads ? for that reason rather than silently substituting another date.

Every open alert was retrieved and the count verified at 272. Each finding's reachability was analyzed against this project's own source, its vendored dependencies, and the execution contexts declared in .k9security/risk-context.yaml. Exploitation evidence came from KEV (the CISA/VulnCheck catalog of vulnerabilities with confirmed exploitation in the wild) and EPSS (a daily model score estimating each vulnerability's probability of exploitation in the next 30 days, read as a percentile against all scored CVEs).

How many findings are exploitable and reachable in production right now? Zero. No alert in this set appears in KEV, and none reaches the top-1% EPSS line, so the Threat factor is absent across all 272. The rubric requires Threat, Exposure, and Impact together for FIX_TODAY, and no finding carries all three.

How much noise did scoring remove? 243 of the 272 open alerts scored, 89%, carry a deferral recommendation because the vulnerable code is not delivered to the context that matters, not reachable from any entry point, or reached only with input this project controls. Focus can be directed elsewhere on those 243.

The reachability and exploitability analysis retired 38,400 of 53,120 inherent NIST-representative risk points, 72% of this project's inherent dependency risk, safely deferred 243 of the 272 alerts, and moved 622 of the 664 context-specific verdicts from a High inherent band to a Low residual band. A further 36 verdicts fell from High to Moderate.

Bucket Count Meaning
FIX_TODAY 0 Fix now
REVIEW 3 Unresolved question; investigate before deciding
SCHEDULE 26 Fix on a planned horizon; SLA below
DEFER 243 No action needed; evidence below

These counts are alerts, and they sum to 272. Each alert was scored once per execution context it belongs to, so an alert can carry different verdicts in different contexts. An alert is counted here under its most urgent verdict across contexts; the per-context detail is in the entries and the Risk scores appendix.

Start with the three REVIEW findings, which are the only place the analysis could not finish, then schedule the 26 by the 60-day window. The 243 deferral recommendations are evidence-backed below and need no engineering work.

FIX_TODAY / REVIEW: act now or resolve the unknown

No finding is exploitable and reachable in production, so FIX_TODAY is empty. Three findings are under REVIEW.

REVIEW is the rubric declining to overclaim. On these three the evidence supports neither "fix" nor "safe to defer", so a person resolves the unknown. That is not a defect in the analysis, and a REVIEW must not be dismissed: dismissing it would convert "we do not know" into "we decided it is fine". These three are not offered for dismissal in the commands appendix. On the two that have a published fix, upgrading may cost less than investigating; whether it does depends on this project's code and deployment, so that choice is the reader's.

All three share one root cause. None of them has been assigned a CVE, so neither KEV nor EPSS carries a record for them and the Threat factor cannot be evaluated at all. The rubric's missing_evidence_floor holds each at the highest residual this asset supports rather than crediting an absence it cannot verify.

GHSA-537c-gmf6-5ccf — a vulnerable OpenSSL is statically linked into the installed cryptography wheel — REVIEW

  • Finding: pypi:cryptography:GHSA-537c-gmf6-5ccf (cryptography 43.0.1, transitive, poetry.lock). Scored in all three Python contexts: redash-server, redash-worker, and redash-build.
  • Why REVIEW: both factors are missing. Threat is unknown because the advisory carries no CVE, so there is no KEV entry and no EPSS score to read. Reachability is undetermined because the advisory names no vulnerable symbol in cryptography's own Python API; it points at OpenSSL's June 2026 security advisory for the flaw itself. With no named symbol there is no call site to enumerate, which is a legitimate answer rather than a gap in the work. Impact is present in all three contexts.
  • Residual risk: high, 80/100 (inherent high 80; 0 points retired), low confidence. A retired value of 0 alongside low confidence is the signature of an analysis that could not finish, reported honestly.
  • SLA expiry: ? The policy dates from the alert's opened date in the tracking system, and the supplied alert list carries no opened date. Once resolved, the residual High band gives a 30-calendar-day window, and that clock runs on resolving the unknown, not on shipping a fix.
  • Evidence: not in KEV. No EPSS score exists, because no CVE was assigned. The advisory states that only wheels built by the cryptography project are affected, and that the vulnerability details live in the upstream OpenSSL advisory of 2026-06-09. The installed 43.0.1 is inside the affected range, which the advisory gives as 0.5.0 up to but not including 48.0.1. Redash installs cryptography from PyPI wheels in its container image, so the statically linked copy is the one running.
  • Recommended action: read the referenced OpenSSL advisory to learn which OpenSSL function is affected, then decide whether redash calls anything that reaches it. Upgrading cryptography to 48.0.1 or later resolves the finding without that investigation, since 48.0.1 is the first release carrying a fixed OpenSSL.

GHSA-27jp-wm6q-gp25 — formatting a long list of tuples hangs the SQL formatter — REVIEW

  • Finding: pypi:sqlparse:GHSA-27jp-wm6q-gp25 (sqlparse 0.5.0, transitive, poetry.lock). REVIEW in redash-server and redash-worker; DEFER in redash-build.
  • Why REVIEW: reachability is determined and positive; only Threat is missing. The vulnerable function is called on user-supplied SQL, so Exposure and Impact are both present, but the advisory carries no CVE and therefore no KEV or EPSS record, leaving Threat unevaluable.
  • Residual risk: high, 80/100 (inherent high 80; 0 points retired), low confidence. The confidence marker reflects the absent Threat record, not the reachability trace, which is firm.
  • SLA expiry: ? Same missing opened date as above. The residual High band gives a 30-calendar-day window, running on resolving the unknown rather than on shipping a fix.
  • Evidence: not in KEV. No EPSS score exists, because no CVE was assigned. redash/handlers/queries.py:66 passes user-submitted SQL straight into sqlparse.format(query, **settings.SQLPARSE_FORMAT_OPTIONS) and returns the result, and any authenticated non-admin user can reach that endpoint. The risk context names authenticated-non-admin-user as an untrusted actor. The advisory reports a hang while formatting a long list of tuples, which is a denial-of-service condition rather than a data-disclosure one. The installed 0.5.0 is inside the affected range of everything below 0.5.4.
  • What resolves it: a CVE assignment for this advisory, which would give KEV and EPSS something to say about how likely exploitation is. Nothing about this project's code needs further tracing; the call path is established.

GHSA-wx77-rp39-c6vg — a slow regular expression in the markdown renderer — REVIEW

  • Finding: npm:markdown:GHSA-wx77-rp39-c6vg (markdown 0.5.0, direct dependency, yarn.lock). REVIEW in redash-browser-bundle; DEFER in redash-build.
  • Why REVIEW: as with sqlparse, reachability is determined and positive while Threat is missing. The advisory carries no CVE, so no KEV or EPSS record exists.
  • Residual risk: high, 80/100 (inherent high 80; 0 points retired), low confidence.
  • SLA expiry: ? Same missing opened date. The residual High band gives a 30-calendar-day window on resolving the unknown.
  • Evidence: not in KEV. No EPSS score exists, because no CVE was assigned. markdown.toHTML() renders dashboard text widgets in TextboxWidget.jsx and VisualizationEmbed.jsx, and those widgets carry stored text authored by one user and rendered in another user's browser. That makes the input attacker-controlled under this project's own trust model. The advisory states the function degrades badly on long strings containing underscores.
  • What resolves it: a CVE assignment, or an upstream advisory update. Note that the advisory records no fixed version and recommends an alternative package, so upgrading is not available as a shortcut here. The decision is whether a browser-side slowdown triggered by another user's dashboard text is worth acting on, which is a judgment about this deployment rather than a fact the analysis can supply.

SCHEDULE: fix on a planned horizon

Here is what the analysis found. Twenty-six alerts have a traced path from an untrusted input to the vulnerable code in at least one execution context, so the Exposure factor is present and established, not assumed. Every one of them is absent from KEV. Their EPSS percentiles run from p0.046 to p0.847, all of them below the p99 top-1% line that the rubric treats as Threat evidence, so none escalates to FIX_TODAY. All 26 resolve to the same 60-calendar-day policy window and the same Moderate 50/100 residual, and no expiry date is computable because the alert list carries no opened date.

Because SLA expiry and residual composite are identical across all 26, neither orders these entries. They are grouped by execution context instead, production runtimes first, then the developer build.

CVE-2026-45409 — a crafted domain name makes the IDNA encoder burn CPU — SCHEDULE

  • Finding: pypi:idna:PYSEC-2026-215 and pypi:idna:GHSA-65pc-fj4g-8rjx (idna 3.10, transitive, poetry.lock). Two alerts for one advisory, both scored in all three Python contexts; SCHEDULE applies to redash-worker only.
  • Why SCHEDULE: a traced call path in redash-worker carries an attacker-controlled hostname into the vulnerable encoder. Threat absent, Exposure present, Impact present on a tier_2 production asset. This is the clearest context split in the report: the same alert is DEFER in redash-server, where no query-runner HTTP call is made, and SCHEDULE in redash-worker, where it is.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? The policy dates from the alert's opened date in the tracking system, and the supplied alert list carries no opened date. The residual Moderate band gives a 60-calendar-day window once that date is known.
  • Evidence: not in KEV. EPSS percentile p0.340, well below the p99 top-1% line. redash/__init__.py:54 imports every default query runner into both the server and the worker, but query execution and connection tests are RQ jobs: redash/handlers/query_results.py:103 enqueues them and redash/handlers/data_sources.py:230 defers test_connection to the worker. The worker's outbound path runs through BaseHTTPQueryRunner.get_response into redash/utils/requests_session.py, and the risk context states plainly that the outbound hostname is attacker-controlled there: a non-admin user supplies an absolute URL that urljoin honors. That hostname reaches idna.encode(). The advisory notes a workaround, enforcing the 253-character domain limit before calling idna.encode(), and redash applies no such limit.
  • Recommended action: upgrade idna to 3.15 or later, the first release that rejects over-long inputs across all of the library's conversion entry points. Version 3.14 fixed only the main path.

CVE-2024-47081 — a crafted URL can leak .netrc credentials to a third party — SCHEDULE

  • Finding: pypi:requests:PYSEC-2026-1872 and pypi:requests:GHSA-9hjg-9r4m-mvj7 (requests 2.32.3, transitive, poetry.lock). Two alerts for one advisory; SCHEDULE applies to redash-worker only.
  • Why SCHEDULE: the same traced worker path as idna. A non-admin user controls the URL that reaches requests, which is exactly the precondition the advisory names. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date, as above. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.549. The advisory states that a URL parsing issue lets maliciously crafted URLs leak .netrc credentials to third parties, and redash's worker hands user-supplied absolute URLs to a requests-derived session. The exposure is bounded by whether a .netrc file exists in the worker container, which the repository does not determine. The risk context's server-side-url-fetching accepted risk does not cover this: its stated bounds are impact-only, and credential leakage to an arbitrary third party is a different impact than the accepted server-side fetch.
  • Recommended action: upgrade requests to 2.32.4 or later. The advisory's interim workaround is to set trust_env=False on the session, which disables .netrc use entirely.

CVE-2026-21441 — a redirect response is decompressed with no size limit — SCHEDULE

  • Finding: pypi:urllib3:PYSEC-2026-1996 and pypi:urllib3:GHSA-38jv-5279-wg99 (urllib3 1.26.19, transitive, poetry.lock). Two alerts for one advisory; SCHEDULE applies to redash-worker only.
  • Why SCHEDULE: the worker follows redirects from a host the user names, so a malicious server on the other end is inside the threat model. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.847, the highest in this report and still below the p99 top-1% line. The advisory states that redirect responses were fully drained and decompressed before any read call, with configured read limits not applied, leaving no safeguard against a decompression bomb. Redash's worker does not disable redirects on query-runner HTTP calls.
  • Recommended action: upgrade urllib3 to 2.6.3 or later, which stops decoding redirect response bodies. Note the fix is on the 2.x line while 1.26.19 is installed. The advisory's interim workaround is redirect=False for requests to untrusted sources.

CVE-2025-66471 — a small compressed response expands without bound while streaming — SCHEDULE

  • Finding: pypi:urllib3:PYSEC-2026-1994 and pypi:urllib3:GHSA-2xpw-w6gg-jr37 (urllib3 1.26.19, transitive, poetry.lock). Two alerts for one advisory; SCHEDULE applies to redash-worker only.
  • Why SCHEDULE: same traced worker path, with the response body coming from a server the user chose. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.499. The advisory states that the decompression logic could fully decode a small amount of highly compressed data in one operation, spending CPU and memory far beyond the requested chunk size.
  • Recommended action: upgrade urllib3 to 2.6.0 or later, which stops decompressing past the requested amount. Taking 2.6.3 covers this and CVE-2026-21441 in one move.

CVE-2025-66418 — an unbounded chain of compression encodings — SCHEDULE

  • Finding: pypi:urllib3:PYSEC-2026-1998 and pypi:urllib3:GHSA-gm62-xv2j-4w53 (urllib3 1.26.19, transitive, poetry.lock). Two alerts for one advisory; SCHEDULE applies to redash-worker only.
  • Why SCHEDULE: same traced worker path. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.499. The advisory states that the number of links in the decompression chain was unbounded, so a malicious server could stack a virtually unlimited number of compression steps.
  • Recommended action: upgrade urllib3 to 2.6.0 or later, which caps the chain at five links. Covered by the same 2.6.3 move as the two above.

CVE-2026-27205 — a caching proxy may serve one user's page to another — SCHEDULE

  • Finding: pypi:flask:PYSEC-2026-2151 and pypi:flask:GHSA-68rp-wp8r-4726 (flask 2.3.2, direct dependency, poetry.lock). Two alerts for one advisory; SCHEDULE applies to redash-server only.
  • Why SCHEDULE: the vulnerable behaviour is in the request-handling path the server runs on every authenticated request, and redash reads the session on those requests. Threat absent, Exposure present, Impact present on the internet-adjacent tier_2 asset.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.274. The advisory states that some forms of session access, such as the Python in operator, were overlooked when Flask decides whether to set Vary: Cookie, and that the real severity depends on the deployment: a caching proxy that does not ignore responses carrying cookies, no Cache-Control marking pages private, and session access that only touches keys. The first two are deployment facts this repository does not answer, which is why this is a scheduled fix rather than an emergency.
  • Recommended action: upgrade Flask to 3.1.3 or later, the release carrying the fix. Setting Cache-Control: private on authenticated responses at the proxy removes the precondition independently of the upgrade.

CVE-2024-49767 — a crafted form body bypasses the upload memory limit — SCHEDULE

  • Finding: pypi:werkzeug:PYSEC-2026-3417 and pypi:werkzeug:GHSA-q34m-jh98-gwm2 (werkzeug 2.3.8, transitive, poetry.lock). Two alerts for one advisory; SCHEDULE applies to redash-server only.
  • Why SCHEDULE: redash accepts multipart/form-data uploads on authenticated endpoints, so an untrusted user reaches the vulnerable parser directly. Threat absent, Exposure present, Impact present. Eleven other werkzeug alerts in this report score DEFER at a lower residual; none of them has a traced path like this one, which is what separates them.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.628. The advisory states that a specially crafted form body bypasses Request.max_form_memory_size, and that max_content_length and platform-level limits are unaffected and remain available as independent controls.
  • Recommended action: upgrade Werkzeug to 3.0.6 or later. Setting max_content_length, or an upload size cap at the reverse proxy, bounds the resource consumption independently of the upgrade.

CVE-2026-65914 — sanitized HTML turns dangerous when re-parsed inside a wrapper tag — SCHEDULE

  • Finding: npm:dompurify:GHSA-h8r8-wccr-v5f2 (dompurify 2.5.4, transitive via @redash/viz, viz-lib/yarn.lock). SCHEDULE in redash-browser-bundle; DEFER in redash-build.
  • Why SCHEDULE: this advisory names no configuration precondition, so it applies to redash's sanitizer call as written, and that sanitizer runs on stored HTML authored by one user and rendered in another user's browser. Threat absent, Exposure present, Impact present. Fourteen other DOMPurify alerts in this report are DEFER precisely because each does name a configuration option that redash never sets.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.063, one of the lowest in this report, which is why it schedules rather than escalates. The advisory describes a mutation-XSS condition where sanitized output is concatenated into a wrapper such as xmp, script, iframe, noembed, noframes, or noscript and re-parsed, at which point attacker text inside an attribute closes the wrapper early and reintroduces executable markup. Redash's sanitizer configuration in viz-lib/src/services/sanitize.ts sets only ADD_ATTR: ["target"] plus one afterSanitizeAttributes hook that reads and writes target and rel, so nothing in the configuration blocks this path. The risk context's custom-js-visualizations accepted risk does not cover this: its own scope text excludes sanitizer bypasses.
  • Recommended action: upgrade the DOMPurify used by viz-lib to 3.3.2 or later, the smallest version that resolves this advisory along with CVE-2026-0540 and CVE-2025-15599 below. Keeping the rendering context stable between sanitizing and inserting, rather than wrapping sanitized output in a new tag, removes the precondition wherever redash does that.

CVE-2026-0540 — five raw-text tags missing from the sanitizer's attribute check — SCHEDULE

  • Finding: npm:dompurify:GHSA-v2wj-7wpq-c8vv (dompurify 2.5.4, transitive via @redash/viz, viz-lib/yarn.lock). SCHEDULE in redash-browser-bundle; DEFER in redash-build.
  • Why SCHEDULE: no configuration precondition, reached on the same stored cross-user HTML path. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.270. The advisory states that noscript, xmp, noembed, noframes, and iframe are missing from the SAFE_FOR_XML regex, so a payload such as </noscript><img src=x onerror=alert(1)> placed in an attribute value executes when the sanitized output lands inside one of those contexts. The installed 2.5.4 sits inside the affected 2.5.3 through 2.5.8 range.
  • Recommended action: upgrade to 3.3.2 or later. The 2.x line has a fix for this specific advisory at 2.5.9, but see CVE-2025-15599 below, whose 2.x branch was never patched.

CVE-2025-15599 — a closing </textarea> in an attribute escapes the sanitizer — SCHEDULE

  • Finding: npm:dompurify:GHSA-v8jm-5vwx-cfxm (dompurify 2.5.4, transitive via @redash/viz, viz-lib/yarn.lock). SCHEDULE in redash-browser-bundle; DEFER in redash-build.
  • Why SCHEDULE: no configuration precondition, same stored cross-user HTML path. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.161. The advisory states that textarea is missing from the SAFE_FOR_XML raw-text validation, so a </textarea> sequence in an attribute value breaks out of that context. It also states plainly that the 3.x branch was fixed in 3.2.7 and the 2.x branch was never patched, which is the fact that decides the upgrade target for all three viz-lib DOMPurify findings.
  • Recommended action: upgrade to 3.3.2 or later. No 2.x release resolves this one.

CVE-2024-47875 — nested markup mutates past the sanitizer — SCHEDULE

  • Finding: npm:dompurify:GHSA-gx9m-whjm-85jf (dompurify 2.0.17, direct dependency, yarn.lock). SCHEDULE in redash-browser-bundle; DEFER in redash-build. This is the older DOMPurify copy in the application's own dependency tree, separate from the viz-lib copy above.
  • Why SCHEDULE: a nesting-based mutation-XSS with no configuration precondition, reached on the same stored cross-user HTML. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.620. The advisory records the fix commit and a regression test in the upstream test suite. The installed 2.0.17 is below the 2.5.0 fix line. The identical sanitizer configuration in client/app/services/sanitize.js sets nothing that would block it. Two further DOMPurify alerts on this same copy, GHSA-mmhx-hmjr-r674 and GHSA-p3vf-v8qc-cwcr, are deferred because they are prototype-pollution gadgets with no pollution primitive available in this bundle.
  • Recommended action: upgrade the application's DOMPurify to 2.5.0 or later, the first release carrying the fix. Moving both copies to a common 3.3.2 would resolve this and the three viz-lib findings together.

CVE-2025-13465 — a user-authored column name can delete built-in prototype properties — SCHEDULE

  • Finding: npm:lodash:GHSA-f23m-r3pf-42rh and npm:lodash:GHSA-xxjr-mmjv-4gpg (lodash 4.17.21, transitive via @redash/viz, viz-lib/yarn.lock). Two alerts for one advisory; SCHEDULE in redash-browser-bundle, DEFER in redash-build.
  • Why SCHEDULE: a traced path carries user-authored text into the vulnerable function as a lodash deep path. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.727. viz-lib/src/visualizations/map/prepareData.ts:21 calls omit(row, [latColName, lonColName]), and both column names come from the map visualization's options, which a user authors. The advisory states that the earlier fix guarded only string key members, so array-wrapped path segments bypass it. It also bounds the impact precisely: the issue permits deletion of properties from built-in prototypes but does not allow overwriting their original behavior. That bound is what keeps this at Moderate and is also the evidence that retires the nine axios prototype-pollution gadget alerts deferred below, since a delete-only primitive cannot add the Object.prototype key those gadgets require.
  • Recommended action: upgrade lodash to 4.18.0. The advisory records no workaround.

CVE-2025-30359 — a malicious site can read the dev server's bundled source — SCHEDULE

  • Finding: npm:webpack-dev-server:GHSA-4v9v-hfq4-rm2v (webpack-dev-server 4.15.1, dev dependency, yarn.lock). SCHEDULE in redash-build; the package is never delivered to browsers, so it is DEFER in redash-browser-bundle.
  • Why SCHEDULE: yarn start runs this server on developer machines, and the attack is triggered by a developer visiting a malicious site while it runs. That is genuinely attacker-controlled input in the build context, so Exposure is present. Threat absent, Impact present on the tier_2 dev asset.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.357. The advisory shows a working proof of concept: because a <script src> request is not subject to same-origin policy, an attacker page loads http://localhost:8080/main.js, uses prototype pollution against Array::forEach to capture the webpack runtime's require reference, and reads every module's source through Function::toString. The attacker needs to know the port and entry path, both of which are conventional.
  • Recommended action: upgrade webpack-dev-server to 5.2.6 or later, which resolves this and the five findings below in one move. Note the fix is on the 5.x line while 4.15.1 is installed.

CVE-2025-30360 — the same source theft over WebSocket in non-Chromium browsers — SCHEDULE

  • Finding: npm:webpack-dev-server:GHSA-9jgg-88mc-972h (webpack-dev-server 4.15.1, dev dependency, yarn.lock). SCHEDULE in redash-build; DEFER in redash-browser-bundle.
  • Why SCHEDULE: same developer-visits-malicious-site path. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.227. The advisory states that webpack-dev-server always allows IP-address Origin headers, so a site served on an IP address can open the hot-module-reload WebSocket and read source from the update messages. It also states that Chrome 94 and later, and other Chromium-based browsers, are unaffected because of private network access blocking, which narrows the exposure to developers using other browsers.
  • Recommended action: upgrade webpack-dev-server to 5.2.6 or later.

CVE-2026-6402 — the earlier source-theft fix is bypassed over plain HTTP — SCHEDULE

  • Finding: npm:webpack-dev-server:GHSA-79cf-xcqc-c78w (webpack-dev-server 4.15.1, dev dependency, yarn.lock). SCHEDULE in redash-build; DEFER in redash-browser-bundle.
  • Why SCHEDULE: same developer-visits-malicious-site path. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.124. The advisory states that the 5.2.1 fix relied on Sec-Fetch-Mode and Sec-Fetch-Site headers, which browsers send only for trustworthy origins, so over plain HTTP the headers are absent and the check is skipped. Chrome 142 and later are unaffected because of local network access restrictions. The advisory names running the dev server over HTTPS as a workaround.
  • Recommended action: upgrade webpack-dev-server to 5.2.6 or later, which sets Cross-Origin-Resource-Policy: same-origin on responses.

CVE-2026-9595 — a broad user proxy swallows the hot-reload WebSocket — SCHEDULE

  • Finding: npm:webpack-dev-server:GHSA-mx8g-39q3-5c79 (webpack-dev-server 4.15.1, dev dependency, yarn.lock). SCHEDULE in redash-build; DEFER in redash-browser-bundle.
  • Why SCHEDULE: same developer-machine context. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.060. The advisory states that a user-configured proxy with a broad context such as / and ws: true also intercepts the dev server's own hot-module-reload WebSocket, leaking the browser's cookies and Origin header to the proxy target and bypassing the server's Host and Origin validation. Whether redash's dev configuration uses such a proxy entry is a configuration question the fix makes moot.
  • Recommended action: upgrade webpack-dev-server to 5.2.6 or later. Scoping any proxy context to specific paths instead of /, or dropping ws: true where WebSocket forwarding is not needed, removes the precondition.

CVE-2026-14620 — any visited site can open a local file in the developer's editor — SCHEDULE

  • Finding: npm:webpack-dev-server:GHSA-f5vj-f2hx-8m93 (webpack-dev-server 4.15.1, dev dependency, yarn.lock). SCHEDULE in redash-build; DEFER in redash-browser-bundle.
  • Why SCHEDULE: same developer-machine context, and this one needs no interaction beyond the visit. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.046, the lowest in this report. The advisory states that the internal /webpack-dev-server/open-editor and /webpack-dev-server/invalidate endpoints act on any GET request without checking the request's origin, so any site a developer visits while the dev server runs can open an arbitrary existing local file in their editor, including files outside the project such as ~/.ssh/config. The file contents are not returned to the attacker. The advisory records no workaround.
  • Recommended action: upgrade webpack-dev-server to 5.2.6 or later, which rejects cross-site requests to both endpoints.

CVE-2026-14631 — a malformed Host or Origin header crashes the dev server — SCHEDULE

  • Finding: npm:webpack-dev-server:GHSA-m28w-2pqf-7qgj (webpack-dev-server 4.15.1, dev dependency, yarn.lock). SCHEDULE in redash-build; DEFER in redash-browser-bundle.
  • Why SCHEDULE: same developer-machine context. Threat absent, Exposure present, Impact present.
  • Residual risk: moderate, 50/100 (inherent high 80; 30 points retired).
  • SLA expiry: ? Missing alert opened date. Residual Moderate gives a 60-calendar-day window.
  • Evidence: not in KEV. EPSS percentile p0.337. The advisory states that an unauthenticated peer who can reach the process can terminate it with a malformed Host header on an ordinary request, or a malformed Origin header on a WebSocket upgrade to /ws, because the host-validation path throws an uncaught exception. It names keeping the server bound to localhost, the default, as the workaround.
  • Recommended action: upgrade webpack-dev-server to 5.2.6 or later, which treats malformed header values as invalid rather than throwing. A single move to 5.2.6 clears all six webpack-dev-server findings.

DEFER: why these alerts are safely deferrable

This section is the evidence that each deferral was earned. The deferrals below were produced by the same analysis, to the same evidentiary standard, as the findings scheduled above: one process, one quality bar, different outcomes on different evidence.

Two facts apply to all 243. None appears in KEV. None reaches the p99 top-1% EPSS line, so the Threat factor is absent throughout, exactly as it is for the scheduled findings. What separates these from the scheduled ones is Exposure: for 237 of them the analysis traced the vulnerable code and found no path an untrusted actor can drive, and for the remaining 6 it could not determine reachability and deferred on confidently-absent threat instead, which is disclosed as its own group below.

The deferrals fall into seven evidence patterns.

Python: not imported by the running services (8 alerts)

jwcrypto (2), pygments (2), pytest (2), virtualenv (2): test and development libraries that are installed in the image but never imported by redash-server or redash-worker. The reachability basis is "code not loaded" (code_not_loaded) in both production contexts. jwcrypto is imported only by tests/test_authentication.py. pygments arrives through ptpython, which redash/cli/__init__.py:89 imports lazily inside a function that the worker entrypoint never calls. pytest and virtualenv are development tooling. In redash-build these same packages do run, and there the basis is "no attacker-controlled input" (no_attacker_controlled_input): they process this project's own source and fixtures.

Residual low, 20/100 across all three contexts; inherent high 80; 60 points retired. Not in KEV; EPSS percentiles from p0.005 to p0.220, far below the top-1% line.

What would change these: an import of one of these packages into a production code path, or a KEV listing.

Python: imported, but no entry point reaches the vulnerable function (64 alerts)

The largest Python group. Each of these packages is loaded in the running services, but the specific vulnerable function has no caller reachable from any redash entry point. The basis is "unreachable from any entry point" (unreachable_from_entrypoint).

Members: authlib (10): the vulnerable paths are in token and assertion handling that redash's OAuth client configuration never enters, and OAuth(app) is constructed with no cache, so the cache-related paths are unused. werkzeug (10): ten of the twelve werkzeug alerts, including the debugger flaw CVE-2024-34069 at EPSS p0.878. Gunicorn serves redash.wsgi:app with no debug flag, so the debugger is never installed. pyjwt (8): signature-verification paths redash does not call. cryptography (4), filelock (4), mako (4), pyopenssl (4), setuptools (4): filelock's TOCTOU flaw is in SoftFileLock, while snowflake/connector/cache.py:19 imports FileLock, the Unix fcntl implementation. h11 (2), jinja2 (2): no SandboxedEnvironment exists anywhere in the codebase, which is the precondition for the Jinja2 sandbox-escape advisory. marshmallow (2), python-dotenv (2), requests (2), snowflake-connector-python (2), urllib3 (2), click (1), msgpack (1).

Residual low, 20/100; inherent high 80; 60 points retired. Not in KEV; EPSS percentiles from p0.013 to p0.878.

What would change these: a code change that introduces a call to one of these functions, or a KEV listing, which would activate the kev_emergency floor regardless of reachability.

Python: called only with first-party or trusted-actor input (26 alerts)

Here the vulnerable function does run, but every input reaching it comes from this project's own code, its configuration, or an actor the risk context declares trusted. The basis is "no attacker-controlled input" (no_attacker_controlled_input).

Members: authlib (10): the OAuth and SAML paths process assertions and id_tokens from the corporate IdP, which .k9security/risk-context.yaml declares a trusted actor on the stated grounds that they arrive over the back channel, and it records that JWT proxy-login is not enabled. pyasn1 (6): parses certificates from configured, operator-supplied trust stores. snowflake-connector-python (4), paramiko (2), pynacl (2): reached with connection parameters that a redash administrator configures, and redash-administrator is a declared trusted actor. pyjwt (1), httplib2 (1).

Residual low, 20/100; inherent high 80; 60 points retired. Not in KEV; EPSS percentiles from p0.017 to p0.503.

This group rests on the trust model stated in the risk context rather than on absence of a call path, so it is the group most sensitive to that model changing.

What would change these: enabling JWT proxy-login, accepting IdP assertions over a front channel, or letting non-admin users supply data-source connection parameters. Any of those would move the affected findings' Exposure factor to present, and with Impact already present the base bucket would become SCHEDULE.

Python: reachability undetermined, threat confidently absent (6 alerts)

These three advisories are deferred on the Threat factor alone, because the analysis could not honestly determine reachability. Each turns on a deployment fact the repository does not record. They carry a higher residual than the rest of the deferrals for exactly that reason, and they are the findings this report most wants added to .k9security/risk-context.yaml.

azure-core CVE-2026-21226 (2 alerts): deserialization of untrusted data, EPSS p0.537, fixed in 1.38.0, installed 1.32.0. The vulnerable path is reachable only if an Azure Data Explorer data source is actually configured, which the repository cannot answer. Undetermined in redash-worker; unreachable from any entry point in redash-server and redash-build.

cryptography CVE-2024-12797 (2 alerts): another statically linked OpenSSL flaw, EPSS p0.832, fixed in 44.0.1, installed 43.0.1. As with the REVIEW finding above, the advisory names no symbol in cryptography's own Python API, so there is no call site to enumerate. Undetermined in all three contexts.

urllib3 CVE-2026-44431 (2 alerts): Authorization, Cookie, and Proxy-Authorization headers are forwarded across origins on redirects followed through the low-level proxied API, EPSS p0.261, fixed in 2.7.0. Whether this applies depends on whether an HTTP proxy is configured for the worker's outbound calls, which the repository does not state. Undetermined in redash-worker; unreachable from any entry point in redash-server and redash-build.

Residual moderate, 50/100 in the undetermined context; inherent high 80; 30 points retired. Medium confidence on those verdicts, reflecting the unknown Exposure factor.

What would change these: stating the three deployment facts in the risk context, which would replace the undetermined Exposure with a determined one in either direction. A KEV listing on any of the three would activate the kev_emergency floor and override the deferral outright.

JavaScript: declared but never imported anywhere (6 alerts)

bootstrap (2): the two advisories, GHSA-q58r-hwc8-rm9j and GHSA-vxmc-5x29-h64v, are both in Bootstrap's JavaScript plugins, and redash imports only the stylesheet through @import "~bootstrap/less/bootstrap". The plugin code never enters the bundle. elliptic (2) and bn.js (1): elliptic is declared in package.json dependencies, but nothing under client/app or viz-lib/src imports it, and bn.js arrives only through elliptic. protocol-buffers-schema (1): reachable only from pbf/bin/pbf, a command-line tool nothing invokes; pbf's own entry point does not require it.

Basis "code not loaded" (code_not_loaded) in both the browser bundle and the build. Residual low, 20/100; inherent high 80; 60 points retired. Not in KEV; EPSS percentiles all below p0.60.

What would change these: an import statement. For bootstrap specifically, adding the JavaScript plugin bundle alongside the stylesheet.

JavaScript: build and test tooling, never shipped to a browser (91 alerts)

The largest group in the report. These packages live in the dependency tree of webpack, Babel, ESLint, Jest, Cypress, and the local dev server. Two determinations carry the group. In redash-browser-bundle the basis is "code not loaded": none of them is reachable from client/app/index.js, so none is bundled. In redash-build they do execute, and the basis is "no attacker-controlled input" or "unreachable from any entry point": the only inputs are this project's own source, its configuration, its test fixtures, and npm-registry tarballs.

Members by package: tar (11), node-forge (7), follow-redirects (5), @babel packages (5), brace-expansion (3), http-proxy-middleware (3), js-yaml (3), minimatch (3), postcss (4), qs (3), serialize-javascript (3), tough-cookie (3), launch-editor (2), flatted (2), form-data (2), lodash.template (2), path-to-regexp (2), picomatch (2), shell-quote (2), tar-fs (2), tmp (2), webpack (2), websocket-driver (2), ws (2), request and @cypress/request (2), and one each of ajv, axios 0.21.4, babel-traverse, body-parser, braces, cookie, cross-spawn, micromatch, node-notifier, nth-check, on-headers, and uuid.

Several deserve their specific evidence rather than the group rule:

  • tar (11), tar-fs (2), tmp (2): the only archives extracted are npm-registry tarballs and the Cypress binary, and yarn.lock pins every one with a sha512 integrity hash, so substituted content fails verification before extraction begins.
  • node-forge (7): reached only through selfsigned, which generates a development certificate. All seven advisories are in certificate and signature verification and ASN.1 parsing paths that generation never enters.
  • launch-editor (2): both advisories are Windows-only, one a cmd injection and one a UNC/NTLM issue, and this project's Node toolchain and CI image are Linux. Developers on Windows machines are the residual caveat, which is worth noting because the group rule alone would not catch it.
  • @babel/helpers, @babel/runtime, @babel/runtime-corejs3 (3, all GHSA-968p-4wvh-cqc8): the vulnerable artifact is generated code for .replace with named capturing groups. Searching client/app and viz-lib/src for named capturing groups returns zero matches, so the helper is never emitted into the bundle.
  • ajv (1): the regular-expression denial of service needs the $data option, which webpack's schema validation never enables.
  • @babel/plugin-transform-modules-systemjs (1): needs modules: "systemjs", which this build never sets.
  • shell-quote (2): reached through glslify's Node transform, and glslify's browser field maps to a stub, so it stays out of the bundle on both sides.

Residual low, 20/100; inherent high 80; 60 points retired. Not in KEV.

What would change these: exposing the build to third-party input, such as building untrusted branches on a shared runner, or bundling one of these packages into shipped code. Either would make the Exposure factor present.

JavaScript: shipped in the browser bundle, vulnerable path not reachable (42 alerts)

These packages do reach the browser, and the deferral rests on the specific path being unreachable rather than the package being absent.

axios (25), all in the viz-lib copy at 0.28.0. The single call site is viz-lib/src/visualizations/choropleth/hooks/useLoadGeoJson.ts:20, which calls axios.get(mapUrl) in the browser. That means the browser XHR adapter is the code path and the Node HTTP adapter never runs, which retires eleven advisories about proxy handling, NO_PROXY, redirect following, stream limits, and Proxy-Authorization outright. No baseURL is set on that instance, so the absolute-URL override advisory describes the intended behaviour with no credential to leak. Nine more are prototype-pollution gadgets, which need something to write a key onto Object.prototype; the only pollution primitive present in this bundle is lodash's _.omit and _.unset, and that advisory states it permits deletion of prototype properties but not overwriting their behavior, so it cannot add the key a gadget requires. The remaining five are reached only with configuration, parameters, or a cookie name that this project supplies.

dompurify (16): fourteen from the viz-lib copy and two from the application copy. Each of the fourteen needs a configuration option that redash never sets, verified line by line against viz-lib/src/services/sanitize.ts and client/app/services/sanitize.js, which are identical and set only ADD_ATTR: ["target"] plus one afterSanitizeAttributes hook touching target and rel. The options these advisories require and redash never sets are SAFE_FOR_TEMPLATES, RETURN_DOM, IN_PLACE, USE_PROFILES, ADD_TAGS, FORBID_TAGS, CUSTOM_ELEMENT_HANDLING, RETURN_TRUSTED_TYPE, a hook that mutates allowedTags or ALLOWED_ATTR, and the predicate form of ADD_ATTR, where redash passes an array. The two on the application copy, GHSA-mmhx-hmjr-r674 and GHSA-p3vf-v8qc-cwcr, are prototype-pollution gadgets, deferred on the same delete-only-primitive argument as the axios gadgets.

lodash (1): GHSA-r5fr-rjxr-66jc, the _.template code injection. The advisory's precondition is untrusted input used as options.imports key names. The one call site, client/app/pages/alert/Alert.jsx:27, uses a static literal template and passes no options.imports at all. The advisory also notes that a polluted Object.prototype would have its keys copied into the imports object, but the only pollution primitive in this bundle deletes rather than adds, so that route is closed too.

Residual low, 20/100; inherent high 80; 60 points retired. Not in KEV.

What would change these: for DOMPurify, setting any of the named configuration options; for axios, adding a Node-side caller or a baseURL; for lodash, passing user input as options.imports keys. Each would make Exposure present, and with Impact already present the base bucket would move to SCHEDULE.

The boundary cases worth monitoring

Three deferred alerts sit in the EPSS gray zone between the p90 and p99 percentiles, high enough to watch and below the p99 top-1% line the rubric treats as Threat evidence: CVE-2021-23337 at p0.974, covering npm:lodash:GHSA-r5fr-rjxr-66jc, npm:lodash.template:GHSA-35jh-r3h4-6jhm, and npm:lodash.template:GHSA-r5fr-rjxr-66jc. One further deferred finding sits just below that band: werkzeug CVE-2024-34069 at p0.878, the debugger flaw, deferred because gunicorn serves the application with no debugger installed.

If EPSS retrains any of these above p0.99, or CISA or VulnCheck adds one to KEV, the Threat factor becomes present. For the three lodash findings, whose Impact factor is already present, that alone moves the base bucket from DEFER to SCHEDULE, and a KEV listing would additionally activate the kev_emergency floor. The six undetermined findings in the fourth group above change on different evidence: three deployment facts, named in that group, which would resolve their Exposure factor in either direction.

Six deferred alerts have no threat record at all and cannot be monitored by percentile: they are the DEFER-side verdicts of the three REVIEW advisories in redash-build, plus the two cryptography and one sqlparse companions. A CVE assignment on any of them would give KEV and EPSS something to report.

Next Steps

Investigate before deciding (3 alerts). These three are the only place the analysis could not finish, and none of them should be dismissed.

  1. pypi:cryptography:GHSA-537c-gmf6-5ccf: read the referenced OpenSSL advisory of 2026-06-09 to learn which function is affected, then determine whether redash reaches it. Missing evidence: both Threat, no CVE assigned, and reachability, no symbol named. Upgrading to cryptography 48.0.1 or later resolves it without the investigation.
  2. pypi:sqlparse:GHSA-27jp-wm6q-gp25: reachability is established through redash/handlers/queries.py:66. Missing evidence: Threat only, because no CVE is assigned. Decide whether an authenticated-user-triggered hang in the SQL formatter warrants action here. sqlparse 0.5.4 carries the fix.
  3. npm:markdown:GHSA-wx77-rp39-c6vg: reachability is established through the dashboard text widgets. Missing evidence: Threat only. No fixed version exists, and the advisory recommends an alternative package, so this one is a decision rather than an upgrade.

Schedule by SLA (26 alerts). All 26 resolve to the same 60-calendar-day window, and no expiry date can be computed until the alert opened dates are available. Four upgrades clear 24 of the 26:

  • urllib3 to 2.6.3 or later clears six alerts across three advisories in redash-worker.
  • webpack-dev-server to 5.2.6 or later clears all six developer-machine findings.
  • DOMPurify to 3.3.2 or later on both copies clears four alerts, including CVE-2025-15599, whose 2.x branch was never patched.
  • lodash to 4.18.0 clears the two _.omit prototype-pollution alerts, and the deferred _.template finding with them.

The remaining alerts are idna to 3.15, requests to 2.32.4, Flask to 3.1.3, and Werkzeug to 3.0.6.

No action needed (243 alerts). Annotate each with its deferral rationale in the tracking system so the evidence travels with the alert. The dismissal-commands appendix carries per-group rationale text sized to fit a 280-character comment field.

One thing to add to the project. Three deployment facts would convert six undetermined verdicts into determined ones on the next run: whether an Azure Data Explorer data source is configured, whether an HTTP proxy is configured for the worker's outbound calls, and how cryptography is installed, from PyPI wheels or from source. Recording them in .k9security/risk-context.yaml, which gather_risk_context will help author, removes the largest remaining source of uncertainty in this report.

Appendix: Triage metadata

This appendix is for the reviewer auditing how these verdicts were produced: who and what produced them, from what inputs, with what caveats.

Project: redash
Date: 2026-08-16
Rubric: k9 Reachable Risk scoring rubric 2026.08.13-v26, workflow 2026.08.12-v33
Scored and reported by: claude-opus-5
Risk context: 2026-07-30+87af4dc
SLA: SafeCo Information Security Policy ISP-07 v1.0, calendar days
Findings scored: 272 alerts (664 context-specific verdicts)

Each alert was scored once per execution context whose declared paths match its manifest, producing one verdict per context. The 120 poetry.lock alerts each bind to three contexts, redash-server, redash-worker, and redash-build, and the 152 npm alerts each bind to two, redash-browser-bundle and redash-build. A reader counting rows in the detail sections will therefore find more verdicts than alerts, which is expected rather than a discrepancy.

  • Count verification: the open-alert list was supplied already fetched from the system of record and contained 272 records. All 272 were scored across 30 score_risk calls, and batch_stats.alerts_total summed to 272 against the supplied count. Alerts were not grouped per advisory before scoring: every raw alert was scored under its own finding_key, reused verbatim as supplied. The 664 verdicts reconcile as 120 × 3 plus 152 × 2. batch_stats.findings_without_context_binding was empty on every call, and default_context_fraction was 0, so no alert fell back to a default asset context.
  • Quality flags: undetermined_above_10_percent fired on three of the 30 batches, at 11%, 50%, and 11%. Across the whole run 13 of 664 verdicts are undetermined, 2.0%, so the flag reflects batch composition rather than a systematic gap. Each undetermined verdict is disclosed by name in the fourth DEFER group and in the REVIEW section, with the specific fact that would resolve it. risk_context_version_omitted was false on every call, and distinct_basis_tokens was 1, meaning the basis-selection procedure was read once and applied consistently across the run.
  • Unbound alerts: none. Every alert matched at least one execution context declared in .k9security/risk-context.yaml.
  • Policy directives: SafeCo ISP-07 v1.0 supplied the residual-risk-to-SLA mapping, the calendar-day basis, and the clock rule that dates from the alert's opened date in the tracking system. Those are the values this report used. The policy also states that exceptions require written CISO approval with a compensating-control justification and an expiry of twelve months or less; that is recorded here as a reported claim about the customer's process, and it did not affect any verdict, bucket, reachability call, or NIST value.

Appendix: Risk scores (NIST SP 800-30)

This appendix is for the reviewer and the engineer reconciling policy: every finding's scores, drivers, and deadlines.

Each verdict carries a rendering of the finding on the NIST SP 800-30 scale, split into two values.

  • Inherent risk: how bad this flaw would be if you assumed the worst, with attackers actively exploiting it and your code running the vulnerable path.
  • Residual risk: what risk remains after checking the two things that usually are not true, whether attackers are really exploiting it and whether your code can even reach the vulnerability. This is the risk you are carrying now, and it is the number this report uses.
  • Retired risk: the risk the analysis ruled out, measured as how far the number fell from inherent to residual.

Across this scan the reachability and exploitability analysis retired 38,400 NIST-representative risk points of 53,120 inherent, 72% of this project's inherent dependency risk, safely deferred 243 of 272 alerts, and moved 622 of 664 verdicts from a High inherent band to a Low residual band, with a further 36 falling from High to Moderate and 6 holding at High where the evidence was incomplete.

The verdict is the action signal; the scores are risk context. Where a residual band sits above its verdict, as it does for the six undetermined DEFER findings at Moderate, that gap is expected rather than a contradiction. When mapping to a priority scheme, anchor priority on the verdict and use the residual score as context or a tiebreaker.

On the SLA column. SafeCo ISP-07 keys on the residual band and resolves cleanly against the NIST scale: High 80–95 gives 30 calendar days, Moderate 21–79 gives 60, Low 1–20 gives 180. The policy starts its clock on the date each alert was opened in the tracking system. The supplied alert list carries no opened date, so no expiry is computable and every cell reads ?. The resolvable half of the mapping, the window length, is stated per row so the deadlines can be computed as soon as the opened dates are available.

Rows are ordered by residual composite, highest first. Alerts that share a vuln_id, verdict, and evidence are presented as one row, with the finding keys listed. Where a finding's verdict differs by execution context, the row carries the most urgent verdict and names the contexts.

Findings with a verdict above DEFER

Vuln ID Verdict Driver Residual risk Residual Inherent Retired SLA Expiry
GHSA-537c-gmf6-5ccf REVIEW missing_evidence_floor; threat + exposure unknown High 80 (low conf) 80 0 ? (30d window)
GHSA-27jp-wm6q-gp25 REVIEW missing_evidence_floor; threat unknown, reachable (traced) High 80 (low conf) 80 0 ? (30d window)
GHSA-wx77-rp39-c6vg REVIEW missing_evidence_floor; threat unknown, reachable (traced) High 80 (low conf) 80 0 ? (30d window)
CVE-2026-21441 SCHEDULE reachable (traced), redash-worker Moderate 50 80 30 ? (60d window)
CVE-2025-13465 SCHEDULE reachable (traced), redash-browser-bundle Moderate 50 80 30 ? (60d window)
CVE-2024-49767 SCHEDULE reachable (traced), redash-server Moderate 50 80 30 ? (60d window)
CVE-2024-47875 SCHEDULE reachable (traced), redash-browser-bundle Moderate 50 80 30 ? (60d window)
CVE-2024-47081 SCHEDULE reachable (traced), redash-worker Moderate 50 80 30 ? (60d window)
CVE-2025-66471 SCHEDULE reachable (traced), redash-worker Moderate 50 80 30 ? (60d window)
CVE-2025-66418 SCHEDULE reachable (traced), redash-worker Moderate 50 80 30 ? (60d window)
CVE-2026-45409 SCHEDULE reachable (traced), redash-worker Moderate 50 80 30 ? (60d window)
CVE-2025-30359 SCHEDULE reachable (traced), redash-build Moderate 50 80 30 ? (60d window)
CVE-2026-14631 SCHEDULE reachable (traced), redash-build Moderate 50 80 30 ? (60d window)
CVE-2026-27205 SCHEDULE reachable (traced), redash-server Moderate 50 80 30 ? (60d window)
CVE-2026-0540 SCHEDULE reachable (traced), redash-browser-bundle Moderate 50 80 30 ? (60d window)
CVE-2025-30360 SCHEDULE reachable (traced), redash-build Moderate 50 80 30 ? (60d window)
CVE-2025-15599 SCHEDULE reachable (traced), redash-browser-bundle Moderate 50 80 30 ? (60d window)
CVE-2026-6402 SCHEDULE reachable (traced), redash-build Moderate 50 80 30 ? (60d window)
CVE-2026-65914 SCHEDULE reachable (traced), redash-browser-bundle Moderate 50 80 30 ? (60d window)
CVE-2026-9595 SCHEDULE reachable (traced), redash-build Moderate 50 80 30 ? (60d window)
CVE-2026-14620 SCHEDULE reachable (traced), redash-build Moderate 50 80 30 ? (60d window)

Finding keys for the rows above, in the same order: pypi:cryptography:GHSA-537c-gmf6-5ccf; pypi:sqlparse:GHSA-27jp-wm6q-gp25; npm:markdown:GHSA-wx77-rp39-c6vg; pypi:urllib3:PYSEC-2026-1996 and pypi:urllib3:GHSA-38jv-5279-wg99; npm:lodash:GHSA-f23m-r3pf-42rh and npm:lodash:GHSA-xxjr-mmjv-4gpg; pypi:werkzeug:PYSEC-2026-3417 and pypi:werkzeug:GHSA-q34m-jh98-gwm2; npm:dompurify:GHSA-gx9m-whjm-85jf; pypi:requests:PYSEC-2026-1872 and pypi:requests:GHSA-9hjg-9r4m-mvj7; pypi:urllib3:PYSEC-2026-1994 and pypi:urllib3:GHSA-2xpw-w6gg-jr37; pypi:urllib3:PYSEC-2026-1998 and pypi:urllib3:GHSA-gm62-xv2j-4w53; pypi:idna:PYSEC-2026-215 and pypi:idna:GHSA-65pc-fj4g-8rjx; npm:webpack-dev-server:GHSA-4v9v-hfq4-rm2v; npm:webpack-dev-server:GHSA-m28w-2pqf-7qgj; pypi:flask:PYSEC-2026-2151 and pypi:flask:GHSA-68rp-wp8r-4726; npm:dompurify:GHSA-v2wj-7wpq-c8vv; npm:webpack-dev-server:GHSA-9jgg-88mc-972h; npm:dompurify:GHSA-v8jm-5vwx-cfxm; npm:webpack-dev-server:GHSA-79cf-xcqc-c78w; npm:dompurify:GHSA-h8r8-wccr-v5f2; npm:webpack-dev-server:GHSA-mx8g-39q3-5c79; npm:webpack-dev-server:GHSA-f5vj-f2hx-8m93.

Deferred findings at Moderate residual

These six carry a higher residual than the other deferrals because their Exposure factor is undetermined and projects conservatively.

Vuln ID Verdict Driver Residual risk Residual Inherent Retired SLA Expiry
CVE-2024-12797 DEFER undetermined; threat absent (p0.832) Moderate 50 (med conf) 80 30 ? (60d window)
CVE-2026-21226 DEFER undetermined; threat absent (p0.537) Moderate 50 (med conf) 80 30 ? (60d window)
CVE-2026-44431 DEFER undetermined; threat absent (p0.261) Moderate 50 (med conf) 80 30 ? (60d window)

Finding keys: pypi:cryptography:PYSEC-2026-1284 and pypi:cryptography:GHSA-79v4-65xg-pq4g, undetermined in all three Python contexts; pypi:azure-core:PYSEC-2026-1208 and pypi:azure-core:GHSA-jm66-cg57-jjv5, undetermined in redash-worker and Low/20 elsewhere; pypi:urllib3:PYSEC-2026-141 and pypi:urllib3:GHSA-qccp-gfcp-xxvc, undetermined in redash-worker and Low/20 elsewhere.

Deferred findings at Low residual

The remaining 237 deferred alerts all carry the same scores: residual Low, 20/100, inherent High, 80, 60 points retired, high confidence, and an SLA of ? against a 180-calendar-day window. Rather than repeat 237 identical score rows, they are listed below by package with their drivers, which is where the variation actually is. Every finding key is accounted for.

Package (alerts) Manifest Driver Advisories
authlib (20) poetry.lock threat+exposure absent; trusted IdP input or unreachable PYSEC-2024-52, GHSA-5357-c2jx-v7qh, PYSEC-2026-1200, GHSA-9ggr-2464-2j32, PYSEC-2026-1202, GHSA-g7f3-828f-7h7m, PYSEC-2026-1203, GHSA-pq5p-34cr-23v9, PYSEC-2026-188, GHSA-r95x-qfjj-fjj2, PYSEC-2026-2116, GHSA-7432-952r-cw78, PYSEC-2026-2117, GHSA-m344-f55w-2m6j, PYSEC-2026-2119, GHSA-w8p2-r796-3vmq, PYSEC-2026-25, GHSA-jj8c-mmj3-mmgv, PYSEC-2026-287, GHSA-wvwj-cvrp-7pv5
werkzeug (10) poetry.lock unreachable from entrypoint PYSEC-2026-2043, GHSA-2g68-c3qc-8985, PYSEC-2026-2044, GHSA-87hc-874c-8fpv, PYSEC-2026-2045, GHSA-f9vj-2wh5-fj8j, PYSEC-2026-2046, GHSA-hgf8-9c2m-6f66, PYSEC-2026-2320, GHSA-29vq-cq3j-8g5m
pyjwt (9) poetry.lock unreachable from entrypoint; one first-party input PYSEC-2025-183, PYSEC-2026-120, GHSA-752w-5fwx-jx9f, PYSEC-2026-175, GHSA-993g-76c3-p5m4, PYSEC-2026-177, GHSA-fhv5-28vv-h8m8, PYSEC-2026-179, GHSA-xgmm-8j9v-c9wx
snowflake-connector-python (6) poetry.lock admin-supplied connection config only PYSEC-2025-26, GHSA-2vpq-fh52-j3wv, PYSEC-2025-27, GHSA-m4f6-vcj4-w5mx, PYSEC-2025-28, GHSA-r2x6-cjg7-8r43
pyasn1 (6) poetry.lock first-party / trusted trust-store input PYSEC-2026-1810, GHSA-63vm-454h-vhhq, PYSEC-2026-2263, GHSA-jr27-m4p2-rc6r, GHSA-8ppf-4f7h-5ppj, GHSA-hm4w-wwcw-mr6r
cryptography (4) poetry.lock unreachable from entrypoint PYSEC-2026-2141, PYSEC-2026-35, GHSA-m959-cc7f-wv43, GHSA-r6ph-v2qm-q3c2
filelock (4) poetry.lock unreachable (SoftFileLock not used) PYSEC-2026-1374, GHSA-qmgc-5h2g-mvrw, PYSEC-2026-1375, GHSA-w853-jp5j-5j7f
mako (4) poetry.lock unreachable from entrypoint PYSEC-2026-2617, GHSA-2h4p-vjrc-8xpq, PYSEC-2026-88, GHSA-v92g-xgxw-vvmm
pyopenssl (4) poetry.lock unreachable from entrypoint PYSEC-2026-2268, GHSA-vp96-hxj8-p424, PYSEC-2026-2269, GHSA-5pwr-322w-8jr4
setuptools (4) poetry.lock unreachable; build-only first-party input PYSEC-2025-49, GHSA-5rjg-fvgr-3xxf, PYSEC-2026-3447, GHSA-h35f-9h28-mq5c
urllib3 (2) poetry.lock unreachable from entrypoint PYSEC-2026-1999, GHSA-pq67-6m6q-mj2v
requests (2) poetry.lock unreachable from entrypoint PYSEC-2026-2275, GHSA-gc5v-m9x4-r6x2
jinja2 (2) poetry.lock unreachable (no SandboxedEnvironment) PYSEC-2026-1471, GHSA-cpwx-vrp4-4pq7
h11 (2) poetry.lock unreachable from entrypoint PYSEC-2026-348, GHSA-vqfr-h8mv-ghfj
marshmallow (2) poetry.lock unreachable from entrypoint PYSEC-2026-1605, GHSA-428g-f7cq-pgp5
paramiko (2) poetry.lock admin-supplied connection config only PYSEC-2026-2858, GHSA-r374-rxx8-8654
pynacl (2) poetry.lock admin-supplied connection config only PYSEC-2026-3002, GHSA-mrfv-m5wm-5w6w
python-dotenv (2) poetry.lock unreachable from entrypoint PYSEC-2026-2270, GHSA-mf9w-mj56-hr94
jwcrypto (2) poetry.lock code not loaded (tests only) PYSEC-2026-70, GHSA-fjrm-76x2-c4q4
pygments (2) poetry.lock code not loaded (lazy ptpython import) PYSEC-2026-2987, GHSA-5239-wwwm-4pmq
pytest (2) poetry.lock code not loaded in production PYSEC-2026-1845, GHSA-6w46-j5rx-g56g
virtualenv (2) poetry.lock code not loaded in production PYSEC-2026-2009, GHSA-597g-3phw-6986
httplib2 (1) poetry.lock first-party input only PYSEC-2026-3444
msgpack (1) poetry.lock unreachable from entrypoint GHSA-6v7p-g79w-8964
click (1) poetry.lock unreachable from entrypoint PYSEC-2026-2132
axios (25) viz-lib/yarn.lock browser XHR adapter only; node paths and pollution gadgets unreachable GHSA-3g43-6gmg-66jw, GHSA-3p68-rc4w-qgx5, GHSA-42h9-826w-cgv3, GHSA-43fc-jf86-j433, GHSA-4hjh-wcwx-xvwj, GHSA-5c9x-8gcm-mpgx, GHSA-62hf-57xw-28j9, GHSA-6chq-wfr3-2hj9, GHSA-7q8q-rj6j-mhjq, GHSA-898c-q2cr-xwhg, GHSA-fvcv-3m26-pcqx, GHSA-hfxv-24rg-xrqf, GHSA-j5f8-grm9-p9fc, GHSA-jr5f-v2jv-69x6, GHSA-m7pr-hjqh-92cm, GHSA-mmx7-hfxf-jppx, GHSA-p92q-9vqr-4j8v, GHSA-pf86-5x62-jrwf, GHSA-pjwm-pj3p-43mv, GHSA-pmv8-rq9r-6j72, GHSA-pmwg-cvhr-8vh7, GHSA-vf2m-468p-8v99, GHSA-w9j2-pvgh-6h63, GHSA-xhjh-pmcv-23jw, GHSA-xx6v-rp6x-q39c
dompurify (16) viz-lib/yarn.lock, yarn.lock config option never set, or pollution gadget GHSA-39q2-94rc-95cp, GHSA-76mc-f452-cxcm, GHSA-c2j3-45gr-mqc4, GHSA-cj63-jhhr-wcxv, GHSA-cjmm-f4jc-qw8r, GHSA-cmwh-pvxp-8882, GHSA-crv5-9vww-q3g8, GHSA-h7mw-gpvr-xq4m, GHSA-hpcv-96wg-7vj8, GHSA-r47g-fvhr-h676, GHSA-rp9w-3fw7-7cwq, GHSA-vhxf-7vqr-mrjg, GHSA-vxr8-fq34-vvx9, GHSA-x4vx-rjvf-j5p4, GHSA-mmhx-hmjr-r674, GHSA-p3vf-v8qc-cwcr
tar (11) yarn.lock integrity-pinned registry tarballs only GHSA-23hp-3jrh-7fpw, GHSA-34x7-hfp2-rc4v, GHSA-83g3-92jg-28cx, GHSA-8qq5-rm4j-mr97, GHSA-8x88-c5mf-7j5w, GHSA-9ppj-qmqm-q256, GHSA-gvwx-54wh-qm9j, GHSA-qffp-2rhf-9h96, GHSA-r6q2-hw4h-h46w, GHSA-vmf3-w455-68vh, GHSA-w8wr-v893-vjvp
node-forge (7) yarn.lock generation path only; verification/parsing unreachable GHSA-2328-f5f3-gj25, GHSA-554w-wpv2-vw27, GHSA-5gfm-wpxj-wjgq, GHSA-5m6q-g25r-mvwx, GHSA-65ch-62r8-g69g, GHSA-ppp5-5v6c-4jwp, GHSA-q67f-28xg-22rw
follow-redirects (5) viz-lib/yarn.lock, yarn.lock build-only; not delivered to browsers GHSA-r4q5-vmmm-2653, GHSA-74fj-2j2h-c42q, GHSA-cxjh-pqwp-8mfp, GHSA-jchw-25xp-jwwc, GHSA-pw2r-vq6v-hr8c
@babel packages (5) viz-lib/yarn.lock, yarn.lock build-only; named-capture-group helper never emitted @babel/core GHSA-4x5r-pxfx-6jf8, @babel/helpers GHSA-968p-4wvh-cqc8, @babel/runtime GHSA-968p-4wvh-cqc8, @babel/runtime-corejs3 GHSA-968p-4wvh-cqc8, @babel/plugin-transform-modules-systemjs GHSA-fv7c-fp4j-7gwp
postcss (4) viz-lib/yarn.lock build-only; first-party stylesheets GHSA-566m-qj78-rww5, GHSA-7fh5-64p2-3v2j, GHSA-qx2v-qp2m-jg93, GHSA-hwj9-h5mp-3pm3
brace-expansion (3) viz-lib/yarn.lock build-only; first-party glob patterns GHSA-3jxr-9vmj-r5cp, GHSA-f886-m6hf-6m8v, GHSA-v6h2-p8h4-qcjw
minimatch (3) viz-lib/yarn.lock build-only; first-party glob patterns GHSA-23c5-xmqv-rm74, GHSA-3ppc-4f35-3m26, GHSA-7r86-cg39-jmmj
js-yaml (3) viz-lib/yarn.lock build-only; first-party config GHSA-52cp-r559-cp3m, GHSA-h67p-54hq-rp68, GHSA-mh29-5h37-fv8m
serialize-javascript (3) viz-lib/yarn.lock build-only; terser output GHSA-5c6j-r48x-rmvq, GHSA-76p7-773f-r4q5, GHSA-qj8w-gfj5-8c6v
http-proxy-middleware (3) yarn.lock router option unused; own backend only GHSA-4www-5p9h-95mh, GHSA-64mm-vxmg-q3vj, GHSA-9gqv-wp59-fq42
qs (3) viz-lib/yarn.lock, yarn.lock build-only; first-party query strings GHSA-6rw7-vpxm-498p, GHSA-w7fw-mjwx-w883, GHSA-q8mj-m7cp-5q26
tough-cookie (3) viz-lib/yarn.lock, yarn.lock build-only; test-harness cookies GHSA-72xf-g2v4-qvf3, GHSA-g7q5-pjjr-gqvp, GHSA-qhv9-728r-6jqg
launch-editor (2) yarn.lock Windows-only; toolchain is Linux GHSA-c27g-q93r-2cwf, GHSA-v6wh-96g9-6wx3
shell-quote (2) viz-lib/yarn.lock glslify browser field stubs it out GHSA-395f-4hp3-45gv, GHSA-w7jw-789q-3m8p
picomatch (2) viz-lib/yarn.lock build-only; first-party glob patterns GHSA-3v7f-55p6-f55p, GHSA-c2c7-rcm5-vvqj
flatted (2) viz-lib/yarn.lock build-only; eslint cache GHSA-25h7-pfq9-p65f, GHSA-rf6f-7fwh-wjgh
form-data (2) viz-lib/yarn.lock build-only; test harness GHSA-fjxv-7rqg-78g4, GHSA-hmw2-7cc7-3qxx
webpack (2) viz-lib/yarn.lock build-only; first-party source GHSA-38r7-794h-5758, GHSA-8fgc-7cc6-rx7x
ws (2) viz-lib/yarn.lock, yarn.lock build-only; jsdom and dev server GHSA-96hv-2xvq-fx4p, GHSA-58qx-3vcg-4xpx
websocket-driver (2) yarn.lock build-only; dev-server traffic GHSA-mp7j-qc5w-4988, GHSA-xv26-6w52-cph6
tar-fs (2) yarn.lock integrity-pinned archives only GHSA-8cj5-5rvv-wf4v, GHSA-vj76-c3g6-qr5v
tmp (2) yarn.lock build-only; first-party temp files GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65
path-to-regexp (2) yarn.lock build-only; express in dev server GHSA-37ch-88jc-xwx2, GHSA-rhx6-c78j-4q9w
lodash.template (2) yarn.lock build-only; static templates in tooling GHSA-35jh-r3h4-6jhm, GHSA-r5fr-rjxr-66jc
bootstrap (2) yarn.lock code not loaded (LESS imported, JS not) GHSA-q58r-hwc8-rm9j, GHSA-vxmc-5x29-h64v
elliptic (2) yarn.lock code not loaded (declared, never imported) GHSA-848j-6mx2-7j84, GHSA-vjh7-7g9h-fjfh
request family (2) viz-lib/yarn.lock, yarn.lock build-only; jsdom and cypress request GHSA-p8p7-x288-28g6, @cypress/request GHSA-p8p7-x288-28g6
lodash (1) viz-lib/yarn.lock _.template called with a static literal, no options.imports GHSA-r5fr-rjxr-66jc
bn.js (1) yarn.lock code not loaded (arrives via unimported elliptic) GHSA-378v-28hj-76wf
protocol-buffers-schema (1) viz-lib/yarn.lock code not loaded (pbf/bin/pbf CLI only) GHSA-j452-xhg8-qg39
ajv (1) viz-lib/yarn.lock $data option never enabled GHSA-2g4f-4pwh-qvx6
axios 0.21.4 (1) yarn.lock build-only via @percy/agent GHSA-wf5p-g6vw-rhxx
babel-traverse (1) yarn.lock build-only; first-party source GHSA-67hx-6x53-jw92
body-parser (1) yarn.lock build-only; dev-server traffic GHSA-v422-hmwv-36x6
braces (1) viz-lib/yarn.lock build-only; first-party glob patterns GHSA-grv7-fg5c-xmjg
cookie (1) yarn.lock build-only; dev-server traffic GHSA-pxg6-pf52-xh8x
cross-spawn (1) viz-lib/yarn.lock build-only; first-party commands GHSA-3xgq-45jj-v275
micromatch (1) viz-lib/yarn.lock build-only; first-party glob patterns GHSA-952p-6rrq-rcjv
node-notifier (1) viz-lib/yarn.lock build-only; jest reporter GHSA-5fw9-fq32-wv5p
nth-check (1) viz-lib/yarn.lock build-only; cheerio in tests GHSA-rp65-9cf3-cjxr
on-headers (1) yarn.lock build-only; dev-server traffic GHSA-76c9-3jph-rj3q
uuid (1) viz-lib/yarn.lock build-only; test harness GHSA-w5hq-g745-h8pq

Package counts in this table sum to 237, which with the 6 Moderate-residual deferrals gives the DEFER total of 243.

Appendix: Alert dismissal commands

This appendix is for the operator executing the deferrals: the ready-to-run record.

No dismissal command can be emitted for this project. The supplied alert list carries finding_key, vuln_id, cve_id, package, ecosystem, installed_version, and manifest_path, but no system-of-record alert number or URL, and a dismissal API call needs that identifier. Rather than guess one, the rationale text is provided below for an operator to paste against each alert in whichever tool holds it.

The durable record of these verdicts lives in two places regardless: this report, and k9's scored-findings capture corpus, where each of the 664 verdicts was persisted under rubric_version 2026.08.13-v26 and risk_context_version 2026-07-30+87af4dc at scoring time.

Each rationale below is within GitHub Dependabot's 280-character dismissed_comment limit and states the reachability basis once.

Python, not imported by the running services (8 alerts):

Not exploitable here. Not in KEV; EPSS well below top 1%. Basis: code_not_loaded. Package is installed but never imported by redash-server or redash-worker; it runs only in the build, on this project's own source. k9 rubric 2026.08.13-v26, risk context 2026-07-30+87af4dc.

Python, no entry point reaches the vulnerable function (64 alerts):

Not exploitable here. Not in KEV; EPSS below top 1%. Basis: unreachable_from_entrypoint. The package is loaded, but the vulnerable function has no caller reachable from any redash entry point. k9 rubric 2026.08.13-v26, risk context 2026-07-30+87af4dc.

Python, called only with first-party or trusted-actor input (26 alerts):

Not exploitable here. Not in KEV; EPSS below top 1%. Basis: no_attacker_controlled_input. The function runs, but every input reaching it comes from this project's config or an actor the risk context declares trusted. k9 rubric 2026.08.13-v26, risk context 2026-07-30+87af4dc.

Python, reachability undetermined, threat absent (6 alerts):

Deferred on threat, not reachability. Not in KEV; EPSS below top 1%. Basis: undetermined -- resolving it needs a deployment fact this repo does not record. Residual Moderate/50, medium confidence. Revisit if a KEV listing appears. k9 rubric 2026.08.13-v26.

JavaScript, declared but never imported (6 alerts):

Not exploitable here. Not in KEV; EPSS below top 1%. Basis: code_not_loaded. The package is declared but no source file imports it, so it reaches neither the browser bundle nor any build step. k9 rubric 2026.08.13-v26, risk context 2026-07-30+87af4dc.

JavaScript, build and test tooling (91 alerts):

Not exploitable here. Not in KEV; EPSS below top 1%. Basis: code_not_loaded in the browser bundle. Build-time tooling only; in the build it sees only this project's own source, config, fixtures, and integrity-pinned registry tarballs. k9 rubric 2026.08.13-v26.

JavaScript, shipped but vulnerable path unreachable (42 alerts):

Not exploitable here. Not in KEV; EPSS below top 1%. Basis: unreachable_from_entrypoint. The package ships, but this advisory needs a config option, adapter, or input this app never uses; verified against the call sites. k9 rubric 2026.08.13-v26, risk context 2026-07-30+87af4dc.

The three REVIEW findings are deliberately absent from this appendix. They must not be dismissed.