Captures the full output of autodev existing-code Phase A through Step 4 (Code Testability Revision) for the Azaion UI workspace: - Step 1 Document: _docs/02_document/ (FINAL_report, architecture, glossary, components/, modules/, diagrams/, system-flows, module-layout) plus _docs/00_problem/ + _docs/01_solution/ + _docs/legacy/ + _docs/how_to_test + README. - Step 2 Architecture Baseline: architecture_compliance_baseline.md. - Step 3 Test Spec: _docs/02_document/tests/ (environment, test-data, blackbox/performance/resilience/security/ resource-limit tests, traceability-matrix), enum_spec_snapshot, expected_results/results_report.md (98 rows), plus the run-tests.sh + run-performance-tests.sh runners. - Step 4 Code Testability Revision: 01-testability-refactoring/ run dir (list-of-changes C01-C07, deferred_to_refactor, analysis/research_findings + refactoring_roadmap) and the 7 child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/ plus _dependencies_table.md. - _docs/_autodev_state.md pins the cursor at Step 4 / refactor Phase 4 entry so /autodev resumes cleanly. Epic AZ-447 (UI testability gates) tracks the 7 child tasks that will land in subsequent commits. Co-authored-by: Cursor <cursoragent@cursor.com>
8.2 KiB
Security Tests
Blackbox security assertions against the SPA's observable surface: token storage discipline, refresh cookie attributes, RBAC route gating, credentials flag, secrets-in-source checks, destructive-action policy, dependency hygiene. These complement the server's RBAC and the suite's security_approach (_docs/00_problem/security_approach.md); they do NOT replace server-side enforcement (O4).
NFT-SEC-01: Bearer is never written to localStorage or sessionStorage
Traces to: AC-02, O2 Profile: static + e2e
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Static code-search on src/ and mission-planner/src/ for `localStorage. |
sessionStorage.nearbearer |
| 2 | E2E: complete a login; inspect localStorage and sessionStorage keys |
neither contains the bearer value |
Pass criteria: row 04 — match_count == 0; runtime storage does not contain the bearer.
Expected result source: results_report.md row 04.
NFT-SEC-02: document.cookie does not expose the refresh token
Traces to: AC-03 Profile: e2e + static
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Static code-search for document.cookie reads against `refreshToken |
refresh-cookie` |
| 2 | E2E: complete login; read document.cookie from page context |
returned string does NOT contain the refresh-token value (row 06) |
Pass criteria: rows 05 + 06.
Expected result source: results_report.md rows 05, 06.
NFT-SEC-03: Refresh cookie attributes — Secure, HttpOnly, SameSite=Strict
Traces to: AC-03, E3, O5 Profile: e2e
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Login via POST /api/admin/auth/login against the suite stack |
Set-Cookie header returned |
| 2 | Inspect header value | matches regex Secure;.*HttpOnly;.*SameSite=Strict (case-insensitive, attribute-order-tolerant) |
Pass criteria: row 07 — regex match.
Notes: this is a server-contract assertion; the UI test exists as defence-in-depth so a suite regression is caught before it lands in production.
Expected result source: results_report.md row 07.
NFT-SEC-04: credentials: 'include' is set on every authenticated fetch
Traces to: AC-01, O3 Profile: fast (apiClient wrapper) + e2e (live capture)
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Issue an authenticated request via apiClient |
RequestInit captured |
| 2 | Inspect | credentials === 'include' (row 01) |
| 3 | Repeat for the bootstrap refresh | same (row 02 — quarantined until Step 4 bootstrap fix) |
Pass criteria: rows 01 + 02.
Expected result source: results_report.md rows 01, 02.
NFT-SEC-05: /admin route blocks non-admins client-side (defence in depth)
Traces to: AC-22
Profile: e2e — quarantined until role-gate is added (Step 4 / Step 8)
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Log in as op_alice (Operator, no admin role) |
session active |
| 2 | Navigate to /admin |
URL changes |
| 3 | Inspect final URL + DOM | URL is /flights; <AdminPage> NOT mounted |
Pass criteria: row 08.
Notes: server-side RBAC is authoritative; the UI gate is a usability + leakage layer.
Expected result source: results_report.md row 08.
NFT-SEC-06: /settings route gate is applied per RBAC
Traces to: AC-22
Profile: e2e — quarantined
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Log in as user without SETTINGS permission | session active |
| 2 | Navigate to /settings |
URL changes |
| 3 | Inspect | URL is /flights; <SettingsPage> NOT mounted |
Pass criteria: row 10.
Expected result source: results_report.md row 10.
NFT-SEC-07: alert() is forbidden anywhere in the SPA
Traces to: AC-14, O10 Profile: static
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Regex sweep src/ and mission-planner/src/ for \balert\( |
match_count == 0 |
Pass criteria: row 50.
Expected result source: results_report.md row 50.
NFT-SEC-08: ConfirmDialog gates every destructive action
Traces to: AC-14, AC-30, O10 Profile: fast
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | For each destructive surface in _docs/ui_design/ (class delete, user deactivate, dataset bulk-overwrite, etc.) |
sequence checked |
| 2 | Confirm sequence on click → before any HTTP fires | dialog present (row 51) |
| 3 | On Confirm in class-delete flow → exactly one DELETE to ^/api/admin/classes/[0-9]+$ |
(row 49) |
Pass criteria: rows 49 + 51.
Expected result source: results_report.md rows 49, 51.
NFT-SEC-09: OpenWeatherMap API key is not shipped in source or bundle
Traces to: AC-20, P10 Profile: static (source) + static (bundle)
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Regex sweep src/ and mission-planner/src/ for the literal current OWM key value |
match_count == 0 (row 63) |
| 2 | Regex sweep for appid= and api_key= literal occurrences in source URLs |
match_count == 0 (row 63) |
| 3 | Scan dist/**/*.js post-build for the literal key |
match_count == 0 (Phase 3 may downgrade to "until Step 4 fix") |
Pass criteria: row 63.
Status: quarantined for source check until Step 4 fix; the bundle-scan check passes immediately for src/ (mission-planner not bundled, AC-31).
Expected result source: results_report.md row 63.
NFT-SEC-10: No in-browser ML libs
Traces to: AC-N2 Profile: static
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Parse package.json and mission-planner/package.json dependencies |
dependency lists |
| 2 | Match against `^(onnxruntime | @?tensorflow(?:js)?(?:/.*)? |
Pass criteria: row 92.
Expected result source: results_report.md row 92.
NFT-SEC-11: No response-signature / JOSE libs on the request path
Traces to: AC-N4 Profile: static
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Parse package.json dependencies |
list |
| 2 | Match against `^(jsrsasign | tweetnacl |
Pass criteria: row 94.
Expected result source: results_report.md row 94.
NFT-SEC-12: No service worker — offline mode is explicitly absent
Traces to: AC-N3 Profile: e2e
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Load the SPA in a fresh browser context | app boots |
| 2 | Read navigator.serviceWorker.getRegistrations() |
empty array |
Pass criteria: row 93 — no service worker registered.
Expected result source: results_report.md row 93.
NFT-SEC-13: Dropped legacy features are not present in source
Traces to: AC-N5 Profile: static
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Regex sweep src/ and mission-planner/src/ for `SoundDetections |
DroneMaintenance` |
Pass criteria: row 95.
Expected result source: results_report.md row 95.
NFT-SEC-14: Anti-criterion AC-N1 — no concurrent-edit reconciliation surfaces
Traces to: AC-N1 Profile: e2e + static
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Open the same annotation in two browser sessions; edit both | both save individually |
| 2 | Inspect each session's DOM | no merge UI; no presence indicator |
Pass criteria: row 91.
Notes: this is an anti-criterion — the test enforces that the feature is NOT silently added.
Expected result source: results_report.md row 91.