Files
bce78481fd 3.0.6
- Voice (Beta): talk to LU and hear it answer
- Computer control (Beta): a model works in the apps you approve, off by default
- Local models with image input download with their image part
- Create in chat (Beta): ask for an image or a clip in a Cloud chat
- Ten more chat models on LU Cloud
- A running task says what it is doing, and dialogs have a new look
- The Linux packages carry the voice engine
- Ten security fixes in the new features
- A microphone field in the settings, and seven fixes from user reports

Co-authored-by: Alan Vo <101240385+ALANDVO@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:43:43 -07:00

55 lines
2.8 KiB
TypeScript

import type { IncomingMessage } from 'http'
import https from 'https'
import http from 'http'
import dns from 'node:dns'
import net from 'node:net'
import { createSsrfPolicy } from '../src/dev/ssrf-policy'
import { checkPublicUrl, type SsrfFetchDeps } from '../src/dev/ssrf-fetch'
// ── Dev-server SSRF guard ───────────────────────────────────────
// The dev proxies that fetch a *user-supplied* ?url= (proxy-image,
// proxy-download) are an SSRF sink: a markdown image / download link could
// point the server at an internal address (169.254.169.254 metadata, LAN
// boxes, localhost services). The packaged desktop app routes these through
// the Rust proxy, which has the strong validate_public_url guard
// (src-tauri/src/commands/proxy.rs); this is the parity guard for the
// `npm run dev` / web build (a user's SSH-tunnel path). Best-effort against
// DNS-rebind — this is a dev server, not the production trust boundary.
//
// The rule table itself now lives in src/dev/ssrf-policy.ts, next to its test:
// it used to decide on the SPELLING of an IPv6 address (`startsWith('fe80')`,
// `/::ffff:…$/`), which only holds while the text is already in canonical
// compressed form. `net.isIP` stays the oracle for "is this an IP literal at
// all" and is handed in rather than reimplemented.
export const ssrf = createSsrfPolicy((value) => net.isIP(value))
// Die drei Dinge, die der reine Wächter nicht selbst haben darf: das
// IP-Orakel, der Resolver und die HTTP-Schicht. Alles echt, nichts nachgebaut —
// derselbe Satz Funktionen, den der Test von src/dev/ssrf-fetch.ts
// hereinreicht, damit dort NICHT eine zweite HTTP-Welt geprüft wird.
export const ssrfDeps: SsrfFetchDeps<IncomingMessage> = {
policy: ssrf,
ipFamily: (value) => net.isIP(value),
resolveHost: async (host) =>
(await dns.promises.lookup(host, { all: true })).map((a) => a.address),
getter: (protocol) => {
const impl = protocol === 'https:' ? https : http
return (url, options, callback) => impl.get(url, options, callback)
},
}
/**
* Der Wächter für EINE URL — und seit dem Rebind-Fix gibt er die geprüften
* Adressen mit heraus, statt sie wegzuwerfen.
*
* Vorher endete er mit `return verdict.url`, und der `http.get` daneben löste
* den Namen ein zweites Mal auf. Zwischen Prüfung und Verbindung lag damit ein
* Fenster, in dem ein Resolver die Antwort wechseln kann (DNS-Rebinding). Wer
* dieses Ergebnis benutzt, muss `createPinnedLookup(addresses, …)` an den
* `get` weiterreichen — sonst ist das Fenster wieder offen. Rust:
* `validate_public_url_addrs` + `pinned_client` (proxy.rs:213 / :290).
*/
export async function assertPublicUrl(urlStr: string) {
return checkPublicUrl(urlStr, ssrfDeps)
}