Server Up beheerde de Docker-daemon als root zonder enige vorm van authenticatie: elke /api/*-route was gelijk aan root-toegang op de host. - Authenticatie toegevoegd (core/auth.py): lokale accounts met scrypt-hash, sessiecookie (HttpOnly, SameSite=Strict) en optionele trusted-proxy-header SSO die alleen vanaf geconfigureerde proxy-IP's wordt vertrouwd. - before_request-guard schermt alle API-routes af; loginscherm en eerste-account-setup in de UI. - CSRF-token verplicht op elke mutatie; GET-varianten van state-wijzigende routes verwijderd (o.a. /api/docker/restart was via <img> te triggeren). - Path traversal in /api/store/install gedicht; gedeelde safe_name()-validatie voor stack-, instantie- en repo-namen. - Git-tokens worden niet meer teruggegeven via /api/repos en /api/settings (has_token-vlag); settings-PUT wist een bestaand token niet meer en kan AUTH niet overschrijven. - Git-URL's beperkt tot http(s)/ssh/scp-syntax; ext::-transport (voert een shell-commando uit) en file:// worden geweigerd. - Boilerplate-templates renderen in een SandboxedEnvironment (SSTI). - Automatisch syncen van repo's bij boot standaard uit (AUTO_SYNC_ON_BOOT), optionele commit-pinning per repo. - Waitress in plaats van de Flask-ontwikkelserver, MAX_CONTENT_LENGTH, ProxyFix, en CSP/X-Frame-Options/nosniff/Referrer-Policy headers. - Front-end libraries (Tailwind, Alpine, htmx) lokaal meegeleverd i.p.v. CDN; Google Fonts verwijderd. Werkt nu ook offline. - SSH host-key-verificatie aan (accept-new + /data/known_hosts). - Lichte /healthz voor de healthcheck i.p.v. `docker info`. - config.json en secret.key met 0600-rechten. - Poort standaard op 127.0.0.1 gebonden. - Audit-log gebruikt één gedeelde SQLite-verbinding (fd-lek per job verholpen); joblogs afgekapt op 2000 regels. - VERSION-bestand is de enige bron voor het versienummer. - pytest-suite toegevoegd (74 tests) en als stap in beide deploy-workflows. - fix-config.sh verwijderd (hardgecodeerd intern IP, overschreef config). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
75 lines
2.4 KiB
Python
75 lines
2.4 KiB
Python
"""Padvalidatie: geen enkele door de gebruiker aangeleverde naam mag buiten
|
|
LIBRARY_DIR of de git-cache kunnen wijzen."""
|
|
import pytest
|
|
|
|
from conftest import login
|
|
|
|
|
|
@pytest.mark.parametrize("naam", [
|
|
"vaultwarden", "app-1", "mijn_stack", "a.b-c", "X9",
|
|
])
|
|
def test_geldige_namen(env, naam):
|
|
assert env["core"].safe_name(naam) == naam
|
|
|
|
|
|
@pytest.mark.parametrize("naam", [
|
|
"", "..", ".", "../etc", "a/b", "/absoluut", "a\\b", "-begint-met-streepje",
|
|
"met spatie", "nul\x00byte", "x" * 65, "a$b", "$(whoami)",
|
|
])
|
|
def test_ongeldige_namen(env, naam):
|
|
assert env["core"].safe_name(naam) is None
|
|
|
|
|
|
def test_install_weigert_traversal_in_instance(client, env):
|
|
csrf = login(client)
|
|
r = client.post("/api/store/install",
|
|
json={"stack": "vaultwarden", "repo_id": "server-up",
|
|
"instance": "../../ontsnapt"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.status_code == 400
|
|
assert not (env["tmp"] / "ontsnapt").exists()
|
|
|
|
|
|
def test_install_weigert_traversal_in_stack(client):
|
|
csrf = login(client)
|
|
r = client.post("/api/store/install",
|
|
json={"stack": "../../../etc", "repo_id": "server-up",
|
|
"instance": "test"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.get_json()["ok"] is False
|
|
|
|
|
|
def test_preview_weigert_traversal(client):
|
|
csrf = login(client)
|
|
r = client.post("/api/store/preview",
|
|
json={"stack": "../..", "repo_id": "server-up"},
|
|
headers={"X-CSRF-Token": csrf})
|
|
assert r.get_json()["ok"] is False
|
|
|
|
|
|
@pytest.mark.parametrize("route", [
|
|
"/api/stacks/{}/env", "/api/stacks/{}/compose", "/api/stacks/{}/logs",
|
|
])
|
|
def test_stackroutes_weigeren_ongeldige_naam(client, route):
|
|
login(client)
|
|
r = client.get(route.format(".."))
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_stackactie_weigert_ongeldige_naam(client):
|
|
csrf = login(client)
|
|
r = client.post("/api/stacks/../start", headers={"X-CSRF-Token": csrf})
|
|
# Werkzeug normaliseert '..' weg; wat er ook overblijft mag nooit 2xx zijn.
|
|
assert r.status_code >= 400
|
|
|
|
|
|
def test_lege_stacknaam_raakt_de_library_zelf_niet(client, env):
|
|
"""`lib / ""` resolvet naar de library zelf — verwijderen zou alles wissen."""
|
|
lib, d = _stack_dir(env, "")
|
|
assert d is None
|
|
|
|
|
|
def _stack_dir(env, naam):
|
|
import app as app_module
|
|
with app_module.app.test_request_context():
|
|
return app_module._stack_dir(naam)
|