Problem
token_ref (the live Proxmox API token and Git PATs) is returned verbatim in API responses, not just stored at rest.
GET /v1/proxmox/hosts → HostOut.token_ref (the full PVEAPIToken=... secret)
GET /v1/catalog/sources → SourceOut.token_ref (Git PAT)
Evidence
app/schemas/v1/proxmox.py:15 — HostOut(HostIn) includes token_ref: str
app/schemas/v1/catalog.py:13,21 — SourceOut.token_ref
app/routes/v1/proxmox/hosts.py _row_to_out() populates token_ref from the DB row
Impact (Critical)
Any caller that can reach the backend harvests every registered Proxmox/Git credential in one GET. The exposure is amplified by the subnet-wide CORS default (separate issue) — a single LAN device can read all tokens.
Suggested fix
Drop token_ref from the *Out response models (use a separate input-only model, or Field(exclude=True) / a masked boolean like has_token). Never echo secrets in responses regardless of the at-rest fix.
Related (at-rest, distinct): #86, #75.
Problem
token_ref(the live Proxmox API token and Git PATs) is returned verbatim in API responses, not just stored at rest.GET /v1/proxmox/hosts→HostOut.token_ref(the fullPVEAPIToken=...secret)GET /v1/catalog/sources→SourceOut.token_ref(Git PAT)Evidence
app/schemas/v1/proxmox.py:15—HostOut(HostIn)includestoken_ref: strapp/schemas/v1/catalog.py:13,21—SourceOut.token_refapp/routes/v1/proxmox/hosts.py_row_to_out()populatestoken_reffrom the DB rowImpact (Critical)
Any caller that can reach the backend harvests every registered Proxmox/Git credential in one GET. The exposure is amplified by the subnet-wide CORS default (separate issue) — a single LAN device can read all tokens.
Suggested fix
Drop
token_reffrom the*Outresponse models (use a separate input-only model, orField(exclude=True)/ a masked boolean likehas_token). Never echo secrets in responses regardless of the at-rest fix.Related (at-rest, distinct): #86, #75.