Files
gps-denied-onboard/_docs/05_security/owasp_top_10_review.md
T
Oleksandr Bezdieniezhnykh bf13549b32
ci/woodpecker/push/02-build-push Pipeline failed
[autodev] Update configuration and documentation for cycle-1
- 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.
2026-05-20 08:05:35 +03:00

16 KiB

Phase 3 — OWASP Top 10 (2021) Review

Review date: 2026-05-19 Scope: SUT production code path (src/gps_denied_onboard/**) and the surfaces it exposes / consumes in production. Method: Walk each OWASP Top 10 (2021) category against the SUT's actual surface; cite the project's threat-model constraints (RESTRICT-OPS-*, NFT-SEC-*) and the Phase 1 + Phase 2 findings.

Surface Map (reference)

Surface Direction Production exposure Authentication Notes
MAVLink to ArduPilot FC bidirectional (C8) UART/serial — physical wiring on UAV MAVLink 2 message signing (Ed25519 derived passkey, AC-4.3 + D-C8-9) Per-flight key rotation logged to FDR (NFT-SEC-03). iNav variant ships with documented residual risk.
Signed STATUSTEXT → GCS outbound (C8) UART → telemetry radio Inherits MAVLink signing Same channel as C8.
Tile download HTTPS → satellite-provider outbound (C11 TileDownloader) Operator-workstation network only (RESTRICT-OPS-1, no airborne path) TLS server-cert validation (httpx defaults, NEVER verify=False) + sidecar SHA-256 check on each tile Pinned base URL from C11Config.
Tile upload HTTPS → satellite-provider ingest outbound (C11 TileUploader) Operator-workstation only (post-landing) TLS + Ed25519 per-flight signing key (C11 PerFlightKeyManager) Public key envelope written to FDR for audit correlation.
Flights-service HTTPS → operator API outbound (C12 FlightsApiClient) Operator-workstation only TLS + bearer auth token (config-injected, _REDACTED in logs) Pinned base URL from C12.flights_api_base_url.
SSH from operator workstation → UAV companion outbound (C12 ParamikoSshSessionFactory) Operator-workstation only SSH key auth + RejectPolicy host-key validation (pinned known_hosts) Default mode is strict; AutoAddPolicy is forbidden by AZ-327.
FDR records → local FDR sink local file write (all components) Onboard tmpfs / local disk N/A (local file) Records are HMAC-chained per fdr_record_schema.md.
Healthcheck python -m gps_denied_onboard.healthcheck inbound Docker HEALTHCHECK probe, localhost only N/A (process exit code) No network surface.
No inbound network listeners in production confirmed by NFT-SEC-05 — DNS blackhole + iptables OUTPUT REJECT in flight This is the most important property of the threat model.

The test-mode mock-suite-sat FastAPI fixture is the ONLY HTTP listener in any e2e topology; it lives in e2e/fixtures/, is excluded from the production Dockerfile, and runs only on the e2e-net.internal: true Docker network during tests.


A01:2021 — Broken Access Control

Status: N/A — no per-user authorization model exists.

  • The SUT is a single-tenant onboard binary that runs as one OS user inside its container. There is no notion of multiple users, roles, or per-resource access policy in production.
  • The two privileged operations that DO exist (start-session, end-session for the C11 upload key; arming the takeoff state in C5) are gated by the C8 FC's own armed/disarmed state — i.e., the safety gate is the physical aircraft state machine, not an application-level RBAC check.
  • The operator-side C12 service runs ONLY on the operator workstation (RESTRICT-OPS-1); it has no airborne path and no role-based authorization beyond the SSH key + flights-API bearer token tied to the workstation identity.

Findings: 0.


A02:2021 — Cryptographic Failures

Status: Pass. All crypto choices are modern and well-implemented.

Concern Status Evidence
Weak hashes for signing / integrity None All integrity = SHA-256 (sidecar, tile content_sha256 column, _canonical_hash.aggregate_tile_hash). All signatures = Ed25519.
Plaintext secrets at rest None C8 MAVLink signing passkey loaded from tmpfs-mounted runtime secret (production) or MAVLINK_SIGNING_PASSKEY_FILE Docker secret (test). C11 signing key is ephemeral per flight. C12 auth token is config-injected (no on-disk plaintext in production beyond the operator-side secret store).
Hardcoded keys / salts None Phase 2 SAST: 0 hardcoded credentials. All key material is generated per-flight via Ed25519PrivateKey.generate() or secrets.token_bytes(...).
TLS verification disabled None Phase 2 SAST: 0 verify=False matches; all httpx.Client(...) constructions use defaults (verify ON).
Secret zeroisation on session end Best-effort with documented residual c11_tile_manager/signing_key.py:340-365 zeros the project-controlled bytearray mirror of the secret. OpenSSL-side buffer freed via Ed25519PrivateKey refcount drop. Documented residual: bounded by upload-session lifetime + RESTRICT-OPS-1 (operator workstation no-swap). AZ-318 Risk-1.
Random source quality Cryptographically secure All key generation uses secrets.token_bytes or cryptography.hazmat's built-in CSPRNG. No random.random() on security paths.
CVE exposure in crypto libs 1 informational (Phase 1 F4) cryptography==42.0.8 has 4 GHSA advisories that touch pkcs12, OpenSSL FIPS provider config, and a CMS recipient bug — NONE of which are reachable from the SUT's actual usage (X.509 cert validation via httpx; Ed25519 sign/verify). Bump-when-convenient.

Findings: 0 (the Phase 1 F4 informational item is recorded there; not duplicated here).


A03:2021 — Injection

Status: Pass. Cleared in Phase 2.

  • SQL injection: 0 findings. All 19 cursor.execute(...) sites in C6 use psycopg %s parameterized placeholders.
  • Command injection: 0 findings. 1 subprocess.run (TensorRT trtexec) — list args, shell=False, no untrusted input.
  • OS-path / file injection: 0 findings. All Path(...) / open(...) calls take SUT-managed paths; no .. traversal patterns matched.
  • No template / XSS surface (no HTML output).

Findings: 0.


A04:2021 — Insecure Design

Status: Pass — the design IS the security boundary.

The SUT's defining design property is "the airborne process has no network egress". That property is enforced at THREE layers:

  1. Architectural — no component constructs an httpx.Client or opens a socket on the airborne path. The C11 downloader / uploader and C12 flights-client are wired ONLY by the operator-workstation composition root (compose_root_operator), not the airborne compose_root_airborne.
  2. Operational (RESTRICT-OPS-1) — airborne container ships with iptables OUTPUT REJECT + DNS blackhole.
  3. Test (NFT-SEC-02 / NFT-SEC-05) — the airborne process is run inside a network namespace with no route to satellite-provider's host; any attempted outbound TCP connection is a release-blocking test failure. The harness runs this test as part of every CI build.
Design constraint Where enforced Verified by
No inbound network listeners on airborne path compose_root_airborne does not wire any FastAPI / uvicorn / grpc server NFT-SEC-05
No outbound network egress on airborne path iptables + DNS blackhole + arch-level component placement NFT-SEC-02 + NFT-SEC-05
Tile downloads happen ONLY at operator workstation (pre-flight) compose_root_operator is the only wiring that includes build_tile_downloader Architecture review + integration test FT-N-01 (operator) vs FT-P-17 (airborne)
Tile uploads happen ONLY post-landing on operator workstation Same — build_tile_uploader is compose_root_operator only NFT-SEC-01
MAVLink signing key generation happens on-bench, not at runtime C8 documentation + boot sequence diagram NFT-SEC-03
Strategy implementations are EXCLUDED from the production binary at compile time BUILD_* flags gate the import in runtime_root/*_factory.py CI pipeline gate (pyproject.toml [tool.gpsdo.build-flags])

Findings: 0.

Defensive observations:

  • The "test substitute" mock-suite-sat is documented as non-architectural (_docs/02_document/architecture.md:277) precisely so it cannot accidentally become a production dependency. The fixture is retired the moment D-PROJ-2 lands the real ingest endpoint.
  • The strategy registry is keyed by string → module path through a CLOSED whitelist (KNOWN_*_STRATEGIES), preventing config-controlled dynamic import vectors (Phase 2 F13).

A05:2021 — Security Misconfiguration

Status: Mostly pass; the configuration & infrastructure review (Phase 4, next) is the deeper check.

  • No debug surface in production: SUT has no FastAPI / Flask / Django app; healthcheck binary uses structured exit codes, never a stack trace. DEBUG=1 is a developer-mode env var that only widens stderr verbosity for the CLI.
  • Pinned dependencies: pyproject.toml pins every direct dep; requirements*.lock (Phase 4 to verify) enforces transitive pins. opencv-python>=4.11.0.86,<4.12 band is documented in _docs/_process_leftovers/2026-05-11_d_cross_cve_1_opencv_pin_deferred.md.
  • Build-flag gating: strategies excluded at compile time via BUILD_PYTORCH_FP16_RUNTIME, BUILD_FAISS_INDEX, BUILD_VPR_*, BUILD_C7_TRT_FP16, etc. — non-built strategies cannot be selected even if config is wrong.
  • Test-only secrets clearly marked: e2e/fixtures/secrets/mavlink-test-passkey.txt carries a "TEST ONLY" annotation; production reads from a separate tmpfs-mounted secret.
  • TODO for Phase 4: container user (non-root?), file ownership in image layers, env var defaults, Docker secret mount permissions, network policy on e2e-net.internal: true.

Findings (Phase 3 surface): 0. Configuration-layer concerns are deferred to Phase 4.


A06:2021 — Vulnerable and Outdated Components

Status: Covered in Phase 1. Summary:

Severity (project context) Count
Critical 0
High 0
Medium 1 (onnx==1.18.0 GHSA-rcgw-4cgp-9q7q — reachable only via developer-mode model loading, not the production airborne path)
Low 11

Findings (Phase 3-specific): 0 new — refer to _docs/05_security/dependency_scan.md.


A07:2021 — Identification and Authentication Failures

Status: Pass. All authenticated surfaces have a strong scheme; no obvious bypass.

Surface Scheme Defense observed
MAVLink ↔ FC MAVLink 2 message signing, per-flight key Per-flight key rotation logged to FDR (NFT-SEC-03). iNav variant cannot enable signing — documented residual risk, surfaced in C8 docs (_docs/02_document/components/10_c8_fc_adapter/description.md).
C11 upload session → satellite-provider Ed25519 per-flight signing key Public-key envelope to FDR for audit correlation. Session lifetime ≤ upload window (minutes).
C12 → flights-service Bearer token Token loaded from config (operator-side secret store), _REDACTED in 100% of error messages and retry logs.
C12 → companion SSH SSH key + RejectPolicy host-key validation paramiko.AutoAddPolicy is FORBIDDEN by AZ-327. Default mode strict rejects any unknown host. CVE-2026-44405 (Phase 1 F6, SHA-1 RSA) is materially mitigated.
C10 manifest verification Ed25519 detached signature + SHA-256 of canonicalized JSON + trust-anchor public-key pinning Fail-closed (verify_manifest returns outcome=FAIL on any deviation; never raises in the success path).

Findings: 0.


A08:2021 — Software and Data Integrity Failures

Status: Pass. Integrity-checking is the spine of the manifest / tile / FDR contracts.

Artifact Integrity check Where
Manifest.json Ed25519 signature (Manifest.json.sig) + SHA-256 sidecar (Manifest.json.sha256) + canonical-JSON hashing of the embedded tile aggregate c10_provisioning/manifest_verifier.py
Each tile JPEG SHA-256 sidecar (<tile>.sha256), enforced both at download (tile_downloader.py) and on read (postgres_filesystem_store.py SELECT + sidecar verify) C6 + C11
FDR records HMAC-chained schema per _docs/02_document/contracts/shared_fdr_client/fdr_record_schema.md fdr_client/records.py
TensorRT engine builds Engine path includes content-hash of source ONNX + build-config; rebuild on mismatch c7_inference/tensorrt_runtime.py
Replay fixtures e2e/fixtures/sitl-replay-fixture-* ship with a manifest builder that derives SHA-256 over every artefact e2e/fixtures/builders/...

Software-update integrity: out-of-scope for the onboard binary; operator-workstation provisioning is handled by the parent suite (D-PROJ-1) which Phase 4 will review at the Dockerfile / image layer.

Findings: 0.


A09:2021 — Security Logging and Monitoring Failures

Status: Pass — disciplined structured logging with security-event coverage.

Concern Status Evidence
Security-relevant events emit a structured FDR record Yes c11.upload.session.key.public, c11.upload.session.key.zeroised, c11.upload.signature_rejected, c12.flights.fetch.failed, c10.manifest.verify.*, c8.mavlink.signing.*
Failures (auth, integrity, transport) emit ERROR-level logs with a stable kind field Yes kind="c12.flights.fetch.failed" + reason∈{auth,not_found,connect:*}, kind="c10.manifest.verify.failed", kind="c2.vpr.dim_mismatch", kind="c11.upload.signature_rejected"
Secrets are not logged Yes 13 files use the _REDACTED / redact pattern. Phase 2 confirmed: auth token, MAVLink passkey, and SSH private-key material are never logged in plaintext.
Logs are tamper-resistant Best-effort FDR records are HMAC-chained; structured logs to stderr/journal use a defined schema (logging/structured.py). Operator-side log retention is a parent-suite concern.
Monitoring / alerting plumbed Out of scope Parent-suite responsibility — _docs/02_document/deployment/observability.md documents what the SUT emits, not where it is shipped.

Findings: 0.


A10:2021 — Server-Side Request Forgery (SSRF)

Status: Pass. No SUT code accepts a URL/host parameter from external input.

  • c11_tile_manager/{tile_downloader,tile_uploader}.py build URLs by concatenating a config-pinned base_url with internally-derived path segments (zoom/x/y.jpg, upload/<flight_id>/<tile_uuid>). No external ?url=... parameter or redirect-following from untrusted source.
  • c12_operator_orchestrator/flights_api/httpx_client.py uses client.get(url, ...) where url is built from self._config.flights_api_base_url (config-pinned) + a derived path segment containing the validated flight_id UUID. No external host control.
  • httpx.Client defaults — follow_redirects=False. SSRF via 30x redirect to internal addresses is blocked unless the caller explicitly opts in (no SUT caller does).
  • DNS resolution happens against the operator's resolver only; in production (airborne) DNS is blackholed.

Findings: 0.


Cross-Reference Index

Source Phase 3 § Note
_docs/05_security/dependency_scan.md A06, A02 Phase 1 — 12 vulnerabilities triaged
_docs/05_security/static_analysis.md A03, A02 Phase 2 — 1 informational hardening recommendation
_docs/02_document/architecture.md § "Operating envelopes" A04 RESTRICT-OPS-1 / NFT-SEC-05 enforcement
_docs/02_document/tests/security-tests.md A04, A07, A08 NFT-SEC-01..05 test contract
_docs/_process_leftovers/2026-05-11_d_cross_cve_1_opencv_pin_deferred.md A06 D-CROSS-CVE-1 deferred pin

Self-Verification

  • All 10 OWASP 2021 categories addressed with status + finding count + evidence
  • N/A categories (A01) explicitly justified against the threat model, not silently skipped
  • Findings from Phase 1 / Phase 2 cross-referenced where they map to OWASP categories rather than re-listed
  • Surface map at top is consistent with _docs/02_document/architecture.md
  • No new HTTP listeners / dynamic-input surfaces missed (confirmed by Grep for FastAPI/uvicorn/server.serve patterns)