| 1 | # Hermes — History |
| 2 | |
| 3 | ## Core Context |
| 4 | - Owns security review for application code, dependencies, and CI workflow changes. |
| 5 | - Evaluates risk with the full pipeline in mind, not just single-file diffs. |
| 6 | |
| 7 | ## Learnings |
| 8 | - Branch protection must stay intact; automation should use the shared `branch-protection-pr-workflow` skill instead of bypasses. |
| 9 | - The current pipeline chains GitHub crawl data, press correlation, AI analysis, content generation, and GitHub Pages deployment, so security review must cover every handoff. |
| 10 | - The safest fallback posture is Copilot CLI first, GitHub Models second, and a bounded no-AI summary path after that. |
| 11 | - Retry logic and prompt sanitation both matter for defense-in-depth when external content can influence analysis prompts. |
| 12 | - Prompt-injection hardening should layer untrusted-content boundaries, pre-render sanitization, output evidence guards, and a closing trusted-mission reminder; sanitize repo descriptions by stripping leading whitespace, escaping boundary-close tags, truncating length, and warning on common injection phrases. |
| 13 | - URL validation must use `urllib.parse.urlparse()` instead of substring checks to avoid time-of-check-time-of-use (TOCTOU) vulnerabilities in test contexts and security checks (CodeQL hardening, PR #164). |
| 14 | - Config-driven external HTTP fetchers must enforce the intended allowlist in code (HTTPS + approved hosts), per-request timeouts, bounded retries, and bounded concurrency before running in CI. |
| 15 | |
| 16 | ## Cookie Consent & Privacy Policy (Issue #183) — 2026-05-25 |
| 17 | |
| 18 | **Legal Text Patterns Used:** |
| 19 | - GDPR Article 6(1)(a) explicit consent (opt-in only, no pre-ticked boxes) |
| 20 | - GDPR Article 5(1)(a) transparency — all data processors disclosed |
| 21 | - GDPR Articles 13/14 privacy information requirements fully covered |
| 22 | - GDPR Article 21 right to object — revocation mechanism documented |
| 23 | - Zero dark patterns: "Accept all" and "Reject all" equally prominent |
| 24 | - Plain English tone, no legalese; links to external authorities (ICO, Google, GitHub) |
| 25 | - GA4 processor agreement noted; 14-month retention disclosed as default |
| 26 | - Subject access requests via GitHub Issues + direct profile link (proportionate to non-commercial research context) |
| 27 | |
| 28 | **Files Created:** |
| 29 | - `content/privacy/_index.md` — Hugo privacy policy (~580 words, covers all GDPR minima) |
| 30 | - `data/cookieconsent.json` — Cookie Consent v3 config (plain English copy, no manipulation) |
| 31 | |
| 32 | ## Cookie Banner BaseURL-Aware Links — 2026-05-25 |
| 33 | |
| 34 | - Cookie-banner in-site links must not hardcode root-relative paths like `/privacy/` because GitHub project Pages deploys SquadScope under `/SquadScope/`. |
| 35 | - Data-file legal copy should use placeholders such as `__PRIVACY_URL__`, with Hugo partials substituting them through URL helpers (`relURL` or `absURL`) at render time. |
| 36 | |
| 37 | ## PR #236 Security Review (2026-06-05) |
| 38 | |
| 39 | - Security review of PR #236 external RSS feeds integration complete |
| 40 | - **Verdict: Request changes** (blocking issues found) |
| 41 | - Formal approval blocked by GitHub own-PR rules (self-authored by Hermes as PR author) |
| 42 | - Posted blocking security comment to PR #236 |
| 43 | - **Blocking Requirements:** |
| 44 | 1. URL validation with `urllib.parse.urlparse()`: enforce HTTPS + allowlist hosts only |
| 45 | 2. Reject credentials, local/private/link-local hosts, unexpected ports |
| 46 | 3. Explicit per-request timeout in feed fetch code path |
| 47 | 4. Bounded retry/backoff behavior |
| 48 | 5. Optional `--max-workers` concurrency validation |
| 49 | - **Ownership:** Bender assigned as revision owner to avoid Leela reviewing own implementation changes |
| 50 | - **Impact:** PR #236 cannot merge until security fixes implemented and re-reviewed |
| 51 | |
| 52 | ## PR #236 Security Re-Review (2026-06-05 final) |
| 53 | |
| 54 | - Re-reviewed PR #236 at Bender fix commit `e91e2a5b33b816191148125d40192b3fff8fbc6a` |
| 55 | - **All security blockers resolved:** |
| 56 | - `urllib.parse.urlparse()` validation implemented with HTTPS + allowlist enforcement |
| 57 | - Credentials, localhost/private IPs, invalid ports rejected |
| 58 | - RSS fetches use `urlopen(..., timeout=DEFAULT_FETCH_TIMEOUT_SECONDS)` with bounded retries |
| 59 | - Parallel RSS crawling capped at `DEFAULT_MAX_WORKERS`; `--max-workers < 1` rejected |
| 60 | - Test coverage: unsafe URL rejection, explicit timeout propagation |
| 61 | - **Validation:** 563 tests passed in isolated PR worktree |
| 62 | - **Decision: Security approval/unblock** — CodeQL checks green |
| 63 | - **GitHub limitation:** Formal approval rejected due to own-PR token restrictions; unblock comment posted instead |
| 64 | - **Outcome:** Technical security gate cleared; awaits admin merge due to policy blocker |
| 65 |