Repository navigation
Scope document upload and download to the session they name - #105
Merged
Merged
Conversation
Closes #104 These were the only session endpoints carrying ApplicantOrOperator without a resource-based ownership check. The policy asserts the caller holds some applicant or operator credential; it says nothing about which session that credential names, which is the whole point of the per-session token. Upload trusted the route's sessionId, so any applicant token could write documents into any session - landing a Submission row that reads in the operator console as the real applicant's upload. Download was worse than an absent check. sessionId and nodeId were in the route but never reached the service, which resolved the file id straight from blob storage: any authenticated caller could fetch any stored document given its id, with the path segments that appeared to scope the request doing nothing. These are identity and address documents, and the ids are not secrets - they are returned to the browser, stored in Submission.DataJson and shown in the operator console. Both now run the ownership check the other endpoints use: Write for upload, so the terminal-status rule applies too, and Read for download. Ownership alone would not close the read, since the owner of one session could still request another's file through their own route. The lookup is now scoped: GetDocumentAsync takes the session and node, and resolves a file only if an upload recorded against them carries the id. The port signature changed rather than a check being added beside it, so the unscoped path cannot be called at all. A file belonging elsewhere returns 404, not 403, since a refusal would confirm the id is real. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #104
Fixes the ownership defect in the document endpoints, written test-first.
The defect
UploadDocumentsandGetDocumentwere the only session endpoints carrying[Authorize(Policy = "ApplicantOrOperator")]without a resource-based ownership check. That policy asserts the caller holds some applicant or operator credential; it says nothing about which session that credential names — the whole point of the per-session token.Upload trusted the route's
sessionId, so any applicant token could write documents into any session, landing aSubmissionrow that reads in the operator console as the real applicant's upload.Download was worse than an absent check.
sessionIdandnodeIdwere in the route but never reached the service, which resolved the file id straight from blob storage:Any authenticated caller could fetch any stored document given its id, with the path segments that appeared to scope the request doing nothing at all. These are identity and address documents, and the ids are not secrets — they are returned to the browser on upload, stored in
Submission.DataJson, and shown in the operator console.The fix
Writefor upload — so the terminal-status rule from Applicant session tokens cannot be refreshed or revoked #101 applies too — andReadfor download.GetDocumentAsynctakes the session and node and resolves a file only if an upload recorded against them carries the id.UploadDocumentsCommandHandlerrecords each upload as aSubmissioncarrying theStoredFileInfo[]. The read path simply never consulted it. No schema change.Verification
Written test-first. Against the unfixed code the new suite ran 7 failed / 4 passed — the 4 passing being the legitimate-access cases, which correctly worked all along. After the fix: 11/11.
The two tests that matter most:
Download_OfAnotherSessionsFile_ThroughMyOwnSessionRoute_IsRefused— the case an authorization check alone would miss, since ownership legitimately passes.Download_NeverReachesStorage_WhenTheFileIsNotRecordedAgainstTheSession— asserts the bytes are never fetched, not merely withheld after the fact.No API shape change: routes, request and response bodies are identical, so the frontend needed no change.
StepRendereralready sends the applicant token on upload, whichUpload_ToMyOwnSession_Succeedspins.🤖 Generated with Claude Code