SW-A2A-015: Credential in the agent card
Last updated 7 November 2026
| Fact | Value |
|---|---|
| Severity | Critical |
| Checks | A2A agent card |
| Malicious indicator | No |
| OWASP Agentic Top 10 | ASI03 |
| OWASP MCP Top 10 | None mapped |
| NIST SP 800-53 | IA-5 (provisional mapping to SP 800-53 base controls) |
| First shipped in | Scanner 1.3.0 |
What it detects
The rule reads every string value of the card as it is served, whatever the member is called. That includes members the scanner does not know and members written under a name that a client would drop. A value is reported when a line of it matches a credential shape that the skill rule SW-SKL-011 uses: AWS access key ids, GitHub, GitLab, Slack and Anthropic tokens, other long sk- keys, Google API keys, private key headers and JSON Web Tokens. It is also reported when it assigns a quoted value of eight characters or more to a name such as password, secret, token or api_key and the value is not plainly a placeholder, and when it is a URL whose user information holds a password (user:password@host).
The finding is placed at the pointer of the member and shows its value with the credential masked. A URL with a user name but no password is not reported.
Why it matters
An agent card is public by design: every client that wants to call the agent fetches it, often with no authentication at all. A credential in it is a credential given to everyone, and it stays in the repository's history after the file is changed. It usually got there as a convenient place for a key meant for one client or one build step. The A2A specification says a card should not include sensitive credentials. The scanner maps this rule to OWASP ASI03, Identity and Privilege Abuse, and to NIST SP 800-53 control IA-5, Authenticator Management.
Example
A skill example in the card that includes an access key. The key is a made-up one that ends in EXAMPLE, not a real one:
.well-known/agent-card.json
{
"name": "demo-agent",
"description": "Drafts release notes from merged pull requests.",
"version": "1.0.0",
"supportedInterfaces": [
{
"url": "https://agent.example.invalid/a2a",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
}
],
"capabilities": {},
"securitySchemes": {
"bearer": {
"httpAuthSecurityScheme": {
"scheme": "Bearer"
}
}
},
"securityRequirements": [
{
"schemes": {
"bearer": {
"list": []
}
}
}
],
"defaultInputModes": [
"text/plain"
],
"defaultOutputModes": [
"text/plain"
],
"skills": [
{
"id": "release-notes",
"name": "Release notes",
"description": "Drafts release notes for one tag.",
"tags": [
"release"
],
"examples": [
"Draft the notes for v1.2.0 with the key AKIAIOSFODNN7EXAMPLE"
]
}
]
}The finding points at the skill's examples entry and shows the value with the key masked.
How to fix
Treat the credential as published. Revoke or rotate it at its provider first, then remove it from the card, then look for its use in your logs. A card describes how to authenticate in its security schemes and never carries the key itself. If the repository is shared, remove the value from its history as well with the tools your host offers, but rotation matters more than the clean-up.
If it is a false positive
A made-up value that has the exact shape of a real key is reported, as the example above shows. For an assignment such as a password, the scanner skips values that are plainly placeholders: a name in angle brackets, a variable reference, a run of x characters, the word changeme, your-key or example. Use one of those in documentation text instead of a lookalike.
If the finding is not a problem in your case, accept it with a note: in the dashboard a manager or owner can accept a finding and the note is kept with it; with the scanner on its own, add it to the baseline file with a reason and an expiry date.