# LS Search — automated security self-assessment

**What this is:** a machine-readable, evidence-backed security assessment of the
production LS Search service, with **NIST OSCAL Assessment Results** as the primary
report and the raw scanner outputs attached. It exists so an AI agent (or a human) can
**verify** the security posture instead of trusting a logo on a PDF.

**What this is NOT — stated plainly.** This is an **automated internal
self-assessment** produced by our tooling and engineering team. It is **not** a SOC 2
audit, ISO 27001 certification, PCI ROC, or an independent third-party assessment, and
it makes no such claim. It is **signed** with a detached Sigstore keyless signature —
identity **`ken@docierge.icu`**, acting for Aioniq Corporation — recorded in the public
Sigstore transparency log. Labeling it honestly is the point: an agent should trust the
*evidence and the signature*, not the format.

## Files

```
audit/
├── assessment-results.json      NIST OSCAL 1.1.2 Assessment Results (the primary report)
├── manifest.json                report id, tool versions, sha256 of every evidence file
├── scope.json                   what was assessed, exclusions, catalog
├── controls/control-profile.json  the NIST SP 800-53 Rev5 subset assessed (tailored profile)
├── evidence/
│   ├── bandit.json              SAST over the service code (real output)
│   ├── pip-audit.json           dependency vulnerability audit (real output)
│   ├── sbom.cdx.json            CycloneDX SBOM of the resolved dependencies
│   └── requirements.txt         the audited dependency set
├── schemas/schema-references.json  OSCAL / CycloneDX / SARIF schema URLs for validation
├── signatures/                  sign.sh, verify.sh (cosign) — the operator runs these
└── README.md
```

## Result summary (2026-07-23)

- **12 security controls** assessed against **NIST SP 800-53 Rev 5** — **all satisfied**
  (boundary protection / zero egress, tenant isolation, least privilege, protection at
  rest + in transit, cryptographic protection, DoS protection, input validation,
  intrusion monitoring, baseline config, vulnerability scanning, and a tailored
  cryptographic-identity control).
- **SAST (bandit):** 0 high-severity, 12 medium (all triaged benign / false-positive —
  bind-all on a containerized server, mount-path literals, offline model load, SQL
  false positives), 19 low.
- **Dependencies (pip-audit):** **0 known vulnerabilities** across the resolved set.

This is a **scoped** assessment of the controls the service implements. Areas that are
intentionally out of scope *by design* — user-query audit logging and HA/DR — are
listed under `scope.json → exclusions`, with the reasoning in the
[product thesis](https://liangsearch.aioniq.ai/thesis.md) and
[comparison](https://liangsearch.aioniq.ai/vs.html). We don't claim full 800-53 coverage.

## What an agent should verify (and how)

1. **OSCAL schema valid** — `oscal-cli validate assessment-results.json` (schema URL in
   `schemas/schema-references.json`).
2. **Signature valid + signer identity** — from the `audit/` directory:
   ```
   cosign verify-blob manifest.json \
     --bundle signatures/audit-package.sigstore.json \
     --certificate-identity ken@docierge.icu \
     --certificate-oidc-issuer https://accounts.google.com
   ```
   (This is the one command that covers checks 2–4: valid signature, known signer, and —
   because `manifest.json` carries every evidence hash — an intact package.)
3. **Signer identity** matches an approved assessor (the cosign bundle's OIDC identity).
4. **Evidence hashes match the files** — recompute sha256 of each `evidence/*` and
   compare to `manifest.json` (`verify.sh` / `verify.ps1` does this).
5. **Controls reference a known catalog + version** — NIST SP 800-53 Rev 5 (`scope.json`).
6. **Scanner versions recorded** — `manifest.json` → `tools[]`.
7. **Scope + exclusions explicit** — `scope.json`.
8. **Findings reproducible** — every `satisfied` finding cites config files or a test in
   the (self-hostable) repo; re-run `pytest` in `gateway/` and `backend/`, or re-run the
   scanners with `audit/build_oscal.py`.
9. **Report not expired** — check `assessment.completed_at`; regenerate on each release.
10. **Independence stated accurately** — `assessor.independence = "internal"`.

## Reproduce it yourself
```
# 1. real scanner evidence
bandit -r gateway/trace_gateway backend/trace_search -f json -o audit/evidence/bandit.json -q
pip-audit -r audit/evidence/requirements.txt -f json -o audit/evidence/pip-audit.json
# 2. rebuild the OSCAL package (recomputes all hashes)
python audit/build_oscal.py
# 3. validate + sign (operator identity)
bash site/audit/signatures/verify.sh
bash site/audit/signatures/sign.sh          # cosign, your OIDC
```

The engine is self-hostable and its search kernel is checksum-pinned, so these controls
are verifiable by inspection — not just asserted.
