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

9.4 KiB
Raw Blame History

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.