| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 | """Was das Modell ohne Rueckfrage darf.
Drei Modi (Umschalten mit Shift+Tab oder /modus):
fragen - Lesen frei; jede Dateiaenderung und jeder Befehl wird erfragt
dateien - Lesen und Aendern frei; Befehle werden erfragt
alles - nichts wird erfragt
Dazu dauerhafte Regeln je Projektordner ("immer erlauben"): fuer Befehle ein
Praefix wie `git status` oder `npm run`, gespeichert in ~/.kode/freigaben.json.
Ein zusammengesetzter Befehl (&&, ||, ;, |) ist nur frei, wenn jeder Teil
frei ist - sonst waere `git status && rm -rf /` durch `git status` gedeckt.
"""
import json
import re
import shlex
from pathlib import Path
from . import konfig, werkzeuge
MODI = ["fragen", "dateien", "alles"]
MODUS_TEXT = {"fragen": "fragt bei Änderungen und Befehlen",
"dateien": "Dateien frei, Befehle werden erfragt",
"alles": "alles frei, keine Rückfragen"}
def _laden() -> dict:
try:
return json.loads(konfig.FREIGABEN.read_text(encoding="utf-8"))
except (OSError, ValueError):
return {}
def _speichern(d: dict) -> None:
konfig.verzeichnisse()
konfig.FREIGABEN.write_text(json.dumps(d, ensure_ascii=False, indent=1), encoding="utf-8")
def regeln(projekt: Path) -> list[str]:
return list(_laden().get(str(projekt), {}).get("bash", []))
def regel_hinzu(projekt: Path, praefix: str) -> None:
d = _laden()
eintrag = d.setdefault(str(projekt), {}).setdefault("bash", [])
if praefix not in eintrag:
eintrag.append(praefix)
_speichern(d)
def regel_weg(projekt: Path, praefix: str) -> bool:
d = _laden()
eintrag = d.get(str(projekt), {}).get("bash", [])
if praefix in eintrag:
eintrag.remove(praefix)
_speichern(d)
return True
return False
_TRENNER = re.compile(r"&&|\|\||;|\||\n")
def teile(befehl: str) -> list[str]:
return [t.strip() for t in _TRENNER.split(befehl) if t.strip()]
def praefix_vorschlag(befehl: str) -> str:
"""Erstes Wort, bei Werkzeugen mit Unterbefehl die ersten zwei."""
try:
w = shlex.split(teile(befehl)[0])
except (ValueError, IndexError):
w = befehl.split()
if not w:
return befehl
if w[0] in ("git", "npm", "pnpm", "yarn", "docker", "pip", "cargo", "go",
"systemctl", "uv", "make", "kubectl", "python3", "python") and len(w) > 1:
return " ".join(w[:2])
return w[0]
def _gedeckt(teil: str, liste: list[str]) -> bool:
# $( ) und Backticks fuehren beliebigen Code aus, > schreibt in beliebige
# Dateien - nie per Regel frei.
if "$(" in teil or "`" in teil or ">" in teil:
return False
return any(teil == r or teil.startswith(r + " ") for r in liste)
def frei(modus: str, projekt: Path, name: str, args: dict) -> bool:
if name in werkzeuge.NUR_LESEN or modus == "alles":
return True
if name in werkzeuge.DATEI_AENDERN:
return modus == "dateien"
if name == "bash":
liste = regeln(projekt)
t = teile(args.get("command") or "")
return bool(t) and all(_gedeckt(x, liste) for x in t)
return False |