9.4 KiB
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: 1–3 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.