Skip to content

Production-readiness pass: security hardening, esbuild bundle, narrowed activation#1

Merged
dmartinochoa merged 10 commits into
mainfrom
prod-ready-hardening
May 19, 2026
Merged

Production-readiness pass: security hardening, esbuild bundle, narrowed activation#1
dmartinochoa merged 10 commits into
mainfrom
prod-ready-hardening

Conversation

@dmartinochoa
Copy link
Copy Markdown
Member

Summary

Four-commit production-readiness pass driven from the pre-marketplace
review in ROADMAP.md. Draft
because of the manual smoke items listed at the bottom.

What landed

Critical

  • C1 — .vsix was missing its runtime dependency. Previous build emitted
    out/extension.js via tsc but .vscodeignore excluded node_modules/,
    so require("vscode-languageclient/node") would throw on activation in
    a clean install. Now bundled with esbuild into dist/extension.js
    (348 KB minified). CI runs npm run smoke (scripts/smoke.js)
    which stubs vscode and asserts activate / deactivate are exported,
    so this regression class fails the build instead of the user.
  • C2 — RCE via workspace settings. pipelineCheck.serverCommand and
    pipelineCheck.serverArgs are now machine-overridable (workspace
    overrides require an explicit prompt). Added
    capabilities.untrustedWorkspaces: "limited" + virtualWorkspaces: false
    so the LSP child process never spawns from a freshly-cloned untrusted
    repo.

High

  • H1 — Publish workflow pinned. @vscode/vsce and ovsx are now
    pinned devDependencies; workflows call them via npx vsce / npx ovsx
    after npm ci. No more @latest with PATs in env. Dependabot's
    existing npm config keeps them current.
  • H3 — Tag must be reachable from main. git merge-base --is-ancestor
    check refuses to publish a tag pointed at an off-main commit.
  • H4 — Activation surface narrowed. Bare onLanguage:* triggers
    replaced with workspaceContains: patterns for the providers we
    scan. documentSelector switches to file-path globs so the LSP only
    sees candidate documents; no more dependency on whether the user has
    the GitHub Actions extension installed (which would hijack the
    github-actions-workflow language ID).

Medium / Low

  • M1SECURITY.md with GitHub Private Vulnerability Reporting, response SLAs, threat model.
  • M2permissions: contents: read at workflow level; publish job opts up to write for the release step. (Step-level permissions aren't a GitHub Actions feature.)
  • M3npm audit --omit=dev --audit-level=high on every push and PR.
  • M4 — publish.yml refuses a tag whose CHANGELOG is missing the matching ## [X.Y.Z] section.
  • L1–L6 — absolute-path note on serverCommand; noEmitOnError; tightened outputChannel typing; defense-in-depth comment on THRESHOLD_RANK fallback; Other added to categories; qna pointed at repo Discussions.

Folded in (parallel work, not from this review)

  • Findings tree view: src/findingsView.ts, onView:pipelineCheck.findings activation event, and the wiring in extension.ts. Rode along on the H4 commit because both edits touched extension.ts.
  • Previously-untracked supply-chain workflows committed: dependabot.yml, codeql.yml, dependency-review.yml, and the Findings panel SVG icon.

Manual smoke items (before promoting from draft)

These cannot be done from a git branch:

  • Create the marketplace GitHub Environment (Settings → Environments → New) with required reviewers, move VSCE_PAT / OVSX_PAT from repo secrets to the environment. Then a one-line PR adds environment: marketplace to the publish job. (H2)
  • Enable Private Vulnerability Reporting (Settings → Code security), or the link in SECURITY.md 404s.
  • Enable Discussions (Settings → General → Features), or the qna link in package.json 404s.
  • Smoke H4 against fixtures — F5 with the sample-workflow profile, open each provider's trigger file (.github/workflows/*.yml, .gitlab-ci.yml, azure-pipelines.yml, bitbucket-pipelines.yml, .circleci/config.yml, cloudbuild.yaml, .buildkite/pipeline.yml, .drone.{yml,yaml}, Jenkinsfile, Dockerfile, Containerfile), confirm diagnostics still appear. Custom workflow paths now do nothing — that's intentional.
  • Confirm v0.1.0 was actually broken in a clean VS Code (the C1 hypothesis). If yes, this branch becomes a 0.1.1 hotfix.

Test plan

  • npm run lint clean
  • npm run compile clean (typecheck + esbuild dev bundle)
  • npm run smoke clean (bundle loads, exports activate/deactivate)
  • npm audit --omit=dev --audit-level=high reports 0 vulnerabilities
  • npx vsce ls confirms the .vsix contents are README, package.json, LICENSE, icon.png, CHANGELOG.md, SECURITY.md, media/pipeline-check.svg, dist/extension.js — no node_modules, no out/

🤖 Generated with Claude Code

dmartinochoa and others added 4 commits May 19, 2026 01:57
- package.json: add capabilities.untrustedWorkspaces (limited) and
  virtualWorkspaces=false so the LSP child never spawns from an
  untrusted workspace. Mark serverCommand and serverArgs as
  machine-overridable so a malicious .vscode/settings.json cannot
  silently swap the interpreter or inject -c "<code>". Document the
  Windows PATH-cwd hazard on serverCommand.
- src/extension.ts: tighten outputChannel typing; capture as
  vscode.OutputChannel at construction, drop optional chaining on use.
- tsconfig.json: noEmitOnError stops a broken local compile from
  shipping junk into out/.
- .github/workflows/publish.yml: pin @vscode/vsce@3.9.1 and ovsx@0.10.12
  (no more @latest with PATs in env), refuse to publish a tag that
  isn't on main, narrow workflow-default permissions to contents:read
  with the publish job opting up for the release step.
- ROADMAP.md: track outstanding production-readiness work, tick the
  items landed here.
- CHANGELOG.md: Security block under Unreleased.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
C1 fix (marketplace blocker):
- esbuild bundle replaces tsc emit. vscode:prepublish builds the
  minified prod bundle to dist/extension.js (348kb); compile builds
  the dev bundle with sourcemap so F5 stays debuggable.
- package.json#main is now ./dist/extension.js. .vsix contents now
  ship the bundled JS instead of the previous out/extension.js,
  which would have thrown "Cannot find module
  'vscode-languageclient/node'" at activation in any clean install
  (node_modules was excluded by .vscodeignore but never bundled).
- scripts/smoke.js stubs the vscode module via Module._load, loads
  dist/extension.js, and asserts activate/deactivate are exported.
  Hooked into CI as `npm run smoke` so a missing runtime dep fails
  the build instead of the user.
- .vscodeignore excludes out/** (legacy emit dir) and the new
  ROADMAP.md.
- .vscode/launch.json outFiles repointed at dist/.

H1 parity:
- ci.yml pins @vscode/vsce@3.9.1 (no secrets in ci.yml, but matches
  publish.yml so a regression in @latest can't sneak in via PRs).

M1: SECURITY.md.
- GitHub Private Vulnerability Reporting as the disclosure channel,
  acknowledge/assessment SLAs, threat model + out-of-scope list.

M3: npm audit step.
- `npm audit --omit=dev --audit-level=high` on every push to main and
  every PR — catches advisories filed after a PR has merged.

M4: CHANGELOG-fold check.
- publish.yml refuses a tag whose CHANGELOG.md is missing a
  `## [X.Y.Z]` header. Protects the release-notes extraction that
  ships into the GitHub release.

L4: defense-in-depth comment on THRESHOLD_RANK[...] ?? LOW so the
intent (a diagnostic must clear LOW, never silently disappear) is
spelled out for the next reader.

L5: added Other next to Linters in categories.

L6: added qna pointing at the repo Discussions page.

ROADMAP.md and CHANGELOG.md updated to reflect the new state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
H4 — narrow activation surface:
- activationEvents replaces bare onLanguage:* triggers with
  workspaceContains: patterns for the providers we actually scan
  (GHA, GitLab, Azure, Bitbucket, CircleCI, Cloud Build, Buildkite,
  Drone, Jenkins, Dockerfile, Containerfile). Opening an unrelated
  package.json or mkdocs.yml no longer activates the extension.
- documentSelector switches from language IDs to matching file-path
  globs. The LSP only sees candidate documents, so the server's
  content-and-path filter is a backstop instead of the first line of
  defence. Also removes the dependency on whether the user has the
  official GitHub Actions extension installed (which would hijack the
  github-actions-workflow language ID).

H1 follow-up — Dependabot coverage:
- @vscode/vsce and ovsx are now pinned devDependencies. Versions live
  in package-lock.json and Dependabot's existing npm config keeps
  them current.
- ci.yml and publish.yml call npx vsce / npx ovsx after npm ci, so
  the locally-installed binary is used (no fresh registry fetch with
  PATs in env). Added npm scripts: package, publish:vsce, publish:ovsx.

Findings tree view scaffolding (ridden along on this commit because
extension.ts edits collided with the H4 work):
- src/findingsView.ts: new FindingsTreeProvider that reads from
  vscode.languages.getDiagnostics() so the panel works whether or not
  the LSP is running.
- extension.ts wires the provider before startClient so the panel is
  available even when the server fails.
- onView:pipelineCheck.findings added to activationEvents.

ROADMAP.md ticks H4 and the H1 Dependabot follow-up; the remaining
open items are the manual repo-settings actions (H2 environment, PVR,
Discussions) that cannot land from a branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These four files were untracked when the production-readiness pass
started; README badges, the new ROADMAP, and SECURITY.md all reference
them, so they're clearly meant to ship.

- .github/dependabot.yml: weekly npm and github-actions bumps. The
  H1 follow-up moves vsce+ovsx into devDeps so this same config keeps
  them current.
- .github/workflows/codeql.yml: CodeQL JavaScript/TypeScript analysis
  on push, PR, and weekly.
- .github/workflows/dependency-review.yml: PR-only dep-review action
  that fails on high-severity advisories or non-allowlisted licenses.
- media/pipeline-check.svg: icon for the Findings panel view that
  ships in the cca86ff commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

@dmartinochoa has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 30 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a72fee5-f0f9-4349-989c-98ba471cfd76

📥 Commits

Reviewing files that changed from the base of the PR and between 055fe81 and 4fc5129.

⛔ Files ignored due to path filters (2)
  • media/pipeline-check.svg is excluded by !**/*.svg
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/dependency-review.yml
  • .github/workflows/publish.yml
  • .vscode/launch.json
  • .vscodeignore
  • CHANGELOG.md
  • ROADMAP.md
  • SECURITY.md
  • package.json
  • scripts/smoke.js
  • src/extension.ts
  • src/findingsView.test.ts
  • src/findingsView.ts
  • src/severityFilter.test.ts
  • src/severityFilter.ts
  • tsconfig.json
  • vitest.config.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch prod-ready-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

dmartinochoa and others added 4 commits May 19, 2026 02:22
Adds the project's first test suite. 25 tests, all pure-logic — no
extension host, no integration boot. Sets up the infrastructure
(vitest, vi.mock for vscode-touching code) so future tests follow the
same pattern.

Refactor:
- src/severityFilter.ts: pure module factored out of the LSP
  middleware. Owns SEVERITY_RANK, THRESHOLD_RANK, passesThreshold, and
  filterByThreshold. No vscode imports, so the test runs without a
  stub.
- src/extension.ts: middleware.handleDiagnostics now calls
  filterByThreshold instead of inlining the logic. Inline severity
  tables removed.

Tests:
- src/severityFilter.test.ts (14): pins down the invariants that keep
  the filter from silently dropping findings — missing/unknown
  severity always passes, unknown threshold falls back to LOW,
  CRITICAL survives every threshold, INFO never does, ordering and
  non-mutation are preserved.
- src/findingsView.test.ts (11): vi.mock("vscode", ...) stubs the
  editor namespace so we can exercise FindingsTreeProvider end-to-end.
  Covers source filtering (only pipeline-check diagnostics appear),
  all three group modes with bucket ordering + counts + leaf labels +
  vscode.open command, severity normalisation, and the
  no-refresh-storm contract on same-mode setGroupMode.

Wiring:
- vitest.config.ts: include src/**/*.test.ts, environment: node.
- package.json: vitest devDep + npm test / npm test:watch scripts.
- ci.yml: "Unit tests" step gates every PR.
- publish.yml: "Unit tests" + "Bundle smoke" gate every tag.

Smoke fix:
- scripts/smoke.js: explicit ThemeIcon and ThemeColor stubs. The
  findings view instantiates ThemeColor at module-load time inside the
  SEVERITY_ICON table; esbuild's __toESM namespace wrapper enumerates
  own keys at load time, so the Proxy's dynamic-class fallback was
  invisible. Pre-populating the two classes restores the
  bundle-loads-cleanly contract.

.vscodeignore already excludes src/** and **/*.ts, so test files and
vitest.config.ts never ship in the .vsix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The leaf-rendering refactor moves the rule id + location from the
TreeItem label into an adaptive description column whose format depends
on which group node sits above the leaf. The tests are updated to pin
that contract down, and to cover the new activity-bar badge wiring.

findingsView.test.ts:
- Leaf-label test now reads `item.label === "GHA-001 title"` and
  `item.description === "GHA-001 · a.yml:23"` (was the previous
  "RULE: title" form).
- New "adaptive leaf description" suite (5 tests) covers severity
  mode, file mode, rule mode, 0-based-to-1-based line conversion,
  and the missing-rule-id case.
- New "activity-bar badge" suite (4 tests) covers the setTreeView
  contract: the badge tracks the visible-finding count, clears to
  undefined on an empty workspace, picks singular vs plural tooltip,
  and refresh() before setTreeView is a no-op.
- groupByFile description test relaxed to match the count-only form
  ("2"), now that parentDir is gone from the description.

findingsView.ts:
- Remove dead `parentDir` helper (no callers after groupByFile no
  longer renders the parent directory in its description). Lint
  caught it.

Total: 34 tests, all passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a dedicated activity-bar surface for Pipeline-Check findings.
The panel is strictly a re-presentation of diagnostics the LSP server
has already published — it never triggers its own scan, so the
thin-transport-adapter promise in extension.ts stays intact.

package.json:
- viewsContainers.activitybar: a Pipeline-Check slot with the custom
  inverted-Y pipeline glyph at media/pipeline-check.svg.
- views.pipelineCheck: a single "Findings" tree view.
- viewsWelcome: empty-state content that leads with what the
  extension does, then a secondary "Not seeing findings?" line that
  links the Restart and Show Log commands.
- Three new commands (groupBy.severity / file / rule) and a refresh
  command, all in the Pipeline-Check category.
- view/title menu wiring so the active group mode is hidden and the
  other two render as icons (\$(symbol-event), \$(file), \$(symbol-key))
  on the right side of the view header.

src/extension.ts:
- Two-phase wiring: createTreeView constructs the view with the
  provider as treeDataProvider, then findingsProvider.setTreeView()
  hands the view back so the provider can drive the activity-bar
  badge.
- Findings provider is wired before startClient so the panel is
  visible even when the server fails to come up.

media/pipeline-check.svg:
- Inverted-Y pipeline glyph designed to read at 24×24 in the
  activity bar and themed via currentColor so light/dark themes pick
  up automatically.

src/findingsView.test.ts (3 new tests on top of the 34 from the
previous commit):
- Group tooltip carries the workspace-relative path.
- Group icon is picked from the maximum severity in the bucket.
- getGroupMode reflects the most recent setGroupMode call.

CHANGELOG.md, ROADMAP.md updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VSCE_PAT and OVSX_PAT now live as environment secrets on the
`production` environment, not as repo secrets, so any workflow that
does not target this environment cannot read them. The publish job
adds `environment: production`; a workflow_dispatch or tag push
stalls at the environment gate until a reviewer approves the run.

This is the last C1/C2/H1/H2 must-have. ROADMAP and CHANGELOG updated
to reflect the new state and the remaining maintainer action items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dmartinochoa dmartinochoa marked this pull request as ready for review May 19, 2026 07:48
dmartinochoa and others added 2 commits May 19, 2026 09:49
PR #1's `analyze` check fails because the repo has GitHub's default
CodeQL setup enabled and our codeql.yml is an advanced config — the
two cannot coexist on SARIF upload. Adds the disable-default-setup
step to the maintainer action items so the green tick is reachable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dmartinochoa dmartinochoa merged commit 2cf4f57 into main May 19, 2026
9 checks passed
@dmartinochoa dmartinochoa deleted the prod-ready-hardening branch May 19, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant