AgentStack
SKILL unreviewed MIT Self-run

Mcp Audit

skill-malkreide-mcp-audit-skill-mcp-audit-skill · by malkreide

Reproduzierbares Audit von MCP-Servern gegen einen versionierten Best-Practice-Katalog. Verwende diesen Skill wenn der User (1) einen MCP-Server gegen Best Practices prüfen will, (2) Sicherheitsfindings für einen Server dokumentieren möchte, (3) den MCP Audit Tracker (Notion) abarbeitet, (4) fragt «ist mein Server sicher / production-ready / standard-konform», (5) den Begriff «Audit», «Findings»,…

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-malkreide-mcp-audit-skill-mcp-audit-skill

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Dangerous shell/eval execution.

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution Used

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Mcp Audit? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

MCP Audit — Standardisiertes Audit-Vorgehen

Dieser Skill kodiert ein reproduzierbares Audit-Verfahren für MCP-Server gegen den im Anhang dokumentierten Best-Practice-Katalog (PDF-Quelle, ~50 Checks in sieben Kategorien). Ziel: bei 30+ Servern im Portfolio dieselbe Methodik anwenden, ohne dass der menschliche Auditor (oder Claude) bei jedem Server das PDF neu interpretiert.

Das Mantra in drei Zeilen:

  1. Profil zuerst, Checks danach — applicability filtert alles
  2. Evidenz schlägt Vermutung — jeder Befund braucht Code-Stelle oder konkretes Verhalten
  3. Severity ohne Mitleidcritical blockiert Produktion, Punkt

Jeder Audit folgt sechs Schritten in dieser Reihenfolge. Abweichungen sind möglich, müssen aber im Audit-Report dokumentiert werden.


Schritt 0: Umgebung vorbereiten

Bevor irgendein Schritt beginnt, müssen Cross-Platform-Voraussetzungen erfüllt sein. Diese Sektion existiert, weil bei realen Audit-Läufen auf Windows wiederholt UTF-8- und Pfad-Probleme aufgetreten sind.

0.1 UTF-8 für Python

Auf Windows defaultet Python stdout/stderr zu cp1252 und crasht bei Emojis oder Umlauten. Vor jedem Python-Snippet:

# Bash/PowerShell — vor Python-Aufrufen exportieren:
export PYTHONUTF8=1            # Bash
$env:PYTHONUTF8 = "1"          # PowerShell

Oder im Python-Code direkt:

from tools.path_utils import force_utf8_stdio
force_utf8_stdio()   # idempotent, sicher mehrfach aufzurufen

0.2 Pfad-Konventionen

| Tool | Erwartetes Pfad-Format | |---|---| | Bash (cat, grep, ls) | POSIX (/c/Users/foo) | | Read / Edit / Write | OS-native (C:\Users\foo auf Windows) | | Python pathlib.Path | beides, aber konsistent halten |

Helper im Repo:

# Bash — sourceable
source tools/paths.sh
native_path=$(to_native_path "/c/Users/foo")    # → C:\Users\foo auf Windows
posix_path=$(to_posix_path "C:\\Users\\foo")    # → /c/Users/foo
# Python
from tools.path_utils import to_native_path, to_posix_path, is_windows
read_path = to_native_path(skill_base)   # für Read-Tool-Aufrufe

0.3 Inline-Heredocs sind verboten

Inline-python3 --base-dir audits/ --catalog-dir checks/ | | Profil-Validierung (Placeholder/Schema-Gate) | python tools/validate_profile.py path/to/profile.yaml | | Catalog parsen (Frontmatter aller *.md) | python tools/parse_catalog.py --format json | | Catalog vs. Manifest validieren | python tools/parse_catalog.py --format manifest-check | | applies_when evaluieren | python tools/eval_applicability.py catalog profile.yaml | | Verification-Results aggregieren | python tools/aggregate_results.py aggregate results.json --out summary.json | | Findings-Set vs. Disk validieren | python tools/aggregate_results.py validate | | Audit-Report generieren | python tools/build_report.py | | Task-Agent-Output verifizieren | python tools/verify_raw_outputs.py raw/ --expected-ids ID1,ID2 | | Task-Agent-Run loggen | python tools/agent_run_log.py log --meta-path audit-meta.json ... | | Release-Vorschlag (Schritt 7) | python tools/propose_release.py propose | | Release anwenden (CHANGELOG + Tag) | python tools/propose_release.py apply --bump | | Tracker-Update (CSV/Notion) | python tools/tracker_sync.py update --from-summary | | Pfad zu Native/POSIX konvertieren | python tools/path_utils.py to-native |

Wenn ein Audit ein Snippet braucht das hier nicht abgedeckt ist: erst Issue im Skill-Repo öffnen, dann Helper-Script bauen, dann verwenden. Inline-Heredoc ist der Anti-Pattern, der nicht-reproduzierbare Audits erzeugt.

0.4 Run-ID + Audit-Meta initialisieren (verbindlich seit Issue #15)

Niemals date +%Y-%m-%d für den Output-Verzeichnisnamen — das hat im ersten Audit zu Drift zwischen UTC-Container und lokalem Kalendertag geführt (2026-04-30 statt 2026-05-01). Stattdessen:

# Erzeugt Output-Dir mit ISO-Timestamp + Timezone-Offset, schreibt
# initiale audit-meta.json mit Skill-Version + Catalog-Hash.
python "$SKILL_BASE/tools/audit_init.py" init "$SERVER_NAME" \
    --base-dir "$TARGET/audits/" \
    --skill-version "1.0.0" \
    --catalog-dir "$SKILL_BASE/checks/"
# Output (JSON): { "run_id": "2026-05-02T091245-Z-srgssr-mcp", "output_dir": "...", "meta_path": "..." }

Run-ID-Format: YYYY-MM-DDTHHMMSS--, wobei ` Z (UTC) oder +HHMM/-HHMM ist. Bei Sekunden-genauer Kollision (Re-Audit unmittelbar danach) wird das Verzeichnis mit -2, -3`, ... gesuffixt; die Run-ID selbst bleibt identisch.

Die initiale audit-meta.json enthält:

  • server_name, run_id, started_at (ISO mit TZ-Suffix), timezone_offset
  • skill_version, catalog_hash (SHA-256 aller checks/*.md + MANIFEST.txt), catalog_dir
  • Leeres agent_runs-Array (wird in Step 4 von agent_run_log.py befüllt)

Der catalog_hash ist der Reproduzierbarkeits-Anker: jeder Re-Audit kann verifizieren, dass derselbe Katalog-Stand verwendet wurde.


Schritt 1: Profil laden

Ziel: Den Server-Kontext aus dem Notion MCP Audit Tracker (DB-ID a2736a65-677d-4cf3-9f94-e874f74a1975) holen, damit nachfolgende Schritte die richtigen Checks filtern können.

1.1 Pflichtfelder aus dem Tracker

Bevor ein Audit beginnt, müssen diese Felder in der Audit-Tracker-Karte gesetzt sein:

| Feld | Werte | Verwendung im Audit | |---|---|---| | Transport | stdio-only / dual / HTTP/SSE | filtert Netzwerk-Checks | | Auth-Modell | none / API-Key / OAuth-Proxy | filtert OAuth-Checks | | Datenklasse | Public Open Data / Verwaltungsdaten / PII | filtert PII-Checks und CH-Compliance | | Schreibzugriff | read-only / write-capable | filtert HITL-Checks | | Deployment | local-stdio / Railway / Render / andere | filtert Cloud-Checks | | Repo URL | GitHub-URL | für Code-Review-Schritte |

Wenn ein Pflichtfeld fehlt, wird der Audit gestoppt und der User aufgefordert, das Feld zu füllen. Audits mit unvollständigem Profil sind wertlos — applicability wird falsch berechnet, die Findings werden unverlässlich.

1.2 Profil-Notation für interne Verwendung

Während des Audits arbeitet Claude mit einem konsolidierten Profil-Objekt:

profile:
  name: zurich-opendata-mcp
  repo: https://github.com/malkreide/zurich-opendata-mcp
  transport: dual
  auth_model: none
  data_class: Public Open Data
  write_capable: false              # bool — kanonisches Feld (siehe Migration unten)
  deployment: [local-stdio, Railway]
  is_cloud_deployed: true           # derived: true iff deployment hat irgendwas ausser local-stdio (siehe Issue #16)
  prio: 14  # aus Tracker-Formel

Dieses Profil ist die einzige Wahrheit für applies_when-Auswertung in Schritt 3.

Schema-Hinweis (seit Issue #13): Das kanonische Profil-Feld ist write_capable: bool. Das frühere write_access: "read-only" | "write-capable" (Enum-String) wurde abgelöst. Der Notion-Tracker behält das Schreibzugriff-Select-Feld zur besseren Lesbarkeit; audit-notion-sync.py mappt es beim pull automatisch auf write_capable: bool. Profile mit Legacy-Feld write_access führen beim Evaluator zu UnknownFieldError — das ist beabsichtigt (siehe docs/applies-when-dsl.md "loud failure"-Prinzip).

1.3 Validation-Gate (verbindlich seit Issue #14)

Bevor Step 2 startet, MUSS das Profil gegen Placeholder und Schema-Lücken geprüft werden. Im ersten realen Audit hatte der User versehentlich das Template mit ...-Werten reingepastet — Claude hat das zwar erkannt, aber nur dank Defensive-Behavior. Jetzt verbindlich:

# Profil als YAML/JSON file-validieren (oder als Inline-Block)
python "$SKILL_BASE/tools/validate_profile.py" path/to/profile.yaml
# exit 0 = clean, exit 1 = Placeholder oder Schema-Fehler

Der Validator catcht:

  • Placeholder-Werte: ..., `, , TODO, leere Strings, null/None`, leere Listen, Listen mit Placeholder-Members
  • Fehlende Pflichtfelder: alle 15 Profil-Top-Level-Felder plus data_source.is_swiss_open_data
  • Type-Mismatches: bool-Feld mit String-Wert, list-Feld mit String-Wert, etc.

Bei Exit-1 wird Step 2 nicht gestartet. Der Output zeigt strukturiert, welche Felder betroffen sind (missing / placeholder / type_mismatch). Nutze das, um den User zur Korrektur aufzufordern.


Schritt 2: Check-Katalog laden

Ziel: Den vollständigen Katalog (checks/*.md) parsen und nach category + severity indizieren.

2.1 Sieben Kategorien

| Kategorie | Quelle | Typische Anzahl Checks | Status v0.5.0 | |---|---|---|---| | ARCH | PDF Sec 2 + Anhang A — Tool-Design, Annotations, Idempotency, Repo-Struktur, Spec-Versionierung | 10–12 | 12 / 12 ✅ | | SDK | PDF Sec 3 — FastMCP, TypeScript, Zod, Lifecycle | 5–7 | 5 / 5 ✅ | | SEC | PDF Sec 4 + Anhang B — Security (grösste Kategorie) | 20–25 | 23 / 23 ✅ | | SCALE | PDF Sec 5 — Transport, LB, Container, Gateway | 5–7 | 6 / 6 ✅ | | OBS | PDF Sec 6 + Anhang B10 — Logging, Errors, SIEM, Tracing | 5–7 | 6 / 6 ✅ | | HITL | PDF Sec 7 — Sampling, Human-in-the-Loop | 4–5 | 5 / 5 ✅ | | CH | Custom — DSG/EDÖB, Schweiz-Compliance | 5–8 | 8 / 8 ✅ | | OPS | Anhang C — Test-Strategie, Doku, Phasenarchitektur | 3–5 | 3 / 3 ✅ | | Total | | ~65 | 68 / 68 ✅ |

2.2 Severity-Stufen

| Stufe | Bedeutung | Konsequenz | |---|---|---| | critical | Sicherheitslücke oder Compliance-Bruch | Blockiert Produktion. Muss vor Release gefixt sein. | | high | Architektureller Mangel mit signifikantem Risiko | Im laufenden Sprint fixen, max. 1 Sprint Karenz. | | medium | Best-Practice-Verletzung, kein akutes Risiko | Im nächsten Sprint planen. | | low | Polish, Optimierung, Stilistik | Backlog. Bei Tippfehler-Audits: low + auto-fix. |

2.3 Check-Schema

Jeder Check ist eine eigenständige Markdown-Datei im Format:

---
id: SEC-001
title: "Confused Deputy: Per-Client Consent Flow"
category: security
severity: critical
applies_when: 'auth_model == "OAuth-Proxy"'
pdf_ref: "Sec 4.1"
evidence_required: 3
---

# Body mit Description, Verification, Pass Criteria, Remediation

Details siehe templates/finding.md und beliebige Datei in checks/.


Schritt 3: Applicability-Filter

Ziel: Aus den ~50 Checks nur diejenigen auswählen, die für das aktuelle Server-Profil tatsächlich relevant sind. Ohne diesen Filter überfluten irrelevante Findings den Report (z.B. OAuth-Checks für stdio-only-Server ohne Auth).

3.1 Auswertung der applies_when-Klausel

Die Klausel ist ein Boolean-Ausdruck gegen die Profil-Felder. Die formale DSL-Spezifikation steht in [docs/applies-when-dsl.md](docs/applies-when-dsl.md), die Referenz-Implementierung in [tools/eval_applicability.py](tools/eval_applicability.py).

| Operator | Beispiel | Bedeutung | |---|---|---| | == | transport == "HTTP/SSE" | exakter String-Vergleich | | != | auth_model != "none" | Negation | | .includes(...) | deployment.includes("Railway") | Multi-Select-Membership | | and / or | transport == "HTTP/SSE" and auth_model == "OAuth-Proxy" | Verknüpfung | | always | always | Check ist universell, läuft immer |

Pflicht: Verwende den kanonischen Evaluator, niemals Python eval() oder ad-hoc-Substitution. Letzteres hat in der Vergangenheit zu nicht-reproduzierbaren Audits geführt (Listen-vs-String-Vergleiche, True vs true, etc.).

# Catalog-Auswertung gegen ein Profil
python tools/eval_applicability.py catalog path/to/profile.yaml --format table

# Einzelner Ausdruck testen
python tools/eval_applicability.py expr 'auth_model != "none"' path/to/profile.yaml

3.2 Typische Filter-Muster

stdio-only-Server ohne Auth, Public Open Data, read-only:

  • Anwendbar: alle ARCH, alle SDK, ~5 SEC (basale Best Practices), OBS-Logging-Basics, einige CH
  • Nicht anwendbar: SSRF, OAuth-Flow, Session-Hijacking, Stateful-LB, Sandboxing
  • Geschätzt: ~15–20 Checks

HTTP/SSE-Server mit OAuth-Proxy, Cloud-Deployment, Verwaltungsdaten:

  • Anwendbar: praktisch alles
  • Geschätzt: ~45–55 Checks

3.3 Applicability-Report (vor Audit-Start)

Bevor der eigentliche Audit beginnt, gibt Claude diese Übersicht aus:

=== Audit applicability for zurich-opendata-mcp ===
Profile: dual transport, no auth, Public Open Data, read-only,
         Deployment: [local-stdio, Railway]

Applicable checks: 23 / 50
  ARCH: 7/7      (universal)
  SDK:  6/6      (universal)
  SEC:  4/18     (cloud-relevant subset)
  SCALE: 3/6     (Railway-relevant subset)
  OBS:  3/5      (universal subset)
  HITL: 0/4      (no write access, no sampling)
  CH:   0/6      (Public Open Data, no PII)

Severity breakdown of applicable checks:
  critical: 4    high: 11    medium: 6    low: 2

Wichtig: Wenn ein Check nicht anwendbar ist, erscheint er gar nicht im Report — nicht einmal als «N/A». Das hält Reports fokussiert und vermeidet Audit-Müdigkeit.


Schritt 4: Check-Ausführung

Ziel: Jeden anwendbaren Check methodisch verifizieren — entweder automatisch (grep, AST, curl) oder via manuellem Code-Review.

4.1 Drei Verifikationsmodi

Jeder Check definiert in seiner verification:-Sektion einen oder mehrere Modi:

| Modus | Wann | Beispiel | |---|---|---| | automated | Pattern existiert/fehlt im Repo | grep -r "expose_headers" src/ für SDK-004 | | code_review | Logische Prüfung erforderlich | OAuth-State-Single-Use bei SEC-010 | | config_check | Repo-Settings, CI, Branch-Protection | cat .github/workflows/*.yml für OBS-Checks | | runtime_test | Live-API-Verhalten testen | curl -H "X-Forwarded-For: 169.254.169.254" für SEC-004 |

4.2 Audit-Reihenfolge: Severity descending

Innerhalb der anwendbaren Checks läuft der Audit in dieser Reihenfolge:

  1. Alle critical-Checks zuerst (Showstopper früh erkennen)
  2. Dann high
  3. Dann medium
  4. low zuletzt (oder skippen falls knappe Zeit)

Wenn ein critical-Check fehlschlägt, kann der Audit nicht «pass» erhalten — egal wie gut die anderen Checks ausgehen.

4.3 Evidenz-Sammlung pro Check

Für jeden ausgeführten Check wird strukturiert dokumentiert:

check_run:
  id: SEC-001
  status: pass | fail | partial | skip
  evidence_collected: 4  # tatsächlich beobachtet
  evidence_required: 3   # Mindestmaß aus Check-Def
  findings:
    - "Per-client consent UI in src/oauth/consent.py:42"
    - "X-Frame-Options: DENY in src/middleware/security.py:18"
    - "State parameter validated single-use in src/oauth/state.py:55"
  gaps:
    - "Cookies nutzen __Secure- prefix statt __Host- — schwächere Subdomain-Isolation"
  evaluator_notes: |
    Die Implementierung ist 90% korrekt. __Host- statt __Secure-
    wäre der vollständige Schutz gemäss Best Practice.

4.4 Pass-Criteria

Ein Check besteht nur dann als pass, wenn:

  • Alle Pflicht-Pass-Criteria im Check erfüllt sind
  • Mindestens evidence_required Punkte beobachtet wurden
  • Keine gaps der Severity ≥ Check-Severity vorliegen

Sonst: partial (wenn 50%+ erfüllt) oder fail.

4.5 Task-Agent-Validation-Gate (verbindlich)

Wenn die Check-Execution per Task-Agent delegiert wird (typisch bei Batch-Verarbeitung mehrerer Checks gleichzeitig), MUSS nach jedem Agent-Aufruf ein Verifikations-Gate laufen. Hintergrund: Im ersten realen Audit hat ein Task-Agent mit Done (68 tool uses · 0 tokens · 2m 20s) zurückgegeben — vollständiger stiller Fehlschlag — und der Skill hat das nicht erkannt.

# 1. Nach jedem Task-Agent-Aufruf: prüfen, dass alle erwarteten raw/-Files
#    existieren UND nicht leer sind (catches the 0-token failure mode).
python "$SKILL_BASE/tools/verify_raw_outputs.py" "$OUTPUT_DIR/raw/" \
    --expected-ids ARCH-001,ARCH-002,SEC-021 \
    --min-bytes 1

# 2. Run-Metadata loggen — Tool-Uses, Tokens, Duration in audit-meta.json.
#    Dieser Befehl exitet 1 wenn der Agent als `empty` oder `incomplete`
#    klassifiziert wird.
python "$SKILL_BASE/tools/agent_run_log.py" log \
    --meta-path "$OUTPUT_DIR/audit-meta.json" \
    --tool-uses 73 -

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [malkreide](https://github.com/malkreide)
- **Source:** [malkreide/mcp-audit-skill](https://github.com/malkreide/mcp-audit-skill)
- **License:** MIT
- **Homepage:** https://github.com/malkreide/swiss-public-data-mcp

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.