Commit da237ad
fix(security): close API key timing-position leak and remote-UDF SSRF (spiceai#10757)
* docs: add product security audit report
Workspace-wide audit of auth, secrets, HTTP/Flight surface, AI/LLM tooling,
data connectors, and the container/build pipeline. Two critical findings
(non-constant-time API key compare; SSRF in remote UDF endpoint validation),
four high (MySQL preferred-mode TLS bypass, Vault tls_skip_verify on remote
hosts, AWS default credential chain unverified at init, no auth-failure rate
limiting), and a set of medium/low hardening items. Each finding cites
file:line and a fix.
https://claude.ai/code/session_01Xu4b9TS1UvGu4H1a7NxHKj
* fix(security): address critical findings from security audit (C1, C2)
C1 — runtime-auth API key allowlist iteration
The per-comparison code was already constant-time: ApiKey's PartialEq<str>
uses subtle::ct_eq, so per-byte timing does not leak key bytes. However,
HTTP, Flight basic, Flight bearer, and gRPC paths all used find / any to
search the configured key list, which short-circuits on the first
matching key. That leaks the *position* of the matching key (1
comparison vs N) and the existence of any match.
Replace find / any with a lookup helper that iterates over every
configured key on every call without short-circuiting; total verify time
is O(N) regardless of which key (if any) matched. Add tests covering
matches at first / middle / last positions and empty-credential
rejection on the Flight basic-auth path.
C2 — remote UDF endpoint SSRF
parse_endpoint previously validated only the URL scheme; any operator-
or LLM-influenced UDF declaration could target loopback, RFC1918, or
the cloud metadata service via 169.254.169.254 — a practical IAM
credential exfiltration path on any cloud deployment.
parse_endpoint now classifies the host:
* IPv4 literals are rejected if loopback, RFC1918, link-local
(covers AWS / GCP / Azure IMDS), multicast, broadcast, unspecified,
or carrier-grade NAT (100.64.0.0/10).
* IPv6 literals are rejected if loopback, unspecified, multicast,
link-local (fe80::/10), unique-local (fc00::/7, covers
fd00:ec2::254), or IPv4-mapped onto any of the above ranges.
* A short list of conventional metadata hostnames (localhost,
metadata, metadata.google.internal) is also blocked. Arbitrary
hostnames are still allowed; resolving DNS at builder time
introduces a TOCTOU/DNS-rebinding hole, so connect-time
enforcement is tracked as follow-up work.
* A new allow_private_endpoints: true param opts back in for trusted
on-host services. Documented in the file header.
Tests cover the default rejection across 12 representative private
addresses (loopback, RFC1918, IMDS, IPv6 ULA, IPv4-mapped IPv6, etc.),
the explicit opt-in path, public-address acceptance, and an
invalid-opt-in-value rejection. Existing round-trip tests that bind a
mock HTTP server on 127.0.0.1 are routed through a new
with_loopback_optin helper so they keep working.
Also update docs/security_audit.md to reflect what was fixed and
correct the earlier claim that the per-comparison code was not
constant-time.
https://claude.ai/code/session_01Xu4b9TS1UvGu4H1a7NxHKj
* docs: remove security audit report
The audit report was a working artifact; the actionable items live in
the code changes (constant-time-position API key lookup, remote-UDF
SSRF allowlist) plus their tests. Remaining findings can be tracked as
issues rather than as in-tree documentation.
https://claude.ai/code/session_01Xu4b9TS1UvGu4H1a7NxHKj
* fix: address security PR review feedback
* fix: improve security checks for remote UDF endpoints and refactor IP validation logic
* fix: resolve ownership issues in IP validation logic for disallowed addresses
* fix: use CIDR allowlist for remote UDF endpoints
* fix: address final security review comments
* fix(security): reject pickle model weights by default; enforce RUSTSEC advisories in CI
Two follow-ups motivated by CVE-2026-7482 (Ollama heap OOB read in GGUF
tensor parsing). The CVE itself does not affect this codebase — Ollama
is not a dependency — but the bug class (malformed binary blob handed
to a complex parser) does apply to several places spiceai consumes.
1. Pickle weight rejection
--------------------------------------------------------------------
The .pt / .pth / .ckpt / .bin extensions are conventionally Python
pickle in PyTorch's ecosystem, and pickle deserialization is RCE by
design. The local-LLM file pipeline previously accepted whatever the
operator pointed at and handed it straight to mistralrs's loader.
Operators commonly download model weights from third-party Hugging
Face repos, so a malicious .bin in such a repo would have run
arbitrary Rust process code at load time.
Add `llms::chat::reject_unsafe_weight_formats` and call it from
`runtime::model::chat::file` before invoking `create_local_model`.
The check refuses pickle-class extensions by default and accepts a
new `params.trust_pickle: true` opt-in for operators who control
the source. The check is case-insensitive and covers the four
conventional pickle extensions. Five unit tests cover rejection,
acceptance of safe formats, opt-in, case-insensitivity, and
extensionless paths.
2. Enforce RUSTSEC advisories in CI
--------------------------------------------------------------------
The `[advisories]` block in deny.toml was schema-pre-v2 and CI only
ran `cargo deny check licenses`. Upgrade the block to `version = 2`
(cargo-deny v2 default semantics: vulnerability / unmaintained /
unsound / notice all fail; yanked already explicitly denied), and
add a second step in `license_check.yml` that runs
`cargo deny check advisories`.
This intentionally turns on enforcement without pre-populating
`ignore` — the workspace currently has known findings (yanked
crates, unmaintained warnings, and a small number of actual CVEs in
transitive deps) that the team will want to triage explicitly rather
than silently bypass.
https://claude.ai/code/session_01Xu4b9TS1UvGu4H1a7NxHKj
* fix: tighten remote UDF endpoint allowlist handling
* fix: correct typo in comment regarding PyTorch pickle file extensions
* fix: update dependencies in Cargo.lock and enhance ApiKeyAuth implementation
* fix: ungate Path import so reject_unsafe_weight_formats builds without local_llm
reject_unsafe_weight_formats and the in-module tests use Path / PathBuf
unconditionally, but the imports were gated behind feature = "local_llm".
The runtime integration test build (no local_llm feature) failed with
E0425 "cannot find type `Path` in this scope". Ungate Path, gate PathBuf
to local_llm or test so it isn't unused in lib builds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: downgrade getrandom dependency from 0.4.1 to 0.3.4
* fix: filter remote UDF DNS results
* fix: update cargo-deny installation command and improve IP address checks for non-public endpoints
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: specify cargo-deny version and improve error messages in remote functions
* fix: update global IP address check to allow 192.0.0.0/24 range
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>1 parent 837c102 commit da237ad
7 files changed
Lines changed: 917 additions & 85 deletions
File tree
- .github/workflows
- crates
- llms/src/chat
- runtime-auth/src/api_key
- runtime-datafusion-udfs/src/user_functions
- runtime/src/model
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
28 | 27 | | |
29 | | - | |
| 28 | + | |
30 | 29 | | |
31 | 30 | | |
32 | 31 | | |
| |||
157 | 156 | | |
158 | 157 | | |
159 | 158 | | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
160 | 167 | | |
161 | 168 | | |
162 | 169 | | |
| |||
759 | 766 | | |
760 | 767 | | |
761 | 768 | | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
0 commit comments