Files
gps-denied-onboard/_docs/02_document/tests/security-tests.md
T

223 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Security Tests
> Black-box security scenarios at the public interfaces. Code-level vulnerability scanning is out of scope here (handled by Phase 4 security audit / `security/SKILL.md`).
---
### NFT-SEC-01: MAVLink2 signing — invalid signature rejected (S-T1)
**Summary**: A GPS_INPUT or other companion-bound MAVLink frame with invalid signing tag is rejected by the FC; SUT and FC both log the rejection.
**Traces to**: M-7, R10, restrictions §Sensors (MAVLink2 signing mandatory), S-T1, F-T9. Tier: T3 (`deferred-sitl`).
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Runner injects a GPS_INPUT with valid schema but signing tag computed against a wrong key | FC discards frame; STATUSTEXT WARN visible at GCS |
| 2 | Inspect FC log | rejection event recorded |
| 3 | Subsequent valid GPS_INPUT | accepted normally |
**Pass criteria**: invalid frame discarded; FC continues on prior valid source; valid frames still accepted.
---
### NFT-SEC-02: HTTPS unauthenticated requests are rejected
**Summary**: All HTTPS API endpoints require valid JWT.
**Traces to**: results_report row 33, restriction "JWT auth on the HTTP boundary". Tier: T1.
**Steps**:
| Step | Endpoint | Auth | Expected Response |
|------|---------|------|-------------------|
| 1 | `POST /sessions` | none | HTTP 401 |
| 2 | `POST /objects/locate` | none | HTTP 401 |
| 3 | `GET /sessions/{id}/stream` | none | HTTP 401 |
| 4 | `GET /health` | none | HTTP 200 (health is intentionally unauthenticated for liveness probes — confirm via S-T2) OR 401 if it requires auth |
**Pass criteria**: 13 return 401; 4's behaviour matches the documented contract (test asserts whichever the contract states). If `/health` is unauthenticated, body still must NOT leak sensitive state (no flight data, no key fingerprints).
---
### NFT-SEC-03: HTTPS — malformed / expired / wrong-issuer JWT
**Summary**: JWTs that fail validation are rejected.
**Traces to**: derived from results_report row 33. Tier: T1.
**Steps**:
| Step | Token | Expected Response |
|------|-------|-------------------|
| 1 | malformed (`.foo.bar`) | HTTP 401 |
| 2 | expired (`exp` in the past) | HTTP 401 |
| 3 | wrong issuer | HTTP 401 |
| 4 | wrong signing algorithm (`none` algorithm) | HTTP 401 |
| 5 | missing required claim (e.g., `sub`) | HTTP 401 |
**Pass criteria**: all return 401 with no leaked state in the body.
---
### NFT-SEC-04: TLS — minimum version + downgrade rejection
**Summary**: TLS ≥1.2; weaker / downgrade attempts rejected.
**Traces to**: S-T2, derived from restriction "telemetry plumbing uses MAVSDK + HTTPS API". Tier: T1.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Connect with TLSv1.0 / TLSv1.1 | refused |
| 2 | Connect with cipher suite from a known weak set (e.g., RC4) | refused |
| 3 | Valid TLSv1.2+ + modern cipher | accepted |
**Pass criteria**: all weak attempts refused; modern accepted.
---
### NFT-SEC-05: Tile-cache write attempt by unauthorized API path
**Summary**: SUT does not expose any HTTP path that allows external clients to write to the tile cache.
**Traces to**: AC-8.5 (storage policy), AC-NEW-7 (cache integrity), restriction §Satellite. Tier: T1.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | `POST /tiles` (or any guess) with valid JWT | 404 or 405 (no such endpoint) |
| 2 | Try `PUT /var/lib/gpsdenied/tiles/...` via any exposed API | 404 / 405 |
| 3 | Inspect the documented OpenAPI contract | no tile-write endpoints |
**Pass criteria**: no successful tile-write paths exist via HTTP; only the post-flight uploader (out-bound to `service-stub`) writes outside the SUT.
---
### NFT-SEC-06: Spoofed sysid / sysid collision (M-31)
**Summary**: A second device claiming sysid 11 (the SUT's sysid) — FC handles per ArduPilot routing rules.
**Traces to**: M-31, F-T9. Tier: T3.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Runner publishes a fake GPS_INPUT from a sysid-collision sender | FC routing handles per documented behaviour (latest-talker wins or rejects) |
| 2 | Confirm FC parameter audit prints the actual sysid configured | matches deployment runbook (M-31 sysid collision-check) |
**Pass criteria**: behaviour matches documented FC routing rule; STATUSTEXT WARN observable; test verifies the deploy runbook's collision-check (M-31) catches this in pre-flight.
---
### NFT-SEC-07: Operator-hint injection — only signed STATUSTEXT consumed
**Summary**: Unsigned operator hints (or hints from a non-allowed sender) are not consumed.
**Traces to**: AC-6.2, M-7. Tier: T3.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Send `RELOC_HINT` STATUSTEXT with invalid MAVLink2 signing | SUT discards; emits WARN |
| 2 | Send from a sysid not on the allowed-list | SUT discards |
| 3 | Send signed by allowed sender | SUT consumes (NFT-RES-05 covers happy path) |
**Pass criteria**: only authenticated, allowed-sender hints are consumed.
---
### NFT-SEC-08: GPS_RAW_INT spoofing chain — SUT promotion is the safety boundary
**Summary**: A spoofed `GPS_RAW_INT` cannot influence SUT's GPS_INPUT directly; SUT only uses GPS_RAW_INT for source-promotion logic, not for fusing.
**Traces to**: AC-NEW-2, restriction §Failsafe. Tier: T3.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Inject GPS_RAW_INT with high-quality false fix | SUT does NOT use it as a position seed; only uses it for the "real-GPS health" rolling average |
| 2 | After scripted spoofing-pattern, SUT promotes its own estimate per AC-NEW-2 | promotion event observable |
**Pass criteria**: SUT GPS_INPUT positions never influenced by spoofed GPS_RAW_INT lat/lon (compare SUT GPS_INPUT vs ground truth from `coordinates.csv` during the spoof window).
---
### NFT-SEC-09: USB bypass surface — bench-only
**Summary**: USB bypasses MAVLink2 signing per restriction; this must be **disabled in production** runtime config.
**Traces to**: M-7, restrictions §Onboard Hardware. Tier: T1 (config audit).
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | At SUT boot, inspect runtime config | USB MAVLink endpoint disabled in production profile (env var `MAVLINK_USB_ALLOWED=false` or absent) |
| 2 | Attempt to connect via USB | refused |
**Pass criteria**: production config refuses USB MAVLink; bench config (env var explicitly enabled) accepts.
---
### NFT-SEC-10: FDR — no sensitive-data leak
**Summary**: FDR contains the documented payload classes only — no private keys, no plaintext JWTs, no MAVLink2 signing keys, no raw frames (AC-8.5).
**Traces to**: AC-8.5, AC-NEW-3, S-T3 (data-at-rest). Tier: T1.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | After a 30 min replay, scan FDR for known-sensitive byte patterns (test-only signing key bytes; test JWT) | none found |
| 2 | Scan for raw JPEG headers in non-thumbnail-log payload classes | none |
| 3 | Verify failure-thumbnail log is ≤ 0.1 Hz and within FDR cap | as spec'd |
**Pass criteria**: no leaks; raw-frame storage policy enforced.
---
### NFT-SEC-11: External-host network policy
**Summary**: SUT does not call external commercial satellite providers at runtime.
**Traces to**: AC-8.1, restrictions §Satellite. Tier: T1.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Run a 5-min replay with `iptables` / Docker network policy capturing all out-bound connections | none of the captured destinations resolves to Maxar / Airbus / Planet / Sentinel-2 / Esri / etc. |
| 2 | The only allowed out-bound is to `service-stub` (the Suite Satellite Service candidate-pool endpoint, post-flight) | matches |
**Pass criteria**: no out-bound to commercial / public ortho providers at runtime.
---
### NFT-SEC-12: HTTPS — payload size + path-traversal hardening
**Summary**: Pathological HTTP requests do not crash the SUT or leak filesystem content.
**Traces to**: AC-3.x (resilience), restrictions (security defaults). Tier: T1.
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | `POST /objects/locate` with a 100 MB body | HTTP 413 (payload too large) |
| 2 | Path-traversal `GET /sessions/../../etc/passwd` | HTTP 404 / 400; no filesystem leak |
| 3 | Header-injection (`X-Forwarded-For: \r\nSet-Cookie: …`) | sanitised; no echo back |
**Pass criteria**: as above; SUT alive; no leak.
---
### NFT-SEC-13: AC-NEW-7 over-confidence injection — gate rejects
**Summary**: Synthetic over-confidence injection (1.5×–3× covariance deflation) does not let bad tiles into the trusted basemap.
**Traces to**: AC-NEW-7. Tier: T2 (`deferred-corpus`).
**Steps**:
| Step | Consumer Action | Expected Response |
|------|----------------|-------------------|
| 1 | Replay AerialVL + Mavic + AerialExtreMatch with synthetic deflation | per-tile geo-misalignment computed |
| 2 | At the σ_xy boundary (3 m, 5 m, 10 m), assert hard-gate behaviour | tiles outside σ_xy ≤ 5 m never written; tiles in (3, 5] m marked `trust_level=soft`; tiles ≤ 3 m `trust_level=candidate` |
**Pass criteria**: P(misalign > 30 m) < 1 %, P(misalign > 100 m) < 0.1 %; voting layer prevents single-flight promotion in non-active sectors.