4 Commits
Author SHA1 Message Date
94b448ffab fix(security): load WASM tool description and schema from capabilities.json (#520)
The extract_tool_description and extract_tool_schema stubs in runtime.rs
returned permissive fallbacks ("WASM sandboxed tool" and
additionalProperties:true) for every WASM tool, defeating parameter
validation and preventing the LLM from using tools correctly.

Add optional `description` and `parameters` fields to CapabilitiesFile so
tool authors can declare proper metadata in their sidecar JSON. The
WasmToolLoader now extracts these fields and passes them through to the
tool registry as overrides. Tools without a capabilities.json or without
these fields get a tracing::warn and fall back to the old stubs.

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-03-11 16:29:59 -07:00
bb06565770 fix(security): resolve DNS once and reuse for SSRF validation to prevent rebinding (#518)
* fix(security): resolve DNS once and reuse for SSRF validation to prevent rebinding

The previous SSRF protection resolved DNS in validate_url() to check IPs
against a blocklist, but then reqwest independently re-resolved DNS when
making the actual HTTP connection.  Between validation and connection, a
DNS rebinding attack could flip the record from a public IP (passes
validation) to a private IP like 169.254.169.254 (AWS metadata endpoint).

Fix: split URL validation into two phases:
- validate_url(): synchronous URL structure checks (scheme, localhost,
  IP literals) -- no DNS resolution
- validate_and_resolve_url(): async DNS resolution via
  tokio::net::lookup_host, validates all resolved IPs, returns
  SocketAddrs
- build_pinned_client(): constructs a per-request reqwest Client with
  resolve() pinning so reqwest connects to the pre-validated IPs without
  a second DNS lookup

Applied to both HttpTool and WebFetchTool.  WebFetchTool builds a fresh
pinned client per redirect hop, ensuring DNS rebinding cannot occur at
any point in a redirect chain.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* style: run cargo fmt

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-03-11 16:18:43 -07:00
28a22f2a59 fix(security): replace regex HTML sanitizer with DOMPurify to prevent XSS (#510)
* fix(security): replace regex HTML sanitizer with DOMPurify to prevent XSS

The previous sanitizeRenderedHtml() used regex patterns to strip dangerous
HTML tags and event handler attributes before assigning to innerHTML. Regex-
based HTML sanitization is notoriously bypassable via:

- SVG/MathML elements not in the blocklist (<svg onload=...>)
- Newline-split event handlers (<img src=x on\nload=alert(1)>)
- Mutation XSS (browser parsing quirks that reconstruct dangerous DOM)
- Encoded attribute values and alternative quote styles
- Nested/recursive tag patterns that defeat linear regex

This is exploitable through prompt injection: if an LLM tool output contains
crafted HTML, it flows through marked.parse() -> sanitizeRenderedHtml() ->
innerHTML, allowing script execution in the user's browser session.

Replace the regex sanitizer with DOMPurify 3.2.3, the industry-standard
DOM-based HTML sanitizer. DOMPurify parses HTML into a real DOM tree and
walks it node-by-node, which eliminates all known bypass vectors. It is
used by Mozilla, Google, and most major web applications.

CDN: cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.3/purify.min.js
SRI: sha384-osZDKVu4ipZP703HmPOhWdyBajcFyjX2Psjk//TG1Rc0AdwEtuToaylrmcK3LdAl

Audited all 60+ innerHTML assignments in app.js:
- 5 use renderMarkdown() -> now protected by DOMPurify
- Remainder use escapeHtml(), static literals, or empty strings

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* fix(security): guard sanitizeRenderedHtml against DOMPurify CDN unavailability [skip-regression-check]

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-03-11 15:55:35 -07:00
6a2a6cd050 fix(security): use OsRng for all security-critical key and token generation (#519)
* fix(security): use OsRng for all security-critical key and token generation

Replace rand::thread_rng() with rand::rngs::OsRng in all security-critical
code paths that generate cryptographic key material, bearer tokens, PKCE
verifiers, CSRF state parameters, and webhook secrets. thread_rng() uses a
userspace CSPRNG (ChaCha) seeded from OS entropy, which is fine for
non-security contexts but adds an unnecessary intermediate layer for
key material where direct OS entropy (OsRng) is the correct choice.

Files changed:
- src/secrets/keychain.rs: master encryption key generation
- src/secrets/crypto.rs: per-secret HKDF salt generation
- src/orchestrator/auth.rs: per-job bearer token generation
- src/channels/web/mod.rs: gateway auth token fallback
- src/cli/oauth_defaults.rs: OAuth PKCE verifier and CSRF state
- src/tools/mcp/auth.rs: MCP OAuth PKCE verifier
- src/extensions/manager.rs: auto-generated extension secrets
- src/setup/channels.rs: webhook secret generation

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* fix(security): address PR review feedback for OsRng migration

- Remove shadowing inner `use rand::rngs::OsRng` in `generate_salt()`;
  use module-level `aes_gcm::aead::OsRng` import instead (same type,
  avoids divergence risk if rand_core versions drift)
- Fix missed callsites in `pairing/store.rs`: `random_code()` and
  `generate_unique_code()` now use `OsRng` for pairing auth codes
- Add regression tests for `generate_salt()`: correct length,
  non-zero output, uniqueness across calls

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-03-06 02:36:38 +00:00