lint: type-checked eslint rules and safer form field reads #102

Sammanfogat
supernaut sammanfogade 1 incheckning från lint/type-checked in i main 2026-07-29 21:40:41 +00:00
Ägare

Enables typescript-eslint's recommendedTypeChecked (#59), completing the rollout across the four
TS repos. This one needed more care than the three services, for two reasons.

Scoping: .astro is excluded, deliberately

Type-aware rules need a real TypeScript program per file, and the Astro parser does not reliably
provide one — enabling them on .astro produces parsing errors rather than findings, which looks
like a broken config rather than a useful signal. So the type-checked block is scoped to
**/*.{ts,mts,cts} with .astro explicitly ignored.

Also: vitest.config.ts and drizzle.config.ts must NOT go in allowDefaultProject here — they are
already in tsconfig's program, and double-claiming them fails to parse. Only the .mjs configs need
it. Parse errors are now 0.

A real input-handling bug on the public sign-up endpoint

no-base-to-string flagged seven sites doing String(form.get("x") ?? ""). FormData.get() returns
string | File | null, so a crafted multipart request that submits a file part where a text field
is expected yields the literal "[object Object]" — which then flows into username, email,
country and cap-token before validation ever sees it.

Fixed with a small formText() helper (src/lib/form.ts) that returns "" for anything that is not
a text part, so a non-string field fails validation honestly instead of arriving as
"[object Object]". Five tests, including one asserting explicitly that a File does not
stringify.

Applied at all seven call sites in src/pages/api/signup.ts and src/pages/api/renovate.ts.
Behaviour is unchanged for ordinary string input.

38 no-unsafe-* findings are deferred, on purpose — see #65

33 of them are in src/lib/auth/**: the OIDC discovery document, JWKS and token responses are parsed
as any and flow into logic unchecked.

The tempting fix is a type assertion, which clears all 38 in an afternoon. That would be worse than
the current state
— it asserts a shape nobody verified, so the code would read as type-safe while
being exactly as unsafe as before. A lint rule satisfied by a lie is a regression in a security
boundary, and the honest any at least says "unvalidated".

So those four rules are off with that reasoning recorded inline, and the real fix — zod validation
at the boundary, which the workspace already uses elsewhere — is filed as #65. Everything else in
recommendedTypeChecked gates normally.

Smaller items

  • require-await: 11 in test files → scoped off for **/*.test.ts, matching the sibling repos. The
    one production hit (validateCapToken) gets a targeted disable with its rationale: it is currently
    synchronous but is an awaited public API whose token store is the kind of thing that moves to shared
    state, so the promise contract is worth keeping.
  • no-unnecessary-type-assertion ×2 auto-fixed in oidc.test.ts — redundant as CryptoKeyPair where
    the return type was already correct. No assertion weakened; the attacker-key rejection test is
    untouched.

Verified: check, eslint, stylelint, format:check, mdlint, drizzle-kit check and build
all pass; 76 tests (71 + 5 new).

Refs #59, #65

Enables typescript-eslint's `recommendedTypeChecked` (#59), completing the rollout across the four TS repos. This one needed more care than the three services, for two reasons. ## Scoping: `.astro` is excluded, deliberately Type-aware rules need a real TypeScript program per file, and the Astro parser does not reliably provide one — enabling them on `.astro` produces **parsing errors** rather than findings, which looks like a broken config rather than a useful signal. So the type-checked block is scoped to `**/*.{ts,mts,cts}` with `.astro` explicitly ignored. Also: `vitest.config.ts` and `drizzle.config.ts` must NOT go in `allowDefaultProject` here — they are already in tsconfig's program, and double-claiming them fails to parse. Only the `.mjs` configs need it. Parse errors are now 0. ## A real input-handling bug on the public sign-up endpoint `no-base-to-string` flagged seven sites doing `String(form.get("x") ?? "")`. `FormData.get()` returns `string | File | null`, so a crafted multipart request that submits a **file part** where a text field is expected yields the literal `"[object Object]"` — which then flows into `username`, `email`, `country` and `cap-token` before validation ever sees it. Fixed with a small `formText()` helper (`src/lib/form.ts`) that returns `""` for anything that is not a text part, so a non-string field fails validation honestly instead of arriving as `"[object Object]"`. Five tests, including one asserting explicitly that a `File` does **not** stringify. Applied at all seven call sites in `src/pages/api/signup.ts` and `src/pages/api/renovate.ts`. Behaviour is unchanged for ordinary string input. ## 38 `no-unsafe-*` findings are deferred, on purpose — see #65 33 of them are in `src/lib/auth/**`: the OIDC discovery document, JWKS and token responses are parsed as `any` and flow into logic unchecked. The tempting fix is a type assertion, which clears all 38 in an afternoon. **That would be worse than the current state** — it asserts a shape nobody verified, so the code would read as type-safe while being exactly as unsafe as before. A lint rule satisfied by a lie is a regression in a security boundary, and the honest `any` at least says "unvalidated". So those four rules are `off` with that reasoning recorded inline, and the real fix — zod validation at the boundary, which the workspace already uses elsewhere — is filed as **#65**. Everything else in `recommendedTypeChecked` gates normally. ## Smaller items - `require-await`: 11 in test files → scoped off for `**/*.test.ts`, matching the sibling repos. The one production hit (`validateCapToken`) gets a targeted disable with its rationale: it is currently synchronous but is an awaited public API whose token store is the kind of thing that moves to shared state, so the promise contract is worth keeping. - `no-unnecessary-type-assertion` ×2 auto-fixed in `oidc.test.ts` — redundant `as CryptoKeyPair` where the return type was already correct. No assertion weakened; the attacker-key rejection test is untouched. Verified: `check`, `eslint`, `stylelint`, `format:check`, `mdlint`, `drizzle-kit check` and `build` all pass; 76 tests (71 + 5 new). Refs #59, #65
supernaut lade till 1 incheckning 2026-07-29 20:40:34 +00:00
lint: type-checked eslint rules and safer form field reads
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m6s
9bd1be563d
supernaut tvångsskickade lint/type-checked från 9bd1be563d
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m6s
till a150ae3439
Alla kontroller lyckades
ci / ci (pull_request) Successful in 1m9s
2026-07-29 21:36:23 +00:00
Jämför
supernaut sammanfogade incheckning 926b9c2079 till main 2026-07-29 21:40:41 +00:00
supernaut tog bort grenen lint/type-checked 2026-07-29 21:40:41 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-web!102
Ingen beskrivning angiven.