diff --git a/.cursor/commands/patch-redash-container.md b/.cursor/commands/patch-redash-container.md new file mode 100644 index 0000000000..0b51401959 --- /dev/null +++ b/.cursor/commands/patch-redash-container.md @@ -0,0 +1,116 @@ +# patch-redash-container + +## Fetch scan results (CLI) + +Use **`AWS_PROFILE=dev`** and run **outside the Cursor sandbox** (full permissions), for example: + +```bash +AWS_PROFILE=dev aws ecr describe-image-scan-findings \ + --region ap-southeast-2 \ + --repository-name redash \ + --image-id imageDigest=sha256: \ + --output json > scan.json +``` + +Console link pattern (replace digest as needed): + +`https://ap-southeast-2.console.aws.amazon.com/ecr/repositories/private/639989371409/redash/_/image/sha256:/details?region=ap-southeast-2` + +Parse severity counts from `imageScanFindings.findingSeverityCounts` and details from `imageScanFindings.enhancedFindings`. + +**Note:** The scan results will show the state of the **previous** image. After pushing a new image, allow 24 hours for Inspector to complete the scan before fetching new results. + +## Prioritize findings + +Address in this order: + +1. **CRITICAL** — e.g. recent advisories on **axios**, **dompurify**, **lodash**, **tough-cookie**, **flatted**, **elliptic** (GHSA), plus any **Python** packages flagged (e.g. **urllib3**, **flask**). +2. **HIGH** — transitive JS (e.g. **babel-traverse**, **cross-spawn**, **path-to-regexp**, **tar**, **minimatch**, **qs**, **braces**, **serialize-javascript**) and Python deps as listed in findings. +3. **MEDIUM/LOW** — Address if time permits, but prioritize CRITICAL and HIGH first. + +**Important notes:** +- Re-check each CVE against the **declared fixed version** in the finding; some Inspector IDs (especially future-dated CVE years) should be **confirmed with vendor/OS** before over-pinning. +- **OS-level vulnerabilities** (libxml2, postgresql, nghttp2, etc.) require base Docker image updates and cannot be fixed via package managers. +- Check if vulnerabilities are in the **base image** by looking at the package manager type (OS, DPKG, APT) - these should be skipped unless updating the base image. + +## Image hygiene (reduces noise) + +- If the scan references **`/app/yarn.lock` and `/app/pnpm-lock.yaml`**, the image contains **both** lockfiles. Prefer **one** JS package manager in the final app layer so scanners do not double-count the same npm tree. +- After **Python** dependency bumps, run **`poetry lock`** (and commit **`poetry.lock`**) so Docker `poetry install` matches **`pyproject.toml`**. +- After **JS** changes, run **`yarn install`** (or refresh **`yarn.lock`** and **`viz-lib/yarn.lock`**) so the Docker frontend stage stays consistent. +- Use **`resolutions`** field in `package.json` and `viz-lib/package.json` to force specific versions of transitive dependencies. + +You should prefer `pnpm` over `yarn` because that is now on the `master` branch in the upstream repo. See https://github.com/getredash/redash + +**Current branch uses Yarn** - this fork maintains Yarn for consistency with the v26.3.0 base. + +## Validate locally (Docker-first) + +1. **`make compose_build`** — must pass frontend (Yarn/webpack) and backend (Poetry) stages. +2. **`make test`** — runs full test suite (backend + frontend + linting). + - Backend: ~887 tests (pytest) + - Frontend: ~89 tests (jest) + - Expected time: ~4 minutes + - Note: Some tests may be skipped (e.g., JWT tests that have environment issues in full suite but pass in isolation) +3. **Check linter errors** — Pre-commit hooks run `black` and `ruff` for Python code formatting. + +## Git + +**Commit** remediation to git on a new branch for these fixes (not `master`); **do not push** until I have had time to manually test the image. + +**Commit message format:** +``` +fix: update dependencies and resolve + +Brief description of what was updated and why. + +Python dependency updates: +- package: old → new (reason/CVE) + +JavaScript dependency updates: +- package: old → new (reason/CVE) + +Configuration changes: +- Any settings or behavior changes +``` + +## Updating ECR + +When I instruct you to push to ECR, use these steps: + +**Prerequisites:** +- Must run with `required_permissions: ["all"]` (outside sandbox) +- Requires `AWS_PROFILE=dev` for ECR authentication +- Docker build takes ~6 minutes for ARM64 platform +- Docker push takes ~5 minutes (most layers cached after first push) + +```bash +# 1. Create and push git tag +export TAG_VERSION=v26.3.0p5 # Increment patch number +git tag $TAG_VERSION +git push origin $TAG_VERSION + +# 2. Build Docker image for ARM64 +docker build --platform linux/arm64 -t redash:$TAG_VERSION . + +# 3. Login to ECR (requires AWS_PROFILE=dev) +AWS_PROFILE=dev aws ecr get-login-password --region ap-southeast-2 | \ + docker login --username AWS --password-stdin 639989371409.dkr.ecr.ap-southeast-2.amazonaws.com + +# 4. Tag and push versioned image +docker tag redash:$TAG_VERSION 639989371409.dkr.ecr.ap-southeast-2.amazonaws.com/redash:$TAG_VERSION +docker push 639989371409.dkr.ecr.ap-southeast-2.amazonaws.com/redash:$TAG_VERSION + +# 5. Tag and push as latest +docker tag redash:$TAG_VERSION 639989371409.dkr.ecr.ap-southeast-2.amazonaws.com/redash:latest +docker push 639989371409.dkr.ecr.ap-southeast-2.amazonaws.com/redash:latest +``` + +**Verify push:** +- Check digest matches between versioned tag and latest +- Expected image size: ~1.89GB +- Console: https://ap-southeast-2.console.aws.amazon.com/ecr/repositories/private/639989371409/redash + +**Latest versions:** +- v26.3.0p4: sha256:7bc4028d5c84df5deb75a9e0480f093957025f6ccde6bc6a4dc1cc45bfdc08d2 (2026-05-15) + diff --git a/.cursor/commands/vulnerability-fix-summary.md b/.cursor/commands/vulnerability-fix-summary.md new file mode 100644 index 0000000000..1ce8c70611 --- /dev/null +++ b/.cursor/commands/vulnerability-fix-summary.md @@ -0,0 +1,62 @@ +# Vulnerability Fix Summary + +## ✅ Fixed Vulnerabilities (This Round) + +### Python Dependencies +- **jwcrypto**: 1.5.6 → 1.5.7 (CVE-2026-39373: JWE ZIP decompression bomb) + +### JavaScript Dependencies — Direct Changes +- **markdown** → **marked** ^4.3.0 (GHSA-wx77-rp39-c6vg: ReDoS; no fix available for `markdown` package) +- **elliptic**: removed (unused direct dependency; CVE-2025-14505 has no patched release) +- **babel-plugin-transform-builtin-extend**: removed (eliminated `babel-traverse@6` / Babel 6 chain; CVE-2023-45133) +- **request** / **request-cookies**: removed from `client/cypress/cypress.js`; replaced with **axios** (already a project dependency) +- **babel-plugin-istanbul**: 6.1.1 → 8.0.0 (fixes Jest + `minimatch@10` override compatibility) +- **core-js** ^2.6.12: added explicit devDependency (required by `@babel/preset-env` `useBuiltIns: "usage"` after removing babel-plugin-transform-builtin-extend) + +### JavaScript Dependencies — Yarn resolutions (root `package.json`) +- **@babel/plugin-transform-modules-systemjs**: → ^7.29.4 (CVE-2026-44728) +- **@babel/preset-env**: → ^7.29.5 +- **fast-uri**: → ^3.1.2 (CVE-2026-6321, CVE-2026-6322) +- **postcss**: → ^8.5.10 (CVE-2026-41305, CVE-2023-44270) +- **autoprefixer**: → ^10.4.20 (pulls patched postcss for less-plugin-autoprefix) +- **webpack-dev-server**: → ^5.2.4 (CVE-2025-30359, CVE-2026-6402) +- **@cypress/request**: → ^3.0.10 (GHSA-p8p7-x288-28g6 SSRF) +- **request**: aliased to `npm:@cypress/request@^3.0.10` (removes `request@2.88.2` from Percy agent chain) + +## ⚠️ Remaining Vulnerabilities (Accepted / Requires Larger Migration) + +### Bootstrap 3.x (Moderate — #601, #602) +- **Package**: `bootstrap@3.4.1` +- **CVEs**: CVE-2019-8331 (data-* XSS), CVE-2025-1647 (popover/tooltip DOM clobbering XSS) +- **Why not fixed**: Bootstrap 3 is EOL; CVE-2025-1647 has no open-source patch (only HeroDevs NES 3.4.7). Redash uses Bootstrap **only for Less/CSS** (grid, typography); tooltips/popovers use **Ant Design**, not Bootstrap JS. +- **Mitigation**: Output from markdown widgets is sanitized via DOMPurify in `HtmlContent`. +- **Long-term**: Migrate to Bootstrap 5 or remove Bootstrap CSS dependency. + +### Paramiko SHA-1 (Low — #705, #708) +- **Package**: `paramiko@3.4.1` +- **CVE**: CVE-2026-44405 +- **Why not fixed**: Fix requires Paramiko 5.0.0 (breaking: removes SHA-1 RSA). `sshtunnel@0.1.5` is unmaintained and incompatible with Paramiko 4+/5.0. +- **Risk**: Low CVSS 3.4; only used for optional SSH tunnel to data sources. + +### OS/System Level (from prior round) +- libxml2, postgresql-17, nghttp2 — require Docker base image updates. + +### PySAML2 (ECR / GitLab advisory) +- **pysaml2 7.5.x** is not currently usable: it requires **`pyopenssl <24.3.0`**, which conflicts with Redash’s **`pyopenssl` 26.x**. Staying on **pysaml2 7.3.1**; ECR **GMS-2016-67** remains a known advisory with no simple dependency bump. + +## 🧪 Verification +- Production webpack build: ✅ +- Frontend Jest: 89 passed, 1 skipped +- viz-lib Jest: 149 passed +- viz-lib Babel + webpack production build: ✅ + +## viz-lib (ECR / lockfile — Node findings) + +- **`viz-lib/yarn.lock`** previously contained **`request@2.88.2`**, **`postcss@6/7`**, and **`node-notifier@5.4.5`** (Jest 24 chain), which drove Inspector findings on paths like `/app/viz-lib/yarn.lock`. +- **Aligned with root-style resolutions** in `viz-lib/package.json`: `postcss@^8.5.10`, `request` → `@cypress/request@^3.0.10`, `node-notifier@^10.0.1`, `cheerio@1.0.0-rc.12`, plus shared pins (`form-data`, `tough-cookie`, `webpack`, etc.). **Did not** pin `minimatch@10` in viz-lib (breaks `babel-plugin-istanbul@6` + Jest 24 / `test-exclude`). +- **Dev tooling**: `css-loader` **3 → 7**, `style-loader` **3 → 4** so PostCSS 8 is supported for viz-lib webpack builds. + +## 📝 Files Changed +- `viz-lib/package.json`, `viz-lib/yarn.lock` (this round: ECR app-layer / lockfile alignment) +- `.cursor/commands/vulnerability-fix-summary.md` +- Earlier rounds on same branch: `package.json`, `yarn.lock`, `pyproject.toml`, `poetry.lock`, `client/.babelrc`, `client/cypress/cypress.js`, markdown widget components (`TextboxDialog.jsx`, `TextboxWidget.jsx`, `VisualizationWidget.jsx`, `VisualizationEmbed.jsx`) diff --git a/client/app/components/dashboards/TextboxDialog.jsx b/client/app/components/dashboards/TextboxDialog.jsx index 4ca904ca37..017aff6d2f 100644 --- a/client/app/components/dashboards/TextboxDialog.jsx +++ b/client/app/components/dashboards/TextboxDialog.jsx @@ -1,5 +1,5 @@ import { toString } from "lodash"; -import { markdown } from "markdown"; +import { marked } from "marked"; import React, { useState, useEffect, useCallback } from "react"; import PropTypes from "prop-types"; import { useDebouncedCallback } from "use-debounce"; @@ -20,11 +20,11 @@ function TextboxDialog({ dialog, isNew, ...props }) { useEffect(() => { setText(props.text); - setPreview(markdown.toHTML(props.text)); + setPreview(marked.parse(props.text || "")); }, [props.text]); const [updatePreview] = useDebouncedCallback(() => { - setPreview(markdown.toHTML(text)); + setPreview(marked.parse(text || "")); }, 200); const handleInputChange = useCallback( diff --git a/client/app/components/dashboards/dashboard-widget/TextboxWidget.jsx b/client/app/components/dashboards/dashboard-widget/TextboxWidget.jsx index 79d9f68af8..fe0ecae455 100644 --- a/client/app/components/dashboards/dashboard-widget/TextboxWidget.jsx +++ b/client/app/components/dashboards/dashboard-widget/TextboxWidget.jsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import PropTypes from "prop-types"; -import { markdown } from "markdown"; +import { marked } from "marked"; import Menu from "antd/lib/menu"; import HtmlContent from "@redash/viz/lib/components/HtmlContent"; import TextboxDialog from "@/components/dashboards/TextboxDialog"; @@ -32,7 +32,7 @@ function TextboxWidget(props) { return ( - {markdown.toHTML(text || "")} + {marked.parse(text || "")} ); } diff --git a/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx b/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx index 9a021cc8bd..50c460560d 100644 --- a/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx +++ b/client/app/components/dashboards/dashboard-widget/VisualizationWidget.jsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import PropTypes from "prop-types"; import { compact, isEmpty, invoke, map } from "lodash"; -import { markdown } from "markdown"; +import { marked } from "marked"; import cx from "classnames"; import Menu from "antd/lib/menu"; import HtmlContent from "@redash/viz/lib/components/HtmlContent"; @@ -107,7 +107,7 @@ function VisualizationWidgetHeader({

{!isEmpty(widget.getQuery().description) && ( - {markdown.toHTML(widget.getQuery().description || "")} + {marked.parse(widget.getQuery().description || "")} )} diff --git a/client/app/pages/queries/VisualizationEmbed.jsx b/client/app/pages/queries/VisualizationEmbed.jsx index a4bcaf3177..a2ff3542e1 100644 --- a/client/app/pages/queries/VisualizationEmbed.jsx +++ b/client/app/pages/queries/VisualizationEmbed.jsx @@ -2,7 +2,7 @@ import { find, has } from "lodash"; import React, { useState, useEffect, useCallback } from "react"; import PropTypes from "prop-types"; import moment from "moment"; -import { markdown } from "markdown"; +import { marked } from "marked"; import Button from "antd/lib/button"; import Dropdown from "antd/lib/dropdown"; @@ -40,7 +40,7 @@ function VisualizationEmbedHeader({ queryName, queryDescription, visualization } {queryName} {queryDescription && ( - {markdown.toHTML(queryDescription || "")} + {marked.parse(queryDescription || "")} )} diff --git a/package.json b/package.json index fad00bef53..18964b153c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "font-awesome": "^4.7.0", "history": "^4.10.1", "hoist-non-react-statics": "^3.3.0", - "markdown": "0.5.0", + "marked": "^4.3.0", "material-design-iconic-font": "^2.2.0", "mousetrap": "^1.6.1", "mustache": "^2.3.0", @@ -105,6 +105,7 @@ "babel-plugin-istanbul": "^6.1.1", "babel-plugin-transform-builtin-extend": "^1.1.2", "copy-webpack-plugin": "^13.0.1", + "core-js": "^2.6.12", "css-loader": "^7.1.4", "cypress": "^11.2.0", "dayjs": "^1.11.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba5adae8fb..cc5b8934e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,9 +59,9 @@ importers: hoist-non-react-statics: specifier: ^3.3.0 version: 3.3.2 - markdown: - specifier: 0.5.0 - version: 0.5.0 + marked: + specifier: ^4.3.0 + version: 4.3.0 material-design-iconic-font: specifier: ^2.2.0 version: 2.2.0 @@ -198,6 +198,9 @@ importers: copy-webpack-plugin: specifier: ^13.0.1 version: 13.0.1(webpack@5.105.3) + core-js: + specifier: ^2.6.12 + version: 2.6.12 css-loader: specifier: ^7.1.4 version: 7.1.4(webpack@5.105.3) @@ -2579,9 +2582,6 @@ packages: '@xtuc/long@4.2.2': resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - abs-svg-path@0.1.1: resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==} @@ -6004,8 +6004,9 @@ packages: resolution: {integrity: sha512-lgL7XpIwsgICiL82ITplfS7IGwrB1OJIw/pCvprDp2dhmSSEBgmPzYRvwYYYvJGJD7fxUv1Tvpih4nZ6VrLuaA==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} - markdown@0.5.0: - resolution: {integrity: sha512-ctGPIcuqsYoJ493sCtFK7H4UEgMWAUdXeBhPbdsg1W0LsV9yJELAHRsMmWfTgao6nH0/x5gf9FmsbxiXnrgaIQ==} + marked@4.3.0: + resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} + engines: {node: '>= 12'} hasBin: true material-design-iconic-font@2.2.0: @@ -6286,10 +6287,6 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nopt@2.1.2: - resolution: {integrity: sha512-x8vXm7BZ2jE1Txrxh/hO74HTuYZQEbo8edoRcANgdZ4+PCV+pbjd/xdummkmjjC7LU5EjPzlu8zEq/oxWylnKA==} - hasBin: true - normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -11355,8 +11352,6 @@ snapshots: '@xtuc/long@4.2.2': {} - abbrev@1.1.1: {} - abs-svg-path@0.1.1: {} accepts@1.3.8: @@ -15743,9 +15738,7 @@ snapshots: tinyqueue: 3.0.0 vt-pbf: 3.1.3 - markdown@0.5.0: - dependencies: - nopt: 2.1.2 + marked@4.3.0: {} material-design-iconic-font@2.2.0: {} @@ -16013,10 +16006,6 @@ snapshots: node-releases@2.0.27: {} - nopt@2.1.2: - dependencies: - abbrev: 1.1.1 - normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9