[AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs

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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 00:38:49 +03:00
parent da0a5aa187
commit 510df68bcf
84 changed files with 13065 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
# 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.` near `bearer|token|accessToken` | `match_count == 0` |
| 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` | `match_count == 0` (row 05) |
| 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)?(?:/.*)?|tflite|coreml|tfjs|@huggingface/.*|transformers\.js)$` | zero matches |
**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|@noble/.*|jose)$` | zero matches |
**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` | `match_count == 0` |
**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.