feat(creds): surface plaintext/b64 secret on reuse findings
The CredentialReuse table only stores the sha256+kind hash of the secret; the printable + b64 forms live on the underlying Credential rows. The dashboard drawer was therefore showing only the hash, which defeats most of the value of having a reuse view in the first place. Repo helpers list_credential_reuses + get_credential_reuse_by_id now issue one batched SELECT against credentials keyed on the sha256s in the result page and graft secret_printable + secret_b64 onto each row before returning. The drawer renders the same printable/b64 code-block the credentials inspector uses.
This commit is contained in:
@@ -927,6 +927,7 @@ class SQLModelRepository(BaseRepository):
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
d[key] = []
|
||||
out.append(d)
|
||||
await self._enrich_with_secret(session, out)
|
||||
return int(total), out
|
||||
|
||||
async def get_credential_reuse_by_id(
|
||||
@@ -944,8 +945,49 @@ class SQLModelRepository(BaseRepository):
|
||||
d[key] = json.loads(d[key])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
d[key] = []
|
||||
await self._enrich_with_secret(session, [d])
|
||||
return d
|
||||
|
||||
@staticmethod
|
||||
async def _enrich_with_secret(
|
||||
session: Any, rows: List[dict[str, Any]]
|
||||
) -> None:
|
||||
"""Tack ``secret_printable`` + ``secret_b64`` onto each reuse row.
|
||||
|
||||
``CredentialReuse`` only stores the sha256+kind hash of the
|
||||
secret — the actual printable/b64 representations live on the
|
||||
underlying ``Credential`` rows. The dashboard wants to show the
|
||||
secret in the drawer, so we lift one matching credential per
|
||||
``(sha256, kind, principal)`` finding. One batched query for the
|
||||
whole page; rows with no surviving credential (shouldn't happen
|
||||
in practice) get nulls.
|
||||
"""
|
||||
if not rows:
|
||||
return
|
||||
sha_set = {r["secret_sha256"] for r in rows}
|
||||
if not sha_set:
|
||||
return
|
||||
stmt = select(
|
||||
Credential.secret_sha256,
|
||||
Credential.secret_kind,
|
||||
Credential.principal,
|
||||
Credential.secret_printable,
|
||||
Credential.secret_b64,
|
||||
).where(Credential.secret_sha256.in_(sha_set))
|
||||
secret_map: dict[
|
||||
tuple[str, str, Optional[str]],
|
||||
tuple[Optional[str], Optional[str]],
|
||||
] = {}
|
||||
for sha, kind, principal, printable, b64 in (
|
||||
(await session.execute(stmt)).all()
|
||||
):
|
||||
secret_map.setdefault((sha, kind, principal), (printable, b64))
|
||||
for r in rows:
|
||||
key = (r["secret_sha256"], r["secret_kind"], r.get("principal"))
|
||||
printable, b64 = secret_map.get(key, (None, None))
|
||||
r["secret_printable"] = printable
|
||||
r["secret_b64"] = b64
|
||||
|
||||
async def get_state(self, key: str) -> Optional[dict[str, Any]]:
|
||||
async with self._session() as session:
|
||||
statement = select(State).where(State.key == key)
|
||||
|
||||
Reference in New Issue
Block a user