fix: improve Blueprint asset handling#1713
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughBlueprint asset storage is restructured into ChangesBlueprint Asset Storage and Security
Sequence DiagramsequenceDiagram
participant Client
participant HttpRoute as HTTP Route<br/>(Server)
participant CoreApi as Asset API<br/>(Server)
participant Fs as Filesystem
Client->>HttpRoute: GET /api/blueprints/assets/file123.png
HttpRoute->>CoreApi: retrieveBlueprintAsset('file123.png')
activate CoreApi
Note over CoreApi: Resolve path under<br/>storePath/assets/
Note over CoreApi: Validate no traversal
CoreApi->>Fs: open(storePath/assets/file123.png)
Fs-->>CoreApi: ReadStream (on 'open')
deactivate CoreApi
CoreApi-->>HttpRoute: Promise<ReadStream>
HttpRoute->>HttpRoute: Set Content-Type, Cache headers
HttpRoute-->>Client: Stream body + 200
Client->>HttpRoute: POST /api/blueprints/assets
HttpRoute->>CoreApi: uploadBlueprintAsset(fileId, base64Data)
activate CoreApi
Note over CoreApi: Resolve path under<br/>storePath/assets/
Note over CoreApi: Validate no traversal
CoreApi->>Fs: mkdir -p dirname(assetPath)
CoreApi->>Fs: writeFile(assetPath, decodedBytes)
deactivate CoreApi
HttpRoute-->>Client: 200 (or 400/500 on error)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
8792b58 to
6b956dc
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@meteor/server/api/blueprints/api.ts`:
- Around line 134-138: The path traversal check in api.ts using assetsDir and
assetPath can be bypassed via sibling directory names; update the validation in
the asset lookup code to resolve real filesystem paths (use fs.realpath or
fs.realpathSync on assetsDir and assetPath), then compute
path.relative(assetsDirReal, assetPathReal) and reject the request if the
relative path starts with '..' or is equal to '..' (and also reject if
path.isAbsolute(relative) is true); ensure you reference the existing assetsDir,
assetPath and fileId variables and perform this stronger check before serving
the file.
- Around line 115-119: The current path traversal check using
assetPath.startsWith(assetsDir) is bypassable; update the containment check for
assetsDir/assetPath (where assetsDir, assetPath, fileId are used) to a robust
sibling-safe test — for example compute path.relative(assetsDir, assetPath) and
reject if it begins with '..' or equals ''/'. Ensure you normalize/resolved both
paths first (using path.resolve) and use a path separator-aware comparison (or
ensure the relative result is not outside the asset directory) before allowing
access.
In `@meteor/server/api/blueprints/http.ts`:
- Around line 203-210: The catch block in the HTTP handler currently returns 501
for retrieval failures; change the logic to return a proper status: detect
path-traversal errors thrown by retrieveBlueprintAsset (e instanceof Error &&
/path[\s-]*traversal/i.test(e.message) or another identifying message) and set
ctx.statusCode = 403 (and log via logger.warn including the error), treat
missing file (e.code === 'ENOENT') as 404 as existing, and for any other
unexpected errors set ctx.statusCode = 500 and log the full error with
logger.error; update the logger messages in this block to include the error
details and use the symbols retrieveBlueprintAsset, logger.warn/logger.error,
and ctx.statusCode to find the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: de827cc1-924c-47b8-bff5-fad590cbe226
📒 Files selected for processing (4)
meteor/server/api/blueprints/api.tsmeteor/server/api/blueprints/http.tspackages/webui/src/client/lib/Components/BlueprintAssetIcon.tsxpackages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx
6b956dc to
87ca6b1
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
meteor/server/api/blueprints/http.ts (1)
203-213:⚠️ Potential issue | 🟠 MajorUse correct status codes for traversal and unexpected failures.
Line 212 returning
501is semantically wrong here; retrieval runtime failures should be500, and traversal attempts (Line 207-209) should be403. Also, unexpected failures should log withlogger.errorto preserve incident visibility.Suggested patch
} catch (e) { if (e instanceof Error && 'code' in e && e.code === 'ENOENT') { logger.warn('Blueprint asset not found: ' + e) ctx.statusCode = 404 } else if (e instanceof Error && e.message.includes('outside of asset storage path')) { logger.warn('Blueprint asset path traversal attempt: ' + e) - ctx.statusCode = 400 + ctx.statusCode = 403 } else { - logger.warn('Blueprint asset retrieval failed: ' + e) - ctx.statusCode = 501 + logger.error('Blueprint asset retrieval failed: ' + e) + ctx.statusCode = 500 } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@meteor/server/api/blueprints/http.ts` around lines 203 - 213, In the catch block in meteor/server/api/blueprints/http.ts that handles blueprint asset retrieval (the try/catch around asset reads), change the response codes and logging as follows: keep the ENOENT branch returning 404 with logger.warn, change the "outside of asset storage path" branch (path traversal) to set ctx.statusCode = 403 and use logger.warn, and change the final fallback branch to set ctx.statusCode = 500 and call logger.error instead of logger.warn so unexpected runtime failures are logged as errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@meteor/server/api/blueprints/http.ts`:
- Around line 203-213: In the catch block in
meteor/server/api/blueprints/http.ts that handles blueprint asset retrieval (the
try/catch around asset reads), change the response codes and logging as follows:
keep the ENOENT branch returning 404 with logger.warn, change the "outside of
asset storage path" branch (path traversal) to set ctx.statusCode = 403 and use
logger.warn, and change the final fallback branch to set ctx.statusCode = 500
and call logger.error instead of logger.warn so unexpected runtime failures are
logged as errors.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: eb595e1b-a934-4ee6-846b-18ba69f039d7
📒 Files selected for processing (4)
meteor/server/api/blueprints/api.tsmeteor/server/api/blueprints/http.tspackages/webui/src/client/lib/Components/BlueprintAssetIcon.tsxpackages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx
✅ Files skipped from review due to trivial changes (1)
- packages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
- meteor/server/api/blueprints/api.ts
87ca6b1 to
620ad0a
Compare
08aeebd to
e9300e5
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (1)
meteor/server/api/blueprints/api.ts (1)
115-119:⚠️ Potential issue | 🟠 Major
startsWith()still doesn't stop symlink escapes.This only constrains the lexical path. If a symlink already exists anywhere under
assets/, afileIdlikelinked-dir/file.pngstill passes the prefix check whilewriteFile/createReadStreamfollow the symlink outside the asset root. Please canonicalize withrealpathand compare withpath.relative(...)against the canonical asset directory before reading or writing.Also applies to: 134-138
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@meteor/server/api/blueprints/api.ts` around lines 115 - 119, The current prefix check using assetsDir and assetPath can be bypassed by symlinks; replace the lexical check in the asset lookup/write paths (references: assetsDir, assetPath, fileId) with a canonicalization-based check: resolve the real paths using fs.promises.realpath (or fs.realpathSync) for both the assets directory and the target asset path, then compute path.relative(realAssetsDir, realAssetPath) and throw if the result starts with '..' or is equal to '..' (or if the relative path is absolute), ensuring you perform this canonical check in both places that currently use startsWith (the asset read path and the asset write path).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@meteor/server/api/blueprints/api.ts`:
- Around line 115-119: The current prefix check using assetsDir and assetPath
can be bypassed by symlinks; replace the lexical check in the asset lookup/write
paths (references: assetsDir, assetPath, fileId) with a canonicalization-based
check: resolve the real paths using fs.promises.realpath (or fs.realpathSync)
for both the assets directory and the target asset path, then compute
path.relative(realAssetsDir, realAssetPath) and throw if the result starts with
'..' or is equal to '..' (or if the relative path is absolute), ensuring you
perform this canonical check in both places that currently use startsWith (the
asset read path and the asset write path).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0d245aff-507f-4ff0-85ba-b6f9cbfab1fd
📒 Files selected for processing (6)
meteor/server/api/blueprints/__tests__/api.test.tsmeteor/server/api/blueprints/__tests__/http.test.tsmeteor/server/api/blueprints/api.tsmeteor/server/api/blueprints/http.tspackages/webui/src/client/lib/Components/BlueprintAssetIcon.tsxpackages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx
✅ Files skipped from review due to trivial changes (1)
- packages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@meteor/server/api/blueprints/http.ts`:
- Around line 203-213: The try/catch can't catch async stream errors from
retrieveBlueprintAsset (which calls createReadStream), so change the response
flow to wait for the stream to open and handle its 'error' event before
assigning to ctx.body: call retrieveBlueprintAsset to get the ReadStream, attach
a one-time 'error' handler that maps ENOENT to ctx.statusCode=404,
path-traversal messages to 400 and other errors to 500, and attach a one-time
'open' handler (or use stream.promises/stream.finished) to only set ctx.body =
stream once the stream is ready; also remove reliance on synchronous exceptions
in the try/catch around retrieveBlueprintAsset and ensure any stream errors
clean up the stream and don't leak unhandled exceptions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e6cc3915-3575-4d7b-9807-fb610fdf3b54
📒 Files selected for processing (5)
meteor/server/api/blueprints/__tests__/api.test.tsmeteor/server/api/blueprints/__tests__/http.test.tsmeteor/server/api/blueprints/api.tsmeteor/server/api/blueprints/http.tspackages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
- meteor/server/api/blueprints/api.ts
- meteor/server/api/blueprints/tests/http.test.ts
- meteor/server/api/blueprints/tests/api.test.ts
…result in the assets route
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@meteor/server/api/blueprints/http.ts`:
- Around line 187-188: The current file extension check on variable filePath in
the HTTP blueprint accepts a trailing dot; update the if condition that uses
filePath.match(...) so it requires a full extension (e.g. use a regex that
enforces a dot followed by one of the allowed extensions and anchors the end,
and make it case-insensitive). Locate the check around the image-only gate (the
filePath variable and the if that currently contains /\.(png|svg|gif)?$/) and
replace the pattern with a strict pattern that does not allow a bare trailing
dot (for example, use a non-capturing group like (?:png|svg|gif) with
end-of-string anchor and /i flag).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 71831444-9c78-4125-8994-5e3ba16d1e7f
📒 Files selected for processing (2)
meteor/server/api/blueprints/api.tsmeteor/server/api/blueprints/http.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- meteor/server/api/blueprints/api.ts
| const filePath = ctx.params.fileId | ||
| if (filePath.match(/\.(png|svg|gif)?$/)) { |
There was a problem hiding this comment.
Require an actual allowed extension here.
/\.(png|svg|gif)?$/ still accepts a bare trailing dot, so /assets/foo. passes the image-only gate. Tighten this to require one of the allowed extensions.
Suggested fix
- if (filePath.match(/\.(png|svg|gif)?$/)) {
+ if (/\.(png|svg|gif)$/.test(filePath)) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const filePath = ctx.params.fileId | |
| if (filePath.match(/\.(png|svg|gif)?$/)) { | |
| const filePath = ctx.params.fileId | |
| if (/\.(png|svg|gif)$/.test(filePath)) { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@meteor/server/api/blueprints/http.ts` around lines 187 - 188, The current
file extension check on variable filePath in the HTTP blueprint accepts a
trailing dot; update the if condition that uses filePath.match(...) so it
requires a full extension (e.g. use a regex that enforces a dot followed by one
of the allowed extensions and anchors the end, and make it case-insensitive).
Locate the check around the image-only gate (the filePath variable and the if
that currently contains /\.(png|svg|gif)?$/) and replace the pattern with a
strict pattern that does not allow a bare trailing dot (for example, use a
non-capturing group like (?:png|svg|gif) with end-of-string anchor and /i flag).
About the Contributor
Type of Contribution
This is a:
Bug fix
Current Behavior
Blueprint assets can be stored in invalid locations, it's possible to use directory traversal attacks to read and write files using the Blueprint API, upload/download failures result in obscure errors. Blueprint asset URLs contain double slashes.
New Behavior
The simplest error (Not found) is correctly reported, directory traversal is not possible beyond Blueprint asset directory.
Testing
Affected areas
This PR affects Blueprint asset API
Time Frame
We intend to finish the development on this feature in two weeks time.
Other Information
Status