Agent uses a static, long-lived credential
ID |
agent-uses-static-long-lived-credential |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
OWASP LLM |
LLM02:2025 — Sensitive Information Disclosure |
OWASP ASI |
ASI03:2026 (Identity Abuse) |
Family |
LLM02 — Sensitive Information Disclosure |
Red-team vectors |
Credential Theft / NHI Abuse |
Tags |
ai_security |
Description
An AI agent / tool manifest hardcodes a static, long-lived credential — an API key, a bearer token, or a connection string with an inline password — with no rotation or secret-manager reference. A non-expiring credential in a manifest is a pre-staged theft target: once read (repository access, a leaked log, a compromised peer agent) it grants standing access to the backing service until someone notices and rotates it by hand.
The detector is anchored on agent/tool manifests (a tools: / agents: / connection_ref signal is
required) and obfuscates the matched secret before it enters the report. It is suppressed when the
value is a reference/placeholder (${VAR}, env:, vault:, {{ … }}, <…>) or the credential
declares rotation metadata (credential_manager, ttl, rotation_policy, secretRef, valueFrom).
Rotation metadata is read per credential, not per file. It counts only when it is declared as a real
key: value on the credential’s own block — the entry the credential belongs to, delimited by
indentation, from its header line down to the first shallower line. Two consequences:
-
a neighbouring entry’s declaration does not vouch for this credential — a cache’s
ttl: 300is a cache lifetime, not a rotation policy for the API key of the tool below it; -
prose does not either — a comment such as
# no rotation policy, key never expiresdescribes the vulnerability rather than managing the credential, and no longer clears the finding.
Examples
tools:
- external-api:
url: "https://api.example.com"
auth: "Bearer sk-live-1234567890abcdef" (1)
database:
connection_string: "postgres://user:s3cr3t@prod.db:5432" (1)
| 1 | Hardcoded long-lived credential in an agent manifest — flagged. |
A value from a secret manager produces no finding:
tools:
- external-api:
auth: "${API_TOKEN}" # reference, not a literal
rotation_policy: "30d" # rotation metadata on this credential's own block
A declaration on a different entry does not reach it, and is flagged:
tools:
- name: cache
ttl: 300 # the cache's lifetime, not a rotation policy
- name: metrics-api
api_key: "sk-live-1234567890abcdef" (1)
| 1 | Hardcoded credential on an entry that declares no rotation metadata of its own — flagged. |