-
Notifications
You must be signed in to change notification settings - Fork 0
🛡️ Sentinel: Enforce HTTPS Endpoint Validation for Remote AI Endpoints #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| ## 2026-08-26 - Enforce HTTPS Endpoint Validation for Remote AI/API Endpoints | ||
|
|
||
| **漏洞:** Unvalidated custom AI endpoint URLs allowed unencrypted HTTP connections to remote servers, exposing API keys and request payloads to Man-in-the-Middle (MitM) attacks. | ||
|
|
||
| **经验心得:** Users frequently configure custom API/AI endpoints, but allowing arbitrary `http://` schemes to remote domains poses a major network security threat. Allowing `http://` only on loopback addresses (`localhost`, `127.0.0.1`, `::1`) retains developer flexibility without compromising production data security. | ||
|
|
||
| **预防措施:** Always validate input URLs for network requests to enforce HTTPS for remote endpoints while explicitly whitelisting loopback hosts for local development. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,4 +29,25 @@ class SettingsService { | |
| final prefs = await SharedPreferences.getInstance(); | ||
| await prefs.setString(_key, jsonEncode(settings.toJson())); | ||
| } | ||
|
|
||
| /// Validates whether an endpoint URL is secure and well-formed. | ||
| /// | ||
| /// Remote endpoints must enforce HTTPS to prevent MiTM attacks. | ||
| /// Unencrypted HTTP is permitted only for local development on loopback hosts. | ||
| static bool isValidEndpointUrl(String url) { | ||
| if (url.trim().isEmpty) return true; | ||
| final uri = Uri.tryParse(url.trim()); | ||
| if (uri == null || | ||
| !uri.hasScheme || | ||
| (uri.scheme != 'http' && uri.scheme != 'https')) { | ||
| return false; | ||
| } | ||
| if (uri.scheme == 'http') { | ||
| final host = uri.host.toLowerCase(); | ||
| final isLoopback = | ||
| host == 'localhost' || host == '127.0.0.1' || host == '::1'; | ||
| if (!isLoopback) return false; | ||
| } | ||
| return true; | ||
| } | ||
|
Comment on lines
+37
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
# 确认 endPoint 是否会直接作为网络请求目标使用。
ast-grep outline lib --items all --type function,method
rg -n -C 3 --type dart '\.endPoint\b|endPoint:|Uri\.(parse|tryParse)|baseUrl|baseUri' libRepository: DylanBolin42/janus Length of output: 21882 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- lib/services/settings_service.dart ---'
cat -n lib/services/settings_service.dart | sed -n '1,90p'
printf '%s\n' '--- endpoint consumers and persistence callers ---'
rg -n -C 5 --type dart 'endPoint|setEndPoint|SettingsService|baseUrl|baseUri|Uri\.parse|Uri\.tryParse' lib/services lib/providers lib/modelsRepository: DylanBolin42/janus Length of output: 46329 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- settings provider initialization and persistence ---'
cat -n lib/providers/settings_provider.dart | sed -n '1,75p'
cat -n lib/providers/settings_provider.dart | sed -n '175,210p'
printf '%s\n' '--- all non-generated endpoint references ---'
rg -n --glob '*.dart' --glob '!*.g.dart' --glob '!*.freezed.dart' '\bendPoint\b|setEndPoint\(' .Repository: DylanBolin42/janus Length of output: 5994 在加载时清理已保存的无效端点
🤖 Prompt for AI Agents
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @coderabbitai 提出具体解决方案 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- relevant file locations ---'
fd -t f . lib test | rg '(app_settings\.dart|settings_provider\.dart|settings_service\.dart|settings_.*test\.dart)$'
printf '%s\n' '--- outlines ---'
for f in \
lib/models/app_settings.dart \
lib/providers/settings_provider.dart \
lib/services/settings_service.dart \
test/unit/settings/settings_provider_test.dart
do
if [ -f "$f" ]; then
echo "--- $f ---"
ast-grep outline "$f" --items all
fi
done
printf '%s\n' '--- endpoint/load/persist references ---'
rg -n -C 5 --type dart \
'\b(load|save|_persist|setEndPoint|isValidEndpointUrl|copyWith)\s*\(|\bendPoint\b' \
lib/models/app_settings.dart lib/providers/settings_provider.dart lib/services/settings_service.dart test \
|| trueLength of output: 32193
You are interacting with an AI system. |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: DylanBolin42/janus
Length of output: 7544
🌐 Web query:
Dart Uri.tryParse("https:api.example.com/v1") hasAuthority host semantics Uri.hasAuthority official documentation💡 Result:
The Dart
Uri.tryParse("https:api.example.com/v1")method interprets the input string according to URI parsing rules, where the authority component must typically be preceded by//[1][2]. In the string"https:api.example.com/v1": 1. The scheme is identified ashttps[1]. 2. Because the scheme is immediately followed by a colon and thenapi.example.com(without the//indicator for an authority), the DartUriparser treatsapi.example.com/v1as the path component of the URI rather than the host [1][2]. 3. Consequently,hasAuthoritywill returnfalsefor thisUriobject [3][4]. 4. Thehostproperty will return an empty string, as no authority component is present [5]. If you intended forapi.example.comto be treated as a host, the string must be formatted as"https://api.example.com/v1"[1]. Official documentation defineshasAuthorityas a boolean property that indicates whether the URI has an authority component [3][4]. An authority component is defined as being composed ofuserInfo,host, andport, and in string representation, it is identified by the presence of//following the scheme [6][1][7].Citations:
🏁 Script executed:
Repository: DylanBolin42/janus
Length of output: 1440
拒绝没有主机的 HTTPS URI。
https:api.example.com/v1的scheme为https,但没有 authority,且host为空。当前校验会返回true,setEndPoint随后会保存该值。要求uri.hasAuthority且uri.host.isNotEmpty,并为此输入添加拒绝测试。🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@coderabbitai 提出具体解决方案
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DylanBolin42,建议使用以下最小修改。在
lib/services/settings_service.dart中,要求 URL 同时具有 authority 和非空 host:static bool isValidEndpointUrl(String url) { if (url.trim().isEmpty) return true; final uri = Uri.tryParse(url.trim()); if (uri == null || !uri.hasScheme || + !uri.hasAuthority || + uri.host.isEmpty || (uri.scheme != 'http' && uri.scheme != 'https')) { return false; }这会拒绝以下值:
同时保留以下有效值:
在
test/unit/settings/settings_provider_test.dart的无效端点测试中,增加此断言:该测试验证
AppSettingsNotifier.setEndPoint不会持久化缺少//host的 HTTPS URI。You are interacting with an AI system.