Skip to content

fix(ai): prevent SSRF in AI chat endpoints#306

Merged
chilingling merged 3 commits intoopentiny:developfrom
hexqi:fix/ai-ssrf-validation
Apr 29, 2026
Merged

fix(ai): prevent SSRF in AI chat endpoints#306
chilingling merged 3 commits intoopentiny:developfrom
hexqi:fix/ai-ssrf-validation

Conversation

@hexqi
Copy link
Copy Markdown
Collaborator

@hexqi hexqi commented Apr 28, 2026

Summary

  • bind AI configuration through Spring and validate final outbound URLs
  • disable redirect following and add allowlisted host checks for AI providers
  • add SSRF-focused tests and tighten handling for special-use address ranges

简介

主要包括配置绑定改造、URL 白名单校验、重定向禁止、特殊网段拦截,以及对应测试补充

Testing

  • mvn -q -pl base -Dtest=AiChatV1ServiceImplTest test
  • mvn -q -pl base -DskipTests compile

Summary by CodeRabbit

  • New Features

    • Added configurable allowlist for external AI service providers
    • Implemented security validation for AI API connections, enforcing HTTPS and blocking internal/private IP ranges
    • New configuration options available across all environment profiles
  • Tests

    • Comprehensive test coverage added for AI service URL validation logic

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

Warning

Rate limit exceeded

@hexqi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 13 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: 8e10f430-c29f-4eb1-8128-3132acb475be

📥 Commits

Reviewing files that changed from the base of the PR and between e9fd2a0 and 616853a.

📒 Files selected for processing (6)
  • app/src/main/resources/application-alpha.yml
  • app/src/main/resources/application-dev.yml
  • app/src/main/resources/application-local.yml
  • base/src/main/java/com/tinyengine/it/config/OpenAIConfig.java
  • base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java
  • base/src/test/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImplTest.java

Walkthrough

This change introduces URL validation for AI API endpoints, enforcing a configurable allowlist of hosts and blocking requests to internal/private IP ranges. Configuration files are updated to support the new ai.allowed-hosts setting, and service implementation adds validation logic with comprehensive test coverage.

Changes

Cohort / File(s) Summary
Configuration Profiles
app/src/main/resources/application-alpha.yml, application-dev.yml, application-local.yml
Adds new ai.allowed-hosts list configuration across environment profiles to define allowed external AI provider domains. Formatting adjustment in local profile to add trailing newline.
Service Configuration
base/src/main/java/com/tinyengine/it/config/OpenAIConfig.java
Adds @ConfigurationProperties(prefix = "ai") annotation and new allowedHosts field (List<String>) to bind AI configuration from properties.
Service Implementation
base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java
Implements constructor injection of OpenAIConfig, configures HttpClient to prevent redirects, and adds validateFinalUrl() with security validation logic: URL parsing, host extraction, optional allowlist enforcement, HTTPS requirement, DNS resolution, and blocking of internal/private/reserved IP address ranges (loopback, link-local, site-local, multicast, CGNAT, IPv6 ULA).
Test Suite
base/src/test/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImplTest.java
New integration test class with deterministic DNS mocking (TestAiChatV1ServiceImpl subclass) covering rejection of internal/private/reserved addresses, non-HTTPS schemes for public hosts, malformed URLs, and allowlist enforcement scenarios including localhost-over-HTTP when explicitly allowlisted.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 The bunny hops through URLs with care,
Checking hosts and IPs everywhere,
No sneaky paths to private lands,
Only safe hosts from our allow-list commands! 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.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 clearly summarizes the main change: adding SSRF (Server-Side Request Forgery) prevention to AI chat endpoints through URL validation and host allowlisting.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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

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

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 `@app/src/main/resources/application-dev.yml`:
- Around line 94-111: Remove the loopback addresses from the dev allowlist to
avoid weakening SSRF protection: delete the "localhost" and "127.0.0.1" entries
from the ai.allowed-hosts list in the dev config so validateFinalUrl() no longer
short-circuits to local services; rely on the separate local profile for
localhost access instead.

In
`@base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java`:
- Around line 270-287: The current AiChatV1ServiceImpl logic treats a null/empty
config.getAllowedHosts() as “allow all”, which is unsafe; change it to
fail-closed: if allowedHosts is null or empty then throw a ServiceException
indicating no allowed hosts configured (preserving the existing isLoopback
short-circuit if you want to allow local-only calls) unless an explicit,
well-named override flag on the config (e.g., config.isAllowAnyHost()) is true;
keep the existing host match flow and calls to enforceHttpsAndIpCheck(uri, host)
and update tests to cover the new behavior.
- Around line 296-308: The current DNS check uses resolveHostAddresses(host)
then later lets HttpClient.perform a fresh DNS lookup, creating a TOCTOU; fix by
pinning the validated IP into the request URI and preserving TLS/Host
verification: call resolveHostAddresses(host), validate each InetAddress with
isBlockedAddress, pick a validated IP (e.g. addresses[0].getHostAddress()),
build a new URI using that IP (keeping scheme, port, path, query, fragment) and
then build the HttpRequest with that IP-URI while adding a "Host" header set to
the original hostname so SNI/HTTP Host are preserved (use
HttpRequest.newBuilder(...) and then call HttpClient.send(...) with that
request). Ensure all references to resolveHostAddresses, isBlockedAddress,
HttpRequest.newBuilder, and HttpClient.send are updated accordingly to avoid any
re-resolution at connect time.
🪄 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: 9855d627-1249-410a-b42c-b7596f7bfed4

📥 Commits

Reviewing files that changed from the base of the PR and between d02daa9 and e9fd2a0.

📒 Files selected for processing (6)
  • app/src/main/resources/application-alpha.yml
  • app/src/main/resources/application-dev.yml
  • app/src/main/resources/application-local.yml
  • base/src/main/java/com/tinyengine/it/config/OpenAIConfig.java
  • base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java
  • base/src/test/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImplTest.java

Comment thread app/src/main/resources/application-dev.yml
Comment thread base/src/main/java/com/tinyengine/it/service/app/impl/v1/AiChatV1ServiceImpl.java Outdated
@chilingling chilingling merged commit 4a10aa5 into opentiny:develop Apr 29, 2026
1 check 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.

2 participants