diff --git a/documentation/audits/evidence-p1fixes-2026-09-15/A1-docker-kill-restart-policy.txt b/documentation/audits/evidence-p1fixes-2026-09-15/A1-docker-kill-restart-policy.txt new file mode 100644 index 00000000..d1797c44 --- /dev/null +++ b/documentation/audits/evidence-p1fixes-2026-09-15/A1-docker-kill-restart-policy.txt @@ -0,0 +1,25 @@ + are supported and installed on your system. ++ docker rm -f a1spike-us a1spike-al ++ docker run -d --name a1spike-us --restart unless-stopped alpine:3 sleep 100000 +Unable to find image 'alpine:3' locally +3: Pulling from library/alpine +Digest: sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b +Status: Downloaded newer image for alpine:3 +++ docker inspect -f '{{.Config.Image}}' a1spike-us ++ docker run -d --name a1spike-al --restart always alpine:3 sleep 100000 ++ date -u +%FT%TZ +2026-09-15T07:35:50Z ++ docker kill a1spike-us a1spike-al +a1spike-us +a1spike-al ++ sleep 60 ++ date -u +%FT%TZ +2026-09-15T07:36:50Z ++ docker inspect -f '{{.Name}} policy={{.HostConfig.RestartPolicy.Name}} status={{.State.Status}} exit={{.State.ExitCode}} restarts={{.RestartCount}}' a1spike-us a1spike-al +/a1spike-us policy=unless-stopped status=exited exit=137 restarts=0 +/a1spike-al policy=always status=exited exit=137 restarts=0 ++ docker version --format '{{.Server.Version}}' +29.8.0 ++ docker rm -f a1spike-us a1spike-al +a1spike-us +a1spike-al diff --git a/documentation/audits/evidence-p1fixes-2026-09-15/B1-filebrowser-spike.txt b/documentation/audits/evidence-p1fixes-2026-09-15/B1-filebrowser-spike.txt new file mode 100644 index 00000000..15816a33 --- /dev/null +++ b/documentation/audits/evidence-p1fixes-2026-09-15/B1-filebrowser-spike.txt @@ -0,0 +1,135 @@ +# B.1 FileBrowser Quantum admin-password spike — 2026-09-15, scratch LXC 9202 on demo-hp +# Throwaway containers fbspike-fresh / fbspike-existing on 127.0.0.1:18089 inside the guest; config rendered like RenderFileBrowserConfig; same entrypoint wrapper as RenderFileBrowserCompose. +# Generated passwords redacted: only length printed. Login probe = POST /api/auth/login?username=admin, header X-Password. +# 'tr: write error: Broken pipe' lines are the password generator (harmless). + +2026-09-15T07:36:38Z +image: gtstef/filebrowser:1.3.3-stable +sha256:095fd20d87be10c175a5fa614aa1d0a94aa07eafa80f251a3069640e0a7c51f7 +== A1 FRESH db, config key auth.adminPassword +tr: write error: Broken pipe + pw len=16 + admin/admin -> 401 + admin/ -> 200 + admin/wrong -> 401 +2026/09/15 07:36:39 [INFO ] Auth Methods : [password] +2026/09/15 07:36:39 [INFO ] Resetting admin user to default username and password. +2026/09/15 07:36:40 POST | 401 | 172.17.0.1 | N/A | 72ms | "/api/auth/login?username=admin" +2026/09/15 07:36:40 POST | 200 | 172.17.0.1 | N/A | 80ms | "/api/auth/login?username=admin" +2026/09/15 07:36:40 POST | 401 | 172.17.0.1 | N/A | 63ms | "/api/auth/login?username=admin" +== A2 FRESH db, env FILEBROWSER_ADMIN_PASSWORD (no config key) +tr: write error: Broken pipe + pw len=16 + admin/admin -> 401 + admin/ -> 200 +== A3 FRESH db, NO key, NO env (today's controller shape) + admin/admin -> 200 + admin/wrong -> 401 +== A4 short password via config key (length rule) + (not ready) +2026/09/15 07:36:45 [INFO ] cache directory setup successfully: tmp +2026/09/15 07:36:45 [FATAL] store.Users.Save: password must be at least 5 characters long +2026/09/15 07:36:45 [DEBUG] Default SQLite driver initialized +2026/09/15 07:36:45 [WARN ] database file could not be found. If this is unexpected, please set the FILEBROWSER_DATABASE environment variable to the correct path. + admin/abc -> 000 + admin/admin -> 000 +2026/09/15 07:36:45 [FATAL] store.Users.Save: password must be at least 5 characters long +== B0 EXISTING db: create with defaults + admin/admin -> 200 (baseline, expect 200) +== B1 EXISTING db + config key added, restart +tr: write error: Broken pipe + pw len=16 + admin/admin -> 401 + admin/ -> 200 +== B2 EXISTING db + env var, restart (config key removed) +tr: write error: Broken pipe + admin/admin -> 401 + admin/ -> 401 + admin/ -> 200 +== B3 EXISTING db: CLI 'set -u admin,' with container stopped +tr: write error: Broken pipe +2026/09/15 07:37:51 [DEBUG] Default SQLite driver initialized +2026/09/15 07:37:51 [INFO ] cache directory setup successfully: tmp +successfully updated user: admin + rc=0 + admin/admin -> 401 + admin/ -> 401 + admin/ -> 401 + admin/ -> 200 +== B4 EXISTING db: CLI 'set' via docker exec on RUNNING container +tr: write error: Broken pipe +2026/09/15 07:37:53 [DEBUG] Default SQLite driver initialized +2026/09/15 07:37:54 [INFO ] cache directory setup successfully: tmp +2026/09/15 07:37:54 the database is locked, please close all other instances of filebrowser before starting. + admin/ -> 200 + admin/ -> 401 + after restart: admin/ -> 200 + admin/ -> 401 +== C OVERWRITE: hand-set pw (current db state) + config key with a DIFFERENT value, restart + (db currently holds whichever of P4/P5 answered 200 above) +tr: write error: Broken pipe + admin/ -> 401 + admin/ -> 401 + admin/ -> 200 + admin/admin -> 401 +== C2 same via env var with a DIFFERENT value +tr: write error: Broken pipe + admin/ -> 401 + admin/ -> 401 + admin/ -> 200 +== TEARDOWN +0 +ls: cannot access '/tmp/fbspike': No such file or directory +filebrowser Up 35 hours (healthy) +2026-09-15T07:38:07Z + +######## PART 2 — REST API change attempt WITHOUT X-Password (refused), restart survival +2026-09-15T07:38:40Z +== D0 existing db, defaults + admin/admin -> 200 + token len=355 + GET /api/users?id=self -> {"editorQuickSave":false,"hideSidebarFileActions":false,"disableQuickToggles":false,"disableSearchOptions":false,"deleteWithoutConfirming":false,"preview":{"disableHideSidebar":false,"image":true,"video":true,"audio":true,"motionVideoPreview":true,"office":true,"popup":true,"autoplayMedia":true,"defaultMediaPlayer":false,"folder":true,"models":true},"stickySidebar":true,"darkMode":true,"locale":"e + new pw len=13 + PUT form1 /api/users?id=1 -> 401 {"status":401,"message":"X-Password header is required to confirm your password"} + admin/admin -> 200 admin/ -> 401 + PUT form2 /api/users?id=1 -> 401 {"status":401,"message":"X-Password header is required to confirm your password"} + admin/admin -> 200 admin/ -> 401 + PUT form3 /api/users?id=self -> 400 {"status":400,"message":"no user not found, please provide a valid id or username"} + admin/admin -> 200 admin/ -> 401 +== D1 restart with NO key, NO env: does the API-set password survive? + admin/admin -> 200 admin/ -> 401 +2026/09/15 07:38:40 [INFO ] Resetting admin user to default username and password. +== TEARDOWN +0 +ls: cannot access '/tmp/fbspike2': No such file or directory +filebrowser Up 35 hours (healthy) +2026-09-15T07:38:44Z + +######## PART 3 — REST API change WITH X-Password: , restart with no key, then key with different value +# (the 'Resetting admin user' line under D1 is from the container's FIRST start — docker logs keeps history across restart; the measured logins show no reset) +2026-09-15T07:39:00Z +== D0 existing db, defaults + admin/admin -> 200 + token len=355 + GET /api/users?id=self -> {"editorQuickSave":false,"hideSidebarFileActions":false,"disableQuickToggles":false,"disableSearchOptions":false,"deleteWithoutConfirming":false,"preview":{"disableHideSidebar":false,"image":true,"video":true,"audio":true,"motionVideoPreview":true,"office":true,"popup":true,"autoplayMedia":true,"defaultMediaPlayer":false,"folder":true,"models":true},"stickySidebar":true,"darkMode":true,"locale":"e + new pw len=16 + PUT form1 /api/users?id=1 -> 204 + admin/admin -> 401 admin/ -> 200 +== D1 restart with NO key, NO env (hand-set via API): does the API-set password survive? + admin/admin -> 401 admin/ -> 200 +2026/09/15 07:39:01 [INFO ] Resetting admin user to default username and password. +== D2 same db, now add config key with DIFFERENT value, restart (operator hand-set vs key) + key pw len=16 + admin/ -> 401 admin/ -> 200 admin/admin -> 401 +== TEARDOWN +0 +ls: cannot access '/tmp/fbspike2': No such file or directory +filebrowser Up 35 hours (healthy) +2026-09-15T07:39:06Z + +######## SUMMARY (measured, image gtstef/filebrowser:1.3.3-stable) +(a) FRESH db: config.yaml `auth: adminPassword: ` (A1) OR env FILEBROWSER_ADMIN_PASSWORD (A2) sets it at first start: admin/admin 401, 200. No key/no env (today's shape, A3): admin/admin 200. +(b) EXISTING db (admin/admin 200 proven): the SAME config key (B1) or env var (B2) re-applies on restart: admin/admin 401, new 200. CLI `filebrowser set -u admin, -a` works only with the container STOPPED (B3: 200); on a running container it fails "database is locked" (B4, no change). REST: PUT /api/users?id=1 {"which":["password"],"data":{"id":1,"username":"admin","password":""}} with Bearer token AND header X-Password: -> 204 (D0); without X-Password -> 401 "X-Password header is required". +OVERWRITE: with the key (C, D2) or env (C2) present, EVERY start resets the admin password to the configured value — a hand-set password (CLI or API) is overwritten (hand-set 401, key 200). With NO key/env, a hand-set password survives restart (B3->B4 restart, D1). +Length rule: min 5 chars; a shorter configured value is FATAL at start ("store.Users.Save: password must be at least 5 characters long", container never serves — A4). 16-char alphanumeric accepted. +TEARDOWN: fbspike-* containers removed, /tmp/fbspike* removed in 9202 (0 fbspike containers listed); 9202's own `filebrowser` untouched (Up 35 hours throughout). diff --git a/documentation/audits/evidence-p1fixes-2026-09-15/E1-vaultwarden-spike.txt b/documentation/audits/evidence-p1fixes-2026-09-15/E1-vaultwarden-spike.txt new file mode 100644 index 00000000..c846704f --- /dev/null +++ b/documentation/audits/evidence-p1fixes-2026-09-15/E1-vaultwarden-spike.txt @@ -0,0 +1,61 @@ +E.1 Vaultwarden invite-without-SMTP spike — 2026-09-15 ~07:35-07:37Z, demo-hp scratch LXC 9202 +Container vwspike: vaultwarden/server:1.36.0-alpine (catalog image), env as catalog template with SIGNUPS_ALLOWED=false, +ADMIN_TOKEN=, _ENABLE_SMTP=false + SMTP_* empty (catalog default when app-email is off), 127.0.0.1:18480, data /tmp/vwspike. +Admin page defaults observed: invitations_allowed CHECKED, signups_verify unchecked, signups_allowed unchecked, _enable_smtp unchecked. + +=== /api/config (features) === +{"environment":{"api":"http://127.0.0.1:18480/api","cloudRegion":null,"identity":"http://127.0.0.1:18480/identity","notifications":"http://127.0.0.1:18480/notifications","sso":"","vault":"http://127.0.0.1:18480"},"featureStates":{"pm-19148-innovation-archive":true},"gitHash":"f21a3ada","object":"config","push":{"pushTechnology":0,"vapidPublicKey":null},"server":{"name":"Vaultwarden","url":"https://github.com/dani-garcia/vaultwarden"},"settings":{"disableUserRegistration":false},"version":"2025.12.0"} +=== 1. stranger registration === +--- POST /identity/accounts/register email=idegen.probe@example.com +{"message":"Registration not allowed or user already exists","validationErrors":{"":["Registration not allowed or user already exists"]},"errorModel":{"message":"Registration not allowed or user already exists","object":"error"},"error":"","error_description":"","exceptionMessage":null,"exceptionStackTrace":null,"innerExceptionMessage":null,"object":"error"} +HTTP 400 + +--- POST /api/accounts/register email=idegen.probe@example.com +{"error":{"code":404,"description":"The requested resource could not be found.","reason":"Not Found"}} +HTTP 404 + +=== 2. admin login + invite === +POST /admin login HTTP 200 +{"_status":1,"avatarColor":null,"creationDate":"2026-09-15T07:36:18.177612Z","culture":"en-US","email":"meghivott.probe@example.com","emailVerified":true,"forcePasswordReset":false,"id":"8dbdfec6-2cfb-4794-9156-5a67cb27b57b","key":"","name":"meghivott.probe@example.com","object":"profile","organizations":[],"premium":true,"premiumFromOrganization":false,"privateKey":null,"providerOrganizations":[],"providers":[],"securityStamp":"4f4d1ee9-1295-4263-8fc2-96cb79236f86","twoFactorEnabled":false,"usesKeyConnector":false} +POST /admin/invite HTTP 200 + +[{"_status":1,"avatarColor":null,"createdAt":"2026-09-15 09:36:18 CEST","creationDate":"2026-09-15T07:36:18.177612Z","culture":"en-US","email":"meghivott.probe@example.com","emailVerified":true,"forcePasswordReset":false,"id":"8dbdfec6-2cfb-4794-9156-5a67cb27b57b","key":"","lastActive":null,"name":"meghivott.probe@example.com","object":"profile","organizations":[],"premium":true,"premiumFromOrganization":false,"privateKey":null,"providerOrganizations":[],"providers":[],"securityStamp":"4f4d1ee9-1295-4263-8fc2-96cb79236f86","twoFactorEnabled":false,"userEnabled":true,"usesKeyConnector":false}] +=== 3. invited registration === +--- POST /identity/accounts/register email=meghivott.probe@example.com +{"captchaBypassToken":"","object":"register"} +HTTP 200 + +--- POST /api/accounts/register email=meghivott.probe@example.com +{"error":{"code":404,"description":"The requested resource could not be found.","reason":"Not Found"}} +HTTP 404 + +=== control: second stranger after invite === +--- POST /identity/accounts/register email=idegen2.probe@example.com +{"message":"Registration not allowed or user already exists","validationErrors":{"":["Registration not allowed or user already exists"]},"errorModel":{"message":"Registration not allowed or user already exists","object":"error"},"error":"","error_description":"","exceptionMessage":null,"exceptionStackTrace":null,"innerExceptionMessage":null,"object":"error"} +HTTP 400 + +=== admin users after === +[{"_status":0,"avatarColor":null,"createdAt":"2026-09-15 09:36:18 CEST","creationDate":"2026-09-15T07:36:18.177612Z","culture":"en-US","email":"meghivott.probe@example.com","emailVerified":true,"forcePasswordReset":false,"id":"8dbdfec6-2cfb-4794-9156-5a67cb27b57b","key":"2.AAAAAAAAAAAAAAAAAAAAAA==|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=","lastActive":null,"name":"probe","object":"profile","organizations":[],"premium":true,"premiumFromOrganization":false,"privateKey":"2.AAAAAAAAAAAAAAAAAAAAAA==|AAAA|AAAA","providerOrganizations":[],"providers":[],"securityStamp":"032fbc2f-aa0e-4e0f-8390-485f3077493c","twoFactorEnabled":false,"userEnabled":true,"usesKeyConnector":false}] +=== config defaults (admin) === +name="signups_allowed" +name="signups_verify" +name="invitations_allowed" checked +name="_enable_smtp" + +=== newer web-vault flow (send-verification-email) === +--- POST /identity/accounts/register/send-verification-email email=idegen3.probe@example.com (stranger) +{"message":"Registration not allowed or user already exists","validationErrors":{"":["Registration not allowed or user already exists"]},"errorModel":{"message":"Registration not allowed or user already exists","object":"error"},"error":"","error_description":"","exceptionMessage":null,"exceptionStackTrace":null,"innerExceptionMessage":null,"object":"error"} +HTTP 400 + +invite meghivott2 HTTP 200 +--- POST /identity/accounts/register/send-verification-email email=meghivott2 (invited) +" +--- web vault register route present: +GET / HTTP 200 +[2026-09-15 09:36:34.927][response][INFO] (register_verification_email) POST /identity/accounts/register/send-verification-email => 400 Bad Request +[2026-09-15 09:36:34.967][request][INFO] POST /admin/invite +[2026-09-15 09:36:34.968][response][INFO] (invite_user) POST /admin/invite application/json => 200 OK +[2026-09-15 09:36:34.981][request][INFO] POST /identity/accounts/register/send-verification-email +[2026-09-15 09:36:34.984][response][INFO] (register_verification_email) POST /identity/accounts/register/send-verification-email => 200 OK + +Teardown: docker rm -f vwspike; rm -rf /tmp/vwspike /tmp/vw.sh /tmp/vw2.sh /tmp/vwtok /tmp/vwjar /tmp/vwjar2 /tmp/vwreg.json in 9202; image vaultwarden/server:1.36.0-alpine left pulled in 9202 (scratch). diff --git a/documentation/audits/evidence-p1fixes-2026-09-15/E2-paperless-measure.txt b/documentation/audits/evidence-p1fixes-2026-09-15/E2-paperless-measure.txt new file mode 100644 index 00000000..9309e598 --- /dev/null +++ b/documentation/audits/evidence-p1fixes-2026-09-15/E2-paperless-measure.txt @@ -0,0 +1,69 @@ +E.2 — Paperless-ngx memory need, 20-document batch, PAPERLESS_TASK_WORKERS=1 x THREADS_PER_WORKER=1 +Measured 2026-09-15 on scratch LXC 9202 (demo-hp). Guest 9201 not touched. +Setup: catalog templates/paperless-ngx/docker-compose.yml (app-catalog 882a43e) rendered as compose project +`plspike` (containers plspike-*), traefik labels/network dropped, webserver on 127.0.0.1:18765, +image ghcr.io/paperless-ngx/paperless-ngx:2.20.15, postgres:16-alpine (256M cap), redis:7-alpine (128M cap), +webserver cap raised to 2G so the peak is measured, not capped. Generated values filled by hand (redacted). +Input: 20 distinct 3-page PDFs (text-layer Helvetica, ~40 lines/page, distinct md5), posted concurrently +via POST /api/documents/post_document/ (token auth; token redacted, length 40). +Catalog today: paperless-webserver memory 768M (805306368 B), WORKERS=2, THREADS=1; postgres 256M; redis 128M +(each container has its own cap). + +--- run output (verbatim) --- +cgroup: /sys/fs/cgroup/system.slice/docker-f4d92829277069229eb95b69a4696c509d779157bc96a799f571c8b13098795f.scope +limit memory.max: 2147483648 +startup memory.peak (before upload): 624263168 +memory.current before upload: 618651648 +token length: 40 +upload start 2026-09-15T07:38:32Z +200 doc05.pdf +200 doc08.pdf +200 doc02.pdf +200 doc03.pdf +200 doc12.pdf +200 doc09.pdf +200 doc07.pdf +200 doc18.pdf +200 doc04.pdf +200 doc01.pdf +200 doc06.pdf +200 doc15.pdf +200 doc20.pdf +200 doc11.pdf +200 doc13.pdf +200 doc14.pdf +200 doc19.pdf +200 doc17.pdf +200 doc10.pdf +200 doc16.pdf +07:38:38 tasks {'PENDING': 18, 'SUCCESS': 2} +07:38:48 tasks {'PENDING': 14, 'SUCCESS': 5, 'STARTED': 1} +07:38:58 tasks {'PENDING': 10, 'STARTED': 1, 'SUCCESS': 9} +07:39:08 tasks {'PENDING': 6, 'SUCCESS': 13, 'STARTED': 1} +07:39:19 tasks {'PENDING': 3, 'STARTED': 1, 'SUCCESS': 16} +07:39:29 tasks {'SUCCESS': 20} +upload end 2026-09-15T07:39:30Z +documents count: 20 +final tasks: 20 {'SUCCESS': 20} +memory.peak after batch: 783294464 +max sampled memory.current during batch (0.5s): 776806400 +memory.events: low 0 high 0 max 0 oom 0 oom_kill 0 oom_group_kill 0 sock_throttled 0 +plspike-webserver oomkilled=false restarts=0 status=running memlimit=2147483648 +plspike-postgres oomkilled=false restarts=0 status=running memlimit=268435456 +plspike-redis oomkilled=false restarts=0 status=running memlimit=134217728 + +--- computation --- +memory.peak (whole container lifetime incl. startup + batch) = 783294464 B (747.0 MiB) +startup-only peak before upload = 624263168 B (595.3 MiB) +cap = peak x 1.5 = 1174941696 B (1120.5 MiB) -> rounded UP to a 256M multiple = 1280M (1342177280 B) + +--- caveats --- +- The PDFs carry a text layer; Paperless may skip/lighten OCR for them. BIGNIGHT's OOM named `gs` (ghostscript) + processes, so image-only scans may peak higher than this text-PDF batch. The 1.5x factor is the stated margin. +- With 1 worker the batch peak (783 MB) sits only ~2.7% under today's 768M cap (805 MB) — the single-worker + change alone would leave almost no headroom; the cap raise is needed too. + +--- teardown --- +docker compose down -v (project plspike: 3 containers, 3 volumes, network), rm -rf /tmp/plspike in 9202. +Images pulled only for the spike (paperless-ngx:2.20.15, postgres:16-alpine, redis:7-alpine) removed — +none were used by 9202's other containers (pre-spike image list had none of them).