Skip to content

fix: allow only GET and HEAD on the read endpoints and refuse CONNECT (#78) - #80

Merged
abienkowski merged 4 commits into
mainfrom
fix/read-endpoints-methods-78
Oct 12, 2026
Merged

abienkowski merged 4 commits into
mainfrom
fix/read-endpoints-methods-78

Conversation

@abienkowski

Copy link
Copy Markdown
Collaborator

Description

Closes #78.

All three routers had an early clause that allowed /_ping, /version, /info and any path starting with /events for every method. So POST /_ping, DELETE /version, OPTIONS /_ping (the daemon's CORS preflight) and CONNECT /_ping were forwarded to the daemon (outside --readonly), and /eventsxyz matched too. In TS, CONNECT was worse: the server had no 'connect' listener, so Node silently dropped the connection, with no response and no audit entry.

The design was reviewed by Claude Fable 5.1 before implementation; its changes (the property's wording, the CONNECT/upgrade order, TS socket handling and test approach, extra discriminating rows) are adopted.

What changes

  • The early allow clause is removed in all three routers. GET/HEAD on those endpoints reach the read passthrough exactly as before (with or without version prefix or query). Every other method falls through to the default deny, endpoint <METHOD> <path> is not allowed. OPTIONS is denied intentionally: it is the daemon's CORS preflight route and is not a read.
  • CONNECT is refused first in every handler, before the Go proxy allows websocket attach (GET /containers/{name}/attach/ws), giving stdin access to containers #76 upgrade check: 403 CONNECT is not allowed, audited (with the authority-form target, e.g. localhost:80, kept in the log). TS: refuseConnect, exported from handler.ts and wired in index.ts via server.on("connect"). It swallows socket errors, writes a raw 403 with Content-Length and Connection: close, then ends and destroys the socket, so an abandoned client cannot hang or crash the proxy.
  • Spec (spec/router.qnt): property readPassthroughOnlyForReads (every allowRead is a GET or HEAD), flag ALLOW_ANY_METHOD_PING, rows for /_ping, and router_pre78 showing the old gap. The handler-level CONNECT refusal is not modelled (METHODS has no CONNECT); the language and integration tests pin it.
  • Tests: the same router table in all three (POST/DELETE/PUT/OPTIONS/PATCH/CONNECT/lowercase connect denied; GET/HEAD allowed; POST /eventsxyz denied with GET /eventsxyz as a pin; POST /v1.45/_ping). Handler tests for origin- and authority-form CONNECT and CONNECT + Upgrade ordering, transport never called. A TS real-server test reads the raw 403 over net.connect and checks the audit entry. Seven integration checks.
  • Docs: README, AGENTS.md, spec/README.md, deploy/test.sh.

Review notes

  • Release: merging cuts v0.3.10 (patch). Simulated squash: bump=patch tag=v0.3.10.
  • A lowercase connect is an extension method, not CONNECT: Go and Rust deny it in the router (endpoint connect /_ping is not allowed) rather than the handler; Node's parser rejects it. Pinned by a table row.

Verification

  • Spec mutation: router with ALLOW_ANY_METHOD_PING = true fails readPassthroughSoundTest, pingPostDenied and pingDeleteDenied; router_pre78 asserts the property fails.
  • Before the fix, the new unit tests fail (Go: TestRouteReadEndpointMethods rows such as Route(POST, /_ping) = 1/"", want 0/"endpoint POST /_ping is not allowed", and TestHandlerRefusesConnect), and the new integration checks fail on all three (55 PASSED, 5 FAILED each), for example:
    FAIL: POST /_ping -> 403 (read endpoint, GET/HEAD only) (expected 403 with "endpoint POST /_ping is not allowed", got 404: {"message":"page not found"})
    FAIL: OPTIONS /_ping -> 403 (CORS preflight denied) (expected 403 with "endpoint OPTIONS /_ping is not allowed", got 200: )
    FAIL: CONNECT /_ping -> 403 (CONNECT refused) (expected 403 with "CONNECT is not allowed", got 000: )   # TS: no response
    
    (The daemon answers 404/200 here: the proxy forwarded them.)
  • This branch: make test-all Go 112, Rust 144, TS 163 (1 skipped); make lint-all, make test-spec, make verify pass; integration ALL 60 TESTS PASSED for Go, Rust and TS; socket suite ALL 15 TESTS PASSED.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Implementation(s) changed

  • Go
  • Rust
  • TypeScript
  • Quint specification
  • CI / infrastructure

Testing

  • Unit tests pass (make test-all)
  • Integration tests pass (60 per implementation; socket suite 15)
  • Quint verification passes (make verify, make test-spec)
  • New tests added for the change

Checklist

  • I have read CONTRIBUTING.md
  • My code follows the project's coding style
  • I have updated documentation as needed

@abienkowski abienkowski added Priority: P3 Added to issues and PRs relating to a low severity bugs. Type: Bug Added to issues and PRs if they are addressing a bug labels Oct 12, 2026
@abienkowski
abienkowski merged commit af15a01 into main Oct 12, 2026
7 checks passed
@abienkowski
abienkowski deleted the fix/read-endpoints-methods-78 branch October 12, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: P3 Added to issues and PRs relating to a low severity bugs. Type: Bug Added to issues and PRs if they are addressing a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Router allows any method on /_ping, /version, /info and /events; CONNECT handled unevenly

1 participant