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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .cursor/commands/patch-redash-container.md
Original file line number Diff line number Diff line change
@@ -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:<digest-from-console-url> \
--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:<digest>/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 <issue-type>

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)

62 changes: 62 additions & 0 deletions .cursor/commands/vulnerability-fix-summary.md
Original file line number Diff line number Diff line change
@@ -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`)
6 changes: 3 additions & 3 deletions client/app/components/dashboards/TextboxDialog.jsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -32,7 +32,7 @@ function TextboxWidget(props) {

return (
<Widget {...props} menuOptions={canEdit ? TextboxMenuOptions : null} className="widget-text">
<HtmlContent className="body-row-auto scrollbox t-body p-15 markdown">{markdown.toHTML(text || "")}</HtmlContent>
<HtmlContent className="body-row-auto scrollbox t-body p-15 markdown">{marked.parse(text || "")}</HtmlContent>
</Widget>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -107,7 +107,7 @@ function VisualizationWidgetHeader({
</p>
{!isEmpty(widget.getQuery().description) && (
<HtmlContent className="text-muted markdown query--description">
{markdown.toHTML(widget.getQuery().description || "")}
{marked.parse(widget.getQuery().description || "")}
</HtmlContent>
)}
</div>
Expand Down
4 changes: 2 additions & 2 deletions client/app/pages/queries/VisualizationEmbed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -40,7 +40,7 @@ function VisualizationEmbedHeader({ queryName, queryDescription, visualization }
<VisualizationName visualization={visualization} /> {queryName}
{queryDescription && (
<small>
<HtmlContent className="markdown text-muted">{markdown.toHTML(queryDescription || "")}</HtmlContent>
<HtmlContent className="markdown text-muted">{marked.parse(queryDescription || "")}</HtmlContent>
</small>
)}
</h3>
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
31 changes: 10 additions & 21 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.