From 9adc38bd8ae8de002d2963f0a892edeb5840dc87 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 10 Aug 2026 19:47:02 +0000 Subject: [PATCH] chore(ci): harden supply-chain controls before reopening PRs Isolate npm publish from PR CI, enforce frozen lockfiles and a 24h minimumReleaseAge, and document safe pull_request_target usage so fork PRs cannot reach publish credentials. Co-authored-by: Rotimi Best --- .github/CODEOWNERS | 5 +++ .github/PULL_REQUEST_TEMPLATE.md | 1 + .../workflows/apply-issue-labels-to-pr.yml | 14 +++--- .github/workflows/assign-issues.yml | 5 +++ .github/workflows/deploy-storybook.yml | 2 +- .github/workflows/mcp-package-build-check.yml | 44 +++++++++++++++++++ .github/workflows/package-build-check.yml | 3 ++ .github/workflows/publish-course-app.yml | 16 ++++--- .github/workflows/publish-mcp.yml | 19 +++----- .github/workflows/semantic-pull-requests.yml | 5 +++ .github/workflows/ui-prefix-check.yml | 3 ++ CONTRIBUTING.md | 4 ++ pnpm-workspace.yaml | 3 ++ 13 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/workflows/mcp-package-build-check.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..b483b324 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# High-risk supply-chain paths — prefer maintainer review before merge. +/.github/workflows/ @rotimi-best +/pnpm-lock.yaml @rotimi-best +/pnpm-workspace.yaml @rotimi-best +/package.json @rotimi-best diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b4770d7a..636493e2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -39,6 +39,7 @@ Fixes # (place issue number here without bracket) - [ ] Removed all `console.logs` - [ ] Merged the latest changes from main onto my branch with `git pull origin main` - [ ] My changes don't cause any responsiveness issues +- [ ] If this PR changes `pnpm-lock.yaml`, `.github/workflows/**`, or publish config: call that out in the PR description ### Appreciated diff --git a/.github/workflows/apply-issue-labels-to-pr.yml b/.github/workflows/apply-issue-labels-to-pr.yml index 30f1340d..dee03a58 100644 --- a/.github/workflows/apply-issue-labels-to-pr.yml +++ b/.github/workflows/apply-issue-labels-to-pr.yml @@ -1,19 +1,23 @@ name: 'Apply issue labels to PR' +# pull_request_target is used so labeling works for fork PRs. +# SAFE ONLY because this workflow never checks out or executes PR code. +# Do not add actions/checkout, pnpm install, build, or cache steps here. + on: pull_request_target: types: - opened +permissions: + contents: none + issues: read + pull-requests: write + jobs: label_on_pr: runs-on: ubuntu-latest - permissions: - contents: none - issues: read - pull-requests: write - steps: - name: Apply labels from linked issue to PR uses: actions/github-script@v5 diff --git a/.github/workflows/assign-issues.yml b/.github/workflows/assign-issues.yml index 35d2294b..0bb3b3f6 100644 --- a/.github/workflows/assign-issues.yml +++ b/.github/workflows/assign-issues.yml @@ -5,6 +5,11 @@ on: types: - created - edited + +permissions: + contents: read + issues: write + jobs: take-issue: name: Disable take issue diff --git a/.github/workflows/deploy-storybook.yml b/.github/workflows/deploy-storybook.yml index 7a9cc216..c2c9c0e6 100644 --- a/.github/workflows/deploy-storybook.yml +++ b/.github/workflows/deploy-storybook.yml @@ -43,7 +43,7 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Install dependencies - run: pnpm i + run: pnpm install --frozen-lockfile - name: Build Storybook run: | diff --git a/.github/workflows/mcp-package-build-check.yml b/.github/workflows/mcp-package-build-check.yml new file mode 100644 index 00000000..a2505b85 --- /dev/null +++ b/.github/workflows/mcp-package-build-check.yml @@ -0,0 +1,44 @@ +name: MCP package build check + +# Verifies @classroomio/mcp still builds on PRs/pushes that touch it. +# Intentionally separate from publish-mcp.yml so PR CI never sees NPM_TOKEN. + +on: + pull_request: + paths: + - 'packages/mcp/**' + - 'packages/question-types/**' + - 'packages/utils/**' + - 'pnpm-lock.yaml' + - '.github/workflows/mcp-package-build-check.yml' + push: + paths: + - 'packages/mcp/**' + - 'packages/question-types/**' + - 'packages/utils/**' + - 'pnpm-lock.yaml' + - '.github/workflows/mcp-package-build-check.yml' + branches: + - main + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20.19.3 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build publishable MCP package + run: pnpm mcp:build diff --git a/.github/workflows/package-build-check.yml b/.github/workflows/package-build-check.yml index c027f9f4..8c9f9b93 100644 --- a/.github/workflows/package-build-check.yml +++ b/.github/workflows/package-build-check.yml @@ -24,6 +24,9 @@ on: - '.nvmrc' - '.github/workflows/package-build-check.yml' +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/publish-course-app.yml b/.github/workflows/publish-course-app.yml index 188f8ec7..f9b58e31 100644 --- a/.github/workflows/publish-course-app.yml +++ b/.github/workflows/publish-course-app.yml @@ -1,15 +1,19 @@ name: Publish @classroomio/course-app to NPM -on: workflow_dispatch +# Publish-only via manual dispatch. Do not add pull_request triggers here. + +on: + workflow_dispatch: jobs: publish: runs-on: ubuntu-latest + environment: production permissions: - packages: write contents: read steps: - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 20.19.3 @@ -17,11 +21,13 @@ jobs: - uses: pnpm/action-setup@v4 - - name: Install, Build & Publish + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build & Publish working-directory: packages/course-app run: | - pnpm i pnpm build pnpm publish --no-git-checks env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-mcp.yml b/.github/workflows/publish-mcp.yml index ac872f49..88f3b9f4 100644 --- a/.github/workflows/publish-mcp.yml +++ b/.github/workflows/publish-mcp.yml @@ -1,22 +1,16 @@ name: Publish @classroomio/mcp to NPM +# Publish-only. Never triggered by pull_request — keeps NPM_TOKEN off the PR trust boundary. +# PR/push build verification lives in mcp-package-build-check.yml. + on: workflow_dispatch: - pull_request: - paths: - - "packages/mcp/**" - - push: - paths: - - "packages/mcp/**" - branches: - - main jobs: publish: runs-on: ubuntu-latest + environment: production permissions: - packages: write contents: read steps: - uses: actions/checkout@v4 @@ -24,18 +18,17 @@ jobs: - uses: actions/setup-node@v4 with: node-version: 20.19.3 - registry-url: "https://registry.npmjs.org" + registry-url: 'https://registry.npmjs.org' - uses: pnpm/action-setup@v4 - name: Install dependencies - run: pnpm install --no-frozen-lockfile + run: pnpm install --frozen-lockfile - name: Build publishable MCP package run: pnpm mcp:build - name: Publish package - if: github.event_name == 'workflow_dispatch' run: pnpm --filter @classroomio/mcp publish --no-git-checks --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index 2dc56709..ed7712aa 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -1,5 +1,9 @@ name: 'Check PR' +# pull_request_target is used so title lint can comment on fork PRs. +# SAFE ONLY because this workflow never checks out or executes PR code. +# Do not add actions/checkout, pnpm install, build, or cache steps here. + on: pull_request_target: types: @@ -9,6 +13,7 @@ on: - synchronize permissions: + contents: read pull-requests: write jobs: diff --git a/.github/workflows/ui-prefix-check.yml b/.github/workflows/ui-prefix-check.yml index bd1809ee..d66a91a0 100644 --- a/.github/workflows/ui-prefix-check.yml +++ b/.github/workflows/ui-prefix-check.yml @@ -12,6 +12,9 @@ on: - 'packages/ui/**' - '.github/workflows/ui-prefix-check.yml' +permissions: + contents: read + jobs: ui-prefix: runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f9f15186..0e5195e6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,10 @@ Ready to dive into the code and make a real impact? Here's your path: Would you prefer a chat before you dive into a lot of work? Our [Discord server](https://dub.sh/ciodiscord) is your harbor. Share your thoughts, and we'll meet you there with open arms. We're responsive and friendly, promise! +## Dependency and workflow changes + +PRs that touch `pnpm-lock.yaml`, `.github/workflows/**`, or npm publish config get extra review. Prefer the smallest lockfile diff that matches your change, and do not add `pull_request_target` workflows that check out or build untrusted PR code. + ## Features If you spot a feature that isn't part of our official plan but could propel ClassroomIO forward, don't hesitate. Raise it as an enhancement issue, and let us know you're ready to take the lead. We'll be quick to respond. diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 77576d55..cb887619 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,6 +6,9 @@ packages: onlyBuiltDependencies: - esbuild - '@tailwindcss/oxide' +# Block brand-new registry publishes for 24h (supply-chain cooldown). +# Locked versions in pnpm-lock.yaml stay installable; this gates adds/updates. +minimumReleaseAge: 1440 minimumReleaseAgeExclude: - '@getworkbench/hono' - '@getworkbench/core'