v0.208.0 — R-254: the last two secrets leave the page source, plus a gate against a fourth
gates / gates (push) Successful in 17s
gates / gates (push) Successful in 17s
Site one. app_info.html rendered {{.InitialCreds.Password}} into a hidden span —
a REAL per-install credential, read live out of the running container, in the
response body of every render. The page now carries the non-secret half plus a
boolean; the value comes from POST /apps/<slug>/initial-credentials/reveal, which
RE-READS the container rather than serving a cached copy (caching it in the
handler would put it back in the body one layer in). no-store, CSRF-covered,
logged as an act. Both buttons go through it. A reveal that cannot read the value
SAYS SO rather than returning an empty string that renders as a blank password.
Site two, established before changing. The hidden input is NOT the defect and was
left alone: it fires only pre-deploy, and README §318 documents why the value must
round-trip — the customer notes the generated secrets down and submitting them
back is what makes the saved value the same one they saw. The defect was the
neighbouring READONLY input, which on an ALREADY-DEPLOYED app rendered the secret
into a page with nothing to submit. Fixed by POST /stacks/<name>/auto-field/reveal,
authorised by requiring a type:secret auto-field of that stack. Both directions
pinned.
The premise that this contradicted a repo rule does not hold: the rule is
CONTEXT.md:2070 'Password fields require explicit input — prevents accidental
empty-password deployments', about EMPTINESS. No line in the repo says 'no silent
auto-fill'.
The gate. scripts/secret_in_markup_gate.py, registered in controller_gates.py,
convicts any template expression that names a secret unless allowlisted with a
reason. Its limits are MEASURED and in its docstring: it catches a launder through
a local variable (the assignment names the secret) but is blind to a secret
arriving under a neutral page-data key — verified both ways. That is the shape of
site two, which this gate would NOT have caught. The runtime body assertion covers
all shapes but only 4 of 27 page templates; the other 23 are R-255, filed rather
than glossed. Two nets, different holes, both named.
Correction to v0.207.0's report: HTML comments do NOT ship in the response body
here — html/template strips them, text/template does not. Measured. A red-proof
planting a secret in a comment therefore correctly does not fail.
This commit is contained in:
@@ -1,3 +1,61 @@
|
||||
## v0.208.0 — the last two secrets leave the page source, and a gate so there is no fourth (2026-08-08, R-254) — MinAgent 0.127.0
|
||||
|
||||
v0.207.0 removed a password from one page. The census that fix required found two more sites; this
|
||||
closes both, and adds a check so the next one is caught rather than searched for.
|
||||
|
||||
### 1. An app's first-login password (R-254 site one)
|
||||
|
||||
`app_info.html` rendered `{{.InitialCreds.Password}}` into a `hidden` span — a **real per-install
|
||||
credential**, read live out of the running container, in the response body of every render. `hidden`
|
||||
stops a browser DRAWING it and nothing else.
|
||||
|
||||
The page now carries the non-secret half (username, note) plus a boolean; the value comes from
|
||||
**`POST /apps/<slug>/initial-credentials/reveal`**, which **re-reads the container** rather than
|
||||
serving a cached copy — caching it in the handler would put it straight back in the body one layer in.
|
||||
`no-store`, CSRF-covered, and **logged as an act**. Both buttons (Megjelenítés *and* Másolás) go
|
||||
through it; neither keeps the value between presses.
|
||||
|
||||
A reveal can now legitimately fail (container stopped, file deleted after first login) and **says so**
|
||||
— an empty string would have rendered as a blank password.
|
||||
|
||||
### 2. The deploy form — established before changing (R-254 site two)
|
||||
|
||||
The task named the hidden input. **It is not the defect, and it was left alone:** it fires only on the
|
||||
PRE-DEPLOY form, and `README §318` documents why the value must round-trip — the customer is shown the
|
||||
generated secrets so they can note them down, and submitting them back is what makes the saved value
|
||||
the same one they saw ("no silent re-generation on submit"). A form must carry what it submits.
|
||||
|
||||
**The defect was the neighbouring readonly display input.** On an ALREADY-DEPLOYED app the hidden
|
||||
input is correctly omitted — nothing is being submitted — yet `<input type="password" value="{{$val}}"
|
||||
readonly>` still rendered the secret into a page the customer merely opens. That is fixed by
|
||||
**`POST /stacks/<name>/auto-field/reveal`**, authorised by requiring the field to be a `type: secret`
|
||||
auto-generated field of *that stack's* catalog metadata. Both directions are pinned by tests: the
|
||||
deployed page must not carry the value, and the pre-deploy form must still submit it.
|
||||
|
||||
**The premise that this contradicted a repo rule does not hold.** The rule is `CONTEXT.md:2070`,
|
||||
*"Password fields require explicit input — prevents accidental empty-password deployments"*: it is
|
||||
about EMPTINESS, not auto-fill. No line anywhere in the repo says "no silent auto-fill".
|
||||
|
||||
### 3. A gate, because three instances in two days is a pattern
|
||||
|
||||
`scripts/secret_in_markup_gate.py` (registered in `controller_gates.py`) reads all 36 templates and
|
||||
convicts any `{{ … }}` whose expression names a secret, unless allowlisted with a stated reason.
|
||||
|
||||
**Its limits are measured, not estimated, and are in its own docstring.** It catches a launder through
|
||||
a local variable (the assignment names the secret). It is **blind to a secret arriving under a neutral
|
||||
page-data key** — `data["Tagline"] = creds.Password` then `{{.AppInfo.Tagline}}` passes it cleanly,
|
||||
verified both ways. That is the shape of site two, which this gate would NOT have caught.
|
||||
|
||||
The complementary net is the runtime body assertion, which catches all of them — but needs each page's
|
||||
data to be constructible, and **only 4 of 27 page templates have that today**. The other 23 have no
|
||||
runtime coverage: **R-255**, filed rather than glossed. Two nets, different holes, both named.
|
||||
|
||||
### A correction to v0.207.0's report
|
||||
|
||||
It stated that HTML comments ship in the response body. **They do not, here** — this package renders
|
||||
with `html/template`, which strips comments (measured: `text/template` keeps them, `html/template`
|
||||
does not). A red-proof that plants a secret in a comment therefore correctly does **not** fail.
|
||||
|
||||
## v0.207.0 — a password stops living in the page source, and two refusals learn to say what to do (2026-08-08, R-249/R-252/R-253) — MinAgent 0.127.0
|
||||
|
||||
Three items the fifth walk exposed by passing. None of them touches the recovery path it proved; all
|
||||
|
||||
Reference in New Issue
Block a user