Skip to content

chore: hasValidRelativePathSegments to align with s3 object-keys - #1387

Open
staaldraad wants to merge 2 commits into
masterfrom
etienne/storage-766-key-restrictions
Open

staaldraad wants to merge 2 commits into
masterfrom
etienne/storage-766-key-restrictions

Conversation

@staaldraad

Copy link
Copy Markdown
Member

What kind of change does this PR introduce?

Bug fix/prevent

What is the current behavior?

'isValidKey` applies the allowed character set, without accounting for character semantics such as path normalisation.

What is the new behavior?

Adds hasValidRelativePathSegments which is evaluated as part of isValidKey. This applies the same prefix limitations as what AWS s3 does, namely;

Object keys that contain relative path elements (for example, ../) are valid if, when parsed left-to-right, the cumulative count of relative path segments never exceeds the number of non-relative path elements encountered

Additional context

https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html

@staaldraad
staaldraad requested a review from a team as a code owner September 14, 2026 10:28
Comment thread src/storage/limits.ts
// only allow s3 safe characters and characters which require special handling for now
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html
return key.length > 0 && VALID_OBJECT_KEY.test(key)
return key.length > 0 && VALID_OBJECT_KEY.test(key) && hasValidRelativePathSegments(key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 isValidKey's new relative-path check is applied retroactively via mustBeValidKey in object.ts's findObject/copyObject/moveObject, so any object key already stored under the old (more permissive) validation with excess '..' segments (e.g. 'videos/../../video1.wmv', previously accepted) now throws InvalidKey on lookup, making pre-existing objects permanently inaccessible even though the underlying data is untouched. Fix: only enforce the stricter relative-path rule on write paths (upload/rename/copy destination), or grandfather already-existing keys, so GET/find on legacy keys does not start failing after this deploy.

Extended reasoning...

Before this change, isValidKey only checked the character-class regex, so a caller could successfully upload an object with objectName 'videos/../../video1.wmv' (or any key with excess '..') and it would be stored and later retrievable via ObjectStorage.findObject, which also calls mustBeValidKey on the same key. After this diff, hasValidRelativePathSegments rejects that same key (depth goes negative), so isValidKey now returns false and mustBeValidKey throws ERRORS.InvalidKey for that exact stored key. Any tenant with such a pre-existing object will get GET/HEAD/copy/move failures for it post-deploy, even though nothing about the object itself changed, since findObject (src/storage/object.ts:124/282) applies the new stricter rule uniformly to reads as well as writes.

Verification: normal (backward-compat regression on read paths). On the base branch, isValidKey only checked the char-class regex VALID_OBJECT_KEY = /^[A-Za-z0-9_/!.*'() &$=@;:+,?-]*$/ (limits.ts:88), which permits both . and /, so a key like videos/../../video1.wmv passed validation and could be uploaded/stored (createObject at object.ts:124 uses the same mustBeValidKey→isValidKey). After this diff,…

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is important to handle

@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34834080191

Coverage increased (+0.008%) to 82.811%

Details

  • Coverage increased (+0.008%) from the base build.
  • Patch coverage: 9 of 9 lines across 1 file are fully covered (100%).
  • 1 coverage regression across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
src/http/routes/s3/index.ts 1 87.42%

Coverage Stats

Coverage Status
Relevant Lines: 14099
Covered Lines: 12125
Line Coverage: 86.0%
Relevant Branches: 8619
Covered Branches: 6688
Branch Coverage: 77.6%
Branches in Coverage %: Yes
Coverage Strength: 730.7 hits per line

💛 - Coveralls

Comment thread src/storage/limits.ts
*/
function hasValidRelativePathSegments(key: string): boolean {
let depth = 0
for (const segment of key.split('/')) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe too much but we could have !contains('..') fast path since easy/small

@ferhatelmas

Copy link
Copy Markdown
Member

@staaldraad do you want us to handle the inline comment for legacy keys?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants