Skip to content

fix: improve Blueprint asset handling#1713

Open
jstarpl wants to merge 6 commits into
Sofie-Automation:mainfrom
nrkno:contrib/fix/improveBlueprintAssetHandling
Open

fix: improve Blueprint asset handling#1713
jstarpl wants to merge 6 commits into
Sofie-Automation:mainfrom
nrkno:contrib/fix/improveBlueprintAssetHandling

Conversation

@jstarpl
Copy link
Copy Markdown
Contributor

@jstarpl jstarpl commented Apr 13, 2026

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

  • I have added one or more unit tests for this PR
  • I have updated the relevant unit tests
  • No unit test changes are needed for this PR

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

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

@jstarpl jstarpl added the Contribution from NRK Contributions sponsored by NRK (nrk.no) label Apr 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Warning

Rate limit exceeded

@jstarpl has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 52 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 29cf1268-ec95-4a6b-bb53-aa6562277036

📥 Commits

Reviewing files that changed from the base of the PR and between 3884ef9 and 6760f8b.

📒 Files selected for processing (2)
  • meteor/server/api/blueprints/api.ts
  • meteor/server/api/blueprints/http.ts
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: improve Blueprint asset handling' directly summarizes the main change in the pull request, which improves blueprint asset handling by fixing directory traversal vulnerabilities and error reporting.
Description check ✅ Passed The description provides relevant context about the bug fix, detailing current security issues (directory traversal, invalid asset storage locations) and how the PR addresses them (improved error reporting, path validation).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 44 minutes and 52 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 13, 2026

Codecov Report

❌ Patch coverage is 72.22222% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
meteor/server/api/blueprints/api.ts 52.38% 10 Missing ⚠️

📢 Thoughts on this report? Let us know!

@jstarpl jstarpl force-pushed the contrib/fix/improveBlueprintAssetHandling branch from 8792b58 to 6b956dc Compare April 14, 2026 08:42
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between aace1e5 and 6b956dc.

📒 Files selected for processing (4)
  • meteor/server/api/blueprints/api.ts
  • meteor/server/api/blueprints/http.ts
  • packages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
  • packages/webui/src/client/ui/PreviewPopUp/PreviewPopUpContext.tsx

Comment thread meteor/server/api/blueprints/api.ts Outdated
Comment thread meteor/server/api/blueprints/api.ts Outdated
Comment thread meteor/server/api/blueprints/http.ts
@jstarpl jstarpl force-pushed the contrib/fix/improveBlueprintAssetHandling branch from 6b956dc to 87ca6b1 Compare April 14, 2026 10:06
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
meteor/server/api/blueprints/http.ts (1)

203-213: ⚠️ Potential issue | 🟠 Major

Use correct status codes for traversal and unexpected failures.

Line 212 returning 501 is semantically wrong here; retrieval runtime failures should be 500, and traversal attempts (Line 207-209) should be 403. Also, unexpected failures should log with logger.error to 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b956dc and 87ca6b1.

📒 Files selected for processing (4)
  • meteor/server/api/blueprints/api.ts
  • meteor/server/api/blueprints/http.ts
  • packages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
  • packages/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

@jstarpl jstarpl force-pushed the contrib/fix/improveBlueprintAssetHandling branch from 87ca6b1 to 620ad0a Compare April 14, 2026 10:23
@jstarpl jstarpl force-pushed the contrib/fix/improveBlueprintAssetHandling branch from 08aeebd to e9300e5 Compare April 14, 2026 11:22
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ 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/, a fileId like linked-dir/file.png still passes the prefix check while writeFile/createReadStream follow the symlink outside the asset root. Please canonicalize with realpath and compare with path.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

📥 Commits

Reviewing files that changed from the base of the PR and between 87ca6b1 and 08aeebd.

📒 Files selected for processing (6)
  • meteor/server/api/blueprints/__tests__/api.test.ts
  • meteor/server/api/blueprints/__tests__/http.test.ts
  • meteor/server/api/blueprints/api.ts
  • meteor/server/api/blueprints/http.ts
  • packages/webui/src/client/lib/Components/BlueprintAssetIcon.tsx
  • packages/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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 08aeebd and 3884ef9.

📒 Files selected for processing (5)
  • meteor/server/api/blueprints/__tests__/api.test.ts
  • meteor/server/api/blueprints/__tests__/http.test.ts
  • meteor/server/api/blueprints/api.ts
  • meteor/server/api/blueprints/http.ts
  • packages/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

Comment thread meteor/server/api/blueprints/http.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Contribution from NRK Contributions sponsored by NRK (nrk.no)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant