fix: keep the web token out of the systemd journal, and verify the unit for real - #13
Merged
Merged
Conversation
…it for real scripts/replicant-web.service had never been started by a systemd. Running it in a container (Debian 12, systemd 252 as PID 1) found a defect that reading it could not. The startup banner printed the URL with ?token= in it. Interactively that is the point. Under systemd, stdout IS the journal, so every start wrote the token in cleartext into a file readable by root and the systemd-journal group. The code had just gone to the trouble of writing that token 0600 in a 0700 directory; the protection was not wrong, it was bypassed by a different sink. The banner now reveals the token only when sys.stdout.isatty(). Otherwise it prints the URL without it, names the file to read it from, and drops the "stop: Ctrl-C" hint, since nobody is at a keyboard. Conditional rather than global, because the interactive path legitimately needs the clickable URL. Verification, all 11 assertions passing against real systemd: - systemd-analyze verify silent; unit active - runs as the non-root service user - serves, and refuses an unauthenticated /api/catalog with 401 - token written 0600 in a 0700 dir despite ProtectHome=read-only and ProtectSystem=full - token absent from the journal (the regression guard for the fix above) - terminal tab disabled on the unit's 0.0.0.0 bind - Restart=on-failure recovers from SIGKILL and serves again - the token survives that restart, so a bookmarked URL keeps working scripts/verify-systemd-unit.sh preserves those assertions and CI runs them on every push (job: systemd-unit). systemd-analyze verify alone would have passed the leaking version, so the job boots systemd rather than parsing the unit. Two harness bugs worth noting, both fixed before the result was believed: the first version asserted "MainPID changed" for the restart check, which raced RestartSec=5s and reported the unit broken while it was still starting, and it used ps, which a minimal Debian image does not have. The [Unverified] marker on the unit is cleared. 519 python tests, 64 frontend.
404SecNotFound
added a commit
that referenced
this pull request
Sep 1, 2026
…mark PAN-OS/CP (#90) Roadmap 2026-09 Now-items 1, 2, 12, 13 (decisions + cheap positioning edits, no code): - #1 Scope every external claim to generator-verified / delivery-unverified until the first observed rule fire. New README maturity note; the lab test is recorded as a hard launch gate in CLAUDE.md and the roadmap section. - #2 Resolve the identity fork on the record: OSS detection-as-code for detection engineers, CLI-first, web optional (CLAUDE.md + README). - #12 Name the flightsim wedge: log-strings-only, single fail-closed egress, no real infrastructure. Added to the README pitch and the prior-art matrix. - #13 Reframe PAN-OS and Check Point as beta / community-verify ask; lead with 'FortiGate, verified' as the trust anchor (badges, intro, roadmap).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
scripts/replicant-web.serviceshipped in #10 marked[Unverified]— no systemd had ever started it. Running it in a container found a defect that reading it could not.The defect
The startup banner printed the URL with
?token=in it. On a terminal that is the point. Under systemd, stdout is the journal:So a codebase that had just gone to the trouble of writing the token
0600in a0700directory was also writing it in cleartext, on every start, into a file readable by root and the entiresystemd-journalgroup. The0600was not wrong. It was bypassed by a different sink.The banner now reveals the token only when
sys.stdout.isatty(). Otherwise it prints the URL without it, names the file to read it from, and drops thestop: Ctrl-Chint since nobody is at a keyboard. Conditional, not global — suppressing it everywhere would break the interactive path that legitimately needs a clickable URL.Verification
Debian 12, systemd 252, PID 1 in a container. All 11 assertions pass:
systemd-analyze verify/api/health/api/catalogunauthenticated0600in a0700dir, despiteProtectHome=read-only0.0.0.0bindRestart=on-failureSIGKILLand serves againThe
ProtectHomerow is the one worth pausing on: it caught a defect at authoring time in #10 (the unit originally kept config in the service user's home, which that directive would have made read-only) and now has a test proving the fix holds.Why a CI job and not a one-off
systemd-analyze verifywas silent on the leaking version. It parses the unit; it cannot tell you what the service writes once it runs. So the newsystemd-unitjob boots systemd rather than parsing, and runsscripts/verify-systemd-unit.sh— the same script, in the repo, reproducible locally with the three commands the job documents.This also closes the pattern from #12: the v0.2.0 screenshot method was done ad hoc and lost, so it had to be reinvented. This verification is preserved and automated instead.
Two harness bugs, fixed before the result was believed
Worth recording because both would have produced a confident wrong answer:
RestartSec=5sputs the socket several seconds behind it — so the check raced and reported the unit broken when it was merely still starting. It now waits for the condition that actually matters: serving again.ps, which a minimal Debian image does not ship. That failed as "runs as ``" rather than as a missing tool. Now reads/proc/PIDowner.Verification of this PR
519 Python tests (516 before, 3 new for the banner), 64 frontend,
black/ruff/mypyclean,shellcheckclean under the pinned v0.11.0 image CI uses. The container run was repeated after the fix with a rotated journal, confirming the token is absent from a clean start.The
[Unverified]marker on the unit is cleared in the CHANGELOG and the README.