Skip to content

fix(security): replace wildcard CORS on core RPC#2328

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
YOMXXX:fix/2262-cors-allowlist
May 20, 2026
Merged

fix(security): replace wildcard CORS on core RPC#2328
senamakel merged 3 commits into
tinyhumansai:mainfrom
YOMXXX:fix/2262-cors-allowlist

Conversation

@YOMXXX
Copy link
Copy Markdown
Contributor

@YOMXXX YOMXXX commented May 20, 2026

Summary

  • Replaces wildcard Core RPC CORS behavior with an explicit allowlist for Tauri and loopback origins.
  • Preserves existing Vary response values while appending Origin.
  • Removes unsafe process-global env mutation from CORS tests by injecting env override input directly.
  • Adds regression coverage for env override exact matching and existing Vary preservation.

Problem

Solution

  • Keep is_origin_allowed(origin) as the production env-reading entry point.
  • Add is_origin_allowed_with_extra(origin, extra_origins) so tests can exercise override parsing without mutating process-global environment.
  • Change with_cors_headers from headers.insert(Vary, Origin) to headers.append(Vary, Origin).
  • Add focused tests for existing Vary preservation and exact-match override behavior.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/coverage.yml. CI coverage gate must confirm this; local focused Rust tests cover the changed paths.
  • Coverage matrix updated — N/A: security boundary fix covered by focused Rust tests; no feature matrix row added/removed/renamed.
  • All affected feature IDs from the matrix are listed in the PR description under ## Related — N/A: no coverage-matrix feature row applies.
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: Core RPC header behavior only; no manual release checklist surface changed.
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Security: arbitrary non-allowlisted browser origins no longer receive ACAO for local Core RPC responses.
  • Compatibility: Tauri webview origins, loopback dev/E2E origins, and non-browser callers remain supported.
  • Operators can still add exact additional debug origins with OPENHUMAN_CORE_ALLOWED_ORIGINS.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/2262-cors-allowlist
  • Commit SHA: 9d1341cff29ef1e1b08721885124ce3267f4a99d

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: Rust-only Core RPC change.
  • pnpm typecheck — N/A: Rust-only Core RPC change.
  • Focused tests: GGML_NATIVE=OFF cargo test --manifest-path Cargo.toml cors_tests --lib — 8 passed.
  • Rust fmt/check (if changed): cargo fmt --manifest-path Cargo.toml --all; GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml --lib; git diff --check.
  • Tauri fmt/check (if changed): N/A: Tauri shell unchanged.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: Core RPC only echoes Access-Control-Allow-Origin for allowlisted browser origins instead of wildcard *.
  • User-visible effect: none expected for packaged app, loopback dev, E2E, or non-browser callers.

Parity Contract

  • Legacy behavior preserved: Tauri origins, loopback origins, debug env overrides, CORS methods/headers/max-age, and no-Origin non-browser callers remain supported.
  • Guard/fallback/dispatch parity checks: focused CORS unit tests cover allowed origins, denied origins, no-Origin callers, exact env override matching, and preserved Vary values.

Duplicate / Superseded PR Handling

Summary by CodeRabbit

  • Bug Fixes

    • Tightened CORS handling to enforce an origin allowlist; only trusted local schemes and loopback addresses are allowed by default, and disallowed origins no longer receive CORS responses.
  • Chores

    • Added support for configuring extra allowed origins via environment configuration.
  • Tests

    • Added comprehensive tests for allowlist decisions, header emission (including Vary behavior), and edge cases.

Review Change Stack

leighstillard and others added 2 commits May 20, 2026 01:14
The in-process JSON-RPC server returned `Access-Control-Allow-Origin: *`
on every response, meaning any browser origin in possession of the bearer
token could make authenticated cross-origin requests against `/rpc`.

Replaces the wildcard with an explicit allowlist:
- Tauri v2 webview origins (`tauri://localhost`, `http(s)://tauri.localhost`)
- Loopback hosts on any port (`http://127.0.0.1:*`, `http://localhost:*`,
  `http://[::1]:*`) for the Vite dev server and E2E harnesses
- Comma-separated env override `OPENHUMAN_CORE_ALLOWED_ORIGINS` for
  operator-controlled debug harnesses

Disallowed origins receive no ACAO header (browser blocks). Non-browser
callers (no `Origin` header) are unaffected. `Vary: Origin` is set so
intermediate caches keep per-origin responses distinct.

Closes tinyhumansai#2262
@YOMXXX YOMXXX requested a review from a team May 20, 2026 09:58
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b3f0101d-29fc-40ae-99cc-cbfa5753abfc

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1341c and 862225e.

📒 Files selected for processing (1)
  • src/core/jsonrpc.rs
💤 Files with no reviewable changes (1)
  • src/core/jsonrpc.rs

📝 Walkthrough

Walkthrough

The JSON-RPC server's HTTP CORS handling is hardened by replacing Access-Control-Allow-Origin: * with an origin allowlist. Requests from Tauri webviews, loopback hosts, and configured debug origins echo their origin in the response; disallowed origins receive no Access-Control-Allow-Origin header, forcing the browser to block the response. Vary: Origin is added for correct cache behavior.

Changes

CORS Origin Allowlist

Layer / File(s) Summary
Origin allowlist mechanism and CORS header handling
src/core/jsonrpc.rs
Introduces is_origin_allowed_with_extra(origin, extra) and updates is_origin_allowed() to read OPENHUMAN_CORE_ALLOWED_ORIGINS once per call. Changes with_cors_headers(response, origin) to accept an optional Origin, append Vary: Origin, echo allowlisted origins in Access-Control-Allow-Origin, omit the header for disallowed origins, and preserve standard Access-Control-Allow-Methods, Access-Control-Allow-Headers, and Access-Control-Max-Age.
CORS allowlist and header validation tests
src/core/jsonrpc_cors_tests.rs
Adds tests that validate Tauri webview origins and loopback hosts (IPv4/IPv6) are allowed and echoed; disallowed and lookalike origins are rejected without Access-Control-Allow-Origin; missing Origin still results in Vary: Origin and no ACAO; env override allows exact-match extra origins (but not suffix lookalikes); and method/header/max-age CORS headers are always present.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

working

Suggested reviewers

  • graycyrus

Poem

🐰 A wildcard once opened the door,
But now only trusted ones pass through for sure.
Tauri and loopback hop in line,
Origins checked — the headers align. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(security): replace wildcard CORS on core RPC' clearly and concisely summarizes the main security fix in the changeset.
Linked Issues check ✅ Passed All code requirements from #2262 and #2266 are met: explicit origin allowlist for Tauri/loopback/env-override origins, echoing origin in ACAO header, Vary header support, rejection of disallowed origins, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes directly support the stated objectives: CORS allowlist implementation, environment-variable configuration, test additions, and logging—with no unrelated modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label May 20, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 20, 2026
@YOMXXX
Copy link
Copy Markdown
Contributor Author

YOMXXX commented May 20, 2026

@senamakel #2328 已作为 #2266 的可合并替代 PR 打开并跑绿:24 success / 3 skipped / 0 pending / 0 failure,CodeRabbit approved,review threads 0 unresolved,当前 head 9d1341cff29ef1e1b08721885124ce3267f4a99d,mergeable。它包含 #2266 的 CORS allowlist 修复,并补了 CodeRabbit 要求的 env-test 安全和 Vary append 两个 review 点。麻烦再看一下。

Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — graycyrus

Solid security fix. The allowlist logic is clean, the test coverage is thorough, and this fully addresses the acceptance criteria in #2262.

What I checked

Area Verdict
Origin parsing (Tauri, loopback, IPv6) Correct — tested against lookalike/suffix attacks
Vary: Origin via append (not insert) Correct — preserves existing Vary values
Env override exact-match only Correct — no substring/suffix matching
with_cors_headers signature change (pub(super), new origin param) Only called from cors_middleware + tests — no breakage
Test isolation (no env::set_var in parallel tests) Properly uses is_origin_allowed_with_extra injection instead
Non-browser callers (no Origin header) Unaffected — no ACAO emitted, other CORS headers still present

Observations (informational, not blocking)

  • Access-Control-Allow-Methods, -Allow-Headers, and -Max-Age are still emitted for rejected origins. Not exploitable (browser blocks without ACAO), but slightly reveals server capabilities. Could gate those behind the allowlist check in a follow-up if desired.
  • std::env::var is read per-request in is_origin_allowed. Negligible for a local RPC server, but if the env override feature sees real use, a cached read might be worth it.

No critical or major findings. LGTM — ready for maintainer approval.

@senamakel senamakel self-assigned this May 20, 2026
# Conflicts:
#	src/core/jsonrpc.rs
#	src/core/jsonrpc_cors_tests.rs
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 20, 2026
@senamakel senamakel merged commit 0311d81 into tinyhumansai:main May 20, 2026
29 of 33 checks passed
@senamakel
Copy link
Copy Markdown
Member

huge thanks @YOMXXX, locking down that wildcard cors with a proper allowlist is exactly the kind of hardening we love to see 🙌 extra points for killing the process-global env mutation in the tests too, way cleaner. thanks again for another solid one!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: Core RPC server uses wildcard CORS (Access-Control-Allow-Origin: *)

4 participants