ordvec 0.2.0

Training-free ordinal & sign quantization for vector retrieval
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# Threat Model — `ordvec`

> **Status:** v0.2.0 (pre-1.0), 2026-05-25. This is the maintained threat model
> for the `ordvec` Rust crate and the `ordvec` PyO3/maturin Python bindings. It
> is reviewed when the attack surface changes (new persistence formats, new
> `unsafe` kernels, new FFI surface, or release-pipeline changes).
>
> Scope discipline: `ordvec` is a **pure computational library** — no network
> surface, no authentication/authorization, no secrets handling, no
> multi-tenancy of its own. This document deliberately does **not** enumerate
> web-application threats (SQLi/XSS/CSRF/session) that do not apply. It covers
> the surfaces that actually exist: untrusted-input parsing, `unsafe` SIMD, the
> Python FFI boundary, the supply chain, and resource use under untrusted
> callers. Deployment-owned risks (corpus trust, co-tenancy, admission control)
> are documented as *context* for integrators, not as library action items.

See also: [`SECURITY.md`](SECURITY.md) (reporting), [`RELEASING.md`](RELEASING.md)
(release controls), [`docs/INDEX_PROVENANCE.md`](docs/INDEX_PROVENANCE.md)
(what the loaders do and do not guarantee).

---

## Scope and security ownership

**`ordvec` owns:**

- Memory safety of all safe public APIs.
- Robust rejection of malformed serialized index files — no panic, no OOM
  abort, no silent data corruption, no trailing-data acceptance.
- Deterministic, finite-input behavior for valid embeddings.
- Clear, documented failure contracts for invalid caller input (non-finite
  floats, dimension mismatches, shape errors) — panic in Rust, `ValueError`
  in Python.
- Supply-chain hygiene for the published crate and Python wheels.

**`ordvec` does not own:**

- Trustworthiness of the upstream embedding model.
- Corpus provenance or document-level poisoning.
- Authorization over which documents may be indexed or retrieved.
- Tenant isolation or microarchitectural isolation on a hosting platform.
- Cryptographic verification of index-file origin (callers add this externally
  — see [`docs/INDEX_PROVENANCE.md`]docs/INDEX_PROVENANCE.md).

> A structurally valid index file can still be semantically malicious. The
> loaders validate format invariants — not truth, authorization, or corpus
> integrity.

## Maintenance budget

`ordvec` is maintained by a single primary contributor. Mitigations are
prioritized when they are (1) low-maintenance once merged, (2) enforceable by
tests or CI, (3) local to the library boundary, and (4) unlikely to add
operational burden downstream. Heavyweight controls (mandatory index signing,
long-running fuzz farms, service-level admission control) are documented as
**deployment guidance** until there is maintainer capacity to own them. The
absence of a second maintainer is itself a tracked supply-chain residual
(see THREAT-SUPPLY-001).

---

## 1. Architecture and trust boundaries

### 1.1 Component map

| Layer | Components | Trust boundary |
|---|---|---|
| **Deserialization** | `rank_io.rs``.tvr` / `.tvrq` / `.tvbm` / `.tvsb` loaders | Untrusted filesystem / network byte stream |
| **Compute kernels** | `fastscan.rs`, `quant_kernels.rs`, `bitmap.rs`, `sign_bitmap.rs` | Trust established after format validation |
| **Index API** | `rank.rs`, `quant.rs`, `bitmap.rs`, `sign_bitmap.rs` | Caller-controlled query embeddings |
| **Python FFI** | `ordvec-python` (PyO3 / maturin) | Python ↔ Rust boundary; NumPy buffers |
| **CI / supply chain** | 13 GitHub Actions workflows; `Cargo.lock`; crates.io + PyPI | GitHub OIDC, crates.io, PyPI trust chains |

The `fuzz/` directory holds **seven** cargo-fuzz targets: `load_rank`,
`load_rankquant`, `load_bitmap`, `load_sign_bitmap` (deserialization);
`roundtrip_rankquant` (write→load round-trip); `search_rankquant` (the
single-rate ingest + asymmetric-search compute path); and `fastscan_b2` (the
FastScan b=2 block-32 kernel — the one `unsafe`-heavy scan path the others do
not reach).

### 1.2 Deployment contexts (for integrators)

- **Offline / batch indexing** — a trusted operator encodes a corpus and writes
  index files. Low risk unless files later cross a trust boundary.
- **Serving pipeline** — an index loaded at startup, then queried by
  user-controlled embeddings. Query vectors cross the trust boundary on every
  search call (see §6).
- **RAG substrate**`ordvec` retrieves the *k* nearest documents fed to an
  LLM. The retrieval layer becomes a target for corpus-level poisoning; this is
  a **deployment risk**, not a parser risk (see §7).
- **Multi-tenant / cloud** — tenants sharing one process share SIMD execution
  units. Microarchitectural isolation is a hosting-platform responsibility
  (see THREAT-SIMD-002).

---

## 2. Deserialization threats (THREAT-DESER) — library-owned

### 2.1 Existing defenses (code-verified)

`rank_io.rs` implements layered parser hardening:

- Magic + version checks before any allocation.
- Fallible allocation via `try_reserve_exact` — an attacker-controlled length
  field returns `InvalidData`, never an OOM abort.
- All payload sizes computed with `usize::checked_mul`; overflow returns `Err`.
- A 128 GiB `MAX_PAYLOAD` cap and `MAX_VECTORS` (64 Mi) / `MAX_DIM` caps,
  enforced on **both** the load and write paths (the write-side cap runs
  *before* `File::create`, so a rejected write cannot truncate an existing
  file).
- Exact file-length match (`check_payload_matches_file`): trailing bytes or
  short files are rejected.
- Per-row **structural** invariants: `Rank` rows must be a true permutation of
  `[0, dim)` (verified by bound + duplicate checks ⇒ pigeonhole);
  `RankQuant` rows must satisfy constant composition (uniform per-bucket
  histogram); `Bitmap` rows must have exactly `n_top` bits set.
- No `panic!` on malformed data — all validation returns
  `io::Error(InvalidData)`.
- The raw `rank_io` read/write functions are `pub(crate)`; the only public
  persistence API is the index types' `write()` / `load()`, making the
  write→load round-trip a type-level guarantee.

The four loaders are covered by cargo-fuzz targets (the `load_*` targets).

### 2.2 Index-file risk classes

**THREAT-DESER-001 (library-owned, P4): Malformed index file.**
The loader must reject corrupt/invalid files without panic, OOM, or
trailing-data acceptance. The current implementation satisfies this for all
four formats. *Residual:* `file.metadata()?.len()` is sampled at open time;
on NFS/FUSE mounts with concurrent writers a TOCTOU window exists between
`metadata()` and the reads. On writable shared mounts the practical outcome is
a read error or `InvalidData`, not an exploit. *Likelihood:* Very Low.
*Impact:* error surfaced. 

**THREAT-DESER-002 (deployment-owned, P3 docs): Malicious-but-valid index.**
A structurally valid index with semantically poisoned contents passes every
parser check and returns attacker-influenced results. This is a *provenance*
problem, not a parser problem. *Mitigation (no format change):*
[`docs/INDEX_PROVENANCE.md`](docs/INDEX_PROVENANCE.md) documents that `ordvec`
validates structure, not origin, and lists verification options (checksum
manifest, artifact-store integrity, Sigstore / GitHub artifact attestation)
for deployments where index files cross trust boundaries. An optional sidecar
verifier (HMAC / BLAKE3) can be added later without a format bump; it is
deliberately **not** shipped now (no concrete deployment requires it, and an
in-format crypto layer would add unowned key management).

---

## 3. Unsafe SIMD and memory-safety threats (THREAT-SIMD) — library-owned

### 3.1 What the FastScan kernel does

`scan_b2_fastscan_avx512` uses unaligned loads (`_mm256_loadu_si256`),
byte-shuffle LUT lookups (`_mm256_shuffle_epi8` / VPSHUFB), broadcast, widen
(`_mm256_cvtepu8_epi16`, `_mm512_cvtepu16_epi32`), and accumulate
(`_mm512_add_epi16/epi32`, `_mm512_storeu_si512`). It is a load/shuffle/widen/
accumulate sequence with **no gather instructions**. The Intel DOWNFALL (GDS)
vulnerability is specific to gather-based data sampling and does **not** apply
to this kernel.

### 3.2 Risks

**THREAT-SIMD-001 (P1, mitigated this cycle):
Unsafe-kernel invariant preservation under future refactors.**
`scan_b2_fastscan_avx512` safety depends on caller-established invariants —
`packed_fs.len() == n_blocks * pairs * 32` (formed via `checked_mul`, overflow
⇒ caller panics) and `lut_u8.len() == pairs * 16`. These are asserted by the
`pub(crate)` entry point `search_asymmetric_fastscan_b2` before dispatch, and
`RankQuantFastscan::search` is the type-level safe wrapper that owns the shape
by construction. A future refactor calling the inner function directly could
bypass the asserts. *Mitigations:* the runtime asserts + the type wrapper are
the primary boundary; the scalar-vs-SIMD equivalence test
(`fastscan_b2_top10_matches_avx512_kernel`) guards behavior; and
**`#![deny(unsafe_op_in_unsafe_fn)]` is now enforced crate-wide** (at the crate
root in `lib.rs`), so every unsafe operation in every SIMD kernel —
`fastscan.rs`, `bitmap.rs`, `sign_bitmap.rs`, `quant_kernels.rs`, and the
`util.rs` NEON popcount — sits in an explicit `unsafe {}` block and stays
visible to future edits. (The lone exception, `horizontal_sum_avx2`, is
register-only with no memory access, so its intrinsics are safe under the
`#[target_feature]` gate and an explicit block would be `unused_unsafe`.)

**THREAT-SIMD-002 (P4, deployment note): Microarchitectural side channels in
co-tenancy.** `ordvec` does not claim protection against microarchitectural
side channels under hostile multi-tenant co-residency. The kernel uses no
gather instructions (ruling out DOWNFALL/GDS), but SIMD execution units are
shared across SMT threads, and port-contention timing channels remain
theoretically possible on vulnerable hardware. Sensitive deployments should
avoid sharing physical cores across trust domains and rely on the
OS/hypervisor side-channel posture. Not a library action item.

**THREAT-SIMD-003 (P3): FastScan approximation is not CPU-dependent
divergence.** The 8-bit global-affine LUT in `build_fastscan_b2_query`
introduces `O(span/255)` per-pair approximation error — an intentional
trade-off matching FAISS FastScan semantics, documented in the code. The
scalar and AVX-512 paths agree on the same quantized inputs (equivalence test),
and `TopK` uses `total_cmp` for deterministic tie-breaking across all paths.
This is approximate *scoring*, not a CPU oracle. FastScan is a `#[doc(hidden)]`
pre-ranker; callers needing exact scores use `RankQuant::search_asymmetric`.

---

## 4. Python FFI threats (THREAT-FFI) — binding-owned

### 4.1 Existing defenses (code-verified)

The binding takes `PyReadonlyArray`, rejects non-C-contiguous arrays with a
clear `ValueError`, validates finiteness (`ensure_finite`), maps shape errors
to `ValueError`, and releases the GIL (`py.detach`) around the pure-Rust
(Rayon-parallel) compute in every heavy method while reading the input arrays
in place. PyO3's `&mut self` borrow tracking means a second thread re-entering
the **same** index object during a released-GIL call gets a clean
`Already borrowed` `RuntimeError`, never concurrent mutation.

### 4.2 Risks (documented contracts, implemented)

**THREAT-FFI-001 (P2, documented): Concurrent input-array mutation during a
released-GIL call.** `PyReadonlyArray` keeps the input buffer alive and blocks
`rust-numpy`-mediated writes for the call's duration, but it cannot stop
another thread or native extension from mutating the *same backing memory*
through a reference obtained before the call. This can yield numerically
inconsistent results — a numeric-extension contract issue, not a UAF. *Status:*
documented in the module docstring and the per-method docs ("do not mutate an
input array from another thread while an `ordvec` call is in progress"),
matching the standard contract for GIL-releasing NumPy extensions. An optional
`safe_copy=True` hard-isolation parameter remains a possible future ergonomic.

**THREAT-FFI-002 (P2, documented): Unsanitized filesystem-path forwarding.**
`write()` / `load()` forward the path to the filesystem unmodified (no `..` /
traversal sanitization). A service exposing these path arguments to user input
could enable traversal or arbitrary-file overwrite. This is a **caller
responsibility**. *Status:* documented in the module docstring and on every
`write`/`load` method ("treat the path as trusted input; web/multi-user
applications must validate paths before calling"). 

---

## 5. Supply-chain threats (THREAT-SUPPLY)

### 5.1 Existing controls (verified)

**Workflow code (all 13 workflows):** third-party actions pinned by commit
SHA; `persist-credentials: false` on every checkout; `permissions: contents:
read` default. **Release workflows** (`release-crate.yml`, `release-python.yml`)
are `workflow_dispatch`-only (no tag/push trigger), run a `require-ci-green`
gate against `main`, publish via **OIDC trusted publishing** (no long-lived
registry tokens), and emit **SLSA build provenance**
(`actions/attest-build-provenance`) **before** publish — a failed attestation
fails the release closed. `release-python` additionally gets **PEP 740**
attestations via Trusted Publishing.

**Static / supply-chain analysis:** **CodeQL** scans Rust, Python, and Actions
(no-build databases); **OpenSSF Scorecard** publishes SARIF to code scanning
and the score badge; **zizmor** audits workflow hardening (pinned); a
`cargo-deny` / audit job gates advisories and licenses. The core crate has near
zero non-Rust dependencies by design (the `deps` gate greps `cargo tree -p
ordvec`); the Python binding's larger tree (numpy → ndarray) is intentional and
scoped to the wheel.

### 5.2 Risks

**THREAT-SUPPLY-001 (mitigated; residual = single-maintainer account
compromise): Release configuration and ownership.** The release **environments**
(`pypi`, `crates-io`) now require **approval by the maintainer** and restrict
deployment to the **`main`** branch only — so a release cannot be dispatched
from an unmerged or attacker branch, and no publish runs without an explicit
human approval. The remaining residual is *maintainer-account compromise*: a
single owner is both dispatcher and approver, so account takeover (or social
engineering) is not caught by a second human. *Mitigations:* strong 2FA /
passkeys on the maintainer account; recruiting a **second owner/maintainer**
(also an open OpenSSF Best-Practices item) — which would additionally make a
deployment **wait timer** worthwhile (a second party able to cancel a bad
release during the window). See [`RELEASING.md`](RELEASING.md).

**THREAT-SUPPLY-002 (mitigated): Release immutability and tag integrity.**
Published artifacts are **immutable by registry design** — crates.io is
yank-only (a published version's bytes can never be overwritten) and PyPI burns
a version on delete (no different artifact may be re-uploaded under the same
version). So post-publish "silent replacement" of a version is not possible on
either registry, and consumers can verify artifacts against the SLSA / PEP 740
provenance above. The GitHub-side mutability surface is now closed too:
`changelog.yml` cuts tagged GitHub Releases, and **GitHub immutable releases is
enabled**, so a published release's `v*` tag cannot be force-moved or deleted
and its assets cannot be replaced after publication; the **`main` branch is
protected** (pull-request review required, force-pushes and deletions blocked)
and is the **only deployment branch** permitted for the `pypi` / `crates-io`
release environments. *Residual:* draft / non-release tags are not covered by
release immutability, and — as with the registries — these GitHub controls
ultimately trust the single maintainer account; that residual folds into
THREAT-SUPPLY-001.

**THREAT-SUPPLY-003 (P3): Typosquatting adjacent names.** Namespace-adjacent
crate/package names (`ord-vec`, `ordvecs`, `order-vec`) could be registered to
typosquat dependents. *Mitigation:* publish the first functional release
promptly; optionally register adjacent names.

---

## 6. Query and resource-exhaustion threats (THREAT-QUERY) — library-adjacent

These arise from correct behavior on large-but-valid inputs from untrusted
callers, not from parser or unsafe bugs.

**THREAT-QUERY-001 (P2, deployment docs): Caller-controlled batch / `k`
exhaustion.** `result_buffer_len(nq, k)` checks `nq * k` overflow and panics
loudly rather than under-allocating; `k` is clamped to `n_vectors`. But a
serving application can still be CPU/memory-exhausted by large query batches
(`nq`), large `k`, or concurrent scans over a large corpus. `ordvec` does not
enforce service-level quotas — by design (it is a library, not a server).
*Mitigation:* callers exposing search over a network must independently bound
batch size, `k`, request rate, and corpus size; a configurable `max_nq` /
`max_k` at the binding level is a possible future convenience.

**THREAT-QUERY-002 (P3): Panic on contract violation in Rust server contexts.**
Rust APIs fail fast on invalid contract input (non-finite floats, dimension /
shape violations) via `assert!` / `expect`. In a Rust-native server an
unhandled panic crashes the thread/process; the Python bindings convert these
to typed `ValueError`. *Mitigation:* Rust service callers must validate
untrusted input before calling, or catch panics at the request boundary.

---

## 7. Corpus and embedding poisoning (THREAT-POISON) — deployment-owned

These sit **outside** the library's security perimeter; they are documented as
context for integrators using `ordvec` as a RAG substrate. Corpus poisoning of
embedding retrievers is a documented attack class (see PoisonedRAG and OWASP
LLM08:2025 in the references); the mitigations are corpus provenance, ingestion
access control, and (where applicable) hybrid lexical + vector retrieval — all
deployment concerns. The points below are the `ordvec`-specific shape of that
class.

**THREAT-POISON-001: Ordinal rank inversion.** Because `ordvec` is
training-free, the rank transform is deterministic and invertible. An attacker
who controls the embedding pipeline can engineer an embedding whose ordinal
(Spearman) correlation with target queries is maximized — the ordinal analogue
of embedding-inversion attacks. `ordvec` has no codebook to protect and cannot
prevent construction of maximally correlated embeddings; mitigation requires
access control and provenance on the embedding source.

**THREAT-POISON-002: Top-`n_top` overlap poisoning.** `Bitmap` scores documents
by `popcount(Q AND D)`. The loader enforces exactly `n_top` bits per row, so an
injected document cannot set arbitrary bits — the realistic attack is crafting a
document whose top-`n_top` coordinates maximally overlap the most-queried
coordinates. Requires knowledge of the query distribution and corpus write
access.

**THREAT-POISON-003: RankQuant boundary exploitation.** `RankQuant` uses
equal-width bucket quantization; documents near bucket boundaries can be crafted
to score highly under the coarse pre-filter yet differ under exact reranking,
exploiting quantization information loss to pass the coarse stage. Requires
knowledge of quantization parameters and the document distribution.

---

## 8. Fuzzing coverage (THREAT-FUZZ)

Seven targets cover the four loaders, the write→load round-trip, the
single-rate compute path, and (new) the FastScan kernel.

**THREAT-FUZZ-001 (closed this cycle): FastScan path was unfuzzed.** The
`fastscan_b2` target now drives `RankQuantFastscan` (`pack_fastscan_b2` +
`search_asymmetric_fastscan_b2` + the scalar/AVX-512 kernel), crossing the
32-doc block boundary so tail-padding blocks are exercised. On
non-AVX-512 CI runners it exercises the scalar reference kernel; under Intel SDE
it exercises the AVX-512 kernel.

**THREAT-FUZZ-002 (mitigated this cycle): CI-bound fuzzing for continuous
regression.** A `fuzz.yml` workflow now runs a bounded smoke on every pull
request and push to `main` (`-max_total_time=60` over `load_rank`,
`load_rankquant`, and `fastscan_b2`) plus a weekly full sweep
(`-max_total_time=300` over all seven targets), so a regression that
reintroduces a loader panic / OOM, breaks the write→load round-trip, or
destabilises the FastScan kernel surfaces in CI rather than only at the next
manual campaign. cargo-fuzz is version-pinned and the actions are SHA-pinned,
matching the repo's scheduled-workflow hardening.

*Note on `load_sign_bitmap`:* all bit patterns are structurally valid for sign
bitmaps (no per-row invariant), so that target is correctly scoped to parser
robustness — no OOM, no panic, no trailing-data acceptance.

---

## 9. CI/CD pipeline threats (THREAT-CICD)

**THREAT-CICD-001 (P3, mitigated by control): Workflow injection via PR
metadata.** If a `run:` step interpolated user-controlled context (PR title,
branch name) into a shell expression via `${{ ... }}` without an `env:` hop, a
script-injection could run in the runner. *Mitigation:* `zizmor` audits exactly
this class of issue and runs in CI; pass user-controlled context through `env:`
rather than inline `${{ }}` in `run:` blocks. SHA-pinned actions bound the
blast radius of a compromised dependency separately.

---

## 10. Threat register

| ID | Category | Owner | Description | Likelihood | Impact | Status / priority |
|---|---|---|---|---|---|---|
| THREAT-SIMD-001 | Memory safety | Library | Unsafe-kernel invariant bypass on refactor | Medium | High | **Mitigated**`unsafe_op_in_unsafe_fn` denied crate-wide + type wrapper + equivalence test |
| THREAT-FFI-001 | FFI | Binding | Concurrent input mutation during released-GIL call | Medium | Medium | **P2** — documented contract |
| THREAT-FFI-002 | FFI | Binding | Unsanitized path forwarding | Medium | Medium | **P2** — documented contract |
| THREAT-SUPPLY-001 | Supply chain | Config | Release config / single-owner | Low | Critical | **Mitigated** (reviewer + main-only); residual = account compromise / 2nd owner |
| THREAT-SUPPLY-002 | Supply chain | Config | Release immutability / tag integrity | Low | High | **Mitigated** — registries immutable; GitHub immutable releases on + `main` protected |
| THREAT-SUPPLY-003 | Supply chain | Config | Typosquatting adjacent names | Medium | Medium | P3 |
| THREAT-QUERY-001 | Resource | Deployment | Batch / `k` exhaustion in serving | Medium | Medium | **P2** — deployment docs |
| THREAT-QUERY-002 | Resource | Deployment | Panic on contract violation (Rust servers) | Low | Medium | P3 |
| THREAT-FUZZ-001 | Fuzzing | Library | FastScan path unfuzzed | Medium | High | **Closed** (`fastscan_b2` added) |
| THREAT-FUZZ-002 | Fuzzing | Library | No CI-bound fuzzing | Medium | Medium | **Mitigated**`fuzz.yml` PR smoke + weekly sweep |
| THREAT-DESER-001 | Deserialization | Library | TOCTOU on shared mounts | Very Low | Low | P4 |
| THREAT-DESER-002 | Provenance | Deployment | Malicious-but-valid index | Medium | High | P3 (docs — `INDEX_PROVENANCE.md`) |
| THREAT-CICD-001 | CI/CD | Library | Workflow injection via PR metadata | Low | High | P3 — mitigated by `zizmor` |
| THREAT-SIMD-002 | Side channel | Deployment | Microarchitectural co-tenancy (no gather) | Low | Medium | P4 |
| THREAT-SIMD-003 | Semantic | Library | FastScan approximation (doc clarity) | Low | Low | P3 |
| THREAT-POISON-001 | Index poisoning | Deployment | Ordinal rank inversion | Medium | High | Deployment |
| THREAT-POISON-002 | Index poisoning | Deployment | Top-`n_top` overlap poisoning | Low | Medium | Deployment |
| THREAT-POISON-003 | Index poisoning | Deployment | RankQuant boundary exploitation | Low | Low | Deployment |

---

## 11. Open mitigations

**Done this cycle:** `#![deny(unsafe_op_in_unsafe_fn)]` enforced **crate-wide**
across all SIMD modules (SIMD-001); the `fastscan_b2` fuzz target (FUZZ-001)
plus a CI `fuzz.yml` — PR smoke + weekly sweep (FUZZ-002); the `rank_to_bucket`
primitive made fail-loud (`rank < d`) to match the rest of the bucket API, with
matching binding guards; release-environment reviewers + main-only deployment
(SUPPLY-001); **GitHub immutable releases enabled + `main` branch protection**
(SUPPLY-002); [`docs/INDEX_PROVENANCE.md`](docs/INDEX_PROVENANCE.md) (DESER-002);
[`RELEASING.md`](RELEASING.md) (SUPPLY-001).

**Open, low cost:**

1. Document recommended `nq` / `k` / corpus bounds for single-process serving
   in the Rust and Python API docs (THREAT-QUERY-001).

**Later (not release blockers):** a second maintainer/owner (then a release
wait timer becomes meaningful); an optional sidecar index verifier
(`ordvec verify` / external HMAC/BLAKE3 manifest) if a deployment requires
tamper-evidence (DESER-002); a `safe_copy=True` FFI isolation option
(FFI-001).

---

## References

Only load-bearing, verifiable sources are listed.

- **PoisonedRAG***Knowledge Corruption Attacks to Retrieval-Augmented
  Generation of Large Language Models* (arXiv:2402.07867). Establishes that
  injecting a small number of poisoned passages into a retriever corpus
  achieves high attack-success rates — context for §7.
- **OWASP LLM08:2025 — Vector and Embedding Weaknesses.** Retrieval-layer risk
  class (poisoning, embedding inversion, access-control bypass) — context for
  §7 / scope.
- **"Memory-Safety Challenge Considered Solved? An In-Depth Study with All Rust
  CVEs"** (arXiv:2003.03296). Real-world Rust memory-safety bugs require
  `unsafe` code — the rationale for the §3 focus on the SIMD kernels.
- **GitHub Security Lab — preventing pwn-requests.** Expression-injection in
  `run:` steps and untrusted-context handling — basis for THREAT-CICD-001.