Skip to content

feat: refuse unrecognized wire input instead of ignoring it - #353

Merged
cuibonobo merged 2 commits into
mainfrom
claude/review-pr-130-server-wo6559
Sep 25, 2026
Merged

cuibonobo merged 2 commits into
mainfrom
claude/review-pr-130-server-wo6559

Conversation

@cuibonobo

Copy link
Copy Markdown
Member

Summary

The ./wire request parsers now return a 400 (bad_request) for input their endpoint doesn't define. Before, they ignored it. An ignored name answers a different request than the one the client sent, and the client never finds out:

  • a misspelled filter widens a query
  • a misspelled purge does a soft delete instead
  • a stale field on a token request mints a token for the wrong identity

Changes by parser:

  • parseQueryParams(), parseChangeParams(), parseJournalParams():
    • An unknown query param is refused.
    • Boolean params take only true or false.
    • parseChangeParams() accepts since, the resume cursor the server reads itself.
  • parseQueryBody():
    • An unknown key is refused at every level: top level, filter, createdBy, attachment, createdAt/updatedAt, relatedTo and its target (checked per kind), and sort.
    • A body that isn't an object is refused.
    • includeDeleted/includeUnlisted must be booleans, and cursor must be a string.
  • createOptionsFromWireRecord(): a key that no wire record carries is refused. Server-assigned keys (createdBy, updatedBy, version, and so on) are still accepted and then ignored, as the spec already requires.

changesFromWireBody() already refused unknown keys and is unchanged.

Spec

New section: docs/spec/wire-format.md § Unrecognized input. It states the rule and three exceptions:

  • keys an endpoint defines as ignored
  • headers
  • responses (clients keep ignoring fields they don't recognize)

Four new error fixtures:

  • error-bad-request-unknown-query-param
  • error-bad-request-non-boolean-param
  • error-bad-request-unknown-query-body-key
  • error-bad-request-unknown-record-key

Verification

pnpm run format:check && pnpm run check:refs && pnpm run lint && pnpm -r test && pnpm run build && pnpm run typecheck all pass.

I also ran haverstack/server's test suite against this build. Two tests failed, both for expected reasons:

Notes for reviewers

  • The changeset is minor. This is a breaking change for any client that sends extra names, but there are no existing users to break (see AGENTS.md § No backward compatibility).
  • adapter-api's conformance test lists the four new fixtures as server-only, because APIAdapter never sends a name the wire doesn't define.
  • After release, the server needs dispatches for the new fixtures, and its ?token= test needs updating.
  • Not covered here: unknown keys inside grant and association elements. That's element validation in Stack, not wire parsing, and belongs in a separate change.

🤖 Generated with Claude Code

https://claude.ai/code/session_018rAh3A4jeexocb6UcZRAwa


Generated by Claude Code

The ./wire request parsers now refuse a query param or body key their
endpoint does not define, at any depth, and take only true/false for
boolean params. An ignored name answers a different request than the
one sent: a misspelled filter widens a query, a stale field mints the
wrong token. Specified in wire-format.md § Unrecognized input, with four
new error fixtures.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018rAh3A4jeexocb6UcZRAwa
@changeset-bot

changeset-bot Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1a27505

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
@haverstack/core Minor
@haverstack/conformance-fixtures Minor
@haverstack/adapter-api Patch
@haverstack/adapter-conformance Patch
@haverstack/adapter-local Patch
@haverstack/blob-adapter-disk Patch
@haverstack/blob-adapter-s3 Patch
@haverstack/commons Patch
@haverstack/record-adapter-do-sqlite Patch
@haverstack/record-adapter-sqlite Patch
@haverstack/wire-types Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

A single-value query param repeated (?includeDeleted=true&includeDeleted=junk)
was read as its first value with the rest silently dropped, which the
Unrecognized input rule forbids. Only the filter lists (typeId, baseId, appId,
createdBySubject, createdByPrincipal, tag, kind) may repeat now.

WIRE_RECORD_KEYS is checked against keyof StackRecord, so a new record field
fails to compile until the create body accepts it.

Also document that parseQueryBody() takes an absent body as undefined, and
name /auth/token among the endpoints a server parses itself.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0193gggj973cZHEHdWJHXGTQ
@cuibonobo
cuibonobo merged commit 81496b7 into main Sep 25, 2026
9 checks passed
@cuibonobo
cuibonobo deleted the claude/review-pr-130-server-wo6559 branch September 25, 2026 22:20
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.

2 participants