Skip to content

security: fix SSRF, API key exposure, pattern bypass, symlink escapes#618

Merged
MarvelNwachukwu merged 3 commits intomainfrom
fix-ssrf-symlink-security
Mar 20, 2026
Merged

security: fix SSRF, API key exposure, pattern bypass, symlink escapes#618
MarvelNwachukwu merged 3 commits intomainfrom
fix-ssrf-symlink-security

Conversation

@MarvelNwachukwu
Copy link
Contributor

@MarvelNwachukwu MarvelNwachukwu commented Mar 19, 2026

Description

Motivation

A security audit identified five vulnerabilities in ADK-TS tools where LLM-controlled inputs could be exploited: two SSRF vectors allowing access to cloud metadata and internal services, an API key leaked in URL query parameters, a command blocklist trivially bypassed via scripting interpreters, and a symlink-based filesystem sandbox escape. These affect any deployment where agents use built-in HTTP, bash, or file tools with untrusted LLM outputs.

Changes

S1/S2 SSRF: Added validateUrlForFetch() utility blocking private IPs (0/8, 10/8, 127/8, 169.254/16, 172.16-31/12, 192.168/16), localhost, IPv6-mapped addresses, and non-HTTP protocols. Revalidates on redirects using redirect: "manual" (http-request-tool) and axios's beforeRedirect hook (web-fetch-tool).

S3 API key exposure: Moved Gemini API key from URL query param (?key=...) to x-goog-api-key header, matching OpenAI/Anthropic patterns and preventing logging/history/proxy exposure.

S4 Pattern bypass: Strengthened BashTool dangerous pattern regex to cover interpreter execution (python, node, perl, ruby, php) and alternative netcat variants (nc/ncat/netcat).

S5 Symlink escape: Fixed FileOperationsTool path validation using fs.realpath with boundary-checked startsWith (prevents /app from matching /app-evil), and switched listDirectory to fs.lstat to avoid following symlinks.

Additional hardening: IPv6-mapped address extraction (::ffff:127.0.0.1), full 0.0.0.0/8 blocking, strict parent-directory validation (no normalize fallback), and comprehensive test coverage (41 new tests).

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Tests

How Has This Been Tested?

All 514 existing tests pass, plus 41 new security tests: 22 URL validation (protocol, IP ranges, IPv6 mapping, dword/octal/hex formats), 7 file operations (path traversal, symlinks, prefix confusion), 15 bash patterns (interpreters, downloads, netcat). Code formatted with Biome, builds without errors.

Checklist

  • My code follows the code style of this project
  • I have added tests to cover my changes
  • All new and existing tests passed
  • My changes generate no new warnings
  • I have checked for potential breaking changes and addressed them

…apes

S1/S2: Add SSRF protection via validateUrlForFetch utility (blocks private IPs, localhost, non-HTTP protocols). Revalidate on redirects in http-request-tool (redirect: manual) and web-fetch-tool (beforeRedirect hook).

S3: Move Gemini API key from URL query param to x-goog-api-key header to prevent logging/history/proxy exposure.

S4: Strengthen BashTool dangerous patterns to cover interpreter execution (python, node, perl, ruby, php) and alternative netcat/ncat/netcat binaries.

S5: Fix FileOperationsTool symlink escape via fs.realpath validation with boundary-checked startsWith, use fs.lstat in directory listing.

Also includes: IPv6-mapped address detection (::ffff:127.0.0.1), full 0.0.0.0/8 blocking, path prefix confusion mitigation, and comprehensive tests (19 URL validation, 7 file ops, 15 bash patterns).
@vercel
Copy link
Contributor

vercel bot commented Mar 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
adk-typescript-docs Ready Ready Preview Mar 19, 2026 3:39pm
adk-web Ready Ready Preview Mar 19, 2026 3:39pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 19, 2026

🦋 Changeset detected

Latest commit: 642778b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@iqai/adk Patch
@iqai/adk-cli Patch
@iqai/mcp-docs Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of LLM-controlled inputs within ADK-TS tools by addressing five critical and high-severity vulnerabilities. The changes focus on preventing Server-Side Request Forgery (SSRF), securing API key handling, improving detection of dangerous bash commands, and mitigating symlink escape attacks in file operations. These updates are complemented by extensive new test coverage to ensure robustness and prevent common attack vectors.

Highlights

  • SSRF Prevention: Implemented validateUrlForFetch() to block private IPs (0/8, 10/8, 127/8, 169.254/16, 172.16-31/12, 192.168/16), localhost, and non-HTTP protocols, with revalidation on redirects for http-request-tool and web-fetch-tool.
  • API Key Security: Moved Gemini API key from URL query parameters (?key=...) to the x-goog-api-key header to prevent logging, history, and proxy exposure.
  • BashTool Pattern Enhancement: Strengthened BashTool dangerous pattern regex to cover interpreter execution (python, node, perl, ruby, php) and alternative netcat variants (nc/ncat/netcat).
  • Symlink Escape Fixes: Updated FileOperationsTool path validation using fs.realpath with boundary-checked startsWith and switched listDirectory to fs.lstat to avoid following symlinks.
  • Comprehensive Testing: Added 41 new security tests covering URL validation (19 tests), file operations (7 tests), and bash patterns (15 tests).
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several important security fixes, including preventing SSRF, avoiding API key exposure, strengthening dangerous command patterns, and fixing symlink escape vulnerabilities. The changes are generally well-implemented and come with good test coverage. However, I've identified a few areas for improvement. The new path validation logic in FileOperationsTool is complex and has a potential security weakness in its fallback mechanism. Additionally, the new URL validation for SSRF prevention is susceptible to bypass via alternative IP address formats. I've also left some minor comments on markdown file formatting for better readability.

Comment on lines +62 to +84
function isPrivateIP(hostname: string): boolean {
const match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
if (!match) return false;

const [, a, b] = match;
const first = Number(a);
const second = Number(b);

// 0.0.0.0/8 (entire block is reserved)
if (first === 0) return true;
// 127.0.0.0/8
if (first === 127) return true;
// 10.0.0.0/8
if (first === 10) return true;
// 172.16.0.0/12
if (first === 172 && second >= 16 && second <= 31) return true;
// 192.168.0.0/16
if (first === 192 && second === 168) return true;
// 169.254.0.0/16 (link-local / cloud metadata)
if (first === 169 && second === 254) return true;

return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The isPrivateIP function is vulnerable to IP address format variations, which can lead to a bypass of the SSRF protection. The current implementation uses a regex that only matches the dotted-decimal format. However, hostnames can be specified in other formats like dword (2130706433), octal (0177.0.0.1), or hex (0x7f.0.0.1), which may be resolved to a private IP address by the underlying fetch implementation. Node's URL parser does not normalize all of these formats (e.g., dword), so they would bypass your regex check. This is a common and critical SSRF bypass vector. To fix this, you should use a robust IP address parsing method that can handle these alternative formats before checking if the IP is private.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Verified this is not exploitable in Node.js. The URL constructor normalizes all alternative IP formats (dword, octal, hex) to standard dotted-decimal before we see the hostname. Added test cases for dword, octal, and hex formats to document this guarantee.

Comment on lines +159 to 209
private async validatePath(filepath: string): Promise<void> {
const realBasePath = await fs.realpath(this.basePath);

const isWithinBase = (p: string, base: string) =>
p === base || p.startsWith(base + path.sep);

try {
const realPath = await fs.realpath(filepath);
if (!isWithinBase(realPath, realBasePath)) {
throw new Error(
"Access denied: Can't access paths outside the base directory",
);
}
} catch (error: unknown) {
if (
error instanceof Error &&
"code" in error &&
(error as NodeJS.ErrnoException).code === "ENOENT"
) {
// File doesn't exist yet (write/mkdir) — validate the parent directory
const parentDir = path.dirname(filepath);
try {
const realParent = await fs.realpath(parentDir);
if (!isWithinBase(realParent, realBasePath)) {
throw new Error(
"Access denied: Can't access paths outside the base directory",
);
}
} catch (parentError: unknown) {
if (
parentError instanceof Error &&
"code" in parentError &&
(parentError as NodeJS.ErrnoException).code === "ENOENT"
) {
// Parent also doesn't exist — fall back to normalize check
const normalizedPath = path.normalize(filepath);
const normalizedBasePath = path.normalize(this.basePath);
if (!isWithinBase(normalizedPath, normalizedBasePath)) {
throw new Error(
"Access denied: Can't access paths outside the base directory",
);
}
} else {
throw parentError;
}
}
} else {
throw error;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The new implementation of validatePath is quite complex with nested try-catch blocks, which harms readability and maintainability. More importantly, the fallback logic for when a file and its parent directory do not exist re-introduces a security risk. It falls back to a path.normalize() check, which does not resolve symbolic links that may exist in the non-existent parts of the path. This could lead to a Time-of-Check to Time-of-Use (TOCTOU) vulnerability if an attacker can create symlinks in the path after validation but before the file operation. For a security-critical function, it would be safer to disallow operations on paths where the parent directory's real path cannot be resolved. Consider refactoring for clarity and adopting a stricter validation approach that avoids this fallback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Removed the normalize fallback entirely — if the parent directory can't be resolved via realpath, we now reject with Access denied. This eliminates the TOCTOU window and simplifies the logic significantly. See 0c189c7.


### 2. Second Step
Content for step 2.
### 1. First Step Content for step 1. ### 2. Second Step Content for step 2.
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This change consolidates the Steps component's content onto a single line, which significantly reduces the readability and maintainability of the markdown source. For better clarity, please restore the original multi-line formatting.

Suggested change
### 1. First Step Content for step 1. ### 2. Second Step Content for step 2.
### 1. First Step
Content for step 1.
### 2. Second Step
Content for step 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is caused by the prettier pre-commit hook collapsing markdown inside code fence examples. The original commit already had this formatting before our changes — it's a pre-existing linter behavior, not something we introduced. Restoring the multi-line format gets re-collapsed on commit.

Comment on lines +166 to +167
<Tab value="pnpm">```bash pnpm add @iqai/adk ```</Tab>
<Tab value="npm">```bash npm install @iqai/adk ```</Tab>
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to the previous comment, consolidating the Tab content onto single lines makes the code harder to read and edit. Please revert to the multi-line format for better maintainability.

Suggested change
<Tab value="pnpm">```bash pnpm add @iqai/adk ```</Tab>
<Tab value="npm">```bash npm install @iqai/adk ```</Tab>
<Tab value="pnpm">
```bash
pnpm add @iqai/adk
```
</Tab>
<Tab value="npm">
```bash
npm install @iqai/adk
```
</Tab>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above — prettier pre-commit hook collapses this. Not introduced by our changes.

- Remove unsafe normalize fallback in validatePath — reject if parent
  directory can't be resolved via realpath (stricter, eliminates TOCTOU)
- Add changeset for patch version bump
- Restore multi-line formatting in SKILL.md (mangled by linter)
- Add tests for dword/octal/hex IP formats (Node URL normalizes them)
@MarvelNwachukwu
Copy link
Contributor Author

@claude review

@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @MarvelNwachukwu's task —— View job


Security Review Complete ✅

I've conducted a comprehensive security review of this PR's vulnerability fixes. Here's my analysis:

Review Checklist:

  • Gather context and understand the security fixes
  • Review URL validation utility for SSRF prevention
  • Examine file operations security improvements
  • Check bash tool pattern enhancements
  • Review test coverage and security test cases
  • Assess responses to previous feedback
  • Provide comprehensive security review feedback

Overall Assessment: LGTM with Strong Security Improvements

This PR successfully addresses five critical security vulnerabilities with well-implemented fixes and comprehensive test coverage. The author has also effectively addressed previous review feedback.

🛡️ Security Fixes Analysis

S1/S2 SSRF Prevention - packages/adk/src/tools/utils/url-validation.ts:5

✅ Excellent implementation with robust coverage:

  • Correctly blocks private IP ranges (0/8, 10/8, 127/8, 169.254/16, 172.16-31/12, 192.168/16)
  • Handles IPv6-mapped IPv4 addresses (::ffff:127.0.0.1 and hex form ::ffff:7f00:1)
  • Validates protocol restrictions (HTTP/HTTPS only)
  • Prevents redirect-based bypasses with redirect: "manual" in HTTP tool and beforeRedirect hook in web-fetch tool
  • Note: The author correctly verified that Node.js URL constructor normalizes alternative IP formats (dword, octal, hex) to dotted decimal, making bypass attempts ineffective

S3 API Key Security - packages/adk/src/memory/summary/llm-summary-provider.ts:305

✅ Proper header-based authentication:

  • Moved Gemini API key from URL query parameter to x-goog-api-key header
  • Eliminates exposure via logs, browser history, and proxy access logs
  • Consistent with OpenAI/Anthropic authentication patterns

S4 Pattern Strengthening - packages/adk/src/tools/defaults/bash-tool.ts:88-92

✅ Enhanced dangerous command detection:

  • Added interpreter execution patterns: python[23]? -c, node -e, perl -e, ruby -e, php -r
  • Extended netcat coverage: nc, ncat, netcat variants with -e flag
  • Maintains existing download-and-execute protections

S5 Symlink Escape Prevention - packages/adk/src/tools/common/file-operations-tool.ts:159-198

✅ Robust path validation improvements:

  • Uses fs.realpath() to resolve symlinks before validation
  • Implements proper boundary checking with startsWith(base + path.sep) to prevent prefix confusion
  • Excellent fix: Removed the path.normalize() fallback that could introduce TOCTOU vulnerabilities
  • Uses fs.lstat() in listDirectory() to avoid following symlinks
  • Strict validation: rejects operations when parent directory can't be resolved

🧪 Test Coverage Assessment

Excellent comprehensive testing with 41 new security tests:

  • URL Validation (19 tests): Covers all IP ranges, protocols, IPv6 mapping, alternative formats
  • File Operations (7 tests): Path traversal, symlink escapes, boundary conditions
  • Bash Patterns (15 tests): Interpreter execution, download patterns, netcat variants

📝 Response to Previous Feedback

The author effectively addressed Gemini Code Assist's concerns:

  • IP Format Bypass: Correctly verified Node.js URL normalization behavior with test cases
  • TOCTOU Risk: Eliminated the problematic normalize() fallback, adopting stricter validation
  • Markdown Formatting: Correctly identified as pre-existing prettier behavior, not introduced by changes

🔍 Additional Security Considerations

  1. DNS Rebinding: Current implementation allows domain names that could resolve to private IPs. Consider adding DNS resolution validation for production environments.

  2. Race Conditions: File operations now properly prevent TOCTOU attacks by requiring resolvable parent directories.

  3. Error Information: Error messages appropriately limit information disclosure while providing clear security boundaries.

Recommendations

  1. Approve and merge - All critical vulnerabilities are properly addressed
  2. Consider adding DNS validation in future iterations for complete SSRF protection
  3. Document security boundaries in tool usage guidelines for developers

The implementation demonstrates strong security engineering principles with defense-in-depth approaches, comprehensive testing, and proper handling of edge cases.


@claude
Copy link
Contributor

claude bot commented Mar 19, 2026

Claude finished @MarvelNwachukwu's task —— View job


Security Review Complete ✅

I've conducted a comprehensive security review of this PR's vulnerability fixes. Here's my analysis:

Review Checklist:

  • Gather context and understand the security fixes
  • Review URL validation utility for SSRF prevention
  • Examine file operations security improvements
  • Check bash tool pattern enhancements
  • Review test coverage and security test cases
  • Assess responses to previous feedback
  • Provide comprehensive security review feedback

Overall Assessment: LGTM with Strong Security Improvements

This PR successfully addresses five critical security vulnerabilities with well-implemented fixes and comprehensive test coverage. The author has also effectively addressed previous review feedback.

🛡️ Security Fixes Analysis

S1/S2 SSRF Prevention - packages/adk/src/tools/utils/url-validation.ts:5

✅ Excellent implementation with robust coverage:

  • Correctly blocks private IP ranges (0/8, 10/8, 127/8, 169.254/16, 172.16-31/12, 192.168/16)
  • Handles IPv6-mapped IPv4 addresses (::ffff:127.0.0.1 and hex form ::ffff:7f00:1)
  • Validates protocol restrictions (HTTP/HTTPS only)
  • Prevents redirect-based bypasses with redirect: "manual" in HTTP tool and beforeRedirect hook in web-fetch tool
  • Note: The author correctly verified that Node.js URL constructor normalizes alternative IP formats (dword, octal, hex) to dotted decimal, making bypass attempts ineffective

S3 API Key Security - packages/adk/src/memory/summary/llm-summary-provider.ts:305

✅ Proper header-based authentication:

  • Moved Gemini API key from URL query parameter to x-goog-api-key header
  • Eliminates exposure via logs, browser history, and proxy access logs
  • Consistent with OpenAI/Anthropic authentication patterns

S4 Pattern Strengthening - packages/adk/src/tools/defaults/bash-tool.ts:88-92

✅ Enhanced dangerous command detection:

  • Added interpreter execution patterns: python[23]? -c, node -e, perl -e, ruby -e, php -r
  • Extended netcat coverage: nc, ncat, netcat variants with -e flag
  • Maintains existing download-and-execute protections

S5 Symlink Escape Prevention - packages/adk/src/tools/common/file-operations-tool.ts:159-198

✅ Robust path validation improvements:

  • Uses fs.realpath() to resolve symlinks before validation
  • Implements proper boundary checking with startsWith(base + path.sep) to prevent prefix confusion
  • Excellent fix: Removed the path.normalize() fallback that could introduce TOCTOU vulnerabilities
  • Uses fs.lstat() in listDirectory() to avoid following symlinks
  • Strict validation: rejects operations when parent directory can't be resolved

🧪 Test Coverage Assessment

Excellent comprehensive testing with 41 new security tests:

  • URL Validation (19 tests): Covers all IP ranges, protocols, IPv6 mapping, alternative formats
  • File Operations (7 tests): Path traversal, symlink escapes, boundary conditions
  • Bash Patterns (15 tests): Interpreter execution, download patterns, netcat variants

📝 Response to Previous Feedback

The author effectively addressed Gemini Code Assist's concerns:

  • IP Format Bypass: Correctly verified Node.js URL normalization behavior with test cases
  • TOCTOU Risk: Eliminated the problematic normalize() fallback, adopting stricter validation
  • Markdown Formatting: Correctly identified as pre-existing prettier behavior, not introduced by changes

🔍 Additional Security Considerations

  1. DNS Rebinding: Current implementation allows domain names that could resolve to private IPs. Consider adding DNS resolution validation for production environments.

  2. Race Conditions: File operations now properly prevent TOCTOU attacks by requiring resolvable parent directories.

  3. Error Information: Error messages appropriately limit information disclosure while providing clear security boundaries.

Recommendations

  1. Approve and merge - All critical vulnerabilities are properly addressed
  2. Consider adding DNS validation in future iterations for complete SSRF protection
  3. Document security boundaries in tool usage guidelines for developers

The implementation demonstrates strong security engineering principles with defense-in-depth approaches, comprehensive testing, and proper handling of edge cases.


- python3.11, python3.12 etc bypass python[23]? pattern — now matches
  python[23]?(\.\d+)?
- node --eval is long form of -e — now matches (-e|--eval)
- Added 3 new test cases for these bypass vectors
@MarvelNwachukwu MarvelNwachukwu merged commit f4cef09 into main Mar 20, 2026
4 checks passed
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.

fix: SSRF, API key exposure, pattern bypass, and symlink escape in LLM tools

2 participants