Skip to content
Back to skills

Plans

ASecurity

**Braid strand:** bd-t8bwkr64 **Immediate motivation:** PR #637 ([Snyk] Upgrade katex from 0.18.2 to 0.18.4) is red on CI with the same failure signature as every previous katex Snyk PR.

  • 265 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 3, 2026
testingrustgoreactnodegitsecurity

Works with

  • cli

Security analysis

A92/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 21 files and shows the line behind each finding

Scanned September 3, 2026

npx -y skills add quarto-dev/q2 --skill plans --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Plans?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Plans
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/quarto-dev-plans-q2/badge)](https://www.skillsdirectory.com/skills/quarto-dev-plans-q2)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Snyk PR remediation skill

**Braid strand:** bd-t8bwkr64
**Immediate motivation:** PR #637 ([Snyk] Upgrade katex from 0.18.2 to 0.18.4) is red on CI with the same failure signature as every previous katex Snyk PR.

## Overview

Snyk integration is mandatory for the org and its bot PRs recur (5 merged so far:
katex #471, #571, #634; react #511; react-dom #512; #637 open now). Every katex
PR arrives red because Snyk edits exactly one file pair —
`hub-client/quarto-hub-sandboxed-preview/package.json` + its lockfile — while the
repo deliberately couples the KaTeX version across four surfaces, enforced by the
guard test `katex_cdn_version_matches_npm_pin`
(`crates/quarto-core/src/stage/stages/math_js.rs:1021`, bd-4b7f1hr7). The CI
system is working as designed; the goal is **not** to weaken it but to codify the
remediation workflow as a reusable skill (`/snyk-pr <PR#>`), so future bot PRs
can be made mergeable in one invocation.

This plan: create the skill, validate it by remediating #637 with it, and file
the small pieces of discovered work.

## What past remediations actually did (evidence)

Studied commits, most instructive first:

- **#634 (katex 0.18.1→0.18.2)** — `32ca70fa` (merge origin/main; snyk branches
  are cut from stale mains), `ccaa8cc9` (the full playbook, see below),
  `2ff292ec` (hub-client changelog two-commit workflow).
- **#571 (0.18.0→0.18.1)** — `3642d362` bumped root pin + CDN URL only. It
  **missed the committed sandbox bundle**, which had to be repaired in follow-up
  PR #573 ("refresh stale q2-sandboxed-preview bundle"). The skill exists to
  prevent exactly this class of partial fix.
- **#471 (0.17.0→0.18.0)** — `c0958658`, same shape as #571's fix.
- **#511/#512 (react, react-dom 19.2.7→19.2.8)** — paired-package upgrades
  arrive as **two PRs that conflict with each other**; after one merges, the
  other needs `origin/main` merged in and the version conflicts resolved by
  taking the new version of both packages (`91977474`).

### The katex playbook (from `ccaa8cc9`, verified against the current tree)

Four surfaces must name one exact version:

1. Root `package.json` (`"katex": "X.Y.Z"`, exact pin — no caret) + root
   `package-lock.json`. Bump via `npm install katex@X.Y.Z --save-exact` from the
   repo root (npm-workspaces root; never `npm install` inside hub-client).
2. `hub-client/quarto-hub-sandboxed-preview/package.json` + its lockfile.
   Snyk bumps this pair, **but writes `^X.Y.Z` into the lockfile's root
   dependency mirror while the package.json says `X.Y.Z`**. hub-client's
   postinstall runs `npm install` in that sub-project, which rewrites the caret
   away — merging without normalizing produces a dirty tree on every
   colleague's next install. Fix: run `npm install` in the sub-project (or
   `npm run build:sandboxed` from hub-client, which does it) and commit the
   lockfile delta.
3. `DEFAULT_KATEX_URL_BASE` in `crates/quarto-core/src/stage/stages/math_js.rs:85`
   (`https://cdn.jsdelivr.net/npm/katex@X.Y.Z/dist/`).
4. `hub-client/public/q2-sandboxed-preview.html` — a **committed** ~1.8 MB
   single-file bundle with KaTeX inlined. The guard test covers the three
   version *declarations*, not the bundled bytes, and there is no
   `git diff --exit-code` freshness gate for this artifact (unlike
   quarto-engine-host-deno). Regenerate with `cd hub-client && npm run
   build:sandboxed`; the rebuild is deterministic. Inspect the diff — for a
   pure version bump it should be ~2 bytes of version string; a larger delta
   means the bundle was already stale (as with #573) and deserves a callout in
   the commit message.

Verification for the katex case:
- `cargo nextest run -p quarto-core -E 'test(katex_cdn_version_matches_npm_pin)'`
- `grep -o "0\.18\.[0-9]" hub-client/public/q2-sandboxed-preview.html | sort | uniq -c`
  (all occurrences on the new version)
- `git status` clean after a fresh root `npm install` (dirty-tree trap, item 2)
- `cd hub-client && npm run build:all` (hub-client CI-strictness rule)

### Generic workflow shape (package-agnostic)

1. **Orient remotely**: `gh pr view N --json ...`, `gh pr checks N`, pull the
   failed job log and extract the failing test(s) — do not assume; #637's
   failure was confirmed as the guard test
   (`left: "0.18.2"` / `right: "0.18.4"`), but a future PR could fail
   differently.
2. **Fetch the bot branch** (`git fetch origin <snyk-upgrade-...>`); work on it
   directly — the bot pushes to the main repo, not a fork, so maintainers can
   push fix commits to the same branch (every prior remediation did).
   Use a worktree (`git worktree add`) to avoid disturbing the main checkout.
3. **Merge `origin/main`** into the branch first; snyk branches are routinely
   stale (the #637 branch predates the doc-branching merge `132c3ad3`). For
   paired-package PRs, expect and resolve version conflicts toward the new
   versions.
4. **Find every copy of the version**, not just the reported file:
   `grep -rn "<pkg>@\|\"<pkg>\":" --include='*.json' --include='*.rs'
   --include='*.ts' --include='*.html' .` (excluding node_modules/target),
   plus committed build artifacts embedding the package. This mirrors the
   path-resolution lesson: enumerate *consumers* of the version, per-package
   guard tests may lag reality.
5. **Apply the package playbook** (katex above; react/react-dom pairing; for an
   unknown package, the grep in step 4 + workspace build is the playbook).
6. **Verify**: targeted guard test, `cargo build --workspace` +
   `cargo nextest run --workspace` when Rust files changed,
   `cd hub-client && npm run build:all` when hub-client changed, per
   CLAUDE.md's pre-push checklist.
7. **Changelog**: if `hub-client/` files changed, the two-commit changelog
   workflow applies (see #634's `2ff292ec`).
8. **Push only with explicit user approval** (GIT PUSH POLICY), then watch
   `gh pr checks N --watch` and report. Merging stays a human decision.

## Skill design

- Location: `.claude/skills/snyk-pr/SKILL.md` + symlink
  `.agents/skills/snyk-pr -> ../../.claude/skills/snyk-pr` (matching the eight
  existing skills).
- Invocation: `/snyk-pr 637` (PR number required; with no argument, list open
  `posit-snyk-bot` PRs and stop).
- Structure: SKILL.md carries the generic workflow (steps 1–8 above);
  `references/katex.md` carries the katex four-surface playbook and
  `references/paired-packages.md` the react/react-dom pattern, so the
  main skill stays short and new per-package playbooks accrete as references.
- The skill must state the invariants explicitly: never weaken or skip the
  guard tests; never `npm install` from hub-client; exact pins (no carets);
  push requires user approval.
- Description/trigger phrases: "snyk PR", "posit-snyk-bot", "[Snyk] Upgrade",
  "snyk-upgrade-" branch names, "make the snyk PR mergeable".

## Work items

### Phase 1 — skill

- [x] Write `.claude/skills/snyk-pr/SKILL.md` (generic workflow) +
      `references/katex.md` + `references/paired-packages.md`; add the
      `.agents/skills/` symlink
- [x] Commit the skill (repo artifact, so colleagues' sessions get it)

### Phase 2 — validate on PR #637 (dogfood)

- [x] Follow the skill end-to-end in `.worktrees/snyk-pr-637`: merged
      origin/main (clean), bumped root pin + lockfile, bumped
      `DEFAULT_KATEX_URL_BASE` to 0.18.4, normalized the sub-project
      lockfile caret, regenerated `q2-sandboxed-preview.html`
      (~19-line delta — real 0.18.3/0.18.4 KaTeX code changes plus minifier
      renumbering, unlike the 2-byte 0.18.2 bump; lockfile diffs katex-only).
      Commits on the branch: `e4a72819` (alignment), `8d584e04` (changelog).
- [x] Verification battery: guard test passes; `cargo nextest run
      --workspace` 13483/13483; ts-packages + hub-client `build:all` green;
      fresh root `npm install` leaves the tree clean. hub-client `test:ci`
      fails in 2 files (29 tests) — **verified identical on origin/main**
      (TS Test Suite + E2E workflows are red on main at 9fc7c176);
      pre-existing, filed as bd-v51cly8i, unrelated to katex.
- [x] hub-client changelog two-commit update (`8d584e04`; `npm run
      test:wasm` gate passes)
- [x] Pushed (user-approved) and confirmed **fully green**: PR #637 at
      `eb024ad7` passes 10/10 checks, and main at `859d37ad` is green on
      all three workflows (Test Suite, TS Test Suite, Hub-Client E2E) —
      main had been red on the latter two since 132c3ad3. bd-v51cly8i
      closed. Remaining: user merges #637, then close bd-t8bwkr64 and
      bd-s36g9dav, and remove `.worktrees/snyk-pr-637`.
- [x] Fold learnings back into the skill: failure-attribution rule
      (compare against origin/main before blaming the upgrade) and
      out-of-gate suite check (preview-renderer `test:integration`)

### Phase 3 — discovered work

- [x] Triaged the three duplicate `.katex-tag` strands: bd-s36g9dav is
      canonical; bd-kn7ln981 and bd-6uyw7w2o marked `duplicates` of it.
      **Verified on the #637 branch that katex 0.18.4 fixes the failure**:
      preview-renderer `test:integration` fully green (632 passed / 1
      skipped; the Equation `\tag` tests 4/4). Close bd-s36g9dav once #637
      merges.
- [x] Filed bd-r3utxrdj: freshness gate for the committed
      `q2-sandboxed-preview.html` bundle (the #571/#573 incident class).
- [x] Filed bd-v51cly8i (discovered during Phase 2): TS Test Suite red on
      main — `useAutomergeSync.test.ts` mock missing `vfsAddFile` (28
      tests) + one BranchBar test; likely from the doc-branching work
      (132c3ad3).
- [x] **Fixed bd-v51cly8i** on main (`41da392c` + changelog `05fa2f1d`),
      three defects from 132c3ad3: the missing `vfsAddFile` mock export;
      BranchBar chips on `onMouseDown` (also broke keyboard switching and
      made delete-× switch branches in a real browser) → `onClick`; and
      the E2E spec never enabling the default-off `documentBranches`
      preference (the Hub-Client E2E redness). Verified: hub-client
      `test:ci` fully green, `branch-bar.spec.ts` passes locally against
      a real hub, `build:all` green. Strand closes after push + green CI.
- [x] **Fourth defect** (surfaced only when CI ran the pushed fix):
      `BranchBar.css` had 8 `lint:css` violations, and the CSS-lint step
      fails *before* vitest in the same CI job (bd-u7zj5p9w masking) while
      being covered by neither `test:ci` nor `cargo xtask verify`. Fixed
      in `a4c3fb9c` (accent-action tokens, opaque color-mix, focus ring);
      skill's verify step now includes `npm run lint:css`. Also filed
      bd-o1f58huc: hub-client eslint is red and gated nowhere (e2e spec
      debt).

## Facts for quick reference

- Guard test: `katex_cdn_version_matches_npm_pin`,
  `crates/quarto-core/src/stage/stages/math_js.rs:1021` (checks root pin ==
  sandboxed pin, exact-version format, and `DEFAULT_KATEX_URL_BASE` ==
  `https://cdn.jsdelivr.net/npm/katex@{root}/dist/`).
- Current pins (main, 2026-09-01): root and sandboxed both `0.18.2`; #637 wants
  `0.18.4`; the committed bundle embeds two `0.18.2` strings.
- Snyk branch naming: `snyk-upgrade-<hash>`; author `posit-snyk-bot`; branches
  live in quarto-dev/q2 itself (pushable).
- Snyk's own checks (`security/snyk`, `license/snyk`) pass on the bot PRs; the
  red comes from our test suite, which is the intended behavior.

Files in this skill

  • 2025-01-25-k-386-template-error-corpus.md8 KB
  • 2025-10-19-sourceinfo-pool-serialization.md12.4 KB
  • 2025-10-20-k-86-fileid-analysis.md3.3 KB
  • 2025-10-20-k-87-sourceinfo-audit.md7.4 KB
  • 2025-10-20-k44-sourceinfo-optimization-complete.md4.3 KB
  • 2025-10-20-k54-unify-l-s-keys-plan.md7.2 KB
  • 2025-10-20-k69-session4-progress.md4.6 KB
  • 2025-10-20-source-map-migration-complete.md5.1 KB
  • 2025-10-20-yaml-tag-preservation.md7.3 KB
  • 2025-10-21-consolidate-to-diagnosticmessage.md19 KB
  • 2025-10-21-definition-list-false-positive-revised.md7.5 KB
  • 2025-10-21-definition-list-false-positive.md7.5 KB
  • 2025-10-21-diagnostic-message-refactor.md4.9 KB
  • 2025-10-21-div-whitespace-performance.md6.6 KB
  • 2025-10-21-fix-quarto-error-reporting-tests.md8.2 KB
  • 2025-10-21-grid-table-false-positive.md10.1 KB
  • 2025-10-21-json-diagnostics-output.md12.1 KB
  • 2025-10-21-k90-k95-investigation.md3.2 KB
  • 2025-10-21-lexical-scope-regression-fix.md2.6 KB
  • 2025-10-21-pr-preparation-plan-revised.md7.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.