diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..684b687 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,21 @@ +# CodeQL configuration for .github/workflows/codeql.yml. +# +# Queries: the default security suite plus the maintainability/reliability +# queries (what GitHub's separate "Code quality" product runs), and the +# pack's alert-suppression query, which is what makes an inline +# // codeql[] +# comment (alone on its line, covering the next line) suppress one finding +# at one place. Without that query the marker is ignored - which is why the +# JWT-grant false positive in packages/esign-server/src/providers/docusign/auth.ts +# came back as alerts 1, 10 and 15 despite the marker from #58. +# +# A suppression travels with the code (an API dismissal is keyed on the +# alert's fingerprint and re-opens after every file move) and silences one +# site, not the query: a real weak password hash elsewhere still fails. +name: esign + +queries: + - uses: security-and-quality + +packs: + - codeql/javascript-queries:AlertSuppression.ql diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ca0023b..e0c6adb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,11 +42,9 @@ jobs: uses: github/codeql-action/init@v4 with: languages: javascript-typescript - # Default security suite plus the maintainability/reliability - # queries (what GitHub's separate "Code quality" product runs). - # Trial: drop this line to go back to security-only if the quality - # findings turn out to be noise next to eslint + typecheck. - queries: security-and-quality + # The query suite and the alert-suppression query (what makes an + # inline `codeql[rule-id]` marker count) live in the config file. + config-file: ./.github/codeql/codeql-config.yml - name: Analyze uses: github/codeql-action/analyze@v4 diff --git a/AGENTS.md b/AGENTS.md index ed88f85..261b2bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -102,6 +102,12 @@ Underlying npm scripts (`npm test`, `npm run typecheck`, `npm run lint`, `examples/*/src/`. `ESignature` is provider-agnostic - adding a provider is a new `SigningSource`, the component never changes - `graphql` stays on 16.x repo-wide (Apollo Server 5 peer range) +- A CodeQL false positive is suppressed where it sits: a + `// codeql[]` comment alone on the line above the flagged line + (`.github/codeql/codeql-config.yml` runs the pack's AlertSuppression + query, without which the marker is ignored). Never dismiss it in the + UI/API (fingerprint-keyed: the same finding re-opened three times across + file moves) and never exclude the query (it stays on for real findings) - The git hooks (lefthook) run format, lint, commitlint and typecheck; CI is the authoritative gate and every workflow must be green before merge diff --git a/CLAUDE.md b/CLAUDE.md index a8f449e..264f8ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -221,6 +221,11 @@ rm -rf node_modules package-lock.json && npm install # Full reinstall (root loc - Live DocuSign E2E is opt-in: repo variable `E2E_LIVE=true` (main, releases, dispatch) or PR label `e2e:live` (same-repo PRs); secrets live in the `docusign-demo` environment. `docs/operations/live-e2e-ci.md`. +- CodeQL (`codeql.yml`, informational) reads `.github/codeql/codeql-config.yml`, + which runs the suite plus the pack's AlertSuppression query. A false + positive is suppressed in place with `// codeql[]` alone on the + line above the flagged line - never by dismissing the alert (fingerprint- + keyed, re-opens on every file move) and never by excluding the query. - Native E2E builds are cached on the inputs `scripts/native-deps-hash.sh` sees plus `android/**` / `ios/**`; bump the cache key's `v` suffix when an input the script cannot see changes. diff --git a/docs/development-guide.md b/docs/development-guide.md index 8569d7c..f276147 100644 --- a/docs/development-guide.md +++ b/docs/development-guide.md @@ -424,7 +424,7 @@ diagram: [CI / Release Pipeline](diagrams/README.md#ci--release-pipeline). | `e2e.yml` | `workflow_call` only | `build-packages` (version stamp, build, publint + arethetypeswrong, pack smoke; uploads the dist for `web` and the tarballs for `Publish`), `docker` (the service image from `examples/full-service-demo/Dockerfile`, same version stamp, booted with the mock provider against `/health` - `make docker-smoke` locally - and uploaded for `Publish`), `server-demos` (boots the mint-only and serverless examples with the mock provider and calls their routes - `make e2e-server-demos`), `live` (opt-in live DocuSign: JWT grant, real mint, Playwright on the real form; [operations/live-e2e-ci.md](operations/live-e2e-ci.md)) plus the E2E suites as jobs: `backend`, `web` (Playwright, bundles the demo against that dist - what a web consumer installs), `build-android` → `android` (emulator), and `build-ios` → `ios` (simulator) **only when opted in** (see below). Outputs the stamped `version` / `disttag` for `Publish` | | `release.yml` | Push to main; CI completed on main | `Release PR / Tag` (push): keeps the `chore(release): X.Y.Z` PR current (version from the Conventional Commits since the last tag, `CHANGELOG.md` entry); when that PR merges, tags `vX.Y.Z`, creates the GitHub Release and dispatches `ci.yml` at the tag with `release_tag` (a release the workflow token creates never fires the `release:` trigger). `Re-run blocked releases` (CI completed green): re-runs the failed Publish of any release run for that commit (releases wait for / refuse a red main run). See [releasing.md](releasing.md) | | `pull-request.yml` | PR closed; PR title edited | `Cancel in-flight runs` + `Remove branch badge` (closed): cancels the PR's still-running runs (the push-to-main run is unaffected) and removes its `gh-pages` badge directory. `Title` (edited): re-lints the PR title only; the gating lint is the `Commits` job in `checks.yml` (a title edit must not re-run the whole pipeline) | -| `codeql.yml` | Push to main, PRs, weekly | CodeQL security-and-quality analysis | +| `codeql.yml` | Push to main, PRs, weekly | CodeQL security-and-quality analysis
(suite + alert-suppression query:
`.github/codeql/codeql-config.yml`) | Badges are per branch by construction: `gh-pages/badges/X/{unit,e2e,coverage}.svg` (and a workflow badge filtered with `?branch=X`) all describe branch `X` diff --git a/packages/esign-server/src/providers/docusign/auth.ts b/packages/esign-server/src/providers/docusign/auth.ts index 869c99a..ed8d761 100644 --- a/packages/esign-server/src/providers/docusign/auth.ts +++ b/packages/esign-server/src/providers/docusign/auth.ts @@ -36,11 +36,13 @@ export const createJwtAssertion = ( const signingInput = `${base64Url(JSON.stringify(header))}.${base64Url(JSON.stringify(payload))}`; // JWT-grant signature (RS256), not a password hash: the payload carries the - // OAuth host as the `aud` claim, which is why static analysis flags it - // (js/insufficient-password-hash, dismissed as a false positive in the - // repository's code-scanning alerts; inline markers are not honoured by - // GitHub's default CodeQL setup). + // OAuth host as the `aud` claim, which is why CodeQL's + // js/insufficient-password-hash flags it. The marker below suppresses that + // one finding on the next line (honoured because .github/codeql/codeql-config.yml + // runs the pack's AlertSuppression query); it travels with the code, unlike + // an alert dismissal, and leaves the query on for everything else. const sign = createSign('RSA-SHA256'); + // codeql[js/insufficient-password-hash] sign.update(signingInput); const signature = base64Url(sign.sign(config.privateKey as string)); return `${signingInput}.${signature}`;