Skip to content

fix(api): validate API responses at the boundary - #1071

Merged
EhabY merged 6 commits into
mainfrom
fix/1050-validate-api-responses
Aug 11, 2026
Merged

fix(api): validate API responses at the boundary#1071
EhabY merged 6 commits into
mainfrom
fix/1050-validate-api-responses

Conversation

@EhabY

@EhabY EhabY commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1050

The SDK casts 2xx bodies to the generated TypeScript types with no runtime check, so a proxy error page, a non-Coder service, or a partial body flowed in as valid and crashed far from the cause — e.g. user?.roles.some(...) in deploymentManager.ts throwing Cannot read properties of undefined.

Responses are now validated at the HTTP call and fail with an error naming the URL and endpoint.

What changed

  • src/api/responseValidation.tsparseApiResponse + InvalidApiResponseError (the Zod failure kept as cause), the schemas, and VALIDATED_RESPONSES mapping each SDK method to its schema. Schemas are looseObjects listing only fields the extension reads; a field is required only if every supported deployment sends it, so unknown fields pass through untouched and newer ones must be .optional().
  • src/api/coderApi.tswrapWithValidation iterates that map in the constructor. Also reimplements waitForBuild: the SDK polls inside a voided IIFE that swallows errors, so a validation failure would hang callers forever.
  • src/oauth/validation.ts plus metadataClient.ts, authorizer.ts, sessionManager.ts — same treatment for the endpoints hit before a session exists (metadata, client registration, token exchange and refresh). validateRequiredEndpoints folded into the schema via .min(1). Errors blame the endpoint's own origin, which may differ from the deployment.

Behavior change

  • A roles-less /api/v2/users/me now fails login with a clear error instead of silently treating the user as non-owner.
  • A malformed build response rejects waitForBuild instead of hanging "Stopping workspace for update..." forever.

Notes

  • Wrapping is by reassignment: SDK Api methods are arrow-function instance properties, so super.getAuthenticatedUser() does not compile (ts(2855)) and override fields would depend on declaration order.
  • No axios response interceptor — URL-to-schema matching is brittle with path params, and non-AxiosError rejections would confuse isApiError handling.
  • Every required field was verified present in codersdk at v0.25.0, the floor featureSet.ts declares. The three collection fields we require (resources, roles, ssh_config_options) are built with make(...) upstream, so they serialize as []/{} rather than Go's null.

Testing

Table-driven: one case per schema pinning the minimal body an old deployment sends, one per validated method for pass and reject, and a guard that fails when a method joins VALIDATED_RESPONSES without a test case. pnpm test (2416 tests), typecheck, and lint all green.


Generated by Coder Agents on behalf of @EhabY

@EhabY
EhabY force-pushed the fix/1050-validate-api-responses branch from 1160549 to 5b16292 Compare August 10, 2026 12:55
@EhabY EhabY self-assigned this Aug 10, 2026
EhabY and others added 4 commits August 11, 2026 13:40
…generated types

The SDK casts 2xx response bodies to generated TypeScript types with no
runtime check. A non-Coder service at the configured URL, a proxy HTML
error page, or a partial body would flow in as if valid and crash far
from the cause (e.g. user.roles.some -> 'Cannot read properties of
undefined').

Add a zod-based parseApiResponse helper and InvalidApiResponseError that
names the endpoint and deployment URL. Validate on CoderApi overrides
for the login/connect paths (users/me, workspace, workspace build,
template version resources, deployment SSH config) and at the OAuth
entry points (metadata, client registration, token exchange/refresh).
Schemas are permissive looseObjects requiring only the fields the
extension reads, so newer deployments adding fields never break login.

A roles-less /users/me now hard-fails login with a clear error instead
of silently treating the user as non-owner.

Fixes #1050
Add a private CoderApi.validate helper supplying the host so each
override is a single call. Trim doc comments to the essential rationale
and merge redundant passthrough tests into one identity assertion.
- Drop hostname_suffix, organization_ids, and architecture from the
  schemas; nothing reads them and older deployments do not send them
- Replace the six validation overrides and captureBaseMethods with a
  typed wrap helper reassigned in the constructor
- Reimplement waitForBuild, whose SDK version swallows errors inside a
  voided IIFE and would hang callers on a validation failure
- Validate getTemplate, stopWorkspace, and startWorkspace responses too
- Validate the *_supported arrays in OAuth metadata and blame the
  endpoint origin, not the deployment, in OAuth validation errors
- Fold validateRequiredEndpoints into the OAuth metadata schema
- Use safeParse and drop the misleading Output > Coder pointer
- Pin the minimal old-deployment body each schema must keep accepting
@EhabY
EhabY force-pushed the fix/1050-validate-api-responses branch from 5b16292 to 4f1103b Compare August 11, 2026 10:40
Replace the nine hand-written wrapper assignments with a single
VALIDATED_RESPONSES map next to the schemas it references, so adding a
validated method is a one-line change and the key serves as both the
method to wrap and the endpoint name in the error.

Mirror the layout on the OAuth side (helper first, schemas below) and
collapse the repeated field expressions. Rework the tests around one
table per concern, including a guard that fails when a method is added
to the map without a case covering it.
@EhabY
EhabY requested a review from jeremyruppel August 11, 2026 11:10
@EhabY EhabY changed the title fix(api): validate API responses at the boundary instead of trusting generated types fix(api): validate API responses at the boundary Aug 11, 2026
Comment thread src/api/coderApi.ts Outdated

@jeremyruppel jeremyruppel left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zod ftw! one type nit but otherwise this looks fantastic. nice one!

The map-driven wrapper reached the SDK methods through an `as unknown as`,
which asserted the shape rather than checking it. Store the schemas as
`as const` pairs so iterating keeps each method name as a literal type,
and assign the instance to a `ValidatedMethods` record that CoderApi
satisfies structurally. `never` parameters accept any signature and the
uniform value type permits assigning by a name held in a variable, so the
whole wrap is compiler-checked with no assertions.

Also tighten the new comments.
@EhabY
EhabY merged commit 908db8c into main Aug 11, 2026
11 checks passed
@EhabY
EhabY deleted the fix/1050-validate-api-responses branch August 11, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validate API responses at the boundary instead of trusting the generated types

2 participants