Identity field validation must match every system the values are projected into #66

Stängd
öppnade 2026-08-02 12:24:03 +00:00 av supernaut · 1 kommentar
Ägare

Three systems each enforce their own rules on a username and an email address — the portal, the
identity provider and the git host — and the portal's rules were never reconciled with either of
the other two. A value the portal accepts can be refused downstream, and the worst case is silent.

An audit at the pinned versions (git host 16.0.1, identity provider 1.10.4, reconciler
v1.2.1) found the portal is more permissive than the intersection on several dimensions:

  • Silent, and the worst of the three. alice., bob-, a--b, api, explore, ghost,
    robots.txt, alice.keys all pass sign-up and are accepted by the identity provider, then the
    account is unusable: the first sign-in to the git host fails with a 500. No error is shown to the
    user, nothing is logged, no metric moves and no alert fires.
  • Dead end. A leading digit (7alice), anything UUID-shaped, or a non-ASCII address such as
    användare@example.se is refused by the identity provider behind a generic "check your details".
    On an email change the single-use token is consumed before the write, so retrying fails
    identically, forever.
  • Divergence. An uppercase username is silently lower-cased by the identity provider, but the
    consent record is keyed on the spelling that was submitted — so the two no longer match, and
    nothing errors.
  • Misattributed alerting. A refused identity write raises the Actions enforcement gauge, so
    the alert that fires names the wrong subsystem and sends the operator after a token scope. It also
    retries forever, so it sticks.

Production is currently clean — all identity metrics at zero, no failures in fourteen days of logs.
That is luck rather than a control.

Decision taken

The username is normalised to lower case visibly at the form, so what the user sees is what is
provisioned everywhere. The identity provider does this silently already; making it visible removes
the divergence rather than hiding it.

Scope

  • gitborg/gitborg-web#151 — one shared module that is the single source of truth for these
    rules, enforcing the intersection of all three systems, imported by every surface that
    validates them
  • gitborg/gitborg-web#152 — distinguish invalid / taken / error on identity writes instead of
    one generic failure
  • Separate the identity projection's health signal from the Actions enforcement signal
    (tracked on the reconciler board)
  • gitborg/gitborg-infra#326 — alert on identity projection failure and on refused account
    creation at first sign-in
  • #71 — document the validation contract, the versions the rules were read from, and the
    upgrade obligation

Sequencing (operator decision)

Observability lands first. Nothing here has bitten anyone yet — production shows zero identity
failures over fourteen days — so the first priority is that a real occurrence is visible and
correctly attributed
rather than reported as a CI-enforcement fault. The reconciler's separate
identity gauge and gitborg/gitborg-infra#326 therefore precede the validation rules in
gitborg/gitborg-web#151 and gitborg/gitborg-web#152.

Done when

A value accepted by the portal is accepted by both downstream systems, a rejected one says which
rule it broke, and an upgrade that changes a downstream rule fails a test rather than a user.

Status — 2026-08-02 (complete)

Observability landed first, as decided, and the validation rules followed. All four are merged and
applied to production; the reconciler runs v1.3.0 and publishes
gitborg_reconciler_identity_projection_degraded and gitborg_reconciler_identity_failures, both
0, with the Actions gauge finally meaning only what its name says. IdentityProjectionDegraded and
the log-based ForgejoAccountCreationRefused are live and evaluating.

The design note landed as design/identity-field-validation.md, linked from ADR 0038 and added to
ADR 0028's upgrade decisions as item 8. Writing it surfaced one thing the audit had not: the display
name is validated by a separate module and is far less constrained than the other two fields, so
there is no intersection to compute for it — only a non-empty, no-control-characters, 100-character
rule, each for a stated reason.

The build scope is delivered. What the note makes explicit is that the remaining risk is not
buildable: nothing detects upstream drift on its own, because the tests assert our transcription of
the upstream rules rather than upstream itself. Re-reading the source at every version bump is the
control, and it now lives in the upgrade cadence decision record rather than in anyone's memory.

Three systems each enforce their own rules on a username and an email address — the portal, the identity provider and the git host — and the portal's rules were never reconciled with either of the other two. A value the portal accepts can be refused downstream, and the worst case is silent. An audit at the pinned versions (git host `16.0.1`, identity provider `1.10.4`, reconciler `v1.2.1`) found the portal is **more permissive than the intersection** on several dimensions: - **Silent, and the worst of the three.** `alice.`, `bob-`, `a--b`, `api`, `explore`, `ghost`, `robots.txt`, `alice.keys` all pass sign-up and are accepted by the identity provider, then the account is unusable: the first sign-in to the git host fails with a 500. No error is shown to the user, nothing is logged, no metric moves and no alert fires. - **Dead end.** A leading digit (`7alice`), anything UUID-shaped, or a non-ASCII address such as `användare@example.se` is refused by the identity provider behind a generic "check your details". On an email change the single-use token is consumed *before* the write, so retrying fails identically, forever. - **Divergence.** An uppercase username is silently lower-cased by the identity provider, but the consent record is keyed on the spelling that was submitted — so the two no longer match, and nothing errors. - **Misattributed alerting.** A refused identity write raises the *Actions enforcement* gauge, so the alert that fires names the wrong subsystem and sends the operator after a token scope. It also retries forever, so it sticks. Production is currently clean — all identity metrics at zero, no failures in fourteen days of logs. That is luck rather than a control. ## Decision taken The username is normalised to lower case **visibly at the form**, so what the user sees is what is provisioned everywhere. The identity provider does this silently already; making it visible removes the divergence rather than hiding it. ## Scope - [x] gitborg/gitborg-web#151 — one shared module that is the single source of truth for these rules, enforcing the intersection of all three systems, imported by every surface that validates them - [x] gitborg/gitborg-web#152 — distinguish invalid / taken / error on identity writes instead of one generic failure - [x] Separate the identity projection's health signal from the Actions enforcement signal (tracked on the reconciler board) - [x] gitborg/gitborg-infra#326 — alert on identity projection failure and on refused account creation at first sign-in - [x] #71 — document the validation contract, the versions the rules were read from, and the upgrade obligation ## Sequencing (operator decision) **Observability lands first.** Nothing here has bitten anyone yet — production shows zero identity failures over fourteen days — so the first priority is that a real occurrence is *visible and correctly attributed* rather than reported as a CI-enforcement fault. The reconciler's separate identity gauge and gitborg/gitborg-infra#326 therefore precede the validation rules in gitborg/gitborg-web#151 and gitborg/gitborg-web#152. ## Done when A value accepted by the portal is accepted by both downstream systems, a rejected one says which rule it broke, and an upgrade that changes a downstream rule fails a test rather than a user. ## Status — 2026-08-02 (complete) Observability landed first, as decided, and the validation rules followed. All four are merged and applied to production; the reconciler runs v1.3.0 and publishes `gitborg_reconciler_identity_projection_degraded` and `gitborg_reconciler_identity_failures`, both 0, with the Actions gauge finally meaning only what its name says. `IdentityProjectionDegraded` and the log-based `ForgejoAccountCreationRefused` are live and evaluating. The design note landed as `design/identity-field-validation.md`, linked from ADR 0038 and added to ADR 0028's upgrade decisions as item 8. Writing it surfaced one thing the audit had not: the display name is validated by a separate module and is far less constrained than the other two fields, so there is no intersection to compute for it — only a non-empty, no-control-characters, 100-character rule, each for a stated reason. The build scope is delivered. What the note makes explicit is that the remaining risk is not buildable: nothing detects upstream drift on its own, because the tests assert our transcription of the upstream rules rather than upstream itself. Re-reading the source at every version bump is the control, and it now lives in the upgrade cadence decision record rather than in anyone's memory.
supernaut lade till detta till projektet Bitborg Roadmap 2026-08-02 12:27:12 +00:00
Upphovsperson
Ägare

All five scope items are delivered, so this epic closes per the closing rule in the issue-tracking guide: the build scope is done, and what remains is a standing obligation rather than work — re-reading the upstream validation rules at every Forgejo or Kanidm bump, now recorded as decision 8 in ADR 0028 and detailed in design/identity-field-validation.md.

All five scope items are delivered, so this epic closes per the closing rule in the issue-tracking guide: the build scope is done, and what remains is a standing obligation rather than work — re-reading the upstream validation rules at every Forgejo or Kanidm bump, now recorded as decision 8 in ADR 0028 and detailed in `design/identity-field-validation.md`.
Logga in för att delta i denna konversation.
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-docs#66
Ingen beskrivning angiven.