Repository navigation
Validate project references, and stop lying about missing handoffs - #22
Merged
Merged
Conversation
POST /memories and POST /sessions accepted any projectId, including one owned by another account and one that does not exist, and answered 201. The record was then invisible from the project it named. POST /handoffs already refused both, so the intended behaviour and its error code were already written. The guard sits in the repositories rather than the routes because MCP calls the same services; a check in the route handlers would not cover save_memory or start_session. The reference failure is now a typed error. The handoff routes previously caught everything and answered 400 INVALID_RELATION, so a database outage was reported to the caller as a bad request.
Claiming an id with no matching handoff answered 409 "closed or already held by a live agent", identical to the genuinely-held case. A mistyped id sent the caller to debug an agent that was never there. It now answers 404, which is what every other endpoint already returns for a record outside the workspace.
Also documents both in the README and the agent guide.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The two you asked me to leave. Both change an API contract, both toward what the code already does elsewhere.
1. A record could be filed against a project the caller cannot see
POST /memoriesandPOST /sessionsaccepted anyprojectId— one owned by another account, or one that never existed — and answered201.POST /handoffsalready refused both with400 INVALID_RELATION, using anownedProject()helper that was right there.Before, with two accounts:
After:
Nothing leaked before this — I checked, and the project's name never surfaced anywhere in B's memory list, session list, summary, or briefing. The damage was that the record became invisible from the project it named, with a
201saying it had worked.The guard sits in the repositories, not the routes. MCP calls the same services, so a check in the route handlers would have covered
POST /memoriesbut notsave_memory, andPOST /sessionsbut notstart_session— which is the same shape as the bug being fixed.2. Claiming a handoff that does not exist blamed a live agent
Both now answer
404 Handoff not found, matching what every other endpoint returns for a record outside the workspace. The genuinely-held case still answers409:A third thing, found on the way
Both handoff routes caught every exception and answered
400 INVALID_RELATION. A database outage duringcreateHandoffwas therefore reported to the caller as a bad request. The reference failure is now a typedReferenceNotFoundError; anything else rethrows.Verified
48/48 against a fresh PostgreSQL 17 — 46 existing plus 2 new. The cross-account matrix above was run separately, because the suite has only one account and cannot reach that case; the tests use a nonexistent id, which is the same guard.
All three commits typecheck standalone.
Note on existing data
Rows written before this may still point at projects their owner cannot see. Nothing cleans them up, and I did not add a migration to do it — worth deciding separately. The durable version of this fix is a composite foreign key on
(userId, projectId), which would make the state unrepresentable rather than merely rejected at the edge; that is a schema change, so I left it.