check_run:
  id: SEC-014
  status: partial
  evidence_collected: 4
  evidence_required: 2
  findings:
    - "No tool allow-list exists in any form: no gateway config, no allowed_tools/tool_allowlist/denied_tools key anywhere in deploy/ or the repo, and no per-role or per-group filtering of the tools/list response. src/swisstopo_mcp/server.py registers all 24 tools unconditionally; mcp.list_tools() at runtime returns all 24 to any caller. Server-side defence-in-depth via group/role claims is impossible by construction — the server is unauthenticated (SECURITY.md:45-51), so there are no claims to check."
    - "The deferral is documented and its premise IS enforced in CI, which is more than prose: SECURITY.md:60-69 states the deferral, and tests/test_tool_hygiene.py:60-66 fails the build if any tool lacks readOnlyHint, tests/test_tool_hygiene.py:68-72 fails if any tool sets destructiveHint. .github/workflows/ci.yml:29-31 runs the suite on every push and PR across three Python versions, so the premise cannot quietly become false. Verified: 570 tests pass."
    - "The test enforces a *declaration*, not a *property*. It reads t.annotations.readOnlyHint — a self-asserted hint on the tool registration (e.g. src/swisstopo_mcp/server.py:112-118). A future tool that performs a write while still carrying readOnlyHint=True would pass the gate. The substantive read-only claim happens to hold today — no POST/PUT/DELETE to a mutating endpoint exists in src/, the only non-GET is the Overpass query POST at src/swisstopo_mcp/overpass.py:159-165, which is a read expressed as POST — but that is verified by me, not by the test."
    - "The second CI-enforced premise is real and stronger: egress is a frozenset at src/swisstopo_mcp/api_client.py:55, and tests/test_egress_allowlist.py:113-143 fails in both directions if code and docs/network-egress.md drift apart. That genuinely bounds what a compromised or mis-specified tool can reach."
  gaps:
    - "No default-deny tool allow-list per team/role — the check's primary criterion — and none is possible without an auth layer."
    - "No server-side group/role check for sensitive tools (no auth model)."
    - "No audit logging or alerting for denied tool calls, because nothing is ever denied."
    - "The read-only premise is enforced at the annotation level only; a write-capable tool that lies in its annotations would slip past tests/test_tool_hygiene.py:60-72."
  evaluator_notes: |
    I was asked to judge whether the tests enforce the deferral's premises or
    merely look like they do. Verdict: they enforce a real, load-bearing thing
    (the annotation contract and the egress frozenset/doc sync), and they will
    actually fail — I ran them. But the read-only gate checks what a tool
    *declares*, not what it *does*, so it is one indirection away from the
    premise it is standing in for. Worth stating plainly rather than letting
    'enforced in CI' carry more weight than it earns.
    Partial: the check's actual criteria (default-deny allow-list, role scoping,
    denied-call auditing) are entirely absent. Not fail, because the deferral is
    architecturally correct for a single unauthenticated read-only server and
    the risk-bounding argument is backed by real gates.
