Skip to content

Fix yarn audit vulnerabilities across backend and frontend#1692

Merged
gugu merged 1 commit intomainfrom
fix/yarn-audit-vulnerabilities
Mar 28, 2026
Merged

Fix yarn audit vulnerabilities across backend and frontend#1692
gugu merged 1 commit intomainfrom
fix/yarn-audit-vulnerabilities

Conversation

@gugu
Copy link
Copy Markdown
Contributor

@gugu gugu commented Mar 28, 2026

  • Upgrade nodemailer ^8.0.2 → ^8.0.4 (SMTP command injection, low)
  • Upgrade @angular/* ~20.3.16 → ~20.3.18 (XSS in i18n bindings, high)
  • Upgrade lodash-es ^4.17.21 → ^4.17.23 + resolution (prototype pollution, moderate)
  • Replace private-ip with ipaddr.js for private IP detection (SSRF, high, no patch available)

Summary by CodeRabbit

  • Chores
    • Updated nodemailer to patch version 8.0.4.
    • Upgraded Angular framework and tooling to version 20.3.18.
    • Updated lodash-es utility library to version 4.17.23.
    • Enhanced IP address validation mechanism.

- Upgrade nodemailer ^8.0.2 → ^8.0.4 (SMTP command injection, low)
- Upgrade @angular/* ~20.3.16 → ~20.3.18 (XSS in i18n bindings, high)
- Upgrade lodash-es ^4.17.21 → ^4.17.23 + resolution (prototype pollution, moderate)
- Replace private-ip with ipaddr.js for private IP detection (SSRF, high, no patch available)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 28, 2026 14:34
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

📝 Walkthrough

Walkthrough

Three dependency versions updated across backend and frontend packages: nodemailer from ^8.0.2 to ^8.0.4, Angular runtime and dev dependencies from ~20.3.16 to ~20.3.18, and lodash-es from ^4.17.21 to ^4.17.23. The private-ip dependency was removed and replaced with ipaddr.js in the hostname validator logic.

Changes

Cohort / File(s) Summary
Backend Dependencies
backend/package.json
Updated nodemailer from ^8.0.2 to ^8.0.4.
Frontend Dependencies
frontend/package.json
Upgraded Angular (@angular/animations, @angular/common, @angular/compiler, @angular/core, @angular/forms, @angular/platform-browser, @angular/platform-browser-dynamic, @angular/router, @angular/compiler-cli, @angular/language-service) from ~20.3.16 to ~20.3.18. Updated lodash-es from ^4.17.21 to ^4.17.23. Removed private-ip dependency. Added lodash-es resolution pinning to 4.17.23.
Frontend Hostname Validator
frontend/src/app/validators/hostname.validator.ts
Replaced private-ip import with ipaddr.js. Added PRIVATE_RANGES set and isPrivateIP() helper function to detect private IPs. Updated hostname validation to use new private IP detection logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Hops through versions, bumps so fine,
Angular dances, code realigns,
Private-ip hops away with grace,
ipaddr steps into its place! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Security Check ⚠️ Warning Hostname validator validates raw input instead of processed hostname, and PRIVATE_RANGES omits ipv4Mapped, allowing SSRF bypass attacks. Replace control.value with hostname on line 34 and add ipv4Mapped to PRIVATE_RANGES set on line 7; add test cases for protocol-prefixed URLs and IPv6-wrapped IPv4 addresses.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix yarn audit vulnerabilities across backend and frontend' accurately summarizes the main objective of the changeset, which involves addressing multiple security vulnerabilities through dependency updates across both backend and frontend packages.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/yarn-audit-vulnerabilities

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

@gugu gugu enabled auto-merge (squash) March 28, 2026 14:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates backend and frontend Node dependencies to address yarn audit security vulnerabilities, including replacing the unpatched private-ip package with ipaddr.js for private IP detection in the frontend.

Changes:

  • Upgraded nodemailer in the backend from ^8.0.2 to ^8.0.4.
  • Upgraded @angular/* packages in the frontend from ~20.3.16 to ~20.3.18.
  • Upgraded/pinned lodash-es to 4.17.23 (including a Yarn resolution) and removed private-ip from the frontend dependency graph.

Reviewed changes

Copilot reviewed 3 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
yarn.lock Updates backend workspace lock entries for nodemailer@^8.0.4.
frontend/yarn.lock Updates Angular packages, removes private-ip and related deps, and updates lodash-es lock entries.
frontend/src/app/validators/hostname.validator.ts Replaces private-ip usage with ipaddr.js-based private IP detection.
frontend/package.json Bumps Angular versions, updates lodash-es, removes private-ip, and adds a lodash-es resolution.
backend/package.json Bumps nodemailer version.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

if (control.value === 'localhost' || (isIP(control.value) && is_ip_private(control.value)))
if (control.value === 'localhost' || (isIP(control.value) && isPrivateIP(control.value)))
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

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

The localhost/private-IP guard is checking control.value, but you normalize into hostname (stripping https:// for DynamoDB and mongodb+srv:// for MongoDB). This allows bypasses like https://127.0.0.1 / mongodb+srv://127.0.0.1 to pass validation because isIP(control.value) is false while isIP(hostname) is true. Use the normalized hostname for the localhost / private-IP checks (and consider normalizing case for localhost).

Suggested change
if (control.value === 'localhost' || (isIP(control.value) && isPrivateIP(control.value)))
const normalizedHostname = hostname.toLowerCase();
if (normalizedHostname === 'localhost' || (isIP(hostname) && isPrivateIP(hostname)))

Copilot uses AI. Check for mistakes.
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/app/validators/hostname.validator.ts`:
- Around line 34-36: The private IP/localhost check currently uses control.value
which can include a scheme and bypass the SSRF check; update the conditional to
use the processed hostname variable instead (e.g., replace uses of control.value
in the first if with hostname) so that you call isIP(hostname) &&
isPrivateIP(hostname) and compare hostname === 'localhost' before validating
isIP(hostname) || isFQDN(hostname); keep the existing calls to isIP, isPrivateIP
and isFQDN and ensure the check occurs prior to the final validity check.
- Around line 7-15: The PRIVATE_RANGES set used by isPrivateIP does not include
the ipaddr.js range name for IPv4-mapped IPv6 addresses, allowing
::ffff:192.168.x.x to bypass the check; update the PRIVATE_RANGES Set (symbol:
PRIVATE_RANGES) to include the string 'ipv4Mapped' so that isPrivateIP
(function: isPrivateIP) treats IPv4-mapped addresses as private when
ipaddr.process(ip).range() returns 'ipv4Mapped'.
🪄 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: 201c4ac1-7058-4dcb-9380-e4dd096f4c1a

📥 Commits

Reviewing files that changed from the base of the PR and between 3546084 and 23ea13a.

⛔ Files ignored due to path filters (2)
  • frontend/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • backend/package.json
  • frontend/package.json
  • frontend/src/app/validators/hostname.validator.ts

Comment on lines +7 to +15
const PRIVATE_RANGES = new Set(['private', 'loopback', 'linkLocal', 'unspecified', 'carrierGradeNat', 'uniqueLocal']);

function isPrivateIP(ip: string): boolean {
try {
return PRIVATE_RANGES.has(ipaddr.process(ip).range());
} catch {
return false;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify ipaddr.js range values and behavior with IPv6-mapped addresses
node -e "
const ipaddr = require('ipaddr.js');
const testIPs = [
  '192.168.1.1',
  '10.0.0.1',
  '127.0.0.1',
  '::ffff:192.168.1.1',
  '::ffff:10.0.0.1',
  '::1'
];
testIPs.forEach(ip => {
  try {
    const parsed = ipaddr.process(ip);
    console.log(ip + ' -> range: ' + parsed.range());
  } catch (e) {
    console.log(ip + ' -> error: ' + e.message);
  }
});
"

Repository: rocket-admin/rocketadmin

Length of output: 904


🏁 Script executed:

# Find the hostname validator file and check if it exists
git ls-files | grep -E "hostname\.validator\.(ts|js)"

Repository: rocket-admin/rocketadmin

Length of output: 117


🏁 Script executed:

# Check if ipaddr.js is in the dependencies
find . -name "package.json" -type f | head -5 | xargs grep -l "ipaddr" 2>/dev/null || echo "No package.json found with ipaddr"

Repository: rocket-admin/rocketadmin

Length of output: 91


🏁 Script executed:

# Check the actual validator file to see the context
cat -n frontend/src/app/validators/hostname.validator.ts 2>/dev/null || echo "File not found at expected path"

Repository: rocket-admin/rocketadmin

Length of output: 1545


🌐 Web query:

ipaddr.js range() method IPv6-mapped addresses behavior documentation

💡 Result:

The ipaddr.js library provides a range method on both IPv4 and IPv6 address instances. This method returns a predefined name (e.g., "unicast", "reserved", "private") for special IP ranges defined by protocols, based on matching the address against hardcoded CIDR ranges in the source code. For IPv6 addresses, including IPv4-mapped addresses (e.g., ::ffff:192.168.1.1), the range method uses IPv6-specific special ranges listed in the source. One explicit range is "ipv4Mapped": [[new IPv6([0, 0, 0, 0, 0, 0xffff, 0, 0]), 96]], covering ::ffff:0:0/96 per RFC 4291. An IPv4-mapped IPv6 address like ipaddr.parse('::ffff:192.168.1.1').range will match the "ipv4Mapped" range if it fits ::ffff:0:0/96, returning "ipv4Mapped". It does not automatically convert to the underlying IPv4 address and use IPv4 ranges (e.g., "private" for 192.168.1.1); that behavior is only in the separate global ipaddr.process method for parsing. The exact ranges are defined in the library source at https://github.com/whitequark/ipaddr.js/blob/main/lib/ipaddr.js (IPv6 around line 530, IPv4 around line 182 as noted in README). Full list for IPv6 includes: unspecified, linkLocal, multicast, loopback, uniqueLocal, ipv4Mapped, rfc6145, rfc6052, 6to4, teredo, as112v6, orchid2, droneRemoteIdProtocolEntityTags, reserved, benchmarking, amt, discard, unicast (default). No official documentation explicitly details IPv4-mapped behavior for range, but source code confirms it treats them as IPv6 addresses matching IPv6 special ranges, specifically identifying them via the "ipv4Mapped" range.

Citations:


Add ipv4Mapped to PRIVATE_RANGES to prevent IPv6-wrapped private IP bypass.

IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1) are recognized by ipaddr.js with the range ipv4Mapped, which is not in PRIVATE_RANGES. This allows private IP addresses wrapped in IPv6 format to bypass the private IP restriction on line 34.

Fix to include ipv4Mapped range
-const PRIVATE_RANGES = new Set(['private', 'loopback', 'linkLocal', 'unspecified', 'carrierGradeNat', 'uniqueLocal']);
+const PRIVATE_RANGES = new Set(['private', 'loopback', 'linkLocal', 'unspecified', 'carrierGradeNat', 'uniqueLocal', 'ipv4Mapped']);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/validators/hostname.validator.ts` around lines 7 - 15, The
PRIVATE_RANGES set used by isPrivateIP does not include the ipaddr.js range name
for IPv4-mapped IPv6 addresses, allowing ::ffff:192.168.x.x to bypass the check;
update the PRIVATE_RANGES Set (symbol: PRIVATE_RANGES) to include the string
'ipv4Mapped' so that isPrivateIP (function: isPrivateIP) treats IPv4-mapped
addresses as private when ipaddr.process(ip).range() returns 'ipv4Mapped'.

Comment on lines +34 to 36
if (control.value === 'localhost' || (isIP(control.value) && isPrivateIP(control.value)))
return { isLocalhost: true };
if (!(isIP(hostname) || isFQDN(hostname))) return { isInvalidHostname: true };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Security: Private IP check bypassed for DynamoDB connections.

The private IP validation at line 34 uses control.value (raw input), but for DynamoDB the hostname is extracted from the https:// prefixed URL. When a user enters https://192.168.1.1:

  1. isIP("https://192.168.1.1") returns false, skipping the private IP check
  2. hostname = "192.168.1.1" passes the isIP(hostname) check at line 36

This defeats the SSRF mitigation this PR intends to address. The check should use the processed hostname variable.

🔒 Proposed fix to check processed hostname
-			if (control.value === 'localhost' || (isIP(control.value) && isPrivateIP(control.value)))
+			if (hostname === 'localhost' || (isIP(hostname) && isPrivateIP(hostname)))
 				return { isLocalhost: true };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/validators/hostname.validator.ts` around lines 34 - 36, The
private IP/localhost check currently uses control.value which can include a
scheme and bypass the SSRF check; update the conditional to use the processed
hostname variable instead (e.g., replace uses of control.value in the first if
with hostname) so that you call isIP(hostname) && isPrivateIP(hostname) and
compare hostname === 'localhost' before validating isIP(hostname) ||
isFQDN(hostname); keep the existing calls to isIP, isPrivateIP and isFQDN and
ensure the check occurs prior to the final validity check.

@gugu gugu merged commit f4cfd96 into main Mar 28, 2026
22 of 24 checks passed
@gugu gugu deleted the fix/yarn-audit-vulnerabilities branch March 28, 2026 14:50
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.

2 participants