safe-mcp-suite
safe-mcp-suite
Zwei MCP-Server – ein Terminal und ein Dateiorganisator – teilen sich einen Sicherheitskern, den keiner von ihnen umgehen darf.
Worum es geht
Suche auf GitHub nach einem MCP-Server, der Shell-Befehle ausführt, und du wirst immer wieder dieselbe Datei finden: ein Tool, dekoriert mit @mcp.tool(), ein Aufruf von subprocess.run(command, shell=True), und das Ergebnis wird direkt an das Modell zurückgegeben. Die Dateisystem-Server haben dieselbe Form – os.rename in einer Schleife, vielleicht mit einem try/except drumherum.
Sie funktionieren. Das ist das Problem. Sie funktionieren, bis das Modell etwas produziert, das niemand vorhergesehen hat, und dann ist die Löschung bereits passiert, und es gibt keine Aufzeichnung darüber, was ausgeführt wurde oder warum es erlaubt war.
Dieses Repo ist diese beiden Server, neu aufgebaut, sodass die Sicherheit das Design ist, nicht ein Wrapper, der obendrauf geschraubt wurde. Ein einziges safety/-Paket besitzt jede Entscheidung, die dir schaden könnte – was erlaubt ist, wo die Grenze liegt, was aufgeschrieben wird, was verborgen wird. Die beiden Server darunter sind nur Verkabelung. Keiner kann am Kern vorbeireichen, weil keiner eine eigene Regel implementiert.
Die Wette dahinter: Deterministische Politik ist vertrauenswürdiger als Modellurteil. Ein Prompt kann ein Modell davon überzeugen, nicht vorsichtig zu sein. Er kann eine Pfad-Containment-Prüfung nicht dazu bringen, True zurückzugeben.
Related MCP server: Safe Terminal MCP Server
Schnellstart
Du brauchst Python 3.12+ und uv.
git clone https://github.com/Asaad-Suliman/safe-mcp-suite.git safe-mcp-suite
cd safe-mcp-suite
uv sync
./scripts/make_demo_sandbox.shDas letzte Skript legt sandbox/terminal, sandbox/files und state/ an, damit beide Server einen legalen Arbeitsbereich haben. Starte dann den, den du möchtest:
uv run safe-mcp terminal --config policy.example.toml
uv run safe-mcp files --config policy.example.tomlÜber das --config-Flag
Es ist nicht optional, und es gibt keinen Fallback. Kein automatisches Suchen nach ./policy.toml, kein Laden von .env, kein Standard-Root, der still auf dein Home-Verzeichnis zeigt. Wenn weder --config noch SAFE_MCP_POLICY_FILE gesetzt ist, gibt der Server den Grund aus und beendet sich.
Das ist beabsichtigt und es ist die am stärksten meinungsbildende Sache am Setup. Eine Sandbox, die stillschweigend auf einen bequemen Ort zurückfällt, ist eine Sandbox, die eines Tages auf einen teuren Ort zurückfallen wird. Sich zu weigern zu starten, ist der billigste mögliche Fehler.
Zwei Policy-Dateien werden mit dem Repo ausgeliefert, und sie sind nicht austauschbar:
Datei | Was es ist | Wird es starten? |
| Funktionierendes Beispiel, verwurzelt in | Ja – starte es jetzt |
| Kommentierte Vorlage mit auskommentierten Roots | Nein, absichtlich |
policy.toml weigert sich zu starten, bis du jail_root und workspace_root selbst ausfüllst. Diese Weigerung ist ein Feature, und es gibt einen Regressionstest, der sie aufrechterhält. Kopiere es, bearbeite es, weise es auf echte Verzeichnisse, wenn du bereit bist.
Die Roots können auch aus der Umgebung kommen, wenn du das bevorzugst:
SAFE_MCP_JAIL_ROOT=/path/to/jail
SAFE_MCP_WORKSPACE_ROOT=/path/to/workspaceRegistrieren bei einem MCP-Client
{
"mcpServers": {
"safe-mcp terminal": {
"command": "uv",
"args": ["run", "safe-mcp", "terminal"],
"env": {
"SAFE_MCP_POLICY_FILE": "/srv/safe-mcp/policy.example.toml",
"SAFE_MCP_JAIL_ROOT": "/srv/safe-mcp/sandbox"
}
},
"safe-mcp files": {
"command": "uv",
"args": ["run", "safe-mcp", "files"],
"env": {
"SAFE_MCP_POLICY_FILE": "/srv/safe-mcp/policy.example.toml",
"SAFE_MCP_WORKSPACE_ROOT": "/srv/safe-mcp/inbox"
}
}
}
}Demo
Alles unten ist echte aufgezeichnete Ausgabe. Nichts hier ist handgeschrieben, gekürzt oder nachträglich verschönert – das sind die tatsächlichen OperationResult-Envelopes, die von einem Live-MCP-Client zurückgegeben werden, der mit beiden Servern spricht, ausgeführt gegen die Sandbox aus ./scripts/make_demo_sandbox.sh mit policy.example.toml.
Lies das code-Feld in jedem. Diese Taxonomie ist der ganze Punkt: Jedes Ergebnis, ob Erfolg oder Ablehnung, kommt in derselben Form zurück.
Terminal-Server
Ein erlaubter Befehl:
>>> tool: run_command args: {"command": "cat notes.txt"}
{
"action": "run_command",
"code": "OK",
"detail": {
"exit_code": 0,
"stderr": "",
"stdout": "demo file\n"
},
"duration_ms": 1,
"ok": true,
"reason": "'cat' is allowed"
}Ein abgelehnter Befehl:
>>> tool: run_command args: {"command": "rm -rf /"}
{
"action": "run_command",
"code": "POLICY_DENIED",
"detail": {},
"duration_ms": 0,
"ok": false,
"reason": "'rm' is on the denylist"
}Beachte, was die Ablehnung nicht ist: kein Traceback, keine ausgelöste Exception, kein String, den das Modell erraten muss. Es ist ein typisierter Code mit einem Grund.
Datei-Server
Diese Sequenz läuft gegen einen vorbereiteten Arbeitsbereich mit gewöhnlichen Dateien, einem Installer, einer Dotfile und einem Symlink – jeweils eines von dem, was die Policy unterschiedlich behandelt.
plan_organize schlägt Verschiebungen vor und listet Übersprungenes auf:
>>> tool: plan_organize args: {}
{
"action": "plan_organize",
"code": "OK",
"detail": {
"created": "2026-08-07T16:49:00.048899+00:00",
"move_count": 2,
"moves": [
{
"category": "Images",
"dest": "Images/photo.png",
"size": 41,
"src": "photo.png"
},
{
"category": "Documents",
"dest": "Documents/report.pdf",
"size": 16,
"src": "report.pdf"
}
],
"plan_id": "7dcf2cff-399d-4af5-bb9a-a4131e1d5288",
"skip_count": 3,
"skips": [
{
"code": "NEEDS_EXPLICIT_REQUEST",
"name": ".bashrc",
"reason": "dotfiles are configuration, not clutter to be filed",
"rule": "organize"
},
{
"code": "POLICY_DENIED",
"name": "link.pdf",
"reason": "'organize' is not permitted by any rule (deny by default)",
"rule": null
},
{
"code": "NEEDS_EXPLICIT_REQUEST",
"name": "setup.exe",
"reason": "installers, executables and application folders are left where the user put them",
"rule": "organize"
}
],
"truncated": false
},
"duration_ms": 0,
"ok": true,
"reason": "proposed 2 move(s), skipped 3"
}apply_plan führt denselben Plan aus:
>>> tool: apply_plan args: {"plan_id": "7dcf2cff-399d-4af5-bb9a-a4131e1d5288"}
{
"action": "apply_plan",
"code": "OK",
"detail": {
"moved": 2,
"moves": [
{
"dest": "Images/photo.png",
"src": "photo.png"
},
{
"dest": "Documents/report.pdf",
"src": "report.pdf"
}
],
"plan_id": "7dcf2cff-399d-4af5-bb9a-a4131e1d5288",
"planned": 2
},
"duration_ms": 2,
"ok": true,
"reason": "moved 2 file(s)"
}Jetzt das interessante Paar. Gleiches Tool, zwei benannte Ziele, zwei verschiedene Antworten.
move_file verweigert durch PROTECTION – derselbe Symlink, den plan_organize oben übersprungen hat, explizit benannt:
>>> tool: move_file args: {"src": "link.pdf", "dest": "Documents/link.pdf"}
{
"action": "move_file",
"code": "POLICY_DENIED",
"detail": {},
"duration_ms": 0,
"ok": false,
"reason": "'organize' is not permitted by any rule (deny by default)"
}move_file erfolgreich beim Installer, den plan_organize mit NEEDS_EXPLICIT_REQUEST aufgeschoben hat, jetzt explizit benannt:
>>> tool: move_file args: {"src": "setup.exe", "dest": "Documents/setup.exe"}
{
"action": "move_file",
"code": "OK",
"detail": {
"dest": "Documents/setup.exe",
"moved": 1,
"src": "setup.exe"
},
"duration_ms": 0,
"ok": true,
"reason": "moved setup.exe"
}Der Planer hat beide abgelehnt. Direkt gefragt, hat der Verschieber einen abgelehnt und den anderen gemacht. Dieser Unterschied ist keine Inkonsistenz – es ist das Zwei-Schichten-Modell, und es bekommt weiter unten einen eigenen Abschnitt.
Systemdesign
Die Server sind Verkabelung, nicht Policy
Keine Serverdatei enthält eine Sicherheitsregel. Jede lebt in safety/, und beide Server erreichen dieselben Funktionen, um dieselben Antworten zu bekommen.
flowchart TD
A["MCP client"] --> B["terminal server"]
A --> C["files server"]
B --> D["safety/policy.py<br/>allow or deny"]
C --> D
D --> E["safety/paths.py<br/>PathJail containment"]
E --> F["execute or move"]
F --> G["safety/redact.py<br/>secrets out, then truncate"]
G --> H["safety/audit.py<br/>append-only JSONL"]
H --> I["OperationResult"]
I --> ADas ist keine Ordnung um ihrer selbst willen. Es bedeutet, dass ein Sicherheitsfix an genau einer Stelle landet, und es bedeutet, dass ein Prüfer, der dieses Repo auditiert, safety/ liest und fertig ist. Es gibt keine zweite Implementierung, die sich in einem Servermodul versteckt und stillschweigend aus dem Takt mit der ersten gerät.
Wie eine Anfrage tatsächlich fließt
Parsen. Die Befehlszeichenkette wird in eine argv-Liste aufgeteilt. Shell-Metazeichen werden hier abgelehnt, bevor irgendetwas interpretiert wird – auch innerhalb von Anführungszeichen. Der letzte Teil ist bewusst konservativ und eine dokumentierte Grenze, kein Versehen.
Bewerten. Die Policy-Engine antwortet mit erlauben oder verweigern. Verweigern gewinnt immer. Alles, was nicht explizit erlaubt ist, wird abgelehnt, also ist eine leere Policy ein nutzloser Server, kein offener.
Eingrenzen. Jeder Pfad wird aufgelöst und gegen den Jail-Root geprüft. Symlinks werden als Links inspiziert und nie durchgefolgt.
Handeln.
subprocess.runmitshell=False, einer bereinigten Umgebung von genauPATH,HOME,LANG, einem Timeout und einer Ausgabegrenze. Oder auf der Dateiseite eine einzelne geschützte Verschiebung, die in einem Journal aufgezeichnet wird.Redigieren, dann kürzen. In dieser Reihenfolge, immer.
Aufzeichnen. An das Audit-Log anhängen. Wenn dieser Schreibvorgang fehlschlägt, schlägt die Operation mit ihm fehl.
Sechs Entscheidungen, die es wert sind, gestohlen zu werden
Fehler sind Daten, keine Ausnahmen. Jedes Ergebnis ist ein OperationResult mit einem typisierten ResultCode. Kein Traceback erreicht jemals den Client. Ein Modell, das einen Stacktrace erhält, wird versuchen, ihn zu umgehen; ein Modell, das POLICY_DENIED erhält, hat etwas Nützliches und Eindeutiges erfahren.
Verweigern gewinnt, immer. Erlauben- und Verweigern-Regeln werden nicht gewichtet oder in einen Tiebreak geordnet. Wenn eine Verweigern-Regel zutrifft, ist die Antwort nein. Argumentregeln matchen Tokens unabhängig von der Reihenfolge, also ist das Umsortieren von Flags kein Bypass.
Nichts startet ohne expliziten Root. Im Schnellstart behandelt, aber es gehört auch in die Designliste, weil „sinnvoller Standard" der Weg ist, auf dem die meisten Sandboxes ihre erste Flucht erwerben.
Das Audit-Log darf dich stoppen. Standardmäßig fail-closed: Wenn das Log nicht geschrieben werden kann, passiert die Operation nicht. Du kannst es mit SAFE_MCP_AUDIT_FAIL_MODE auf fail-open umstellen, und das ist eine Entscheidung, die du absichtlich triffst, in einer Konfigurationsdatei, wo jemand sie sehen kann.
Redigieren vor Kürzen. Drehe die beiden um, und eine 64-KB-Ausgabegrenze kann ein Geheimnis in zwei Hälften schneiden und das erste Fragment ausgeben, ohne ein Muster getroffen zu haben. Das ist eine Zwei-Zeilen-Reihenfolgeentscheidung, die eine ganze Kategorie von Leck schließt.
Entropiebasierte Geheimniserkennung ist standardmäßig aus. Sie feuert ständig auf Hashes, UUIDs und Base64-Payloads. Ein Redaktor, der Wolf schreit, wird von seinen eigenen Benutzern deaktiviert, was schlimmer ist als einer, der seine Grenzen im Voraus zugibt.
PROTECTION vs. RESTRAINT
Die Überspringungsregeln teilen sich in zwei Schichten, und jede Regel deklariert, zu welcher sie gehört.
PROTECTION wird von jedem Tool durchgesetzt, ohne Ausnahme. Nur ein Teil davon ist eine deklarierte Regel – unsafe-name deckt Steuerzeichen in einem Dateinamen ab. Der Rest ist strukturell: Ein Pfadausbruch scheitert an der Containment-Prüfung in safety/paths.py, ein belegtes Ziel scheitert am lstat-Check in servers/files/apply.py, und ein Symlink matcht überhaupt keine Erlauben-Regel und fällt auf Deny-by-default zurück – was evaluate_layered absichtlich als PROTECTION klassifiziert. Siehe die Anmerkung unten. In jedem Fall gibt es kein Flag, keinen Override, kein „Ich weiß, was ich tue"-Argument. Das sind Invarianten.
RESTRAINT wird nur von plan_organize durchgesetzt. Installer, Anwendungsordner, Systemdateien, versteckte Dateien, Verzeichnisse. Diese sind nicht gefährlich – sie sind Dinge, über die ein automatischer Klassifikator nicht in deinem Namen raten sollte. Der Planer meldet sie als NEEDS_EXPLICIT_REQUEST und macht weiter.
Die Konsequenz ist das Demo-Paar oben. move_file wird einen Installer verschieben, den du selbst benannt hast, weil das zu verweigern Paternalismus wäre, nicht Sicherheit. Es wird keinen Symlink verschieben, egal wie explizit du fragst, weil das Containment ist.
Eine Nuance, die es wert ist, explizit zu sein: Der Ablehnungsgrund des Symlinks lautet
"'organize' is not permitted by any rule (deny by default)" und nicht
irgendetwas, das „Symlink" erwähnt. Ein Symlink matcht keine Erlauben-Regel in
keiner Schicht, also fällt er auf Deny-by-default zurück – und safety.policy.evaluate_layered
klassifiziert einen ungematchten Deny-by-default-Fallthrough absichtlich als PROTECTION,
als die strengste Lesart von etwas, für das die Policy kein Vokabular hat. Die
allgemeine Formulierung ist keine schwächere Garantie; move_file, das den Symlink
direkt benennt, oben, ist der Beweis.
Ein Implementierungsdetail, das ich überall wiederholen würde: Die PROTECTION-Menge wird durch Filtern der vollständigen Regelsatz abgeleitet, nie als eigene Liste zusammengestellt. Zwei handgepflegte Listen driften auseinander, und die Fehlerart des Driftens ist hier eine Schutzregel, die stillschweigend verschwindet. Ein Filter kann nicht vergessen.
Zu den Tests
661 Tests, alle hermetisch. Kein Test schreibt echten Zustand: Alles, was ausgeführt wird, läuft gegen tmp_path, und die beiden Suiten, die die mitgelieferten policy.toml und policy.example.toml lesen, kopieren sie zuerst nach tmp_path. Kein echtes Audit-Log oder Arbeitsbereich wird jemals berührt. CI führt die Suite plus einen gitleaks-Scan bei jedem Push und Pull Request aus.
Die Zahl wird veraltet sein, sobald ich einen Test hinzufüge. Die Eigenschaft, die zählt, ist die Isolation, nicht die Anzahl.
Garantien
Jede Zeile nennt das Modul und die Funktion, die sie durchsetzt. Wenn eine Behauptung hier nicht durch Code gestützt ist, den du öffnen kannst, sollte sie nicht in der Tabelle stehen.
Garantie | Durchgesetzt durch |
Standardmäßig verweigern, in beiden Servern |
|
Eine passende Verweigerung schlägt immer eine passende Erlaubnis |
|
Keine Shell im Terminal-Server |
|
Shell-Metazeichen werden vor jeder Richtlinienprüfung abgelehnt |
|
Befehle sind auf ein Verzeichnis beschränkt |
|
Dateiverschiebungen sind auf einen Arbeitsbereich beschränkt, symlink-sicher bei der letzten Komponente |
|
Zweischichtige Dateirichtlinie: Sicherheitsinvariante vs. Vermeidung von Vermutungen |
|
Kein Ziel wird jemals stillschweigend überschrieben |
|
Es gibt kein Löschwerkzeug, weder geschützt noch sonst |
|
Jede Verschiebung ist rückgängig machbar, einschließlich eines gesamten angewendeten Plans |
|
Ein veralteter Plan verschiebt überhaupt nichts |
|
Ein hängender Befehl wird nach einer Frist beendet |
|
Die Ausgabe wird begrenzt, nach der Schwärzung, niemals davor |
|
Der Kindprozess erhält eine bereinigte Umgebung |
|
Geheimnisse werden sowohl aus der Ausgabe als auch aus den Audit-Feldern entfernt |
|
Das Audit-Protokoll ist standardmäßig fail-closed |
|
Jede Ablehnung wird mit einem Grund protokolliert |
|
Bedrohungsmodell – ausdrücklich nicht im Geltungsbereich
Ein gefährlicher Befehl, den Sie auf die Zulassungsliste gesetzt haben. Wenn Sie einen Interpreter oder ein shell-ähnliches Werkzeug (
bash,python,sh,find -exec,awk,env, …) zulassen, kann das Modell alles tun, was dieses Werkzeug kann. Die Stärke der Richtlinie liegt vollständig in der Zulassungsliste des Betreibers.Kernel-/Sandbox-Escapes. Das Jail ist eine Pfad-Containment-Prüfung, keine Kernel-Sandbox – keine Namespaces, cgroups oder seccomp. Siehe „Ehrliche Einschränkungen“.
Host-Zugriff auf die Zustandsdateien. Das Audit-Protokoll und das Undo-Journal sind manipulationssicher gegenüber dem Server, nicht manipulationssicher gegenüber jedem mit Host-Dateisystemzugriff.
Vollständigkeit der Schwärzung. Musterbasiert und nach bestem Bemühen; eine Geheimnisform, die die Muster nicht erkennen, wird durchgelassen.
Multi-Tenant-Identität oder Ratenbegrenzung. Es gibt keine Authentifizierung pro Aufrufer in beiden Servern – die Vertrauensgrenze ist „wer diesen Prozess starten kann“, was ein MCP-Client durch das Starten durchsetzt, nicht dieser Code.
Ein Wettlauf zwischen der Validierung eines Pfads und der Aktion darauf (TOCTOU). Siehe „Ehrliche Einschränkungen“ unten.
Im Vergleich zu einem naiven MCP-Server
Viele schnelle MCP-Server umhüllen subprocess.run(cmd, shell=True) für den Terminalzugriff
und os.rename für Dateiverschiebungen. Beides ist bequem und unsicher. Diese
Tabelle ist sachlich, kein Anspruch auf perfekte Sicherheit.
Bedenken | Naiver MCP-Server | safe-mcp-suite |
Befehlsausführung |
| nur |
Shell-Metazeichen | Interpretiert ( | Abgelehnt vor jeder Richtlinienprüfung |
Dateiverschiebungen |
| Auf einen Arbeitsbereich beschränkt; ein Symlink an einem Ende wird abgelehnt, niemals verfolgt |
Überschreiben einer Datei | Normalerweise still – POSIX | Immer abgelehnt; keine nummerierte Variante ( |
Rückgängig machen | Keine | Jede Verschiebung wird protokolliert; |
Löschen von Dateien | Oft vorhanden, oft ungeschützt | Es gibt kein Löschwerkzeug in diesem Server, Punkt. |
Umgebung, die einem Kindprozess gegeben wird | Vollständige Elternumgebung, einschließlich Geheimnisse | Bereinigt auf |
Geheimnisse in Ausgabe oder Protokollen | Durchgereicht | Geschwärzt, vor der Kürzung, sowohl in der Antwort als auch im Audit-Protokoll |
Prüfbarkeit | Standardmäßig keine | Nur-Anhängen-JSONL, standardmäßig fail-closed |
Automatische vs. ausdrücklich angeforderte Aktion | Ein Codepfad behandelt beide gleich |
|
Beide Werkzeuge geben ein OperationResult zurück:
OperationResult {
ok: bool
code: ResultCode
action: str
reason: str
detail: dict # stdout, stderr, exit_code — empty when nothing ran
duration_ms: int
}run_command(command: str, cwd: str | None = None)–commandgegen die Richtlinie auswerten und, falls erlaubt, sandboxed ausführen.cwdist optional und muss innerhalb des Jail-Roots aufgelöst werden; ein Traversal, Symlink oder absoluter Pfad, der entkommt, gibtPATH_ESCAPEzurück, ohne etwas auszuführen. Jeder Aufruf wird protokolliert; bei fail-closed-Protokollierung gibt ein nicht beschreibbares ProtokollAUDIT_UNAVAILABLEzurück, anstatt unprotokolliert auszuführen. Ein Exit-Code ungleich Null ist immer nochok: true– der Befehl wurde ausgeführt; ob er erfolgreich war, ist seine eigene Sache.explain_command(command: str)– der Probelauf. Erreicht dieselbe Analyse und Auswertung wierun_commandund kehrt vor dem Ausführenden zurück, sodassdetailniemalsstdout,stderroder einen Exit-Code enthält – nichts wurde ausgeführt.
Ergebniscodes, die dieser Server zurückgeben kann: OK, POLICY_DENIED, INVALID_REQUEST,
PATH_ESCAPE, TIMEOUT, OUTPUT_TRUNCATED, OPERATION_FAILED,
AUDIT_UNAVAILABLE, INTERNAL_ERROR.
Sechs Werkzeuge, und die Liste ist das Design – es gibt kein siebtes, und keines davon löscht.
list_files(subdir: str | None = None)– schreibgeschützt. Meldet den Namen, die Größe, die Kategorie jedes Eintrags, ob der Organisator ihn verschieben würde, und warum nicht, wenn er es nicht täte.plan_organize()– schlägt Verschiebungen und Überspringungen vor. Ändert nichts, nicht einmal Zielordner. Gibt eineplan_idzurück, die anapply_planübergeben wird.apply_plan(plan_id: str)– führt einen Plan aus. Jede Datei wird zuerst erneut geprüft; wenn sich seit der Planung etwas geändert, verschoben oder verschwunden hat, wird der gesamte Plan abgelehnt. Einmalig verwendbar – eine ID kann nicht erneut abgespielt werden.move_file(src: str, dest: str)– verschiebt eine benannte Datei.destist der vollständige Zielpfad, kein Ordner. Ein Ziel, das bereits existiert, wird abgelehnt, niemals überschrieben und niemals umbenannt. Gehorcht nur den PROTECTION-Regeln – siehe „Das zweischichtige Modell“ oben.undo_last_action()– macht die letzte Verschiebung oder den letzten angewendeten Plan als eine Aktion rückgängig. Nichts wird überschrieben, um Platz für eine wiederhergestellte Datei zu schaffen.redo_last_action()– wendet die zuletzt rückgängig gemachte Aktion erneut an. Der Redo-Stapel wird geleert, sobald neue Arbeit aufgezeichnet wird.
Ergebniscodes, die dieser Server zusätzlich zurückgeben kann: NEEDS_EXPLICIT_REQUEST
(alle Sicherheitsinvarianten erfüllt, nur abgelehnt, weil unaufgefordertes Handeln eine Vermutung wäre – benennen Sie das Ziel direkt und fragen Sie).
Eine Datei, policy.toml, wird von beiden Servern gelesen:
audit_log = "audit.jsonl" # shared
audit_fail_mode = "closed" # shared: "closed" or "open"
[redaction] # shared
enabled = true
entropy_fallback = false
extra_patterns = [] # [{ name = "...", regex = "..." }]
[terminal]
# jail_root = "/srv/safe-mcp/sandbox" # REQUIRED — here or via env
[terminal.limits]
timeout_seconds = 30
max_output_bytes = 65536
[terminal.allowlist]
commands = ["ls", "cat", "echo", "pwd", "git"]
[terminal.denylist]
commands = ["rm", "shutdown", "reboot", "curl", "wget", "chmod", "sudo"]
[[terminal.rules]]
command = "git"
deny_args = ["push --force", "push -f"]
reason = "force-push rewrites shared history"
[files]
# workspace_root = "/srv/safe-mcp/inbox" # REQUIRED — here or via env
journal = "organizer-journal.json"
max_plan_moves = 500
[files.categories]
Documents = [".pdf", ".doc", ".docx", "..."]
# ...
[[files.skip]]
layer = "protection" # or "restraint" — required, no default
when = ["unsafe-name"]
reason = "..."Die Richtliniendatei selbst hat keinen Standardort. Weisen Sie mit --config darauf hin:
safe-mcp terminal --config /path/to/policy.toml
safe-mcp files --config /path/to/policy.tomlUmgebungsvariablen werden weiterhin unterstützt und jede überschreibt den passenden policy.toml-Schlüssel. SAFE_MCP_POLICY_FILE ist die eine Ausnahme, die erwähnenswert ist: Es ist eine Alternative zu --config, keine Überschreibung davon – --config gewinnt, wenn beide angegeben sind, und der Start verweigert, wenn keines angegeben ist.
Variable | Bedeutung | Standard |
| Pfad zu | keiner — Start wird verweigert |
| Terminal-Jail-Verzeichnis (erforderlich, hier oder | keiner — Start wird verweigert |
| Arbeitsbereichsverzeichnis für Dateien (erforderlich, hier oder | keiner — Start wird verweigert |
| Undo/Redo-Journalpfad (muss außerhalb des Arbeitsbereichs liegen) |
|
| Gemeinsamer Audit-Pfad (muss außerhalb beider Jails liegen) |
|
|
|
|
Der Start schlägt laut fehl — eine gedruckte fatal:-Meldung und ein Exit-Code ungleich Null — wenn überhaupt kein Richtliniendateipfad angegeben ist (weder --config noch SAFE_MCP_POLICY_FILE), eine fehlende oder ungültige policy.toml, ein nicht gesetztes oder kein Verzeichnis darstellendes Jail-/Arbeitsbereichs-Root, ein ungültiger Operator-Redaktions-Regex, ein unbeschrifteter [[files.skip]]-Eintrag oder ein Audit-Protokoll/Journal, das sich innerhalb eines Jails befindet, das der Server dann verschieben oder fälschen könnte.
Ehrliche Einschränkungen
Dies ist eine Härtungsschicht, kein Tresor. Lesen Sie diese, bevor Sie einen der beiden Server bereitstellen.
Das Jail ist eine Pfad-Eindämmungsprüfung, keine Kernel-Sandbox. Keine Namespaces, cgroups oder seccomp. Ein Kernel-Exploit oder eine aus einem zugelassenen Binärprogramm erreichbare Fluchtmöglichkeit ist nicht eingedämmt.
Das Audit-Protokoll ist manipulationssicher, nicht manipulationsfest, und hat keine Rotation. Append-only mit Flush + fsync pro Datensatz bedeutet, dass es bei einem Absturz keine Datensätze verliert, aber jeder mit Host-Dateisystemzugriff auf
audit.jsonlkann es lesen, ändern oder löschen — und die Datei wächst unbegrenzt; es gibt keine eingebaute Rotation oder Aufbewahrungsrichtlinie.Die Redaktion ist musterbasiert und nach bestem Bemühen. Sie erfasst häufige Geheimnisformen; ein neuartiges oder ungewöhnliches Format passiert unredigiert. Der optionale Entropie-Fallback ist standardmäßig deaktiviert, weil er bei git-SHAs, UUIDs und base64-Daten verrauscht ist, nicht weil er schwach ist.
Terminal-Metazeichen werden auch innerhalb von Anführungszeichen abgelehnt — eine bekannte Grenze.
echo "a;b"wird abgelehnt, obwohl das;innerhalb der Anführungszeichen inert ist, weil die Prüfung eine rohe Teilstring-Prüfung ohne Kenntnis von Anführungszeichen ist. Das ist die sichere Richtung, um falsch zu liegen — es gibt keinen Anführungszeichen-Trick, der einen Operator an einer Prüfung vorbeibringt, die Anführungszeichen von vornherein ignoriert — aber es bedeutet, dass einige legitime Eingaben abgelehnt werden.TOCTOU: Ein Pfad, der validiert und dann bearbeitet wird, kann sich dazwischen ändern. Sowohl
safety/paths.pyals auchservers/files/apply.pyprüfen Eindämmung oder Belegung und handeln dann auf einem separaten Syscall; ein ausgetauschter Symlink oder eine in dieser Lücke erstellte Datei ist nicht abgedeckt. Im Code als bewusste, benannte Grenze dokumentiert (# NOTE:-Kommentare in beiden Dateien), mit einem Upgrade-Pfad (O_NOFOLLOWplus dir-fd-relative Operationen) für den Fall, dass er jemals benötigt wird.Enkelprozesse werden nicht eingesammelt. Die eigenen Kindprozesse eines beendeten oder abgelaufenen Befehls befinden sich nicht in einer separaten Prozessgruppe; der Ausführer tötet nur das direkte Kind, sodass alles, was dieser Befehl erzeugt hat, ihn überleben kann.
Verwandte Arbeiten
hardened-terminal-mcp — der eigenständige Vorgänger des Terminalservers dieser Suite.
MCP-file-organizer — der eigenständige Vorgänger des Dateiservers dieser Suite.
Lizenz
MIT — siehe LICENSE.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
- Parallel Task MCPOAuth
An MCP server for deep research or task groups
Run commands and read/write files on your servers over Termalin's keyless tunnels (hosted MCP).
A MCP server built for developers enabling Git based project management with project and personal…
An MCP server that provides read access to your cloud storage providers, bank accounts and more.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceA secure MCP server for shell operations, terminal management, and process control, enabling AI assistants to safely execute commands and manage interactive sessions.2046MIT
- FlicenseNot gradedqualityCmaintenanceA secure, controlled terminal MCP server that enables executing whitelisted shell commands safely with multiple security layers.
- AlicenseNot gradedqualityCmaintenanceA production-ready MCP server for secure, session-based command execution, file manipulation, and system inspection via local terminal sessions.14ISC
- AlicenseAqualityAmaintenanceSecurity-hardened MCP server that runs only allowlisted commands with no shell, jailed to a single directory, and bounded execution.2MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Asaad-Suliman/safe-mcp-suite'
If you have feedback or need assistance with the MCP directory API, please join our Discord server