Merge branch 'main' into main
This commit is contained in:
commit
30a679ee9c
@ -61,6 +61,7 @@ Status: unreleased.
|
|||||||
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.
|
||||||
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
||||||
- Gateway: suppress AbortError and transient network errors in unhandled rejections. (#2451) Thanks @Glucksberg.
|
- Gateway: suppress AbortError and transient network errors in unhandled rejections. (#2451) Thanks @Glucksberg.
|
||||||
- TTS: keep /tts status replies on text-only commands and avoid duplicate block-stream audio. (#2451) Thanks @Glucksberg.
|
- TTS: keep /tts status replies on text-only commands and avoid duplicate block-stream audio. (#2451) Thanks @Glucksberg.
|
||||||
|
|||||||
@ -218,6 +218,7 @@ Prefer `chat_guid` for stable routing:
|
|||||||
## Security
|
## Security
|
||||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
||||||
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
||||||
|
- Localhost trust means a same-host reverse proxy can unintentionally bypass the password. If you proxy the gateway, require auth at the proxy and configure `gateway.trustedProxies`. See [Gateway security](/gateway/security#reverse-proxy-configuration).
|
||||||
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|||||||
12
docs/gateway/security-formal-verification.md
Normal file
12
docs/gateway/security-formal-verification.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
title: Formal Verification (Security Models)
|
||||||
|
summary: Redirect to the canonical Formal Verification page.
|
||||||
|
permalink: /gateway/security/formal-verification/
|
||||||
|
---
|
||||||
|
|
||||||
|
This page moved to: [/security/formal-verification/](/security/formal-verification/)
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Best-effort client-side redirect for Mintlify/Next.
|
||||||
|
window.location.replace("/security/formal-verification/");
|
||||||
|
</script>
|
||||||
@ -119,9 +119,16 @@ function mergeConfig(
|
|||||||
const provider = overrides?.provider ?? defaults?.provider ?? "auto";
|
const provider = overrides?.provider ?? defaults?.provider ?? "auto";
|
||||||
const defaultRemote = defaults?.remote;
|
const defaultRemote = defaults?.remote;
|
||||||
const overrideRemote = overrides?.remote;
|
const overrideRemote = overrides?.remote;
|
||||||
const hasRemote = Boolean(defaultRemote || overrideRemote);
|
const hasRemoteConfig = Boolean(
|
||||||
|
overrideRemote?.baseUrl ||
|
||||||
|
overrideRemote?.apiKey ||
|
||||||
|
overrideRemote?.headers ||
|
||||||
|
defaultRemote?.baseUrl ||
|
||||||
|
defaultRemote?.apiKey ||
|
||||||
|
defaultRemote?.headers,
|
||||||
|
);
|
||||||
const includeRemote =
|
const includeRemote =
|
||||||
hasRemote || provider === "openai" || provider === "gemini" || provider === "auto";
|
hasRemoteConfig || provider === "openai" || provider === "gemini" || provider === "auto";
|
||||||
const batch = {
|
const batch = {
|
||||||
enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? true,
|
enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? true,
|
||||||
wait: overrideRemote?.batch?.wait ?? defaultRemote?.batch?.wait ?? true,
|
wait: overrideRemote?.batch?.wait ?? defaultRemote?.batch?.wait ?? true,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
|
||||||
|
|
||||||
vi.mock("../agents/model-auth.js", () => ({
|
vi.mock("../agents/model-auth.js", () => ({
|
||||||
resolveApiKeyForProvider: vi.fn(),
|
resolveApiKeyForProvider: vi.fn(),
|
||||||
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
|
requireApiKey: (auth: { apiKey?: string; mode?: string }, provider: string) => {
|
||||||
@ -193,6 +195,13 @@ describe("embedding provider auto selection", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses gemini when openai is missing", async () => {
|
it("uses gemini when openai is missing", async () => {
|
||||||
|
const fetchMock = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({ embedding: { values: [1, 2, 3] } }),
|
||||||
|
})) as unknown as typeof fetch;
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
const { createEmbeddingProvider } = await import("./embeddings.js");
|
const { createEmbeddingProvider } = await import("./embeddings.js");
|
||||||
const authModule = await import("../agents/model-auth.js");
|
const authModule = await import("../agents/model-auth.js");
|
||||||
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
||||||
@ -214,6 +223,44 @@ describe("embedding provider auto selection", () => {
|
|||||||
|
|
||||||
expect(result.requestedProvider).toBe("auto");
|
expect(result.requestedProvider).toBe("auto");
|
||||||
expect(result.provider.id).toBe("gemini");
|
expect(result.provider.id).toBe("gemini");
|
||||||
|
await result.provider.embedQuery("hello");
|
||||||
|
const [url] = fetchMock.mock.calls[0] ?? [];
|
||||||
|
expect(url).toBe(
|
||||||
|
`https://generativelanguage.googleapis.com/v1beta/models/${DEFAULT_GEMINI_EMBEDDING_MODEL}:embedContent`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps explicit model when openai is selected", async () => {
|
||||||
|
const fetchMock = vi.fn(async () => ({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({ data: [{ embedding: [1, 2, 3] }] }),
|
||||||
|
})) as unknown as typeof fetch;
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
|
||||||
|
const { createEmbeddingProvider } = await import("./embeddings.js");
|
||||||
|
const authModule = await import("../agents/model-auth.js");
|
||||||
|
vi.mocked(authModule.resolveApiKeyForProvider).mockImplementation(async ({ provider }) => {
|
||||||
|
if (provider === "openai") {
|
||||||
|
return { apiKey: "openai-key", source: "env: OPENAI_API_KEY", mode: "api-key" };
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected provider ${provider}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await createEmbeddingProvider({
|
||||||
|
config: {} as never,
|
||||||
|
provider: "auto",
|
||||||
|
model: "text-embedding-3-small",
|
||||||
|
fallback: "none",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.requestedProvider).toBe("auto");
|
||||||
|
expect(result.provider.id).toBe("openai");
|
||||||
|
await result.provider.embedQuery("hello");
|
||||||
|
const [url, init] = fetchMock.mock.calls[0] ?? [];
|
||||||
|
expect(url).toBe("https://api.openai.com/v1/embeddings");
|
||||||
|
const payload = JSON.parse(String(init?.body ?? "{}")) as { model?: string };
|
||||||
|
expect(payload.model).toBe("text-embedding-3-small");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user