From 845202e14bf534ebf51210d32910bbb6b6f6bc65 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Sun, 23 Aug 2026 00:39:20 -0500 Subject: [PATCH] http: make xrpcError terminate the request; guard-bypass regression in smoke MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit same shape as the oauthError fix: http_api.xrpcError wrote the error body and returned success, so every guard written as `return http_api.xrpcError(...)` inside a helper called with `try` — requireRepoScope, requireBlobScope, requireRepoMatches, requireActiveAccount, requirePublicRepoAvailable, requireSpaceScope, validatePlcOperation, verifyCreateAccountServiceAuth, verifyImportedRepoCar, and the rest of the audit — let the handler continue and overwrite the error with a 200. for createRecord that meant an OAuth token without the collection's scope, or a body naming someone else's repo, still wrote the record. xrpcError now returns error.HandledResponse after writing; the server loop already treats that as "answered". every existing non-terminal call site was followed by `return error.HandledResponse`, so nothing skips cleanup, and parseJsonBody's bad-JSON path becomes the 400 it meant to be instead of a 500. tools/smoke.sh asserts createRecord for another repo is 400 InvalidRepo and writes nothing, and getRecord on a missing repo stays 404; both fail on the previous code. Co-Authored-By: Claude Fable 5 --- src/http/api.zig | 5 +++++ tools/smoke.sh | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/http/api.zig b/src/http/api.zig index 76d7a17..4d703b1 100644 --- a/src/http/api.zig +++ b/src/http/api.zig @@ -333,6 +333,10 @@ pub fn text(request: *Request, status: http.Status, body: []const u8) !void { res.body = try res.arena.dupe(u8, body); } +// writes the xrpc error body and fails with HandledResponse so the caller +// stops too. a guard like `return http_api.xrpcError(...)` inside a helper +// invoked with `try` used to return success after writing, and the handler +// went on to do the work and overwrite the error with a 200. pub fn xrpcError( request: *Request, status: http.Status, @@ -342,6 +346,7 @@ pub fn xrpcError( var buf: [512]u8 = undefined; const body = try std.fmt.bufPrint(&buf, "{{\"error\":\"{s}\",\"message\":\"{s}\"}}", .{ error_name, message }); try json(request, status, body); + return error.HandledResponse; } const json_headers = [_]http.Header{ diff --git a/tools/smoke.sh b/tools/smoke.sh index 787e846..6d3afd6 100755 --- a/tools/smoke.sh +++ b/tools/smoke.sh @@ -254,6 +254,22 @@ invalid_like_status=$(curl -sS -o /tmp/zds-invalid-like.json -w '%{http_code}' - test "$invalid_like_status" = "400" grep -q '"error":"InvalidRequest"' /tmp/zds-invalid-like.json +# a guard that writes an xrpc error must end the request. requireRepoMatches +# once wrote 400 and returned success, and createRecord went on to write the +# record and answer 200 over it. +wrong_repo_body="${TMPDIR:-/tmp}/zds-smoke-wrong-repo.json" +wrong_repo_status=$(curl -sS -o "$wrong_repo_body" -w '%{http_code}' -X POST "$base/xrpc/com.atproto.repo.createRecord" \ + -H "authorization: Bearer $token" \ + -H 'content-type: application/json' \ + --data '{"repo":"did:plc:someoneelse","collection":"app.bsky.feed.post","record":{"$type":"app.bsky.feed.post","text":"must not land","createdAt":"2026-05-22T00:00:03.000Z"}}') +test "$wrong_repo_status" = "400" +grep -q '"error":"InvalidRepo"' "$wrong_repo_body" +! curl -fsS "$base/xrpc/com.atproto.repo.listRecords?repo=did:plc:smoketest&collection=app.bsky.feed.post&limit=10" | grep -q 'must not land' +# and a 404 from requirePublicRepoAvailable stays a 404 rather than a 500 +missing_repo_status=$(curl -sS -o "$wrong_repo_body" -w '%{http_code}' "$base/xrpc/com.atproto.repo.getRecord?repo=did:plc:nobody&collection=app.bsky.feed.post&rkey=3zds") +test "$missing_repo_status" = "404" +grep -q '"error":"RepoNotFound"' "$wrong_repo_body" + records=$(curl -fsS "$base/xrpc/com.atproto.repo.listRecords?repo=did:plc:smoketest&collection=app.bsky.feed.post&limit=10") printf '%s' "$records" | grep -q '"text":"smoke"' -- 2.51.2