Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
6.5 KiB
Security Tests
Status: produced by autodev
/test-specPhase 2 (2026-05-14). Naming: post-rename target. Security tests focus on the JWT bearer + Authz boundary defined in AC-5 and AC-9. Out-of-scope (suite-tracked): theiss/audvalidation gap (AC-5.3, CMMC L2 row 3, AZ-487 / AZ-494) is documented but NOT enforced today. Tests assert today's behaviour (AC-5.3 returns 200) — when the suite-wide remediation lands, update NFT-SEC-04.
NFT-SEC-01: Missing Authorization header → 401
Summary: Verifies AC-5.4 — every protected endpoint rejects requests without an Authorization header.
Traces to: AC-5.4
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | GET /vehicles with no Authorization header |
401 |
| 2 | GET /missions with no Authorization header |
401 |
| 3 | GET /missions/{any}/waypoints with no Authorization header |
401 |
| 4 | POST /vehicles with no Authorization header + valid body |
401 (no row written — verify via side-channel count unchanged) |
Pass criteria: every protected endpoint returns 401; no DB side-effect.
NFT-SEC-02: Invalid signature → 401
Summary: Verifies AC-5.5 — token signed with a different secret is rejected. Traces to: AC-5.5
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Mint token T_bad with WRONG_SECRET=other-secret-32-chars-min!!!!!!, otherwise valid (exp = now + 1h, permissions=FL) |
|
| 2 | GET /vehicles with Authorization: Bearer T_bad |
401 |
Pass criteria: 401.
NFT-SEC-03: Expired token outside skew → 401; inside skew → 200
Summary: Verifies AC-5.6 + AC-5.2 (1-min skew tighter than .NET's 5-min default). Traces to: AC-5.2, AC-5.6
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Mint token T_exp with exp = now - 120s (outside 60s skew); permissions=FL |
|
| 2 | GET /vehicles with Authorization: Bearer T_exp |
401 |
| 3 | Mint token T_skew with exp = now - 30s (inside 60s skew); permissions=FL |
|
| 4 | GET /vehicles with Authorization: Bearer T_skew |
200 |
Pass criteria: T_exp rejected; T_skew accepted.
NFT-SEC-04: Missing iss and aud claims accepted (today's behavior, AC-5.3)
Summary: Verifies the ValidateIssuer = false and ValidateAudience = false configuration. This test will FAIL once the suite-wide remediation (AZ-487 / AZ-494) lands — that's good news; update the test then.
Traces to: AC-5.3
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Mint token with NO iss and NO aud claim, valid signature + lifetime, permissions=FL |
|
| 2 | GET /vehicles with that token |
200 |
Pass criteria: 200 today; will become 401 post-remediation.
NFT-SEC-05: Missing permissions claim → 403
Summary: Verifies AC-5.8 — valid signature + lifetime is not enough; the permissions=FL claim is required.
Traces to: AC-5.8, AC-9.1
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Mint token with no permissions claim, valid otherwise |
|
| 2 | GET /vehicles |
403 |
Pass criteria: 403.
NFT-SEC-06: Wrong permissions claim value → 403
Summary: Verifies AC-9.2 — the policy is exact-string match, hardcoded. Traces to: AC-9.2
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Mint token with permissions="ADMIN", valid otherwise |
|
| 2 | GET /vehicles |
403 |
| 3 | Mint token with permissions="fl" (lowercase), valid otherwise |
|
| 4 | GET /vehicles |
403 |
| 5 | Mint token with permissions="FLight", valid otherwise |
|
| 6 | GET /vehicles |
403 |
Pass criteria: 403 for every wrong-value case.
NFT-SEC-07: Health endpoint exempt from auth
Summary: Verifies AC-7.1, AC-9.4 (contrast) — /health is anonymous.
Traces to: AC-7.1, AC-9.4
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | GET /health with no Authorization |
200 |
| 2 | GET /health with Authorization: Bearer <expired token> |
200 (auth not evaluated) |
Pass criteria: 200 in both cases.
NFT-SEC-08: Stack trace not leaked in 500 body
Summary: Verifies AC-8.6 + AC-10.3 — internal exception details stay in the log, not the HTTP body. Traces to: AC-8.6, AC-10.3
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | Force a 500 (drop vehicles table mid-test, then GET /vehicles/{any}) |
|
| 2 | Inspect response body | body == { "statusCode":500, "message":"Internal server error" } exactly; NO key matching stack, stackTrace, exception, inner, trace; NO file path; NO type name in the body |
| 3 | docker logs missions | grep "Unhandled exception" |
At least one matching line; line contains the file path of the throw site OR the exception type name (the log-side info is private to operators) |
Pass criteria: response body contains only statusCode, message; log contains stack info.
NFT-SEC-09: SQL injection guard via parameterised queries
Summary: Defensive — verifies linq2db's parameterised query path is in effect for filter strings. Traces to: AC-1.6 (filter), AC-2.3 (filter), defensive
Steps:
| Step | Consumer Action | Expected Response |
|---|---|---|
| 1 | GET /vehicles?name='%20OR%20'1'%3D'1 (URL-encoded ' OR '1'='1) |
200; body.length == 0 (no row matches the literal ' OR '1'='1 string against BR-01 etc.) |
| 2 | GET /missions?name=%3B%20DROP%20TABLE%20vehicles%3B%20-- (URL-encoded ; DROP TABLE vehicles; --) |
200; body.TotalCount == 0; side-channel verifies vehicles table still exists |
Pass criteria: filter inputs are treated as literal strings; no SQL execution; no DDL side-effect.
Notes
- Tests that drop tables (NFT-SEC-08) run in a per-class fixture that recreates the schema before subsequent tests.
- The CMMC L2 row 3 (
iss/aud) gap is acknowledged but NOT remediated in this Epic; NFT-SEC-04 documents today's permissive behavior so a future enforcement change is detected. - No fuzz testing today (recommended follow-up under a separate refactor cycle).