Automatischer Push: 14 Datei(en) geändert

- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/bremse_echtlauf.py
- .vault-tools/trading-plattform-pruefung/browser_w.py
- .vault-tools/trading-plattform-pruefung/generalprobe_echtlauf.py
- .vault-tools/trading-plattform-pruefung/ha_aktiv_setzen.py
- .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py
- .vault-tools/trading-plattform-pruefung/hausmelder_echtlauf.py
- .vault-tools/trading-plattform-pruefung/killswitch_simulation.py
- .vault-tools/trading-plattform-pruefung/killswitch_simulation_mutanten.py
- .vault-tools/trading-plattform-pruefung/latenz_auswertung.py
- .vault-tools/trading-plattform-pruefung/s1_alpaca_lesen.py
- .vault-tools/trading-plattform-pruefung/s5_echtlauf.py
- … und 2 weitere

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-09-22 04:00:01 +02:00
1 parent 246af19683
commit 593acdd122
14 files changed
+2090 -5

No files matched your search

@@ -0,0 +1,216 @@
"""Gegenmessung zu Paket S5 gegen eine laufende Probe-Instanz: dieselben Angriffe, die der Sicherheitspruefer gemessen hat.
Koordinator-Pruefcode (WSL). Gemessen wird gegen 127.0.0.1:<port>; die Instanz laeuft aus einer Kopie mit
Broker-Attrappe, Wegwerf-Register und Wegwerf-Zugang. Nichts davon beruehrt den Betrieb.
Aufruf: python s5_echtlauf.py <port> <ablage-der-probe> <code-baum>
"""
import base64
import hashlib
import hmac
import http.client
import json
import os
import struct
import subprocess
import sys
import threading
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import requests
PORT = int(sys.argv[1])
ABLAGE = Path(sys.argv[2])
CODE = Path(sys.argv[3])
BASIS = f"http://127.0.0.1:{PORT}"
KOPF = {"Origin": BASIS}
z = json.loads((ABLAGE / "zugang.json").read_text(encoding="utf-8"))
BEN, PW, GEH = z["benutzer"], z["passwort"], z["geheimnis"]
befunde = []
def code(versatz=0):
s = base64.b32decode(GEH)
mac = hmac.new(s, struct.pack(">Q", int(time.time()) // 30 + versatz), hashlib.sha1).digest()
v = mac[-1] & 0x0F
return f"{(struct.unpack('>I', mac[v:v + 4])[0] & 0x7FFFFFFF) % 1_000_000:06d}"
def melde_an():
s = requests.Session()
s.trust_env = False
a = s.post(f"{BASIS}/api/anmeldung", json={"benutzer": BEN, "passwort": PW, "code": code()}, headers=KOPF, timeout=60)
return s, a.status_code
def naechster_schritt():
time.sleep(30.5 - (time.time() % 30))
def server_pid():
aus = subprocess.run(["pgrep", "-f", "start_probe_w.py"], capture_output=True, text=True).stdout.split()
return int(aus[0]) if aus else None
def speicher_mib(pid):
werte = {}
for zeile in Path(f"/proc/{pid}/status").read_text().splitlines():
if zeile.startswith(("VmRSS", "VmHWM")):
name, wert = zeile.split(":")
werte[name] = int(wert.split()[0]) / 1024
return werte
def pruefe(text, ok):
print((" OK " if ok else " BEFUND ") + text)
if not ok:
befunde.append(text)
pid = server_pid()
print(f"Server-PID {pid}, Speicher zu Beginn {speicher_mib(pid)}")
print("== 1: 422 spiegelt die Eingabe nicht mehr")
lang = "P" * 300
a = requests.post(f"{BASIS}/api/anmeldung", json={"benutzer": BEN, "passwort": lang, "code": "123456"}, headers=KOPF, timeout=10)
pruefe(f"HTTP {a.status_code}, Passwort im Koerper: {lang in a.text}", a.status_code == 422 and lang not in a.text)
print(f" Koerper: {a.text[:200]}")
print("== 2: Rumpf 512 MiB mit content-length, unangemeldet")
vor = speicher_mib(pid)
t0 = time.perf_counter()
verbindung = http.client.HTTPConnection("127.0.0.1", PORT, timeout=60)
verbindung.putrequest("POST", "/api/anmeldung")
verbindung.putheader("Origin", BASIS)
verbindung.putheader("Content-Type", "application/json")
verbindung.putheader("Content-Length", str(512 * 1024 * 1024))
verbindung.endheaders()
gesendet = 0
status = None
try:
block = b"x" * (1024 * 1024)
for _ in range(512):
verbindung.send(block)
gesendet += len(block)
except OSError as ausnahme:
print(f" Senden brach nach {gesendet / 1024 / 1024:.0f} MiB ab: {type(ausnahme).__name__}")
try:
antwort = verbindung.getresponse()
status = antwort.status
antwort.read()
except Exception as ausnahme: # noqa: BLE001
print(f" Antwort nicht lesbar: {type(ausnahme).__name__}")
verbindung.close()
nach = speicher_mib(pid)
print(f" Status {status}, {time.perf_counter() - t0:.2f} s, gesendet {gesendet / 1024 / 1024:.0f} MiB, Speicher {vor} -> {nach}")
pruefe("413 und Spitzenspeicher waechst um weniger als 50 MiB", status == 413 and nach["VmHWM"] - vor["VmHWM"] < 50)
print("== 3: Rumpf ohne content-length (chunked), unangemeldet")
vor = speicher_mib(pid)
t0 = time.perf_counter()
verbindung = http.client.HTTPConnection("127.0.0.1", PORT, timeout=60)
verbindung.putrequest("POST", "/api/anmeldung")
verbindung.putheader("Origin", BASIS)
verbindung.putheader("Content-Type", "application/json")
verbindung.putheader("Transfer-Encoding", "chunked")
verbindung.endheaders()
gesendet = 0
status = None
try:
block = b"y" * (256 * 1024)
for _ in range(800):
verbindung.send(hex(len(block))[2:].encode() + b"\r\n" + block + b"\r\n")
gesendet += len(block)
verbindung.send(b"0\r\n\r\n")
except OSError as ausnahme:
print(f" Senden brach nach {gesendet / 1024 / 1024:.1f} MiB ab: {type(ausnahme).__name__}")
try:
antwort = verbindung.getresponse()
status = antwort.status
antwort.read()
except Exception as ausnahme: # noqa: BLE001
print(f" Antwort nicht lesbar: {type(ausnahme).__name__}")
verbindung.close()
nach = speicher_mib(pid)
print(f" Status {status}, {time.perf_counter() - t0:.2f} s, gesendet {gesendet / 1024 / 1024:.1f} MiB, Speicher {vor} -> {nach}")
pruefe("413 (oder Abbruch der Verbindung) und Spitzenspeicher waechst um weniger als 50 MiB",
status in (413, None) and nach["VmHWM"] - vor["VmHWM"] < 50)
print("== 4: Killswitch unter 40 parallelen teuren Kerzenabrufen")
sitz, st = melde_an()
print(f" Anmeldung HTTP {st}")
cookie = "; ".join(f"{c.name}={c.value}" for c in sitz.cookies)
ds = sitz.get(f"{BASIS}/api/markt/datensaetze", timeout=120).json().get("datensaetze", [])
intraday = [d for d in ds if d.get("intraday")]
if not intraday:
print(" kein Intraday-Datensatz im Katalog -- Lastmessung nicht moeglich")
befunde.append("Lastmessung nicht moeglich")
else:
bt = intraday[0]["bar_type"]
teuer = f"{BASIS}/api/markt/kerzen?bar_type={bt}&aufloesung_min=60&anzahl=3000"
t0 = time.perf_counter()
einzel = requests.get(teuer, headers={"Cookie": cookie}, timeout=600)
print(f" ein einzelner teurer Abruf: HTTP {einzel.status_code}, {time.perf_counter() - t0:.1f} s")
t0 = time.perf_counter()
sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "grundlinie"}, headers=KOPF, timeout=60)
print(f" Grundlinie POST /api/killswitch/anhalten ohne Last: {time.perf_counter() - t0:.2f} s")
laeuft = [True]
messung = {"gesund": [], "anhalten": []}
def miss():
while laeuft[0]:
t = time.perf_counter()
try:
requests.get(f"{BASIS}/api/gesund", timeout=300)
messung["gesund"].append(time.perf_counter() - t)
except Exception: # noqa: BLE001
messung["gesund"].append(-1.0)
t = time.perf_counter()
try:
r = sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "lastprobe"}, headers=KOPF, timeout=300)
messung["anhalten"].append((time.perf_counter() - t, r.status_code))
except Exception: # noqa: BLE001
messung["anhalten"].append((-1.0, 0))
time.sleep(0.5)
faden = threading.Thread(target=miss, daemon=True)
faden.start()
t0 = time.perf_counter()
with ThreadPoolExecutor(max_workers=40) as pool:
codes = list(pool.map(lambda _: requests.get(teuer, headers={"Cookie": cookie}, timeout=900).status_code, range(40)))
dauer = time.perf_counter() - t0
laeuft[0] = False
faden.join(timeout=310)
zeiten = [w for w, _ in messung["anhalten"]]
print(f" 40 Kerzenabrufe: {dauer:.1f} s, Status {sorted(set(codes))}")
print(f" anhalten waehrend der Last: n={len(zeiten)}, max {max(zeiten):.2f} s, Status {sorted({s for _, s in messung['anhalten']})}")
print(f" gesund waehrend der Last: n={len(messung['gesund'])}, max {max(messung['gesund']):.2f} s")
pruefe("Killswitch antwortet unter Last in unter 5 s (vorher gemessen: 118 s)", 0 <= max(zeiten) < 5.0)
print("== 5: ueberlanger Grund landet nicht im Register")
db = ABLAGE / "registry.db"
vorher = db.stat().st_size
a = sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "G" * (1024 * 1024)}, headers=KOPF, timeout=120)
print(f" grund mit 1 MiB -> HTTP {a.status_code}, registry.db {vorher} -> {db.stat().st_size} Bytes")
pruefe("abgelehnt (413 oder 422), Register waechst nicht", a.status_code in (413, 422) and db.stat().st_size - vorher < 65536)
print("== 6: Passwortwechsel beendet die Sitzung")
pruefe(f"Sitzung gilt vor dem Wechsel (HTTP {sitz.get(f'{BASIS}/api/uebersicht', timeout=60).status_code})", True)
sys.path.insert(0, str(CODE))
os.chdir(CODE)
from trading.dashboard.zugang import Benutzer, lies_benutzer, schreibe_benutzer, setze_passwort # noqa: E402
bestand = lies_benutzer(ABLAGE / "zugang.toml")
bestand[BEN] = Benutzer(name=BEN, passwort=setze_passwort("ein-neues-passwort-2026"), totp_geheimnis=bestand[BEN].totp_geheimnis)
schreibe_benutzer(ABLAGE / "zugang.toml", bestand)
time.sleep(1.0)
danach = sitz.get(f"{BASIS}/api/uebersicht", timeout=60).status_code
pruefe(f"alte Sitzung nach dem Passwortwechsel: HTTP {danach}", danach == 401)
halt = sitz.post(f"{BASIS}/api/killswitch/anhalten", json={"grund": "nach-wechsel"}, headers=KOPF, timeout=60).status_code
pruefe(f"Killswitch mit der alten Sitzung: HTTP {halt}", halt == 401)
print(f"Speicher am Ende {speicher_mib(pid)}")
print(f"{len(befunde)} Befund(e): {befunde}")
sys.exit(2 if befunde else 0)