security: make one-time downloads atomic under concurrency (ai:gpt-5) #49

Closed
opened 2026-05-23 21:23:07 +02:00 by heiko · 1 comment
Owner

Finding from a whole-codebase security review.

Affected code:

  • cmd/once-server/download.go:65-83 opens content, then removes it.
  • cmd/once-server/store/file.go:67-80 removes content and updates metadata after the caller already has an open reader.

Impact:
Two concurrent POST requests for the same download key can both open the content file before either request removes it. Both requests then keep valid file handles and can receive the secret, violating the documented exactly-once guarantee.

Suggested fix:

  • Claim a download atomically before streaming, for example by renaming content to a per-download in-progress path with os.Rename, or by using an advisory lock/claim file in the UUID directory.
  • Treat an already-claimed or already-removed object as downloaded.
  • Update metadata consistently with the successful claim.
  • Add a concurrency test that fires two POST downloads at the same key and asserts that only one gets the content.
Finding from a whole-codebase security review. Affected code: - cmd/once-server/download.go:65-83 opens `content`, then removes it. - cmd/once-server/store/file.go:67-80 removes `content` and updates metadata after the caller already has an open reader. Impact: Two concurrent POST requests for the same download key can both open the content file before either request removes it. Both requests then keep valid file handles and can receive the secret, violating the documented exactly-once guarantee. Suggested fix: - Claim a download atomically before streaming, for example by renaming `content` to a per-download in-progress path with `os.Rename`, or by using an advisory lock/claim file in the UUID directory. - Treat an already-claimed or already-removed object as downloaded. - Update metadata consistently with the successful claim. - Add a concurrency test that fires two POST downloads at the same key and asserts that only one gets the content.
Author
Owner

Fixed by commit 4e53e38 (fix: make one-time downloads atomic).

Implements atomic claiming via File.Claim() method which uses atomic os.Rename():

  1. Renames contentcontent.download atomically
  2. Opens the claimed file
  3. Deletes the claimed marker

Prevent TOCTOU vulnerabilities and concurrent downloads of the same file. Verified with test coverage (TestClaimAllowsOnlyOneDownloader).

Fixed by commit 4e53e38 (fix: make one-time downloads atomic). Implements atomic claiming via File.Claim() method which uses atomic os.Rename(): 1. Renames `content` → `content.download` atomically 2. Opens the claimed file 3. Deletes the claimed marker Prevent TOCTOU vulnerabilities and concurrent downloads of the same file. Verified with test coverage (TestClaimAllowsOnlyOneDownloader).
heiko closed this issue 2026-05-24 20:15:59 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
IUS/once#49
No description provided.