Skip to content

🛡️ Sentinel: Harden image processing routes against SSRF and injection#35

Open
Sumit-5002 wants to merge 2 commits into
mainfrom
sentinel-harden-image-routes-3256619706500965542
Open

🛡️ Sentinel: Harden image processing routes against SSRF and injection#35
Sumit-5002 wants to merge 2 commits into
mainfrom
sentinel-harden-image-routes-3256619706500965542

Conversation

@Sumit-5002

@Sumit-5002 Sumit-5002 commented Mar 27, 2026

Copy link
Copy Markdown
Owner

I have hardened the server/routes/image.js file to address several security risks.

Key improvements:

  1. SSRF Mitigation: The html-to-image endpoint now explicitly blocks redirects (3xx status codes) when fetching target URLs. This prevents attackers from bypassing the initial internal IP check by redirecting the server to a local resource. I also ensured that IPv6 addresses are correctly wrapped in square brackets during URL construction to prevent parsing errors.
  2. Input Validation: Added a strict whitelist of supported image formats (jpeg, png, webp, etc.) for all transformation endpoints. This prevents processing of unexpected or malicious file types that could exploit underlying library vulnerabilities.
  3. Information Leakage: Removed the inclusion of raw library error messages in 500 status responses to prevent leaking sensitive implementation details about the server's environment.

Verification:

  • Confirmed that SSRF attempts to internal IPs and via redirects are correctly blocked (returning 403 Forbidden).
  • Verified code quality with pnpm lint.
  • Ensured no unrelated lockfile churn was included in the submission.

PR created automatically by Jules for task 3256619706500965542 started by @Sumit-5002

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Image processing endpoints now validate and reject unsupported formats with appropriate error responses
    • Fixed IPv6 address handling in HTML-to-image conversion
    • Simplified error messages across image operations for consistency
  • Security

    • HTML-to-image conversion now blocks redirect responses

This change improves the security of the image processing API by:
- Mitigating SSRF in `/html-to-image` by disabling automatic redirects and manually rejecting 3xx responses.
- Ensuring IPv6 addresses are correctly formatted in target URLs for SSRF protection.
- Implementing a strict whitelist for allowed image formats (`ALLOWED_IMAGE_FORMATS`) in resize, compress, convert, and crop operations.
- Sanitizing error responses to prevent internal library details from leaking to clients.
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Sumit-5002 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 7 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 7 seconds.

⌛ 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: 57517cd3-fa0c-49cf-b234-db7c2f1b5944

📥 Commits

Reviewing files that changed from the base of the PR and between 851601e and f96ae47.

📒 Files selected for processing (1)
  • server/routes/image.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sentinel-harden-image-routes-3256619706500965542

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

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

@Sumit-5002 Sumit-5002 marked this pull request as ready for review March 27, 2026 07:53
@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@server/routes/image.js`:
- Around line 64-67: Validation paths currently await fs.unlink(req.file.path)
which can throw and prevent sending the 400 response; change each occurrence
(the checks using ALLOWED_IMAGE_FORMATS and the other validation blocks that
call fs.unlink before res.status(400).json) to perform best-effort cleanup: call
fs.unlink(req.file.path) inside a try/catch (or attach .catch(()=>{}) if using
promises) and ignore any unlink errors so the code always proceeds to return
res.status(400).json({ error: ... }); ensure you update all places that
reference fs.unlink(req.file.path) in the file so cleanup failures never block
the response.
- Around line 308-311: The rebuilt fetch target currently drops parsed.port;
update construction of targetUrl (where ipPart and targetUrl are created) to
include the port when present (e.g., append `:${parsed.port}` after ipPart)
while preserving IPv6 brackets logic in ipPart, and ensure the Host header sent
uses parsed.host (which includes hostname:port) instead of hostname only so
requests to hosts like https://example.com:8443/... use the correct port and
Host header.
- Line 8: Normalize any 'jpg' occurrences to 'jpeg' before using the format
value in response headers and filenames: update the logic that uses
ALLOWED_IMAGE_FORMATS and the format variable (the place that builds
Content-Type as `image/${format}` and the Content-Disposition filename) to map
'jpg' -> 'jpeg' (e.g., if (format === 'jpg') format = 'jpeg') so headers use the
standards-compliant MIME type and filenames use 'jpeg' while keeping
ALLOWED_IMAGE_FORMATS unchanged for input validation.
🪄 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: 329b3e81-e4c2-4856-ad6b-2d14cf800708

📥 Commits

Reviewing files that changed from the base of the PR and between dc284d7 and 851601e.

📒 Files selected for processing (1)
  • server/routes/image.js

Comment thread server/routes/image.js
Comment thread server/routes/image.js
Comment thread server/routes/image.js
@Sumit-5002

Copy link
Copy Markdown
Owner Author

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 `@server/routes/image.js`:
- Around line 64-67: Validation paths currently await fs.unlink(req.file.path)
which can throw and prevent sending the 400 response; change each occurrence
(the checks using ALLOWED_IMAGE_FORMATS and the other validation blocks that
call fs.unlink before res.status(400).json) to perform best-effort cleanup: call
fs.unlink(req.file.path) inside a try/catch (or attach .catch(()=>{}) if using
promises) and ignore any unlink errors so the code always proceeds to return
res.status(400).json({ error: ... }); ensure you update all places that
reference fs.unlink(req.file.path) in the file so cleanup failures never block
the response.
- Around line 308-311: The rebuilt fetch target currently drops parsed.port;
update construction of targetUrl (where ipPart and targetUrl are created) to
include the port when present (e.g., append `:${parsed.port}` after ipPart)
while preserving IPv6 brackets logic in ipPart, and ensure the Host header sent
uses parsed.host (which includes hostname:port) instead of hostname only so
requests to hosts like https://example.com:8443/... use the correct port and
Host header.
- Line 8: Normalize any 'jpg' occurrences to 'jpeg' before using the format
value in response headers and filenames: update the logic that uses
ALLOWED_IMAGE_FORMATS and the format variable (the place that builds
Content-Type as `image/${format}` and the Content-Disposition filename) to map
'jpg' -> 'jpeg' (e.g., if (format === 'jpg') format = 'jpeg') so headers use the
standards-compliant MIME type and filenames use 'jpeg' while keeping
ALLOWED_IMAGE_FORMATS unchanged for input validation.
🪄 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: 329b3e81-e4c2-4856-ad6b-2d14cf800708

📥 Commits

Reviewing files that changed from the base of the PR and between dc284d7 and 851601e.

📒 Files selected for processing (1)
  • server/routes/image.js

@jules check the comments of coderabbit

@google-labs-jules

Copy link
Copy Markdown

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 `@server/routes/image.js`:
- Around line 64-67: Validation paths currently await fs.unlink(req.file.path)
which can throw and prevent sending the 400 response; change each occurrence
(the checks using ALLOWED_IMAGE_FORMATS and the other validation blocks that
call fs.unlink before res.status(400).json) to perform best-effort cleanup: call
fs.unlink(req.file.path) inside a try/catch (or attach .catch(()=>{}) if using
promises) and ignore any unlink errors so the code always proceeds to return
res.status(400).json({ error: ... }); ensure you update all places that
reference fs.unlink(req.file.path) in the file so cleanup failures never block
the response.
- Around line 308-311: The rebuilt fetch target currently drops parsed.port;
update construction of targetUrl (where ipPart and targetUrl are created) to
include the port when present (e.g., append `:${parsed.port}` after ipPart)
while preserving IPv6 brackets logic in ipPart, and ensure the Host header sent
uses parsed.host (which includes hostname:port) instead of hostname only so
requests to hosts like https://example.com:8443/... use the correct port and
Host header.
- Line 8: Normalize any 'jpg' occurrences to 'jpeg' before using the format
value in response headers and filenames: update the logic that uses
ALLOWED_IMAGE_FORMATS and the format variable (the place that builds
Content-Type as `image/${format}` and the Content-Disposition filename) to map
'jpg' -> 'jpeg' (e.g., if (format === 'jpg') format = 'jpeg') so headers use the
standards-compliant MIME type and filenames use 'jpeg' while keeping
ALLOWED_IMAGE_FORMATS unchanged for input validation.
🪄 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: 329b3e81-e4c2-4856-ad6b-2d14cf800708

📥 Commits

Reviewing files that changed from the base of the PR and between dc284d7 and 851601e.

📒 Files selected for processing (1)
  • server/routes/image.js

@jules check the comments of coderabbit

I have implemented all the requested changes:

  1. Updated all fs.unlink calls in validation paths to use a best-effort approach with .catch(() => {}), ensuring that cleanup failures do not block error responses.
  2. Corrected the targetUrl construction in the html-to-image endpoint to include the port when present and updated the Host header to use parsed.host (which includes both hostname and port).
  3. Added logic to normalize 'jpg' to 'jpeg' for response headers and filenames while maintaining input validation for both.

@coderabbitai

coderabbitai Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Request timed out after 900000ms (requestId=7bae6a79-a35b-412d-9a09-b5e3759f4790)

This commit addresses the following:
- Updated `fs.unlink` cleanup in validation paths to be best-effort (`.catch(() => {})`) to avoid blocking error responses.
- Fixed SSRF mitigation in `html-to-image` to correctly handle custom ports and use the full `Host` header (hostname + port).
- Normalized 'jpg' format to 'jpeg' for response headers and filenames for better standard compliance.
- Removed sensitive error messages from 500 responses across all image processing endpoints.
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.

1 participant