Reject a non-UUID guid before it becomes a file id#304
Merged
Conversation
…le id A source PUT can supply a guid form field. On a new document that guid becomes the file id and is written into the reserved .da-versions key space. These tests assert that a guid which is not a plain UUID, or which contains a slash or a .da-versions segment, is rejected with 400 and never written to storage, while a valid UUID guid still works. The tests are red against current code.
…ile id A source PUT or POST may include a guid form field. When the target has no stored file id yet, that guid becomes the document file id. The file id is then used as a path segment inside the reserved .da-versions key space. Before this change the guid was accepted without any checks. A value with a slash or a .da-versions segment, or any other value that is not a plain UUID, was stored as the file id and used to build keys. This change validates the guid against a UUID allowlist so only a plain UUID is accepted as a new file id. A missing or empty guid still falls back to a generated UUID, so normal writes are unaffected. A file id read back from stored metadata is also treated as untrusted. An id persisted before this validation existed could contain key steering characters. The storage transport builds a URL from the key, so it collapses a dot or double dot segment, treats a backslash as a separator, decodes a percent escape, and strips whitespace. A stored id that uses any of those, or that has a slash or a .da-versions segment, is now refused before it is used to build reserved keys. This check is weaker than the UUID allowlist on purpose, so a benign legacy single-segment id keeps working. Document the optional guid field, its UUID constraint, the 400 response, and the 409 response for a mismatch against an existing stored identifier on the source API, and regenerate the API docs. Add unit, integration, and OpenAPI contract tests for the new validation.
benpeter
marked this pull request as ready for review
July 16, 2026 18:36
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
kptdobe
approved these changes
Jul 17, 2026
adobe-bot
pushed a commit
that referenced
this pull request
Jul 17, 2026
# [1.13.0](v1.12.4...v1.13.0) (2026-07-17) ### Features * reject a non-UUID guid before it becomes a file id ([#304](#304)) ([818c553](818c553))
Collaborator
|
🎉 This PR is included in version 1.13.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
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.
What
A source PUT or POST can send a
guidform field. When the object has no storedfile id, that
guidbecomes the file id. The file id is a path segment in thereserved
.da-versionskey space.The old code took the
guidas is. A slash, a.da-versionssegment, or anynon-UUID value became the file id and shaped the keys. Now the
guidmust be aplain UUID. A file id read back from stored metadata is also untrusted. An unsafe
stored id no longer builds reserved keys.
Change
isValidIdtosrc/storage/version/paths.js. It allowlists the UUIDshape. It returns true only for a plain UUID.
putObjectWithVersion, reject a suppliedguidthat fails the allowlistwith a 400. This runs before the
guidbecomes the file id.isSafeIdtosrc/storage/version/paths.js. Apply it to the file id fromstored metadata. Stored metadata is untrusted. An id written before this check
could carry key steering characters. The storage transport turns the key into a
URL. That URL collapses a dot or double dot segment, treats a backslash as a
separator, decodes a percent escape, and strips whitespace.
isSafeIdrefusesan id that uses any of those. It also refuses a slash or a
.da-versionssegment. It is weaker than the UUID allowlist on purpose. A benign legacy
single-segment id still works.
guidfield, its UUID constraint, the 400 response, andthe 409 mismatch response on the source API (
docs/openapi/schemas.yaml,docs/openapi/source-api.yaml). Regeneratedocs/index.html.A missing or empty
guidstill falls back to a generated UUID. Normal writes donot change. A document with a benign stored id still works.
Testing
guidshapes: slash, leading slash,.da-versionssegment, non-UUID string, and a UUID with a trailing path. Avalid UUID is still accepted and stored as the document id.
isValidIdandisSafeIddirectly. A regression suiterefuses an unsafe stored id (slash,
.da-versionssegment, bare.da-versions,a single or double dot segment, a backslash, and a percent-encoded dot segment)
with a 400 and writes nothing. A benign legacy single-segment stored id,
including one that contains a dot, still updates.
guidreturns 400. Asource PUT with a valid UUID
guidreturns 201 with the id in theX-da-idheader.
descriptions keep documenting the
guidfield, the UUID constraint, and the400 and 409 responses.
npm test,npm run lint, andnpm run test:itall pass.