From ede275e4dc9bb0b6161292451759c6c16fe8495d Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Sat, 3 Oct 2026 13:20:00 +0200 Subject: [PATCH] fix(server): apply the outbound address policy to remote cert pinning The remote certificate fetch now dials through the same netsafe guard as the REALITY target scan. A private or loopback endpoint is refused unless the request carries allowPrivate; the inbound form asks the operator to confirm and retries with the opt-in. --- docs/public/openapi.json | 4 ++ frontend/public/openapi.json | 4 ++ frontend/src/pages/api-docs/endpoints.ts | 7 ++++ .../src/pages/inbounds/form/security/tls.tsx | 2 +- .../pages/inbounds/form/useSecurityActions.ts | 20 +++++++++- internal/web/controller/server.go | 9 ++++- internal/web/service/server.go | 11 ++++-- .../service/server_remote_cert_hash_test.go | 38 +++++++++++++++++++ 8 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 internal/web/service/server_remote_cert_hash_test.go diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 307cf7396..298f96690 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -7797,6 +7797,10 @@ "server": { "type": "string", "description": "Remote server as domain or domain:port (default port 443), e.g. cloudflare-dns.com." + }, + "allowPrivate": { + "type": "boolean", + "description": "Ping a private/internal/loopback server (LAN, Docker service name). Default false (SSRF guard blocks it and the error response sets obj.privateTarget=true)." } }, "required": [ diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index 307cf7396..298f96690 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -7797,6 +7797,10 @@ "server": { "type": "string", "description": "Remote server as domain or domain:port (default port 443), e.g. cloudflare-dns.com." + }, + "allowPrivate": { + "type": "boolean", + "description": "Ping a private/internal/loopback server (LAN, Docker service name). Default false (SSRF guard blocks it and the error response sets obj.privateTarget=true)." } }, "required": [ diff --git a/frontend/src/pages/api-docs/endpoints.ts b/frontend/src/pages/api-docs/endpoints.ts index a8002dde9..a0964c959 100644 --- a/frontend/src/pages/api-docs/endpoints.ts +++ b/frontend/src/pages/api-docs/endpoints.ts @@ -876,6 +876,13 @@ export const sections: readonly Section[] = [ type: 'string', desc: 'Remote server as domain or domain:port (default port 443), e.g. cloudflare-dns.com.', }, + { + name: 'allowPrivate', + in: 'body (form)', + type: 'boolean', + optional: true, + desc: 'Ping a private/internal/loopback server (LAN, Docker service name). Default false (SSRF guard blocks it and the error response sets obj.privateTarget=true).', + }, ], body: 'server=cloudflare-dns.com', response: '{\n "success": true,\n "obj": [\n "e8e2d3..."\n ]\n}', diff --git a/frontend/src/pages/inbounds/form/security/tls.tsx b/frontend/src/pages/inbounds/form/security/tls.tsx index 230c7f7ea..e3b08d8e5 100644 --- a/frontend/src/pages/inbounds/form/security/tls.tsx +++ b/frontend/src/pages/inbounds/form/security/tls.tsx @@ -398,7 +398,7 @@ export default function TlsForm({ />