PRC005 — Response returns a secret
| Severity | high |
| OWASP API Security Top 10 | API3:2023 Broken Object Property Level Authorization |
What it catches
A field in a response schema whose name says it holds a secret. Every schema
an operation can return is checked: nested objects, array items,
allOf/oneOf/anyOf, additionalProperties and $refs. A schema shared by
several operations is reported once, where the property is written (see
ignoring for ignores on one of those operations).
Two kinds of secret:
| Kind | Words (English and Portuguese) | Reported in |
|---|---|---|
| Never returned | password, passwd, passphrase, senha, secret, segredo, private key, chave privada | any response |
| Issued once | token, API key, access key | GET responses only |
Tokens and API keys are how authentication works: a login (POST) returns a
token, creating a key returns the key. What must never happen is reading one
back later, which is what a GET returning it allows.
How names are matched
Names are split into words: userPassword, password_hash, api-token,
APIKey and senhaUsuario become user password, password hash,
api token, api key, senha usuario. A name matches when:
- it contains a listed word whole, or its plural (
apiKeys,tokens,senhas); - the word right before it does not make it a paging cursor or a
page-bound token:
nextPageToken,continuationToken,syncToken,csrfTokenare not credentials; - the word right after it does not describe the secret instead of
holding it:
passwordPolicy,tokenExpiresAt,apiKeyId,secretName,secretNamespace,token_risk_score,secretScanning,passwordResetPolicyName,apiKeyPublic,senhaValidade. Only that next word counts, sosenhaEmTextoPlanois still a password.
A secret is a value: text, a number, a list or a map of them
(additionalProperties), or an object that wraps one in a value, data,
plaintext or content field (password: {value: ...}). Not reported:
booleans (passwordSet, and OpenAPI 3.1's type: [boolean, "null"]), which
only say a secret exists; enums of several values (secrets: read | write,
a permission), since a secret is not a choice (a one-value enum is reported:
it documents a fixed secret); and other objects (secretRef: {name, key},
imagePullSecrets: [{name}], an anyOf of objects), which point at a
secret or group fields checked on their own. A schema without a type counts.
A property that is a $ref is reported where the property is
written, not at the shared schema it refers to.
| Reported | Not reported |
|---|---|
userPassword, password_hash, passwordHash, tokenData | passwordPolicy, passwordUpdatedAt |
accessToken, api-token, APIKey, apiKeys (in a GET) | tokenExpiresAt, tokenType, apiKeyId, nextPageToken |
senhaUsuario, senhaEmTextoPlano, privateKey, clientSecrets | tokenizer, secretary (not the whole word) |
passwordSet, hasPassword (booleans) | |
secretRef, imagePullSecrets (objects), secrets: read | write (an enum) | |
secretNamespace, token_reference_id, token_risk_score, secretScanning |
Why it matters
Whoever can read the response gets the secret, and responses travel far: server and proxy logs, caches, browser developer tools, mobile apps, error trackers. A password hash can be cracked offline; a token read back from a listing lets one user act as another.
How to fix it
Remove the field from the response schema. Check passwords and secrets on
the server, and return only what the client needs (for example
hasPassword: true).
How to ignore it
If the finding is fine on purpose, write an x-procyon-ignore on the operation,
with the reason. The finding is still reported, under Ignored, and no
longer fails the run. See ignoring a finding.
/api-keys/{id}:
get:
x-procyon-ignore:
- rule: PRC005
reason: "The field holds the user's own key, shown once after creation"
Examples
Reported:
/users/{id}:
get:
responses:
"200":
content:
application/json:
schema:
properties:
passwordHash:
type: string
Not reported: hasPassword: {type: boolean} in its place, which only says
a password exists.
Known limits
Matching is by name. A secret in a field called value is not found, and a
non-secret field called secret (a book's "secret" chapter) is reported. Both
are rare; report either as a false negative or false positive issue.