Skip to content
Merged
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
71 changes: 71 additions & 0 deletions .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# CodeQL code scanning, modelled on terraform-provider-kosli's codeql.yaml
# (analyze job only; the Kosli attestation is deferred until this repo
# reports to a Kosli flow, see #91).
#
# Scans TypeScript/JavaScript (src/, scripts/, test/) and the GitHub Actions
# workflows themselves. Neither needs a build. Results land under
# Security > Code scanning.
#
# Needs no secrets, so it runs unguarded on fork PRs too.

name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Weekly baseline re-scan, so new queries catch old code. An off-peak,
# off-the-hour minute keeps it out of the top-of-hour queue.
- cron: '23 5 * * 3'

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
# Cancel superseded PR runs only. A cancelled run on main uploads no SARIF
# and leaves the code scanning baseline stale.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
security-events: write
# Lets codeql-action/analyze read workflow run details if the repository
# is ever made private.
actions: read
strategy:
fail-fast: false
matrix:
include:
# Both are extracted from source, so no build step is needed.
- language: javascript-typescript
build-mode: none
- language: actions
build-mode: none
steps:
- name: Harden runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit

- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Initialize CodeQL
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
queries: security-extended

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
with:
category: "/language:${{ matrix.language }}"
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ A Model Context Protocol (MCP) server that exposes the Kosli API to LLM clients

- **`main` is protected by a ruleset**: `deletion`, `non_fast_forward`, `pull_request`, `required_signatures`. Every commit must be signed, including bot-generated ones — that's why `update-catalog.yml` passes `sign-commits: true` to `peter-evans/create-pull-request` (it commits via the GitHub API, which signs as `github-actions[bot]`). An unsigned commit cannot be merged, no matter how green CI is.
- **Actions are pinned to full commit SHAs** with a trailing `# vX.Y.Z` comment, and every job runs `step-security/harden-runner` first. Keep both when adding jobs. Dependabot (`.github/dependabot.yml`) updates the pins weekly and preserves the comment; its PR titles are prefixed `ci:`/`chore:` to satisfy Conventional Commits.
- **Four workflows**: `ci.yml` (build, `typecheck`, test, `smoke` against `dist/`, pack, `mcpb validate`, `smoke` against the unzipped bundle), `release.yml` (tags `v*`), `update-catalog.yml` (weekly catalog regeneration PR), `claude-review.yaml` (automated PR review).
- **Five workflows**: `ci.yml` (build, `typecheck`, test, `smoke` against `dist/`, pack, `mcpb validate`, `smoke` against the unzipped bundle), `release.yml` (tags `v*`), `update-catalog.yml` (weekly catalog regeneration PR), `claude-review.yaml` (automated PR review), `codeql.yaml` (CodeQL `security-extended` scan of TypeScript and the workflows, on PRs, pushes to `main` and weekly; results under Security → Code scanning; advisory, not a required check).
- **Fork PRs get no secrets.** The repo is public, so PRs from forks run without repository secrets and with a read-only token. Guard any job needing credentials with `github.event.pull_request.head.repo.full_name == github.repository`, or it will fail on every external contribution.
- **`claude-review.yaml`** has three jobs, each guarded against forks first, then keyed on `pull_request.user.login`: Dependabot PRs, `update-catalog` PRs (`github-actions[bot]` on branch `chore/update-catalog`; catalog diff — removals need confirming against the live spec), and everything else. Drafts are skipped until marked ready. Reviews are phased: a full review on open, then on each push a `git patch-id` step compares the PR's own diff with the previous push's — a pure rebase runs no review, Dependabot and catalog PRs get a full review again, and general PRs get a cheaper follow-up scoped to the new commits that rechecks earlier findings and resolves fixed threads. The general prompt points at this file for invariants rather than restating them, which is how it drifted before. Auth is OIDC federation via org-wide `ANTHROPIC_*` variables, not secrets — which is why it also works on Dependabot-triggered runs. The action validates its own workflow file and skips with `"The workflow file must exist and have identical content to the version on the repository's default branch"` when it differs — so changes to this file can't be reviewed by themselves and take effect only after merge (observed on PR #33).
- **Two Node floors, deliberately.** The *consumer* floor is `engines.node` (`>=22`) — what the published server needs. The *dev* floor is higher (`>=22.12`, pinned in `.nvmrc` and the CI matrix low end) because vitest pulls Vite 8, which requires it. Don't raise `engines.node` to satisfy a devDependency: it constrains consumers, and nothing under `devDependencies` ships (`files: ["dist"]`).
Expand Down
Loading