mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
fix(security): grant private AI endpoints by exact origin, dialled pinned
ALLOW_LOCAL_AI_PRIVATE_HOSTS=true let any user of the instance point the Local provider or the OpenAI-compatible gateway at any private address the server could reach, the metadata range included, and a granted host was dialled with no connect-time pin at all. AI_PRIVATE_ORIGINS grants a private AI endpoint with the exact-origin grammar NOTIFICATION_PRIVATE_ORIGINS and NIGHTSCOUT_PRIVATE_ORIGINS use. A granted call goes through the pinned operator-approved dispatcher: redirects forbidden, and metadata, link-local and unspecified answers dropped at dial time even for a granted name. Everything else stays on the public pin. The Local client (buffered and streaming) and the gateway and admin-key tags of the OpenAI client all take their policy from one function. The host-list form of ALLOW_LOCAL_AI_PRIVATE_HOSTS keeps working with the same pinning; a never-grantable literal or a host:port entry is refused with a one-time warning. Its "true" form grants nothing any more and is reported in the boot readiness summary with the replacement. A failed lab-report scan no longer puts the endpoint's response body into the wide event; status and model stay. The response to the caller never carried it.
This commit is contained in:
1 parent
f7b3ce6ee0
commit
1a674cf4b0
20 files changed
+508
-181
No files matched your search
@@ -129,3 +129,23 @@ describe("formatReadiness + logReadinessSummary", () => {
|
||||
expect(errors).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("collectReadiness — retired AI grant", () => {
|
||||
it("warns, without blocking, when ALLOW_LOCAL_AI_PRIVATE_HOSTS is still `true`", () => {
|
||||
const env = { ...baseEnv(), ALLOW_LOCAL_AI_PRIVATE_HOSTS: " TRUE " };
|
||||
const line = lineFor(env, "ALLOW_LOCAL_AI_PRIVATE_HOSTS");
|
||||
expect(line?.status).toBe("warn");
|
||||
expect(line?.detail).toContain("AI_PRIVATE_ORIGINS");
|
||||
expect(collectReadiness(env).hasBlocker).toBe(false);
|
||||
});
|
||||
|
||||
it("says nothing for a host list or an unset variable", () => {
|
||||
expect(
|
||||
lineFor(
|
||||
{ ...baseEnv(), ALLOW_LOCAL_AI_PRIVATE_HOSTS: "ollama.lan" },
|
||||
"ALLOW_LOCAL_AI_PRIVATE_HOSTS",
|
||||
),
|
||||
).toBeUndefined();
|
||||
expect(lineFor(baseEnv(), "ALLOW_LOCAL_AI_PRIVATE_HOSTS")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,11 @@
|
||||
* signpost in front of them.
|
||||
*/
|
||||
|
||||
import {
|
||||
LEGACY_ANY_HOST_WARNING,
|
||||
legacyAnyHostConfigured,
|
||||
} from "@/lib/ai/local-host-allowlist";
|
||||
|
||||
export interface ReadinessLine {
|
||||
/** Short label, e.g. "ENCRYPTION_KEY". */
|
||||
label: string;
|
||||
@@ -121,6 +126,19 @@ export function collectReadiness(env: Env = process.env): ReadinessReport {
|
||||
: "Secure cookie flag OFF — fine for plain-HTTP LAN; do NOT expose plain HTTP to the internet",
|
||||
});
|
||||
|
||||
// --- Retired AI grant (a warning, never a blocker) ----------------------
|
||||
// `ALLOW_LOCAL_AI_PRIVATE_HOSTS=true` used to open every private host on
|
||||
// the network to every user's AI base URL. It grants nothing since
|
||||
// v1.39.3; an operator who still has it set has a local AI endpoint that
|
||||
// just stopped answering, and this is the line that says why.
|
||||
if (legacyAnyHostConfigured(env)) {
|
||||
lines.push({
|
||||
label: "ALLOW_LOCAL_AI_PRIVATE_HOSTS",
|
||||
status: "warn",
|
||||
detail: LEGACY_ANY_HOST_WARNING,
|
||||
});
|
||||
}
|
||||
|
||||
// --- Optional subsystems (informational) --------------------------------
|
||||
const optional: Array<[string, boolean]> = [
|
||||
["SMTP (email)", allSet(env, ["SMTP_HOST", "SMTP_PORT", "SMTP_FROM"])],
|
||||
|
||||
Reference in new issue
Block a user