Skip to content

Commit 8f2e349

Browse files
committed
fix: document webapp limits and release provenance
1 parent 3734692 commit 8f2e349

9 files changed

Lines changed: 121 additions & 30 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
66
## [Unreleased]
77

88
### Build / Packaging
9+
- `releases/v3.0.0/PROVENANCE.md` dokumentiert den tatsächlichen lokalen Artefaktstand. Das historische v3.0.0-Bundle ist wegen fehlender Commitkette und einer vom gespeicherten Wert abweichenden EXE-SHA-256 bis zu einem reproduzierbaren Neubuild gesperrt; es wurde weder gelöscht noch durch eine neue Version ersetzt.
910
- `build_exe.bat` ergänzt einen reproduzierbaren PyInstaller-Build mit lokalem Workpath (lokales Build-Verzeichnis), zentralem Build-Exclude-Scanner und Kopie der fertigen EXE nach `dist\MethodenAnalyser.exe` sowie `MethodenAnalyser.exe`.
1011
- `START.bat` startet unter Windows bevorzugt die gebaute EXE und fällt erst danach auf den Python-Start zurück.
1112
- `MethodenAnalyser.spec` nutzt relative Projektpfade, bündelt Icon und `locales/` und deaktiviert UPX.
@@ -42,6 +43,8 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
4243
- `releases/windowsstore/store_settings.json`, `BUILD.md` und die DE/EN-Store-Listings sind auf den realen Projektstand, aktuelle GitHub-URLs und den dokumentierten Pretest-Workflow synchronisiert.
4344

4445
### Hinzugefügt / Added
46+
- `tests/test_webapp_server.py` sichert jetzt die HTTP-Request-Grenze (413), ungültiges Base64, beschädigte und Python-lose ZIPs, Einzeldatei-/Gesamtgrößen, Dateianzahl sowie Windows-Backslash-Traversal ab.
47+
- Die lokale Web-Runtime weist für den bewussten LAN-Testmodus auf lokales HTTP ohne Authentifizierung/TLS hin. WEBAPP, README-Paar, Portierungsplan und Privacy-Policy halten den Loopback-Standard, die vertrauenswürdige-LAN-Grenze und den Nicht-Ziel-Status der Mobile-Produktlinie konsistent fest.
4548
- GitHub-Actions-Smoke-Matrix prüft den Quellstand jetzt auf Windows (Python 3.10-3.12) sowie zusätzlich auf Ubuntu und macOS (Python 3.11), inklusive Compile-, Tkinter-Import- und `unittest`-Smoke.
4649
- Lokaler Web/PWA-Companion unter `webapp/` mit Snippet-/Einzeldatei-Analyse über den bestehenden Python-Analyse-Kern.
4750
- Web/PWA-Companion kann jetzt auch kleine ZIP-Archive mit `.py`-Dateien lokal hochladen, temporär entpacken und als Mini-Projekt analysieren.

PRIVACY_POLICY.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Privacy Policy — MethodenAnalyser
22

3-
**Last updated:** March 13, 2026
3+
**Last updated:** July 22, 2026
44
**Publisher:** Geiger
55

66
## Overview
@@ -21,6 +21,10 @@ The application analyzes Python source files that you choose to open or scan. Al
2121

2222
MethodenAnalyser does not connect to any external services or the internet. It is a fully offline application.
2323

24+
## Optional Local Web Helper
25+
26+
The optional browser helper listens on `127.0.0.1` by default and processes uploaded code only in the local Python process. An explicit `--host 0.0.0.0` start is a technical LAN test mode, not a cloud service or mobile product. It uses local HTTP without authentication or TLS; use it only on a trusted private network and do not submit sensitive code from an untrusted network.
27+
2428
## Local Storage
2529

2630
The application may store user preferences (e.g., window size, last opened directory) locally on your device. No data is synced to external servers.
@@ -43,7 +47,7 @@ For questions about this privacy policy:
4347

4448
# Datenschutzrichtlinie — MethodenAnalyser
4549

46-
**Zuletzt aktualisiert:** 13. März 2026
50+
**Zuletzt aktualisiert:** 22. Juli 2026
4751
**Herausgeber:** Geiger
4852

4953
## Überblick
@@ -64,6 +68,10 @@ Die Anwendung analysiert Python-Quelldateien, die Sie öffnen oder scannen möch
6468

6569
MethodenAnalyser stellt keine Verbindung zu externen Diensten oder dem Internet her. Es handelt sich um eine vollständig offline nutzbare Anwendung.
6670

71+
## Optionaler lokaler Web-Hilfsmodus
72+
73+
Der optionale Browser-Hilfsmodus bindet standardmäßig an `127.0.0.1` und verarbeitet hochgeladenen Code nur im lokalen Python-Prozess. Ein expliziter Start mit `--host 0.0.0.0` ist ein technischer LAN-Testmodus, kein Cloud-Dienst und keine Mobile-Produktlinie. Er nutzt lokales HTTP ohne Authentifizierung oder TLS; deshalb nur in einem vertrauenswürdigen privaten Netz verwenden und keinen sensitiven Code aus einem fremden/offenen Netz übermitteln.
74+
6775
## Lokale Speicherung
6876

6977
Die Anwendung kann Benutzereinstellungen (z. B. Fenstergröße, zuletzt geöffnetes Verzeichnis) lokal auf Ihrem Gerät speichern. Es werden keine Daten mit externen Servern synchronisiert.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ Or double-click `START_WEBAPP.bat` on Windows. The server runs at `http://127.0.
114114
```bash
115115
python webapp/server.py --host 0.0.0.0 --port 8765
116116
```
117-
For further details, consult [WEBAPP.md](WEBAPP.md).
117+
This deliberate LAN test mode uses local HTTP without authentication or TLS. Use it only on a trusted private network; it is neither a cloud service nor a mobile product line. For further details, consult [WEBAPP.md](WEBAPP.md).
118118

119119
### 5. macOS and Linux Source Verification
120120
While the GUI is optimized for Windows (using Tkinter), the codebase is fully verified to run from source on macOS and Linux. You can run unit tests and compile checks with:
@@ -172,7 +172,7 @@ WINDOW_GEOMETRY = "1200x700" # Desktop window dimensions
172172

173173
## Data & Privacy
174174

175-
MethodenAnalyser operates 100% locally. Your Python code, local file paths, and analysis results are never sent over the internet. There are no analytics, cloud integrations, telemetry features, or third-party tracking scripts.
175+
MethodenAnalyser operates 100% locally. Your Python code, local file paths, and analysis results are never sent over the internet. There are no analytics, cloud integrations, telemetry features, or third-party tracking scripts. The optional web helper binds to `127.0.0.1` by default. Its explicit `--host 0.0.0.0` LAN test mode has no authentication or TLS and must be used only in a trusted private network.
176176

177177
Build, packaging, and sign-related files are configured in `.gitignore` to stay outside of the version control system.
178178

README_de.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ python webapp/server.py --host 0.0.0.0 --port 8765
115115
```
116116

117117
Die lokale Weboberfläche kann LAN-URLs anzeigen, falls der Server bewusst im Netzwerk freigegeben wird. Das bleibt ein technischer Testpfad, kein geplantes Android-/iOS-Produkt.
118+
Dieser bewusste LAN-Testmodus nutzt lokales HTTP ohne Authentifizierung oder TLS und gehört ausschließlich in ein vertrauenswürdiges privates Netz. Er ist weder ein Cloud-Dienst noch eine Mobile-Produktlinie. Details stehen in [WEBAPP.md](WEBAPP.md).
118119
Zusätzlich bündelt die **PWA-Testkarte** Install-Status, Service-Worker-, Speicher- und Viewport-Diagnostik in einer kopierbaren Kurzfassung für lokale Browser-Smokes.
119120

120121
### macOS- und Linux-Smoke
@@ -171,7 +172,7 @@ WINDOW_GEOMETRY = "1200x700" # Fenstergröße
171172

172173
## Datenschutz / Privacy
173174

174-
MethodenAnalyser arbeitet vollständig lokal. Der ausgewählte Python-Code, Dateipfade und Analyseergebnisse werden nicht an den Entwickler oder externe Dienste übertragen.
175+
MethodenAnalyser arbeitet vollständig lokal. Der ausgewählte Python-Code, Dateipfade und Analyseergebnisse werden nicht an den Entwickler oder externe Dienste übertragen. Der optionale Web-Hilfsmodus bindet standardmäßig an `127.0.0.1`; sein expliziter LAN-Testmodus `--host 0.0.0.0` hat keine Authentifizierung und kein TLS und darf nur in einem vertrauenswürdigen privaten Netz verwendet werden.
175176

176177
Release-Artefakte wie EXE-Dateien, lokale Builds und Store-Pakete bleiben außerhalb des Git-Repositorys und gehören in lokale `releases/`-Ordner oder GitHub Releases.
177178

RELEASES.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# RELEASES - MethodenAnalyser
22

3-
Stand: 2026-05-01
4-
Aktuelles lokales EXE-Bundle: `v3.0.0`
3+
Stand: 2026-07-22
4+
Direktdownload-Bundle `v3.0.0`: **gesperrter Legacy-Stand, nicht aktuell freigegeben**.
55

66
## Struktur
77

@@ -16,15 +16,23 @@ releases/
1616
`-- ...
1717
```
1818

19-
## Aktueller Stand
19+
## Aktueller Stand und Abgrenzung
2020

21-
- `dist/MethodenAnalyser.exe` ist der frische lokale Build aus dem aktuellen Quellstand.
22-
- `releases/v3.0.0/` enthält die lokalen GitHub-/Direktdownload-Artefakte.
21+
- `releases/v3.0.0/` enthält historische lokale GitHub-/Direktdownload-Artefakte. Ihr genauer Auditstand steht in [`releases/v3.0.0/PROVENANCE.md`](releases/v3.0.0/PROVENANCE.md).
22+
- `dist/MethodenAnalyser.exe` ist derzeit nicht vorhanden. Ein Root-EXE oder ein historisches v3.0.0-EXE darf deshalb nicht als frischer `dist`-Build ausgegeben werden.
23+
- Das v3.0.0-EXE weicht von der gespeicherten SHA-256-Zeile ab; Source-ZIP und Changelog haben keinen belegten Quellcommit. Bis zu einem sauberen Neubuild mit Commit- und Hashnachweis ist das Bundle nicht für Direktdownload oder Promotion freigegeben.
2324
- `releases/windowsstore/` bleibt getrennt für den MSIX-/Store-Workflow.
25+
- Das MSIX-/WACK-Gate ist ein eigener Ablauf und ersetzt keine Direktdownload-Provenienz.
2426
- Der Ordner `releases/` ist absichtlich per `.gitignore` ausgeschlossen. Verteilbare Binärartefakte gehören in lokale Release-Ordner oder GitHub Releases, nicht in den Git-Quellbaum.
2527

28+
## Nächster freigegebener Buildablauf
29+
30+
1. Einen konkreten, geprüften Quellcommit und die Buildumgebung festhalten.
31+
2. EXE, Source-ZIP und Changelog aus genau diesem Stand erzeugen und ihren Start-/Inhaltscheck dokumentieren.
32+
3. Erst dann `SHA256SUMS.txt` ausschließlich aus diesem vollständigen Artefaktsatz erzeugen, gegenlesen und eine neue Aktualitätsentscheidung treffen.
33+
4. Store/MSIX nur als separaten, zusätzlich zu belegenden Gate-Schritt behandeln.
34+
2635
## Letzte Pflege
2736

28-
- 2026-04-29: Lokales EXE-Bundle, Source-ZIP und Checksummen aus dem aktuellen Arbeitsstand aktualisiert.
37+
- 2026-07-22: Provenienz-Audit: v3.0.0 wegen fehlender Commit-Kette und abweichender EXE-Checksumme gesperrt; keine Dateien gelöscht, keine neue Version oder neue Checksumme erzeugt.
2938
- 2026-05-01: Release-Dokumentation an die GitHub-Policy angepasst: Artefakte bleiben lokal oder in GitHub Releases.
30-
- 2026-05-16: GitHub-Hygiene geprüft; Build-, Cache-, Coverage- und Signierartefakte bleiben ignoriert, verteilbare Dateien weiterhin außerhalb des Quellbaums.

WEBAPP.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MethodenAnalyser Lokale Weboberfläche
22

3-
Stand: 2026-05-24
3+
Stand: 2026-07-22
44

55
Die lokale Weboberfläche ist ein Hilfs-/Demo-Modus für schnelle Snippet-, Einzeldatei- und kleine ZIP-Analysen auf demselben Rechner. Sie ersetzt nicht die Desktop-App für ganze Projektordner, ist keine Companion-App und keine eigene Mobile-Produktlinie. Sie nutzt denselben Analysekern und dasselbe JSON-Format wie CLI und GUI.
66

@@ -30,7 +30,7 @@ Für Android-/iOS-Tests im selben WLAN:
3030
python webapp/server.py --host 0.0.0.0 --port 8765
3131
```
3232

33-
Die Oberfläche kann dann erkannte LAN-URLs anzeigen. Das ist nur ein technischer Testpfad für lokale Browser-Smokes, kein geplanter Android-/iOS-Releasepfad.
33+
Die Oberfläche kann dann erkannte LAN-URLs anzeigen. Das ist nur ein technischer Testpfad für lokale Browser-Smokes, kein geplanter Android-/iOS-Releasepfad. Der LAN-Modus nutzt bewusst nur lokales HTTP: Es gibt keine Authentifizierung und kein TLS. Deshalb nur in einem vertrauenswürdigen, eigenen Netz starten und keinen Quellcode oder Bericht über fremde/offene Netze senden.
3434

3535
## Funktionen
3636

@@ -83,12 +83,14 @@ Response:
8383
}
8484
```
8585

86-
## Datenschutz
86+
## Datenschutz und LAN-Grenze
8787

88-
Der Web Companion läuft auf `127.0.0.1`. Code wird an den lokalen Python-Prozess gesendet, nicht an externe Dienste. Es gibt keine Telemetrie, keine Cloud-Synchronisierung und keine externen CDN-Abhängigkeiten.
88+
Der Standardstart bindet ausschließlich an `127.0.0.1`; damit ist die Oberfläche nur auf diesem Rechner erreichbar. Code wird an den lokalen Python-Prozess gesendet, nicht an externe Dienste. Es gibt keine Telemetrie, keine Cloud-Synchronisierung und keine externen CDN-Abhängigkeiten.
89+
90+
`--host 0.0.0.0` oder eine andere LAN-Adresse ist eine bewusste Ausnahme für einen technischen Test im selben vertrauenswürdigen Netz. Der Server bietet dabei weder Authentifizierung noch TLS und ist kein Internet-Deployment. Alle Clients, die die LAN-URL erreichen, können die lokale Analyse-API verwenden; keine sensitiven Dateien oder Berichte über ein fremdes/offenes Netz übertragen.
8991

9092
## Grenzen
9193

9294
- Große Projektordner bleiben Aufgabe der Desktop-/CLI-Version.
9395
- ZIP-Uploads sind bewusst klein gehalten: nur `.py`-Dateien, begrenzte Archivgröße und keine beliebigen Binärdateien.
94-
- Android und iOS sollen über dieselbe PWA-Linie getestet werden; native Apps sind weiterhin kein Ziel.
96+
- Android und iOS können nur über diesen technischen PWA-/Browser-Smoke im selben WLAN geprüft werden; native Apps und eine Mobile-Produktlinie sind weiterhin Nicht-Ziele.

tests/test_webapp_server.py

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import base64
22
import io
33
import json
4+
import tempfile
45
import threading
56
import unittest
67
import urllib.error
@@ -10,24 +11,32 @@
1011
from pathlib import Path
1112
from urllib.request import urlopen
1213

13-
from webapp.server import MethodenAnalyserPwaHandler, analyze_payload, build_runtime_info
14+
from webapp.server import (
15+
MAX_REQUEST_SIZE,
16+
MAX_ZIP_FILE_COUNT,
17+
MAX_ZIP_MEMBER_SIZE,
18+
MethodenAnalyserPwaHandler,
19+
_extract_python_zip,
20+
analyze_payload,
21+
build_runtime_info,
22+
)
1423

1524

1625
PROJECT_ROOT = Path(__file__).resolve().parents[1]
1726

1827

28+
def make_zip_payload(files: dict[str, str | bytes], filename: str = "sample.zip") -> dict[str, str]:
29+
buffer = io.BytesIO()
30+
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
31+
for path, content in files.items():
32+
archive.writestr(path, content)
33+
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
34+
return {"source_kind": "zip", "filename": filename, "zip_base64": encoded}
35+
36+
1937
class MethodenAnalyserWebappServerTests(unittest.TestCase):
2038
def make_zip_payload(self, files: dict[str, str], filename: str = "sample.zip") -> dict[str, str]:
21-
buffer = io.BytesIO()
22-
with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive:
23-
for path, content in files.items():
24-
archive.writestr(path, content)
25-
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
26-
return {
27-
"source_kind": "zip",
28-
"filename": filename,
29-
"zip_base64": encoded,
30-
}
39+
return make_zip_payload(files, filename)
3140

3241
def test_snippet_payload_returns_json_report(self) -> None:
3342
payload = analyze_payload(
@@ -99,6 +108,7 @@ def test_runtime_info_for_local_only_server(self) -> None:
99108
info = build_runtime_info("127.0.0.1", 8765, address_supplier=lambda: ["192.168.0.5"])
100109

101110
self.assertTrue(info["local_only"])
111+
self.assertFalse(info["lan_enabled"])
102112
self.assertFalse(info["candidate_urls"])
103113
self.assertEqual(info["mobile_command"], "python webapp/server.py --host 0.0.0.0 --port 8765")
104114

@@ -110,13 +120,49 @@ def test_runtime_info_for_wildcard_server_includes_lan_urls(self) -> None:
110120
)
111121

112122
self.assertFalse(info["local_only"])
123+
self.assertTrue(info["lan_enabled"])
113124
self.assertTrue(info["mobile_ready"])
125+
self.assertIn("keine Authentifizierung", info["mobile_notes"]["network"])
114126
self.assertEqual(
115127
info["candidate_urls"],
116128
["http://10.0.0.8:8765/", "http://192.168.0.5:8765/"],
117129
)
118130

119131

132+
class MethodenAnalyserZipBoundaryTests(unittest.TestCase):
133+
def test_rejects_invalid_base64_and_corrupt_zip(self) -> None:
134+
with self.assertRaisesRegex(ValueError, "gültiges Base64"):
135+
analyze_payload({"source_kind": "zip", "zip_base64": "%%%"})
136+
with self.assertRaises(zipfile.BadZipFile):
137+
analyze_payload({"source_kind": "zip", "zip_base64": base64.b64encode(b"not-a-zip").decode("ascii")})
138+
139+
def test_rejects_archive_without_python_files(self) -> None:
140+
with self.assertRaisesRegex(ValueError, "keine Python-Dateien"):
141+
analyze_payload(make_zip_payload({"README.txt": "keine Quelle"}))
142+
143+
def test_rejects_member_total_and_file_count_limits(self) -> None:
144+
with self.assertRaisesRegex(ValueError, "größer als 512 KB"):
145+
analyze_payload(make_zip_payload({"large.py": b"x" * (MAX_ZIP_MEMBER_SIZE + 1)}))
146+
147+
total_files = {f"part_{index}.py": b"x" * MAX_ZIP_MEMBER_SIZE for index in range(4)}
148+
total_files["tail.py"] = b"x"
149+
with self.assertRaisesRegex(ValueError, "mehr als 2048 KB"):
150+
analyze_payload(make_zip_payload(total_files))
151+
152+
too_many_files = {f"module_{index}.py": "pass\n" for index in range(MAX_ZIP_FILE_COUNT + 1)}
153+
with self.assertRaisesRegex(ValueError, "mehr als 64 Python-Dateien"):
154+
analyze_payload(make_zip_payload(too_many_files))
155+
156+
def test_rejects_windows_traversal_without_extracting_outside_root(self) -> None:
157+
payload = make_zip_payload({r"..\escape.py": "print('no')\n"})
158+
zip_bytes = base64.b64decode(payload["zip_base64"])
159+
with tempfile.TemporaryDirectory() as temp_dir:
160+
temp_root = Path(temp_dir) / "extract-root"
161+
with self.assertRaisesRegex(ValueError, "Unsicherer ZIP-Pfad"):
162+
_extract_python_zip(zip_bytes, temp_root)
163+
self.assertFalse((Path(temp_dir) / "escape.py").exists())
164+
165+
120166
class MethodenAnalyserInvalidBodyTests(unittest.TestCase):
121167
"""Verifies that malformed POST bodies return 400, not 500."""
122168

@@ -160,6 +206,12 @@ def test_valid_but_empty_json_body_returns_400(self) -> None:
160206
self.assertEqual(status, 400)
161207
self.assertFalse(payload["ok"])
162208

209+
def test_request_larger_than_limit_returns_413(self) -> None:
210+
status, payload = self.post_raw(b"x" * (MAX_REQUEST_SIZE + 1))
211+
self.assertEqual(status, 413)
212+
self.assertFalse(payload["ok"])
213+
self.assertIn("2 MB", payload["error"])
214+
163215

164216
class MethodenAnalyserStaticHttpTests(unittest.TestCase):
165217
@classmethod
@@ -232,6 +284,21 @@ def test_runtime_endpoint_returns_mobile_metadata(self) -> None:
232284
self.assertIn('"local_only": true', payload)
233285
self.assertIn('"local_url": "http://127.0.0.1:', payload)
234286
self.assertIn('"mobile_command": "python webapp/server.py --host 0.0.0.0 --port', payload)
287+
self.assertIn('"lan_enabled": false', payload)
288+
289+
def test_documentation_matches_the_lan_security_boundary(self) -> None:
290+
webapp_doc = (PROJECT_ROOT / "WEBAPP.md").read_text(encoding="utf-8")
291+
privacy_doc = (PROJECT_ROOT / "PRIVACY_POLICY.md").read_text(encoding="utf-8")
292+
readme_en = (PROJECT_ROOT / "README.md").read_text(encoding="utf-8")
293+
readme_de = (PROJECT_ROOT / "README_de.md").read_text(encoding="utf-8")
294+
295+
self.assertIn("127.0.0.1", webapp_doc)
296+
self.assertIn("0.0.0.0", webapp_doc)
297+
self.assertIn("keine Authentifizierung und kein TLS", webapp_doc)
298+
self.assertIn("vertrauenswürdigen", webapp_doc)
299+
self.assertIn("ohne Authentifizierung oder TLS", privacy_doc)
300+
self.assertIn("authentication or TLS", readme_en)
301+
self.assertIn("Authentifizierung oder TLS", readme_de)
235302

236303

237304
if __name__ == "__main__":

0 commit comments

Comments
 (0)