feat(defenseurs): add GET /defenseurs/findings?project=X route #9
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/vps-health-api#9
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-3-defenseurs-findings"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #3
Summary
Adds
GET /defenseurs/findings?project=Xto drill into a project's Defenseur findings via HTTP+Bearer. Designed for the Vercel admin dashboard (no SSH/Tailscale path) and a future portable/analyse-vulnerabiliteskill.Behavior
projectparamcategory/severityagents-map.json){findings: [], status: "no_data"}{agent, project, timestamp, findings: []}(nostatusfield){agent, project, timestamp, findings: [...]}agents-map.jsonunreadable / corruptedSeverity rule (asymmetric — see issue #3 clarif 2026-05-12)
severity->MEDIUM+HIGH+CRITICAL(default hides noise)severity=LOW->LOW+MEDIUM+HIGH+CRITICALbut always hidesINFOseverity=INFO->INFOonly (explicit opt-in)Implementation
allowedSeverities(threshold),findLatestReportForAgent(agent)(scansREPORTS_DIRandREPORTS_DIR/archive, picks the freshest bytimestamp)DEFENSEURS_AGENTS_MAP_PATH(default/data/defenseurs/agents-map.json)validRoutesserver.listenguarded byrequire.main === moduleso tests can spin up ephemeral serversTests
Bootstraps vitest (devDep; runtime stays 0-dep). 14 tests, all green:
?category=depsexact match?severity=HIGH→ HIGH+CRITICAL?severity=LOW→ LOW+MEDIUM+HIGH+CRITICAL, hides INFO?severity=INFO→ INFO onlyarchive+ top-levelstatusfield{findings:[], status:"no_data"}agents-map.jsonPre-merge checklist (operational, out of repo)
ls /home/defenseur/defenseurs/agents-map.json(j'ai pas pu valider — Tailscale SSH demande auth navigateur)/home/defenseur/defenseurs/agents-map.json->/data/defenseurs/agents-map.jsonavant le deploy (sinon la route renvoie 500)Test plan
npm test-> 14/14 vertagents-map.jsonpresent sur le VPScurl -H "Authorization: Bearer $TOKEN" "https://health.lacompagniemaximus.com/defenseurs/findings?project=la-suite-booking&severity=HIGH"🤖 Generated with Claude Code
Drill-down endpoint exposing detailed findings per project. Resolves the HTTP gap for the Vercel admin dashboard, which cannot SSH/Tailscale to the VPS, plus a future portable /analyse-vulnerabilite skill. - Project -> agent lookup via /data/defenseurs/agents-map.json (Sergent snapshot) - findLatestReportForAgent scans REPORTS_DIR + REPORTS_DIR/archive (post-07:30 UTC rotation) - Filters: category exact match, severity threshold inclusive upward - Asymmetric severity rule: default hides LOW+INFO; ?severity=LOW returns LOW+MEDIUM+HIGH+CRITICAL but still hides INFO; INFO opt-in via explicit param - Distinguishes "report present + scan clean" (no status field) from "no report at all" ({findings:[], status:"no_data"}) - Bootstraps vitest (devDep; runtime stays 0-dep), 14 tests covering auth, validation, filters, asymmetry, mtime selection, error paths - Refactor: export handler so tests can spin up ephemeral servers; server.listen guarded by require.main === module Closes #3 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Verdict: APPROVE
Clean implementation of
/defenseurs/findingsmatching the issue #3 spec (asymmetric INFO severity rule included). 14 vitest tests cover auth, validation, filter combinations, latest-report-across-archive, scan-clean vs no_data, and corruptagents-map.json— all green locally. No security regressions: path traversal is structurally prevented (projectis a map key, never a path component); theagentvalue used as filename prefix comes from the server-controlledagents-map.jsonwritten by the Sergent.Suggestions (non-blocking)
.env.examplemissingDEFENSEURS_AGENTS_MAP_PATH— the new env var is documented inCLAUDE.mdandREADME.mdbut not in.env.example. Worth adding for parity._tsmutation infindLatestReportForAgent(index.js:195-205) — assigning thendelete-ing_tson the parsed report works but mutates the user-facing payload. A separatelatestTsvariable would keep the parsed object pristine. Cosmetic.err.messagefor corruptagents-map.json(index.js:307-309). Consistent with the existing/reports/scans500 branch — not a new regression, but worth either suppressing in both places or explicitly accepting as intentional for a Bearer-gated internal API.{findings:[], status:"no_data"}omitsagent/project/timestampwhile the present-report case includes them. Spec-aligned and tested, but consumers (Vercel dashboard,/analyse-vulnerabilite) will need to handle the union. Worth flagging in the README field table.agents-map.jsonpresence on VPS) is the real risk — code is correct but deploy without those surfaces as 500s. Verify both before merging.Checks
npm test-> 14/14)feat(defenseurs): ...)require.main === moduleguard) is correctly scoped — only theserver.listenis gated, the handler/exports are always available/defenseurs/findingschecked before the fallthrough to/healthand not shadowed by/defenseurs(exact-equality routing)Adversarial review by Claude Code.
Review — APPROVE
Verdict : APPROVE
Résumé : Implémentation propre et conforme à l'issue #3 — l'asymétrie INFO est correctement codée et testée, l'auth fail-closed couvre la nouvelle route, et le refactor module (
handlerexporté,require.main === module) est fait sans régression. Suite relancée localement par le reviewer : 14/14 verts.Vérifications clés
projectne touche jamais le filesystem, seulagent(issu du fichier trustedagents-map.json) construit le préfixe de fichier ; auth Bearer placée avant toutes les routes.allowedSeveritiesjuste pour les 6 cas ;isScanReportgarantitArray.isArray(findings)donc pas de TypeError sur le.filter; timestamps NaN skippés ; sélection du plus récent across top-level + archive validée par test.COPY package.json index.js ./, pas denpm install) ignore le nouveaupackage-lock.json— le runtime reste 0-dep, aucun impact image..skip/.only, serveurs éphémères sur port 0, cleanup tmpdir correct. Commit en format conventionnel, PR liée à #3.Suggestions (non bloquantes)
const agent = agentsMap[project]est hors try. Deux edge cases : (a) unagents-map.jsoncontenant littéralementnull(JSON valide) passe le parse puisnull[project]lance un TypeError dans le handler async → unhandled rejection → crash du process sous Node 22 ; (b)?project=__proto__ou?project=constructorremonte un objet hérité du prototype (truthy) → 200no_dataau lieu de 404. Fix 2 lignes :const agent = agentsMap?.[project]; if (typeof agent !== "string") → 404. Fichier trusted + appelant authentifié = probabilité faible, donc non bloquant.category/severity→ 400 » mais aucun test ne couvre?category=bogusni?severity=bogus. Deux tests d'une ligne fermeraient l'écart contrat/couverture.err.messagedans les 500 : expose le chemin du fichier. Pattern préexistant du repo, derrière Bearer — à durcir seulement si l'API devient publique.Rappel opérationnel (déjà dans le PR body) : bind-mount
agents-map.jsonsur Coolify avant le deploy, sinon 500 en prod.