# 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.
