Skip to content

Commit 2cf4f57

Browse files
authored
Merge pull request #1 from greylag-ci/prod-ready-hardening
Production-readiness pass: security hardening, esbuild bundle, narrowed activation
2 parents 055fe81 + 4fc5129 commit 2cf4f57

20 files changed

Lines changed: 9251 additions & 1158 deletions

.github/dependabot.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: 2
2+
3+
# Weekly dep bumps. npm covers the extension itself; github-actions
4+
# keeps the workflow action versions current so we don't drift onto
5+
# deprecated runners.
6+
7+
updates:
8+
- package-ecosystem: npm
9+
directory: /
10+
schedule:
11+
interval: weekly
12+
day: monday
13+
open-pull-requests-limit: 5
14+
groups:
15+
typescript-eslint:
16+
patterns:
17+
- '@typescript-eslint/*'
18+
types:
19+
patterns:
20+
- '@types/*'
21+
22+
- package-ecosystem: github-actions
23+
directory: /
24+
schedule:
25+
interval: weekly
26+
day: monday
27+
open-pull-requests-limit: 3

.github/workflows/ci.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,24 @@ jobs:
3434
- name: TypeScript compile
3535
run: npm run compile
3636

37+
- name: Unit tests
38+
run: npm test
39+
40+
- name: Bundle smoke
41+
# Catches the "vsix packages but won't load" failure mode — a
42+
# successful vsce package doesn't prove the bundle has all its
43+
# runtime deps. Loads dist/extension.js with a vscode stub and
44+
# asserts activate/deactivate are exported.
45+
run: npm run smoke
46+
47+
- name: npm audit (prod deps, high+)
48+
run: npm audit --omit=dev --audit-level=high
49+
3750
- name: Verify vsix packs cleanly
51+
# vsce is a pinned devDependency (see package.json) — Dependabot
52+
# bumps it via the npm config. `npm ci` has already installed it.
3853
run: |
39-
npx --yes @vscode/vsce@latest package --out pipeline-check.vsix
54+
npx vsce package --out pipeline-check.vsix
4055
ls -lh pipeline-check.vsix
4156
4257
- uses: actions/upload-artifact@v4
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Dependency Review
2+
3+
# Fails any PR that adds a dependency with a known CVE or an
4+
# incompatible license. Cheap, PR-only, no secrets required.
5+
6+
on:
7+
pull_request:
8+
branches: [main]
9+
10+
permissions:
11+
contents: read
12+
pull-requests: write
13+
14+
jobs:
15+
review:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: actions/dependency-review-action@v4
21+
with:
22+
fail-on-severity: high
23+
comment-summary-in-pr: on-failure
24+
allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, CC0-1.0, Unlicense

.github/workflows/publish.yml

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,29 @@ on:
2525
required: true
2626
type: string
2727

28+
# Workflow default is read-only; the publish job widens to
29+
# `contents: write` for the GitHub release step. GitHub Actions doesn't
30+
# support step-level permissions, so this is the tightest scope
31+
# available without splitting into two jobs.
2832
permissions:
29-
contents: write # creating the GitHub release
33+
contents: read
3034

3135
jobs:
3236
publish:
3337
runs-on: ubuntu-latest
38+
# Gate the job on the `production` GitHub Environment so VSCE_PAT
39+
# and OVSX_PAT are only readable from a run that has cleared
40+
# whatever reviewers/branch rules the environment imposes. Anyone
41+
# with push access can still fire workflow_dispatch, but the
42+
# publish steps will block on the environment gate.
43+
environment: production
44+
permissions:
45+
contents: write # needed by the "Create GitHub release" step
3446
steps:
3547
- uses: actions/checkout@v4
3648
with:
3749
ref: ${{ inputs.tag || github.ref }}
50+
fetch-depth: 0 # needed for the merge-base check below
3851

3952
- uses: actions/setup-node@v4
4053
with:
@@ -44,43 +57,82 @@ jobs:
4457
- run: npm ci
4558

4659
- name: Verify tag matches package.json version
60+
env:
61+
# On tag push GITHUB_REF_NAME is the tag; on workflow_dispatch
62+
# it's the dispatching branch, so prefer the explicit input.
63+
REF_NAME: ${{ inputs.tag || github.ref_name }}
4764
run: |
4865
set -euo pipefail
49-
tag="${GITHUB_REF_NAME#v}"
66+
tag="${REF_NAME#v}"
5067
pkg=$(node -p "require('./package.json').version")
5168
if [ "$tag" != "$pkg" ]; then
5269
echo "::error::Tag v$tag does not match package.json version $pkg"
5370
exit 1
5471
fi
5572
echo "Tag and package.json agree on version $pkg"
5673
74+
- name: Verify tag is reachable from main
75+
env:
76+
REF_NAME: ${{ inputs.tag || github.ref_name }}
77+
run: |
78+
set -euo pipefail
79+
git fetch origin main
80+
if ! git merge-base --is-ancestor "$REF_NAME" origin/main; then
81+
echo "::error::Tag $REF_NAME is not reachable from origin/main — refusing to publish"
82+
exit 1
83+
fi
84+
echo "Tag $REF_NAME is on main"
85+
86+
- name: Verify CHANGELOG has a section for this version
87+
# publish.yml already enforces tag/version parity. This step
88+
# closes the matching changelog-fold gap: if the "Unreleased"
89+
# entries haven't been moved into a "## [X.Y.Z]" section, the
90+
# release-notes extraction below would ship the wrong content.
91+
run: |
92+
set -euo pipefail
93+
version=$(node -p "require('./package.json').version")
94+
if ! grep -E "^## \[${version}\]" CHANGELOG.md > /dev/null; then
95+
echo "::error::CHANGELOG.md is missing a '## [${version}]' section — fold the Unreleased entries before tagging"
96+
exit 1
97+
fi
98+
echo "CHANGELOG section found for ${version}"
99+
57100
- name: Lint
58101
run: npm run lint
59102

60103
- name: TypeScript compile
61104
run: npm run compile
62105

106+
- name: Unit tests
107+
run: npm test
108+
109+
- name: Bundle smoke
110+
run: npm run smoke
111+
63112
- name: Package vsix
113+
# vsce and ovsx are pinned devDependencies in package.json, so
114+
# `npm ci` above installed the exact versions and Dependabot
115+
# bumps them via the standard npm config. `npx` here resolves
116+
# the local binary — no fresh fetch with PATs in env.
64117
run: |
65118
version=$(node -p "require('./package.json').version")
66-
npx --yes @vscode/vsce@latest package \
67-
--out "pipeline-check-${version}.vsix"
119+
npx vsce package --out "pipeline-check-${version}.vsix"
68120
ls -lh "pipeline-check-${version}.vsix"
69121
echo "VSIX_PATH=pipeline-check-${version}.vsix" >> $GITHUB_ENV
70122
71123
- name: Publish to VS Code Marketplace
72124
env:
73125
VSCE_PAT: ${{ secrets.VSCE_PAT }}
74126
run: |
75-
npx --yes @vscode/vsce@latest publish \
127+
npx vsce publish \
76128
--packagePath "$VSIX_PATH" \
77129
--pat "$VSCE_PAT"
78130
79131
- name: Publish to Open VSX
80132
env:
81133
OVSX_PAT: ${{ secrets.OVSX_PAT }}
82134
run: |
83-
npx --yes ovsx@latest publish "$VSIX_PATH" \
135+
npx ovsx publish "$VSIX_PATH" \
84136
--pat "$OVSX_PAT"
85137
86138
- name: Create GitHub release

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"--extensionDevelopmentPath=${workspaceFolder}"
1010
],
1111
"outFiles": [
12-
"${workspaceFolder}/out/**/*.js"
12+
"${workspaceFolder}/dist/**/*.js"
1313
],
1414
"preLaunchTask": "npm: compile"
1515
},
@@ -22,7 +22,7 @@
2222
"${workspaceFolder}/test-fixtures/sample-workflow"
2323
],
2424
"outFiles": [
25-
"${workspaceFolder}/out/**/*.js"
25+
"${workspaceFolder}/dist/**/*.js"
2626
],
2727
"preLaunchTask": "npm: compile"
2828
}

.vscodeignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ src/**
55
test-fixtures/**
66
scripts/**
77
docs/**
8+
out/**
9+
ROADMAP.md
810
.gitignore
911
.eslintrc.json
1012
tsconfig.json

CHANGELOG.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ commit collapses this section into `## [X.Y.Z] — <date>`.
1111

1212
### Added
1313

14+
- **Findings panel.** A dedicated activity-bar slot
15+
("Pipeline-Check" — custom inverted-Y pipeline glyph at
16+
`media/pipeline-check.svg`) carries a `Findings` tree that
17+
re-groups the diagnostics the LSP server has already published.
18+
Strictly a re-presentation: never triggers its own scan, so the
19+
thin-transport-adapter promise in `extension.ts` stays intact.
20+
The activity-bar icon carries a live count badge so "how many
21+
findings does this workspace have right now?" is answerable
22+
without expanding the panel. Three group modes — severity
23+
(default), file, rule — are switched via a `Change Grouping`
24+
Quick Pick that marks the active mode with `$(check)`. Each leaf
25+
renders as the rule title plus a `RULE · file:LINE` description
26+
that drops whichever component is already implied by the parent
27+
group; clicking opens the file at the diagnostic range.
28+
CRITICAL is rendered as `flame` and HIGH as `error` so the two
29+
distinguish in the severity-grouped tree without breaking parity
30+
with the editor gutter (which has no "more red than red" state);
31+
INFO uses `circle-outline` themed to `descriptionForeground` so
32+
it is visibly the quietest row instead of inheriting the default
33+
foreground. The welcome state leads with what the extension does
34+
rather than what is missing; the diagnostic recovery links sit
35+
on a secondary "Not seeing findings?" line.
36+
1437
- **`pipelineCheck.severityThreshold` setting.** A new enum knob
1538
(`low` / `medium` / `high` / `critical`, default `low`) that mirrors
1639
the CLI's `--severity-threshold`. Drives a client-side
@@ -24,8 +47,70 @@ commit collapses this section into `## [X.Y.Z] — <date>`.
2447
unconditionally, so an older server (or a non-pipeline-check
2548
publish) is never hidden.
2649

50+
### Security
51+
52+
- **`pipelineCheck.serverCommand` and `pipelineCheck.serverArgs` are now
53+
`machine-overridable`.** Workspace overrides require an explicit
54+
prompt, so a malicious `.vscode/settings.json` can't silently swap
55+
the interpreter or inject `-c "<code>"` once the user trusts the
56+
workspace.
57+
- **Declared `capabilities.untrustedWorkspaces: "limited"`** and
58+
`virtualWorkspaces: false`. The extension stays inactive in
59+
untrusted workspaces until the user trusts them, so the LSP child
60+
process never spawns from a freshly-cloned, untrusted repo.
61+
- **Hardened the publish workflow.** Pinned `@vscode/vsce` and `ovsx`
62+
to specific versions (no more `@latest` with PATs in env), added a
63+
`git merge-base` check that refuses to publish a tag that isn't on
64+
`main`, added a CHANGELOG-fold check, and narrowed workflow-level
65+
permissions to `contents: read` with the publish job opting up to
66+
`contents: write`. The publish job is gated on the `production`
67+
GitHub Environment so `VSCE_PAT` / `OVSX_PAT` are only readable from
68+
a run that has cleared required reviewers.
69+
- **Added [SECURITY.md](SECURITY.md)** with GitHub Private Vulnerability
70+
Reporting as the disclosure channel, response SLAs, and a published
71+
threat model.
72+
73+
### Tests
74+
75+
- **Vitest unit suite added.** 25 tests covering the severity threshold
76+
filter (extracted into [src/severityFilter.ts](src/severityFilter.ts))
77+
and the Findings tree's pure logic (collection from
78+
diagnostics, group-by-severity / file / rule, severity normalisation,
79+
no-refresh-storm contract). `npm test` runs the suite; both ci.yml
80+
and publish.yml gate on it. Test files live next to the code they
81+
cover and are stripped from the .vsix.
82+
83+
### Fixed
84+
85+
- **The published `.vsix` was missing its runtime dependency.** The
86+
previous build emitted `out/extension.js` via `tsc` but excluded
87+
`node_modules/` from the package, so `require("vscode-languageclient/node")`
88+
threw on activation in a clean install. Now bundled with esbuild into
89+
a single `dist/extension.js` (the only JS in the `.vsix`); a CI
90+
smoke step ([scripts/smoke.js](scripts/smoke.js)) stubs the `vscode`
91+
module, loads the bundle, and asserts `activate` / `deactivate` are
92+
exported so this regression class fails the build instead of the user.
93+
2794
### Changed
2895

96+
- **`npm audit --omit=dev --audit-level=high` now runs on every push to
97+
`main`** so advisories filed after a PR has merged still surface.
98+
- **Activation tightened.** The extension used to activate on every YAML
99+
/ JSON / Dockerfile / Terraform / Groovy file in any workspace, then
100+
rely on the server's content filter to drop unrelated documents.
101+
`activationEvents` is now a `workspaceContains:` list of the trigger
102+
files we actually scan (`.github/workflows/*`, `.gitlab-ci.yml`,
103+
`azure-pipelines.yml`, etc.). The LSP's `documentSelector` is
104+
switched from language IDs to matching file-path globs, so the
105+
server only sees candidate documents — no more spurious activations
106+
on `package.json`, `mkdocs.yml`, or a Helm `values.yaml`.
107+
- **`@vscode/vsce` and `ovsx` are pinned devDependencies.** Workflows
108+
invoke them via the locally-installed binaries (`npx vsce`,
109+
`npx ovsx`) after `npm ci`. Versions live in `package-lock.json`
110+
and Dependabot's existing npm config keeps them current.
111+
- **Marketplace metadata polish.** Added `Other` to `categories`,
112+
pointed `qna` at the repo Discussions page.
113+
29114
- **Marketplace polish pass.** The `package.json` `description` is
30115
rewritten so the numbers that differentiate this extension (22
31116
providers, 14 compliance frameworks beyond OWASP Top 10 CI/CD,

0 commit comments

Comments
 (0)