check_run:
  id: SEC-015
  status: partial
  evidence_collected: 5
  evidence_required: 2
  findings:
    - "A real self-scan exists and runs in CI: tests/test_tool_hygiene.py:78-80 rejects zero-width/bidi/word-joiner characters (range at tests/test_tool_hygiene.py:32-34), tests/test_tool_hygiene.py:82-85 parametrises six override-phrase patterns in German, French and English (tests/test_tool_hygiene.py:36-49), tests/test_tool_hygiene.py:87-90 rejects non-ASCII tool names (the homoglyph vector), and tests/test_tool_hygiene.py:92-95 rejects descriptions under 40 chars. Executed: all 11 tests in the file pass, and .github/workflows/ci.yml:29-31 runs them on every push/PR."
    - "The pattern list is genuinely better than an off-the-shelf English one for this repo — the descriptions are German (e.g. src/swisstopo_mcp/server.py:121-126) and tests/test_tool_hygiene.py:38-43 covers ignoriere/missachte/vergiss + vorherigen/bisherigen/obigen and 'du bist jetzt'. Writing the invisible-character class as escapes (tests/test_tool_hygiene.py:32-34) rather than literals is a deliberate, correct choice."
    - "It scans the wrong surface only partially. The fixture returns mcp.list_tools() and every assertion reads `t.name` or `t.description`. Nothing scans `t.inputSchema`, whose per-field `description` strings are shipped to the model in exactly the same context window — e.g. the free-text descriptions at src/swisstopo_mcp/coords.py:193-204 and src/swisstopo_mcp/server.py:63-98's `instructions` block (a 36-line prose payload sent to every client). An injection placed in a Field(description=...) or in the server instructions passes every test in this file."
    - "Two of the check's named pattern classes are missing: there is no match for embedded system-prompt markers `<SYSTEM>`, `[INST]` or `### Instructions:` (tests/test_tool_hygiene.py:43 matches the literal phrase 'system prompt' only, not the tag form), and there is no description-length ceiling (the check names ~4000 chars as a smuggling signal; tests/test_tool_hygiene.py:92-95 only enforces a floor of 40). No suspicious-URL-host check either."
    - "The complementary control is real: tool-hashes.json pins name+description+inputSchema per tool (scripts/snapshot_tool_hashes.py:44-58) and .github/workflows/ci.yml:43-44 fails the build on drift — verified by running `--check`, which reports 'up to date (24 tools)'. That does cover the input-schema surface for *change detection*, though not for *content* detection. SECURITY.md:73-84 states plainly that this is a self-scan and cannot see across servers."
  gaps:
    - "Input-schema field descriptions and the server-level `instructions` string (src/swisstopo_mcp/server.py:63-98) are not scanned at all, despite reaching the model's context identically to tool descriptions."
    - "Missing pattern classes: system-prompt tag markers (<SYSTEM>, [INST], ### Instructions:) and an over-length description ceiling."
    - "No gateway-level pre-flight filter, no default-deny on high-risk definitions, no audit events and no SIEM alerting — the check's central mechanism is absent, as the deferral states."
    - "Homoglyph detection is `name.isascii()` (tests/test_tool_hygiene.py:89) rather than an NFKC-normalisation comparison; it catches Cyrillic-in-name but not a non-canonical ASCII-compatible form, and it does not cover descriptions."
  evaluator_notes: |
    Asked to judge whether the tests enforce the deferral's premises or merely
    look like they do: they do enforce something real and they do run — but the
    scan is narrower than SECURITY.md:73-84 implies. The most valuable finding
    here is the surface gap: parameter descriptions and the 36-line server
    `instructions` block are prime injection real estate and are not covered by
    a single assertion, even though the file's own docstring frames itself as
    scanning 'this server's own descriptions'.
    Partial. The gateway-level control the check is actually about does not
    exist (legitimately deferred), the self-scan covers 2.5 of the 4 required
    pattern classes, and it misses a whole category of shipped text.
