Never save "****" as a secret #206

Merged
jcoffey-dev merged 1 commits from fix/keep-masked-secrets into main 2026-10-18 22:21:30 +00:00
Owner

A stored secret reads back as ****. Any client that wrote back what it
read saved **** as the secret (a webhook's Basic password or signing
key, a store's credentials, and so on), and the real one was lost. Our
console doesn't do this today, but nothing on the server stopped it.

Now, in every registry /set except accounts (whose credentials already
keep **** in principal.rs):

  • **** written straight at a secret (a patch like
    httpAuth/secret/secret or signatureKey/secret) keeps the stored
    secret, and the rest of the update applies.
  • **** as a secret anywhere else (a create, a variant switch, a whole
    object replaced) has no stored secret to stand for, so the write is
    refused: invalidProperties on the top-level property, saying to send
    the secret itself or leave the field out.

New in crates/jmap/src/inbuxa/masked_secrets.rs, hooked into
registry/set.rs.

Tests: system::masked_secrets::masked_secrets_tests (x:WebHook with
Basic auth and a signing key: masked read-back, keep on a direct patch,
refused inside an object and in a create, a real secret still saved).
With the guard turned off it fails with ("****", "****") stored. It
passes, and so does system::system_tests. Fork checks clean.

A stored secret reads back as `****`. Any client that wrote back what it read saved `****` as the secret (a webhook's Basic password or signing key, a store's credentials, and so on), and the real one was lost. Our console doesn't do this today, but nothing on the server stopped it. Now, in every registry `/set` except accounts (whose credentials already keep `****` in `principal.rs`): - `****` written straight at a secret (a patch like `httpAuth/secret/secret` or `signatureKey/secret`) keeps the stored secret, and the rest of the update applies. - `****` as a secret anywhere else (a create, a variant switch, a whole object replaced) has no stored secret to stand for, so the write is refused: `invalidProperties` on the top-level property, saying to send the secret itself or leave the field out. New in `crates/jmap/src/inbuxa/masked_secrets.rs`, hooked into `registry/set.rs`. Tests: `system::masked_secrets::masked_secrets_tests` (x:WebHook with Basic auth and a signing key: masked read-back, keep on a direct patch, refused inside an object and in a create, a real secret still saved). With the guard turned off it fails with `("****", "****")` stored. It passes, and so does `system::system_tests`. Fork checks clean.
jcoffey-dev added 1 commit 2026-10-18 21:54:50 +00:00
Never save "****" as a secret
ci / fork-checks (pull_request) Skipped
ci / build (pull_request) Skipped
github/ci (branch) GitHub Actions
ci / github (pull_request) Successful in 24m7s
8bd5b3f697
A stored secret reads back as "****", and a registry write that sent it
back stored "****" as the secret: a webhook's password or signing key,
a store's or directory's password, a DNS provider's key, an OAuth
client's secret. Written straight at a secret it now keeps the stored
one; anywhere else, inside a whole object, a create or a variant switch,
the write is refused with invalidProperties, since there's no stored
secret it could stand for. Accounts keep their own credential checks,
which already did this.
jcoffey-dev merged commit b7ed8b9c1d into main 2026-10-18 22:21:30 +00:00
jcoffey-dev deleted branch fix/keep-masked-secrets 2026-10-18 22:21:30 +00:00
Sign in to join this conversation.