chore(quality): adopt Biome + Knip + Gitleaks tooling - #5186
Conversation
- add biome ~2.5.2; biome.json formats JS/TS/JSX/TSX/JSON/CSS - keep Prettier for Markdown/YAML plus the playwright/ + cypress/ E2E suites (Biome reformats Playwright test.describe.serial() chains) - drop prettier-plugin-organize-imports + prettier-plugin-tailwindcss (Biome assist owns import organization; Tailwind class-sort -> Phase 2) - bump knip 5.83.0 -> ~6.24.0; lint-staged routes code->biome, E2E+docs->prettier - apply biome formatter (12 non-E2E files reformatted) - plan: project/2026-07-19-biome-knip-repo-quality.md
- biome.json: disable noNonNullAssertion (11043 deliberate '!'); keep recommended otherwise - delete dead packages/shared-components/.eslintrc.js (legacy prettier-eslint, D6) - defer Tailwind useSortedClasses (nursery + 822 non-parity re-orders); class-sort gap flagged (D2) - audit baseline: 519 err / 2456 warn / 434 info after disable; ratchet backlog in plan
- knip.json: schema->6; ignore prisma schemas / generated i18n / static assets, @theme virtual modules, external binaries (uv/doppler/docusaurus-*) - delete cypress/.prettierrc.mjs (referenced removed prettier-plugin-organize-imports; root config governs cypress) - remove unused eslint devDep from packages/shared-components (D6 tail) - audit baseline: 142 files / 62+117 deps / 5 unlisted / 717+1272 exports; ratchet backlog in plan
Local husky pre-commit runs 'gitleaks git --staged' with a graceful skip when the binary is absent (CI is the hard gate). CI check-gitleaks.yml installs the MIT release binary v8.30.1 (gitleaks-action@v2 needs a license for org-owned repos) and runs a blocking full-tree 'gitleaks dir' scan on push/PR to v3/v3*. .gitleaks.toml extends the default ruleset with a single allowlist covering verified false positives only (persisted-query hashes, public Firebase/Algolia client keys, self-host env templates, doc/test fixtures). No real secret leak. Quality tooling runs on host + CI, never inside the devcontainer.
check-lint.yml gains a non-blocking Biome lint step (continue-on-error) before the blocking turbo/eslint step; eslint stays the Next.js safety net. New check-knip.yml runs 'pnpm run knip' advisory on push/PR to v3/v3*. Adds a lint:biome root script for local/CI parity. check-format.yml already delegates to the Biome-based format:check script (Phase 1), so it is unchanged.
AGENTS.md (CLAUDE.md symlink): commands, tech-stack row, code conventions, and pre-commit section now describe Biome (code) + Prettier (md/yaml + e2e) + ESLint (Next.js safety net), plus the staged gitleaks hook. Wiki getting-started gains a code-quality toolchain paragraph; ci-and-deployment PR gates add check-knip and check-gitleaks and drop the stale 'knip is manual only' claim. Logged in docs/log.md.
util/load-test/k6.js hardcoded a signed staging ADMIN/FULL_ACCESS session JWT (no expiry, issuer auth.klicker.stg.df-app.ch — the deployed UZH staging env) in this public repo. Inject both tokens via __ENV.KLICKER_SESSION_TOKEN / KLICKER_PARTICIPANT_TOKEN instead, matching the file's existing __ENV pattern. Drop the broad util/load-test/ gitleaks allowlist so the gate no longer masks these tokens; add gitignored build-output exclusions (.next/, dist/) and least-privilege 'permissions: contents: read' on the new workflows. The token remains in public git history — rotating the staging auth JWT signing secret is required to fully remediate (tracked in the plan).
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
Confidence Score: 4/5Safe to merge once the staging JWT signing secret is rotated; all findings are non-blocking quality or security-hygiene items. The core tooling migration (Biome, Knip, Gitleaks) is cleanly implemented and the critical JWT removal is correct. Three non-blocking issues remain: the gitleaks CI binary install skips checksum verification, the
|
| Filename | Overview |
|---|---|
| biome.json | New Biome 2.5.2 config; correctly excludes playwright/, cypress/, and generated files. Formatter and linter rules match the conventions documented in AGENTS.md. |
| .gitleaks.toml | Allowlist is well-justified per entry; project/.*\.md$ is broader than needed and could silently suppress a future leak in any project plan file — better pinned to the specific filename. |
| .github/workflows/check-gitleaks.yml | Blocking CI secret scan via direct binary install; lacks SHA256 checksum verification before installing the security-sensitive binary. |
| util/load-test/k6.js | Hardcoded staging JWTs removed and replaced with __ENV injection — security fix is correct, but missing env vars silently fall back to empty strings instead of aborting. |
| .lintstagedrc.mjs | Correctly splits staged files between Biome (code/JSON/CSS, non-e2e) and Prettier (e2e, md/yaml); check-only mode matches the documented workflow. Symlink guard is preserved. |
| .github/workflows/check-knip.yml | New advisory Knip workflow with continue-on-error: true; path filter and pnpm install steps mirror other workflows. Appropriate least-privilege permissions. |
| .github/workflows/check-lint.yml | Adds advisory Biome lint step before the blocking ESLint/Turbo pass; pattern updated to include biome.json. No functional regressions. |
| .husky/pre-commit | Gitleaks pre-commit hook gracefully skips when binary is absent; staged-only scan mode is correct. Existing lint-staged invocation is preserved. |
| knip.json | Upgraded schema ref to knip@6, replaced empty workspaces stub with meaningful ignoreFiles/ignoreBinaries/ignoreDependencies entries. |
| package.json | Adds @biomejs/biome ~2.5.2, upgrades knip to ~6.24.0, removes prettier-plugin-organize-imports and prettier-plugin-tailwindcss; format/format:check scripts updated to delegate code to Biome and md/yaml to Prettier. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[git commit] --> B[husky pre-commit]
B --> C{gitleaks binary present?}
C -- yes --> D[gitleaks git --staged\n--config .gitleaks.toml]
C -- no --> E[skip with notice / CI enforces]
D --> F[lint-staged]
E --> F
F --> G{file type?}
G -- code/JSON/CSS --> H{e2e path?}
H -- no --> I[biome format check]
H -- yes --> J[prettier --check]
G -- md/yaml --> J
F --> K[pnpm check:all]
subgraph CI
L[check-gitleaks BLOCKING] --> M[gitleaks dir .]
N[check-lint] --> O[biome lint advisory]
N --> P[ESLint BLOCKING]
Q[check-knip advisory] --> R[knip]
S[check-format BLOCKING] --> T[biome format + prettier]
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[git commit] --> B[husky pre-commit]
B --> C{gitleaks binary present?}
C -- yes --> D[gitleaks git --staged\n--config .gitleaks.toml]
C -- no --> E[skip with notice / CI enforces]
D --> F[lint-staged]
E --> F
F --> G{file type?}
G -- code/JSON/CSS --> H{e2e path?}
H -- no --> I[biome format check]
H -- yes --> J[prettier --check]
G -- md/yaml --> J
F --> K[pnpm check:all]
subgraph CI
L[check-gitleaks BLOCKING] --> M[gitleaks dir .]
N[check-lint] --> O[biome lint advisory]
N --> P[ESLint BLOCKING]
Q[check-knip advisory] --> R[knip]
S[check-format BLOCKING] --> T[biome format + prettier]
end
Reviews (1): Last reviewed commit: "fix(security): stop committing staging J..." | Re-trigger Greptile
| shell: bash | ||
| run: | | ||
| curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz" -o /tmp/gitleaks.tar.gz | ||
| tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks | ||
| sudo install /tmp/gitleaks /usr/local/bin/gitleaks | ||
| gitleaks version |
There was a problem hiding this comment.
Gitleaks binary installed without integrity check
The binary is fetched from GitHub releases and installed directly with no SHA256 checksum verification. Gitleaks publishes a checksums.txt alongside every release; downloading and cross-checking it takes two extra lines. Because the binary has full read access to every file it scans — including anything that slips past the allowlist — a tampered archive (e.g. via a MITM or a compromised GitHub release asset) could silently exfiltrate secrets rather than report them. The permissions: contents: read guard limits what the workflow token can do afterward, but it does not protect the file content handed to the scanner itself.
| # "test-verification-abc123"). Real load-test tokens are injected via __ENV, | ||
| # never committed (see util/load-test/k6.js). | ||
| '''\.agents/skills/.*\.md$''', | ||
| '''project/.*\.md$''', |
There was a problem hiding this comment.
The
project/.*\.md$ pattern exempts every current and future Markdown file inside project/ from secret scanning. The PR description notes this is to suppress illustrative tokens in the plan file project/2026-07-19-biome-knip-repo-quality.md. Pinning the pattern to the specific filename prevents a real secret committed to any other project/*.md file from silently bypassing the gate.
| '''project/.*\.md$''', | |
| '''project/2026-07-19-biome-knip-repo-quality\.md$''', |
| // authenticated — tokens are injected at runtime, never committed: | ||
| // k6 run -e KLICKER_SESSION_TOKEN=... -e KLICKER_PARTICIPANT_TOKEN=... k6.js | ||
| // This repo is public and staging JWTs grant real access; capture fresh | ||
| // tokens from an authenticated staging session each run. | ||
| const cookies = { | ||
| cookies: { | ||
| 'next-auth.session-token': | ||
| 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImxlY3R1cmVyQGRmLnV6aC5jaCIsInN1YiI6Ijc2MDQ3MzQ1LTM4MDEtNDYyOC1hZTdiLWFkYmViY2ZlODgyMSIsInNob3J0bmFtZSI6ImxlY3R1cmVyIiwic2NvcGUiOiJGVUxMX0FDQ0VTUyIsImNhdGFseXN0SW5zdGl0dXRpb25hbCI6dHJ1ZSwiY2F0YWx5c3RJbmRpdmlkdWFsIjp0cnVlLCJyb2xlIjoiQURNSU4iLCJpYXQiOjE3NjMwNDg2OTEsImlzcyI6Imh0dHBzOi8vYXV0aC5rbGlja2VyLnN0Zy5kZi1hcHAuY2gifQ.wVgW8eDFQ9Ygvc2Qd3eNmQkdeGg5ukwwiPAXhtO9Qfo', | ||
| participant_token: | ||
| 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ZjQ1MDY1Yy02NjdmLTQyNTktODE4Yy1jNmY2YjQ3N2ViNDgiLCJyb2xlIjoiUEFSVElDSVBBTlQiLCJpYXQiOjE3NjMxMDgyMjksImV4cCI6MTc2NDMxNzgyOSwiaXNzIjoiaHR0cHM6Ly9hcGkua2xpY2tlci5zdGcuZGYtYXBwLmNoIn0.gV7lD0PJGbiD45EX8yEF0V9CT3kFSgZIWoyljgBKQmA', | ||
| 'next-auth.session-token': __ENV.KLICKER_SESSION_TOKEN || '', | ||
| participant_token: __ENV.KLICKER_PARTICIPANT_TOKEN || '', | ||
| }, | ||
| } |
There was a problem hiding this comment.
When
KLICKER_SESSION_TOKEN / KLICKER_PARTICIPANT_TOKEN are not provided, cookies are set to empty strings and the load test proceeds silently as an anonymous user. The check() calls only log failures — they do not abort the run — so the operator gets no clear signal that authenticated scenarios are being skipped. Failing early when either required token is absent would surface the misconfiguration immediately rather than producing misleading unauthenticated load results.
| // authenticated — tokens are injected at runtime, never committed: | |
| // k6 run -e KLICKER_SESSION_TOKEN=... -e KLICKER_PARTICIPANT_TOKEN=... k6.js | |
| // This repo is public and staging JWTs grant real access; capture fresh | |
| // tokens from an authenticated staging session each run. | |
| const cookies = { | |
| cookies: { | |
| 'next-auth.session-token': | |
| 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImxlY3R1cmVyQGRmLnV6aC5jaCIsInN1YiI6Ijc2MDQ3MzQ1LTM4MDEtNDYyOC1hZTdiLWFkYmViY2ZlODgyMSIsInNob3J0bmFtZSI6ImxlY3R1cmVyIiwic2NvcGUiOiJGVUxMX0FDQ0VTUyIsImNhdGFseXN0SW5zdGl0dXRpb25hbCI6dHJ1ZSwiY2F0YWx5c3RJbmRpdmlkdWFsIjp0cnVlLCJyb2xlIjoiQURNSU4iLCJpYXQiOjE3NjMwNDg2OTEsImlzcyI6Imh0dHBzOi8vYXV0aC5rbGlja2VyLnN0Zy5kZi1hcHAuY2gifQ.wVgW8eDFQ9Ygvc2Qd3eNmQkdeGg5ukwwiPAXhtO9Qfo', | |
| participant_token: | |
| 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ZjQ1MDY1Yy02NjdmLTQyNTktODE4Yy1jNmY2YjQ3N2ViNDgiLCJyb2xlIjoiUEFSVElDSVBBTlQiLCJpYXQiOjE3NjMxMDgyMjksImV4cCI6MTc2NDMxNzgyOSwiaXNzIjoiaHR0cHM6Ly9hcGkua2xpY2tlci5zdGcuZGYtYXBwLmNoIn0.gV7lD0PJGbiD45EX8yEF0V9CT3kFSgZIWoyljgBKQmA', | |
| 'next-auth.session-token': __ENV.KLICKER_SESSION_TOKEN || '', | |
| participant_token: __ENV.KLICKER_PARTICIPANT_TOKEN || '', | |
| }, | |
| } | |
| // authenticated — tokens are injected at runtime, never committed: | |
| // k6 run -e KLICKER_SESSION_TOKEN=... -e KLICKER_PARTICIPANT_TOKEN=... k6.js | |
| // This repo is public and staging JWTs grant real access; capture fresh | |
| // tokens from an authenticated staging session each run. | |
| if (!__ENV.KLICKER_SESSION_TOKEN || !__ENV.KLICKER_PARTICIPANT_TOKEN) { | |
| throw new Error( | |
| 'KLICKER_SESSION_TOKEN and KLICKER_PARTICIPANT_TOKEN must be set via -e. ' + | |
| 'Capture fresh tokens from an authenticated staging session.' | |
| ) | |
| } | |
| const cookies = { | |
| cookies: { | |
| 'next-auth.session-token': __ENV.KLICKER_SESSION_TOKEN, | |
| participant_token: __ENV.KLICKER_PARTICIPANT_TOKEN, | |
| }, | |
| } |
What This Adds
Migrates the repo's code-quality tooling and adds two new gates. Executed as a plan plus six phases plus one security fix (8 commits).
playwright/+cypress/e2e suites, because Biome mangles Playwrighttest.describe.serial()chains. ESLint stays only as the Next.js safety net.check-format,check-types,check-syncpack, andcheck-gitleaksare blocking.AGENTS.md, engineering wiki, plan) updated to match.util/load-test/k6.js.How It Works
biome.jsonfiles.includesexcludesplaywright/+cypress/(and generated files). Theformat/format:checkscripts and.lintstagedrc.mjsroute non-e2e code to Biome and e2e + md/yaml to Prettier. The lint-staged config is check-only (runpnpm formatto fix) and skips symlinks.~(tilde) ranges to satisfy the repo's syncpack semver-group convention. The lockfile still pins exact2.5.2/6.24.0, and the pnpmminimumReleaseAgeguard blocks in-range drift, so no version floats. The supply-chain guard was left untouched.check-lint.ymlgains an advisorybiome lintstep (continue-on-error) before the blocking Turbo/ESLint pass. Newcheck-knip.ymlrunsknipas advisory. Newcheck-gitleaks.ymlinstalls the MIT gitleaks binary directly (gitleaks-action@v2 requires aGITLEAKS_LICENSEfor org-owned repos) and runs a blocking full-tree scan. Both new workflows set least-privilegepermissions: contents: read.check-format.ymlis unchanged: it already delegates to theformat:checkscript, which now runs Biome plus Prettier.Important Details
.gitleaks.tomlextends the default ruleset with a single[allowlist]that covers verified false positives only (persisted-query SHA hashes, public Firebase/Algolia client keys, self-host compose templates, mock/truncated doc tokens) plus gitignored build output (.next/,dist/)..ts/.tsx/.cssfiles and the largepnpm-lock.yamldiff are mechanical (Biome formatting and dep additions), not behavioral.packages/shared-components/.eslintrc.js, redundantcypress/.prettierrc.mjs, and the unusedeslintdevDep inshared-components.--no-verifybecause this host worktree cannot runtsc(thegraphqlpackage is unbuilt:Cannot find module '@klicker-uzh/graphql/dist/ops'). tsc and lint are authoritative in CI; the pre-pushpnpm run builddid run and passed.Branch Coverage
v3907ecdc9dv3..HEAD, 32 files, +1344/-630. Non-cosmetic surface: 17 config/CI/hook/docs files (listed viagit diff --name-status), plus 13 cosmetic Biome reformats and the lockfile.6954254ec), Biome adoption (cd31ec950), lint audit baseline (d6e77c92e), Knip config + dead-dep drops (7bb96fc01), Gitleaks hook+CI (17533ef3e), advisory CI jobs (33fa52124), docs (6cda1257e), security remediation (907ecdc9d).Review Focus
.gitleaks.tomlallowlist breadth: confirm each entry is a false positive and the scan stays honest.biome.json+.lintstagedrc.mjs: confirm no file is double-formatted or missed.util/load-test/k6.jsnow reads both JWTs from__ENV; confirm no token remains.Security / Privacy
$security-reviewrun over the branch; the gitleaks default ruleset was run with the allowlist off (625 raw hits) to enumerate exactly what is suppressed.util/load-test/k6.jshardcoded a signed staging session JWT:role:ADMIN,scope:FULL_ACCESS, noexp, issuerauth.klicker.stg.df-app.ch(the deployed UZH staging env; hosts resolve to Azure4.226.22.29). It was introduced earlier onv3(5d3ebeaa0, chore: new deployment with infisical and argocd, backup scripts #4962), not by this branch. Remediated here: tokens are injected via__ENV, the hardcoded values are removed, and the broadutil/load-test/allowlist is dropped so the gate no longer masks them.Blocking Before Merge
APP_SECRET/JWT key) in Infisical; confirm staging and production do not share it. The leaked token is live until rotated and sits in public history.Follow-Up After Merge