Pārlūkot izejas kodu

Docs: mark R01-N11 fixed, refresh SPEC §9 / §13

REVIEW_01.md flips the R01-N11 status from `open` to
`fixed-in-4ae1817`, captures the test count delta (163 / 417, was
159 / 413), and strikes through the matching entry in the suggested
ordering list. SPEC.md §9 gains a new "Shipped" entry under R01-N11
explaining what the foot-gun was and how the runtime whitelist closes
it; §13's git history grows the new commit at the top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 2 dienas atpakaļ
vecāks
revīzija
851f8cf15a
2 mainītis faili ar 30 papildinājumiem un 2 dzēšanām
  1. 20 0
      SPEC.md
  2. 10 2
      doc/REVIEW_01.md

+ 20 - 0
SPEC.md

@@ -1117,6 +1117,25 @@ with a `BOOTSTRAP_ADMIN` audit row.
       the behaviour in. Tests: 150 / 430 (was 149 / 424). First fix
       from `doc/REVIEW_01.md`.
 
+- [x] **R01-N11 — Whitelist column in `AuditRepository::distinctColumn`**
+      (`4ae1817`). The private helper interpolated its `$col` argument
+      directly into SQL (`"SELECT DISTINCT {$col} FROM audit_log ORDER BY
+      {$col} ASC"`). Both internal callers (`distinctActions`,
+      `distinctEntityTypes`) pass literal strings, so it was
+      non-exploitable today, but the contract was implicit — any future
+      caller wiring user input through the helper would hand over a SQL
+      injection vector. Added an explicit
+      `in_array($col, ['action', 'entity_type'], true)` whitelist at the
+      top of `distinctColumn()`; anything else throws
+      `\InvalidArgumentException` before the SQL is prepared. The phpdoc
+      was tightened to the literal-string union (`'action'|'entity_type'`)
+      so static analysers carry the contract too. New
+      `tests/Repositories/AuditRepositoryTest.php` (4 cases) covers the
+      happy paths for both supported columns and two reflection-based
+      regression guards (rejects `user_email`, rejects an injection
+      attempt). Tests: 163 / 417 (was 159 / 413). Fifth fix from
+      `doc/REVIEW_01.md`.
+
 - [x] **New sprint form: drop weeks input + task list row hover**
       (`3728106`). The `/sprints/new` form no longer collects an
       `n_weeks` value — the week count is derived from `start_date` /
@@ -1233,6 +1252,7 @@ before acting — nothing here is load-bearing once it grows stale.
 ## 13. Git history (as of this writing)
 
 ```
+4ae1817 Fix R01-N11: whitelist column in AuditRepository::distinctColumn
 d16bff4 Fix R01-N15: add noreferrer to external task URL link
 48a351c Docs: mark R01-N01 fixed, refresh SPEC §9 / §11 / §13
 857df15 Fix R01-N01: hash-only LOCAL_ADMIN_PASSWORD_HASH (no plaintext fallback)

+ 10 - 2
doc/REVIEW_01.md

@@ -258,7 +258,15 @@ note. Do not delete entries — they're history.
 ### R01-N11 — `AuditRepository::distinctColumn` interpolates a column name
 - **Severity**: MEDIUM (non-exploitable today; same brittleness class as
   N10).
-- **Status**: open.
+- **Status**: fixed-in-`4ae1817` — `distinctColumn()` now opens with an
+  `in_array($col, ['action', 'entity_type'], true)` whitelist; anything
+  else throws `\InvalidArgumentException` before the interpolated SQL is
+  prepared. The phpdoc was also tightened to the literal-string union so
+  static analysers carry the contract. New
+  `tests/Repositories/AuditRepositoryTest.php` (4 cases) covers the happy
+  paths for both supported columns and two reflection-based regression
+  guards (rejects an unsupported known column like `user_email`, rejects
+  a SQL-injection attempt). Tests: 163 / 417 (was 159 / 413).
 - **Where**: `src/Repositories/AuditRepository.php` lines 109-122.
 - **What**: `"SELECT DISTINCT {$col} FROM audit_log ORDER BY {$col} ASC"`
   interpolates `$col`. Today the only callers are `distinctActions()` and
@@ -655,7 +663,7 @@ A reasonable cadence (do not treat as binding):
    closes R01-N31.
 2. ~~**R01-N04** (`SESSION_SECRET` doc cleanup)~~ — fixed in `296883c`.
 3. ~~**R01-N15** (`rel="noopener noreferrer"`)~~ — fixed in `d16bff4`.
-4. **R01-N11** (audit column whitelist) — defensive, trivial.
+4. ~~**R01-N11** (audit column whitelist)~~ — fixed in `4ae1817`.
 5. ~~**R01-N01** (local-admin password hashing)~~ — fixed in `857df15`
    (hash-only, no plaintext fallback per operator decision).
 6. **R01-N06** (login throttling) — tiny new repo + integration.