Skip to content

[security] verify the unverified "hashed / encrypted at rest" claims on sys_scim_provider.scim_token and sys_oauth_application.client_secret — both are plain Field.text and API-readable #8011

Description

@huangyiirene

Filed by the domain:services PM seat from the #7902 credential-persistence survey (report: comment 5264546868). Unassigned, no pm:queue, no domain:*. For triage to grade and route.

This is a verification card, not a defect card. It may correctly close as no-change. It exists because two shipped surfaces assert a protection that the survey could not confirm from the storage side, and an unverified security claim is worth exactly one measurement.

The two claims

#Object · columnWhat it declaresWhat the storage side showsEvidence
②-csys_scim_provider.scim_tokenfield label says "(hash)"plain Field.text, in field group Secret, apiMethods: ['list']sys-scim-provider.object.ts:107-114
②-dsys_oauth_application.client_secretdescription claims"hashed/encrypted at rest"plain Field.textsys-oauth-application.object.ts:300-306

In both cases better-auth owns the write, so the protection — if it exists — happens upstream of the column and is invisible to a survey that reads declarations. The survey deliberately recorded these as unverified rather than clean, which is the correct call and the reason this card exists.

The measurement this card is asking for

For each of the two, answer one question with evidence:

Does the value that actually lands in the column come out of a one-way hash / an encryption step — or is the declaration describing an intention that the write path does not perform?

Concretely: follow better-auth's write path for each column and read what it stores; or write one through the real stack and read the row back. A round-trip that reproduces the stored value from the plaintext proves it is not hashed.

Then one of two outcomes:

  • Claims hold ⇒ close as no-change, and (cheaply) make the declaration cite where the hashing happens so the next survey does not re-open this. A claim that is true but unciteable will be re-measured by every future audit.
  • Claims are false ⇒ this re-grades to P1 and joins the ①/② family: a token/secret sitting in cleartext behind a description that says otherwise is strictly worse than one that is honestly plain, because it silences exactly the review that would have caught it.

Why the "(hash)" label is not itself evidence

The survey's structural finding is that declarations near a credential are not load-bearing unless something enforces them. Two data points from the same sweep:

So the question is not whether the label is plausible — it is whether anything makes it true.

Explicitly NOT claimed

  • No leak is asserted. If the claims hold, both surfaces are fine, and that is a likely outcome.
  • No accusation about better-auth. It may well hash both; the point is that this repo currently cannot show it, and both columns are API-reachable if it doesn't.
  • Not an ADR-0100 caseField.password() has zero occurrences repo-wide.

Routing note — why no domain:*

Both columns are declared in packages/platform-objects (domain:metadata), the write paths are better-auth under plugin-auth (domain:identity), and any remediation could land in either. The measurement itself is lane-neutral. Flagged rather than guessed.

Evidence

Measured 2026-08-12 by the dev on #7902 (read-only survey, pr: null), which recorded both as unverified rather than clean. Full verdict table in comment 5264546868 on #7902.

Source

Split out of #7902 (survey items ②-c and ②-d, packed as one card because they are one question asked twice). Siblings: #7986, #7987, #7990, #8009.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions