- Enhanced `.env.example` with detailed CMake build flags and replay-mode strategy flags for development and CI environments. - Updated `.gitignore` to include a new deploy rollback bookmark. - Revised `_docs/_autodev_state.md` to reflect the current task status and steps. - Added new lessons to `_docs/LESSONS.md` regarding testing and architectural improvements. - Documented changes in `_docs/02_document/deployment/ci_cd_pipeline.md` to reflect the relaxed OpenCV version pin. - Updated test data documentation in `_docs/02_document/tests/test-data.md` to clarify fixture usage and paths. This commit continues the cycle-1 documentation sync and addresses various configuration updates for improved clarity and functionality.
Security Audit — Consolidated Report
Audit window: 2026-05-19
Auditor: autodev Step 14 (greenfield flow) — .cursor/skills/security/SKILL.md § Phases 1–5
Scope: gps-denied-onboard SUT — production code path (src/gps_denied_onboard/**), pinned dependencies, container build artefacts, docker-compose topology, committed secret hygiene.
Out of scope: parent-suite components (D-PROJ-1 deploy/observability stack, D-PROJ-2 ingest service, satellite-provider, operator GCS UI); third-party SITL images beyond their tagging hygiene.
Executive Summary
The SUT's security posture is built around a single defining design property: the airborne process has no inbound or outbound network surface. That property is enforced at three independent layers (architectural, operational iptables + DNS blackhole, test-harness verification via NFT-SEC-02 / NFT-SEC-05), which keeps the externally-reachable attack surface at zero in flight. The pre-flight (operator-workstation) phases DO touch the network — over TLS, with pinned hosts, parameterized SQL, redacted logs, and Ed25519-signed manifests / uploads.
Result: 0 Critical, 0 High, 5 Medium, 17 Low. No release-blocking finding. The Medium findings are container-hardening gaps (root user in the production image, dev extras in the production image, moving-tag base images for SITL) that would be raised to High under a multi-tenant or internet-exposed threat model but are bounded to Medium here by the closed-system invariant.
| Severity | Count | Phase 1 (Deps) | Phase 2 (SAST) | Phase 3 (OWASP) | Phase 4 (Infra) |
|---|---|---|---|---|---|
| Critical | 0 | 0 | 0 | 0 | 0 |
| High | 0 | 0 | 0 | 0 | 0 |
| Medium | 5 | 1 | 0 | 0 | 4 |
| Low | 17 | 11 | 1 (informational) | 0 | 5 |
| Total | 22 | 12 | 1 | 0 | 9 |
Release-blocking findings: 0.
Remediation expected before next major dependency refresh: F14 (non-root container), F15 (production [dev] extras), F16 (mavproxy:latest).
Remediation deferred to a future hardening pass: F13 (plugin-loader assertion), F17 (digest-pin production base images), F18-F22 (housekeeping).
Cross-component leftover already tracked: D-CROSS-CVE-1 — opencv-python pin coupling with gtsam numpy<2 ABI block (_docs/_process_leftovers/2026-05-11_d_cross_cve_1_opencv_pin_deferred.md).
Threat Model Snapshot
| Property | Status | Phase | Evidence |
|---|---|---|---|
| Airborne process has no network listener | enforced | A04 | compose_root_airborne wires no HTTP server; NFT-SEC-05 enforces at runtime |
| Airborne process has no network egress | enforced | A04 | iptables OUTPUT REJECT + DNS blackhole (RESTRICT-OPS-1); NFT-SEC-02 verifies in CI |
| Operator-workstation HTTP traffic is TLS-validated | enforced | A02 | 0 verify=False matches; pinned base URLs only |
| Tile / Manifest integrity end-to-end | enforced | A08 | SHA-256 sidecar + Ed25519 detached signatures |
| MAVLink ↔ FC channel is signed | enforced (AP) / documented residual (iNav) | A07 | Per-flight key rotation logged to FDR (NFT-SEC-03) |
| In-container runtime executes as non-root | GAP — F14 | A05, F14 | Production Dockerfiles missing USER directive |
| Production runtime image excludes dev tools | GAP — F15 | A05, F15 | pip install -e ".[dev]" ships pytest, ruff, mypy into prod |
| All container images are digest-pinned | GAP — F16/F17 | A05, F16, F17 | Mixed: tile-cache-builder digest-pinned, production images use floating tags |
| Single-tenant binary (no per-user RBAC) | by design | A01 | One-OS-user container, FC arm/disarm IS the privilege gate |
| Secrets never logged | enforced | A09 | _REDACTED convention across 13 files |
| Test secrets are synthetic, marked TEST ONLY | enforced | P6 | Both committed passkey files contain 0123…ef-repeated bytes |
Per-Phase Outcomes
Phase 1 — Dependency Scan (_docs/05_security/dependency_scan.md)
pip-audit against the pinned environment surfaced 12 advisories across 5 packages.
| Package | Advisories | Project-specific finding |
|---|---|---|
onnx==1.18.0 |
1 (GHSA-rcgw-4cgp-9q7q) | Reachable only via developer-mode model loading; SUT serves prebuilt engines on the production path. Medium. |
cryptography==42.0.8 |
4 | None reachable from SUT usage (Ed25519 sign/verify + httpx default X.509). Low. |
numpy==1.26.4 |
4 | None reachable from SUT usage (no untrusted-input numerical ingestion). Low. |
protobuf==4.25.3 |
2 | None reachable; protobuf usage is internal serialization to the FDR layer. Low. |
setuptools |
1 | Dev-time only. Low. |
D-CROSS-CVE-1 outcome: re-running pip-audit against the relaxed pin (opencv-python>=4.11.0.86,<4.12) confirmed that CVE-2025-53644 is NOT flagged for opencv-python==4.11.0.86. The deferred-bump leftover was updated to record this; the residual risk window is effectively closed at the current pin. Bump to 4.12.x remains coupled to the gtsam numpy-2 ABI work.
Phase 2 — Static Analysis (SAST) (_docs/05_security/static_analysis.md)
Pattern-driven ripgrep scan across 259 Python files in src/.
| Category | Findings |
|---|---|
| SQL injection | 0 — all 19 cur.execute(...) sites are parameterized |
| Command injection | 0 — 1 subprocess.run (TensorRT trtexec), list args, no untrusted input |
| Hardcoded secrets / weak crypto | 0 — Ed25519, SHA-256, secrets.token_bytes |
| Insecure deserialization | 0 — no pickle.loads / eval / exec on untrusted data; json.loads only on local SUT-managed files |
| TLS / verification bypass | 0 — all httpx.Client defaults preserved |
F13 (informational) — Dynamic __import__ via plugin registry |
1 — bounded by closed KNOWN_*_STRATEGIES whitelist; hardening recommended |
Defense-in-depth observations: per-flight ephemeral Ed25519 signing in C11; secrets.token_bytes for MAVLink passkey generation; paramiko.RejectPolicy (NOT AutoAddPolicy) for SSH; _REDACTED convention enforced across 13 files; secret-buffer zeroising on session end (AZ-318).
Phase 3 — OWASP Top 10 Review (_docs/05_security/owasp_top_10_review.md)
All 10 categories assessed against the SUT's actual surface. A01 (Broken Access Control) is N/A by design (single-tenant binary, no per-user RBAC). A02 through A10 each cleared with 0 findings beyond those already reported in Phases 1, 2, and 4. The closed-system threat model is itself the strongest A04 (Insecure Design) property and is enforced at three layers.
Phase 4 — Configuration & Infrastructure (_docs/05_security/config_infra_review.md)
9 findings (4 Medium, 5 Low) — all container-hardening gaps in the production images. The test stack is well-secured (internal: true network, digest-pinned tile-cache-builder, public-boundary discipline on the runner). The gap is consistency: the project already KNOWS how to do this (see e2e/fixtures/tile-cache-builder/Dockerfile) — production images need to match that bar.
Remediation Plan
Recommended before next dependency-refresh cycle
| ID | Title | Effort | Coupling |
|---|---|---|---|
| F14 | Add non-root USER directive to docker/companion-tier1.Dockerfile + docker/operator-orchestrator.Dockerfile |
2 pts | Couples with F22 (chown WORKDIR) |
| F15 | Replace pip install -e ".[dev]" with runtime-only install in both production Dockerfiles |
2 pts | Standalone |
| F16 | Pin ardupilot/mavproxy:latest and ardupilot/ardupilot-sitl:plane-stable to explicit versions or SHA256 digests |
1 pt | Standalone |
| F1 | Bump onnx from 1.18.0 → 1.19+ when next dep cycle lands (GHSA-rcgw-4cgp-9q7q) |
2 pts | Couples with C7 model-loader review |
Recommended at next hardening pass
| ID | Title | Effort |
|---|---|---|
| F13 | Add assert strategy in KNOWN_*_STRATEGIES at each __import__ call site (defense-in-depth, redundant today) |
2 pts |
| F17 | Pin production base images by SHA256 digest, with explicit refresh cadence | 1 pt |
| F18 | Delete or fix the orphan docker/mock-suite-sat-service.Dockerfile |
1 pt |
| F19 | Remove unused curl from docker/operator-orchestrator.Dockerfile |
1 pt |
| F20 | Add upper bound to runner's opencv-python pin |
1 pt |
| F21 | Update .env.example to current secret paths + env var names |
1 pt |
| F22 | Coupled with F14 — chown WORKDIR after adding USER directive |
included in F14 |
Deferred / external
- D-CROSS-CVE-1 —
opencv-python4.12.x bump deferred untilgtsamships a numpy-2 compatible release. Tracked at_docs/_process_leftovers/2026-05-11_d_cross_cve_1_opencv_pin_deferred.md. Residual exposure window is currently closed (CVE-2025-53644 no longer flagged at 4.11.0.86). - Per-flight rekeying audit cadence — out of scope for this audit; will be revisited when D-PROJ-2 ingest endpoint lands and the upload contract is finalised.
- Operator-workstation host hardening (no-swap RESTRICT-OPS-1, etc.) — parent-suite responsibility; cross-referenced in
_docs/02_document/architecture.md§ Operating envelopes.
Audit Method Footnote
- Phase 1 used
pip-auditagainst a freshly resolvedpip freezeof the SUT venv (the project's editable install was filtered out per the project-package exclusion rule). - Phase 2 used
ripgrepwith the patterns enumerated in.cursor/skills/security/SKILL.md§ Phase 2; every match was cross-validated by reading the call site. - Phase 3 was a desk review against the OWASP 2021 list, with each category mapped to the SUT's actual production surface (see
static_analysis.md"Surface Map") rather than a generic web-app threat model. - Phase 4 was a desk review of all 6 Dockerfiles, 2 docker-compose files, all committed secrets,
.env.example, and.gitignore.
Self-Verification
- Each phase has its own dedicated artefact and is cross-referenced here
- Severity counts in the Executive Summary sum to the per-phase counts in the Outcomes section (22 = 12 + 1 + 0 + 9)
- No finding is silently downgraded — every project-specific severity calibration is justified at the finding site
- D-CROSS-CVE-1 leftover updated and cross-referenced; no stale or missing tracker entry
- Remediation effort estimates use the user's complexity-points rule (2-3 pts default, 5 pts max for big items, no 13-point monolith)