Aventuras/scripts
Tony Frazier 47d52a822b
Some checks failed
CI / build-desktop (push) Waiting to run
CI / build-android (push) Waiting to run
CI / build-ios (push) Waiting to run
Rust Lint / Format (push) Has been cancelled
Rust Lint / Clippy (push) Has been cancelled
Harden the HTTP plugin transport: body timeouts, no Origin, decompression, OS roots (#622)
## Summary

Remote HTTP goes through Tauri's HTTP plugin, and the plugin's defaults
fall short of what the app's callers need. A response body that stalled
after the headers hung forever. Local servers (stock Ollama, ComfyUI)
rejected the webview `Origin` the plugin attaches. Responses arrived
uncompressed, TLS ignored the OS trust store, and the plugin kept a
cookie jar nobody used. Vault portrait downloads bypassed the plugin
altogether. This branch fixes each of these and routes every plugin call
through one wrapper, `httpFetch`.

## Changes

### Timeouts and aborts

- **Request guard** (`utils/idleTimeout.ts`): `createRequestGuard`
combines the header timer, the caller's abort signal and a per-read idle
timer on the body. `withIdleTimeout` re-wraps the body so a stalled read
errors with `RequestTimeoutError`. The timer runs only while a read
waits on upstream, so a slow consumer never trips it.
`createTimeoutFetch`, `imageFetch` and `imageGetFetch` all use the guard
in place of their own timer/abort code.
- **Timeout errors**: `RequestTimeoutError` (`utils/error.ts`) is
deliberately not named `TimeoutError`, which the AI SDK treats as an
abort. `mapError` also turns the plugin's bare-string and plain-`Error`
abort rejections into an `AbortError` when the caller aborted.
`imageFetch` no longer reports a caller-caused `AbortError` as a
timeout.
- **LLM fetch**: error responses and non-JSON (streaming) responses get
the idle timer. Buffered JSON responses don't; the header timer bounds
them, since `dispose()` runs after `response.text()`.
- **Fake provider**: new `stream-stall` model (some text, then silence
with the connection open), documented in `docs/development/testing.md`.

### One entry point into the plugin

- **`httpFetch`** (`utils/http.ts`) wraps the plugin's `fetch` and sets
`Origin: ''` unless the caller sets one. The plugin drops an empty
`Origin` under `unsafe-headers`, so local servers no longer see the
webview origin. A caller's own `Origin` and `Referer` are sent as set.
- All plugin callers now use it: `createTimeoutFetch`, the image fetch
helpers, ComfyUI's `fetchApi`, `pingModel`, `corsFetch` and the updater.
An ESLint `no-restricted-imports` rule rejects any other import of the
plugin's `fetch` outside `http.ts` and tests.

### Plugin features (`src-tauri/Cargo.toml`)

- `tauri-plugin-http` default features are off and its list is spelled
out, so the `cookies` jar stays out: `rustls-tls`, `http2`, `charset`,
`system-proxy`, plus the three below. The plugin's cookie jar is gone,
and startup deletes the `.cookies` file an older build left in the app
cache dir (`lib.rs`).
- **`gzip`, `brotli`, `zstd`**: requests send `Accept-Encoding: gzip,
br, zstd` and responses are decoded in Rust before the WebView sees
them.
- **`unsafe-headers`**: needed to drop `Origin`. It lifts the plugin's
whole forbidden-header filter (`Host`, `Cookie`, `Content-Length`,
`Sec-*`, …), so `httpFetch` is where to filter if untrusted input ever
feeds request headers. Every caller today is app code.
`docs/architecture/overview.md` records this.
- **`rustls-tls-native-roots`** on Windows and Linux only: the OS trust
store is added to the bundled roots. Android and iOS have no store
`rustls-native-certs` can read, and on macOS every request would walk
the keychain, so those keep the bundled roots. The target-specific entry
also sets `default-features = false`, or Cargo would merge the defaults
(cookies included) back in.

### Vault portraits

- Importing a card with a `sourceUrl` downloads the portrait with
`fetchPublicImageBase64` (public URLs and redirects only, 15s timeout)
instead of a bare WebView `fetch`. The download runs alongside
`sanitize`. The result is accepted only if `sniffImageMime` recognises
the bytes; otherwise the import keeps the URL as the portrait, as
before.
- **`sniffImageMime`** (`utils/image.ts`) identifies PNG, APNG, JPEG,
GIF, WebP, AVIF, HEIC and HEIF from the leading bytes. Unlike
`base64ImageMime`, it returns `null` rather than defaulting to PNG.
- `fetchPublicImageBase64` accepts `timeoutMs`.

### iOS

- `NSLocalNetworkUsageDescription` now also names local AI servers,
since the app connects to them over the LAN.

## Changes outside the main purpose

- `@tauri-apps/plugin-http` 2.8.2 → 2.8.3, with the matching
`tauri-plugin-http` crate. The lockfile update also moved
`tauri-plugin-fs` 2.7.0 → 2.7.1.
- In `fetch.ts` and `fetchAdapter.ts`, the debug-log bodies moved into
`logErrorResponse`, `logStreamResponse` and `logImageResponse`, and
`normalizeHeaders` / the FormData key listing were simplified. Behaviour
is unchanged.

## Testing

New unit tests cover `idleTimeout`, `httpFetch`, the `fetch.ts` and
`fetchAdapter.ts` guard integration, and `sniffImageMime`. The Rust and
platform changes (Cargo features, native roots, `Origin`, decompression,
the iOS string) have no automated coverage and need a release build to
check:

- Run the fake provider with the `stream-stall` model and confirm the
generation errors after the timeout instead of hanging.
- On Windows and Android, point the app at stock Ollama on loopback and
confirm requests succeed without `OLLAMA_ORIGINS`.
- On Windows or Linux, reach an HTTPS server whose CA is only in the OS
store.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_019V1AUE2StsGUjVV6KzUx2G


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added safeguards for stalled AI requests, with clearer timeout errors
and support for cancelling requests.
* Character imports can now fetch and validate portrait images, with a
fallback to the original image URL.
* Improved image format detection for PNG, JPEG, GIF, WebP, AVIF, HEIC,
and HEIF files.
* Updated the local network permission message to include connections to
local AI servers.

* **Bug Fixes**
* Improved handling of request headers and response timeouts across AI,
discovery, and update requests.
  * App startup now attempts to clear cached cookies.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 13:27:54 -04:00
..
ci Key Rust caches on the dependency set and stop caching Gradle (#612) 2026-10-11 21:31:21 -04:00
android-setup.sh Current 2026-01-01 14:55:31 -07:00
build-ios-unsigned.sh iOS follow-ups: iPadOS detection, backup restore, app icon, and a first pass of iOS bug fixes (#559) 2026-10-09 10:56:10 -04:00
check_migrations.js Fix CRLF Migrations (#162) 2026-02-22 15:46:59 -05:00
check_wry_injection.js Add Incognito Keyboard option for Android (#467) 2026-08-26 20:45:30 -04:00
fake-provider.mjs Harden the HTTP plugin transport: body timeouts, no Origin, decompression, OS roots (#622) 2026-10-18 13:27:54 -04:00
package-manager.js Route dev/build/release scripts through aube when it installed node_modules (#552) 2026-09-28 14:34:27 -04:00
package-manager.test.js Route dev/build/release scripts through aube when it installed node_modules (#552) 2026-09-28 14:34:27 -04:00
pm.js Route dev/build/release scripts through aube when it installed node_modules (#552) 2026-09-28 14:34:27 -04:00
release.js Run Clippy on push and in CI, and split lint into lint:ts and lint:rust (#608) 2026-10-11 14:50:50 -04:00
remote.js Release script: push over the contributor's own remote instead of a hardcoded HTTPS URL (#432) 2026-08-08 22:08:59 +02:00
remote.test.js Release script: push over the contributor's own remote instead of a hardcoded HTTPS URL (#432) 2026-08-08 22:08:59 +02:00
sync-ios-icons.sh iOS follow-ups: iPadOS detection, backup restore, app icon, and a first pass of iOS bug fixes (#559) 2026-10-09 10:56:10 -04:00
version.js Overhaul CI/CD: Node 24, shared release workflows, faster cached builds (#518) 2026-09-16 22:38:32 -04:00
version.test.js Validated release script, extracted version arithmetic, lint config fixes (#416) 2026-08-08 19:55:23 +02:00