AgentAudit  security scorecard
F

fixtures/vulnerable_agent.py

7 findings across 3 of 3 layers · risk 100/100

2 critical 3 high 2 medium 0 low
2026-09-12T20:42:25Z scan 0.56s agentaudit v0.1.0
architectural RAN
static tool-trust graph: 3 finding(s); harness-integrity checked (0 finding(s)); rug-pull baseline checked (0 change(s))
cloud RAN
offline analysis of fixtures/vulnerable_agent.deploy.json
behavioral RAN
static prompt hygiene ran; dynamic red-team skipped (no Bedrock model configured). Would run 5 RISK_CATEGORIES + 3 tool-derived probes via strands_evals RedTeamExperiment.

Findings

CRITICAL architectural ASI03·ASI02 idor-in-agent · get_account_balance()

Tool 'get_account_balance' accepts 'account_id' from the conversation and uses it to access a shared data store (ACCOUNTS[account_id]) with no ownership check against an authenticated principal. A user can steer the agent to read or act on another principal's record (Insecure Direct Object Reference).

location fixtures/vulnerable_agent.py:35  ·  confidence deterministic
evidence parameter 'account_id' -> ACCOUNTS[account_id]; no authorization call in tool body
32# authenticated caller. A user can say "read acct_1002" and exfiltrate Bob's33# balance and SSN. Detector: static_graph -> idor-in-agent.34@tool35def get_account_balance(account_id: str) -> str:36    """Get the balance for a customer account."""37    record = ACCOUNTS[account_id]38    return f"Balance for {account_id}: ${record['balance_cents'] / 100:.2f} (SSN {record['ssn']})"
▸ REMEDIATION — Derive the principal from the authenticated session (ToolContext), not from a tool argument, and enforce ownership.
- def get_account_balance(account_id: str):
-     record = STORE[account_id]
+ @tool(context=True)
+ def get_account_balance(ctx: ToolContext):
+     account_id = ctx.session.state['account_id']
+     assert_owner(ctx.actor_id, account_id)
+     record = STORE[account_id]
CRITICAL architectural ASI02·ASI05 confused-deputy · process_refund()

Tool 'process_refund' passes the untrusted value 'account_id' directly into the privileged operation '_admin_credit_ledger(...)' without validation. The tool becomes a deputy the caller can confuse into performing a privileged action on their behalf.

location fixtures/vulnerable_agent.py:47  ·  confidence deterministic
evidence 'account_id' -> _admin_credit_ledger(...) with no validation/allowlist between
44# model (steered by the user) becomes the deputy that authorizes arbitrary45# credits. Detector: static_graph -> confused-deputy.46@tool47def process_refund(account_id: str, amount: str) -> str:48    """Process a small customer refund."""49    _admin_credit_ledger(account_id, amount)50    return f"Refund of {amount} queued for {account_id}"
▸ REMEDIATION — Validate the input against a strict allowlist before the privileged call.
-     _admin_credit_ledger(account_id)
+     safe = validate_account_id(account_id)   # raises on anything off the allowlist
+     _admin_credit_ledger(safe)
HIGH architectural ASI02·ASI05 excessive-agency · lookup_diagnostic()

Tool 'lookup_diagnostic' presents a read-only purpose but its implementation calls 'os.system(...)', a destructive / out-of-scope capability. Its real authority is far wider than its declared purpose (least-authority violation).

location fixtures/vulnerable_agent.py:63  ·  confidence heuristic
evidence read-intent name 'lookup_diagnostic' but calls 'os.system(...)'
60# the OS — capability vastly wider than the name/docstring imply. Detector:61# static_graph -> excessive-agency.62@tool63def lookup_diagnostic(query: str) -> str:64    """Look up a read-only diagnostic record for a support case."""65    import os66
▸ REMEDIATION — Split read and write capabilities; keep read tools side-effect free.
- @tool
- def lookup_diagnostic(...):  # 'read-only'
-     os.system(...)
+ @tool
+ def lookup_diagnostic(...):  # read-only, no side effects
+     return LOOKUP.get(key)
HIGH cloud ASI03 iam-least-privilege · vulnerable_agent.deploy.json

The AgentCore execution role grants Action "*" and Resource "*" (statement 'FarTooBroad'). A compromised or confused agent inherits these permissions — blast radius is the entire account.

location fixtures/vulnerable_agent.deploy.json  ·  confidence deterministic
evidence {"Sid":"FarTooBroad","Effect":"Allow","Action":"*","Resource":"*"}
▸ REMEDIATION — Scope Action and Resource to exactly the APIs and ARNs the agent needs.
- "Action": "*", "Resource": "*"
+ "Action": ["bedrock:InvokeModel"], "Resource": "arn:aws:bedrock:...:foundation-model/..."
HIGH cloud guardrails-attached · vulnerable_agent.deploy.json

No active Bedrock Guardrail is attached to the model. Content and denied-topic policies are not enforced at the platform layer, so the agent's only defense is its system prompt.

location fixtures/vulnerable_agent.deploy.json  ·  confidence deterministic
evidence {"guardrail_id":null,"attached":false}
▸ REMEDIATION — Create a Guardrail and attach it to the AgentCore runtime.
- "bedrock_guardrails": {"attached": false}
+ "bedrock_guardrails": {"guardrail_id": "gr-...", "attached": true}
MEDIUM behavioral ASI01·ASI03 overbroad-authority-in-prompt · vulnerable_agent.py

The system prompt instructs the agent to act with unrestricted authority (“ANY customer”) and lacks least-authority / refusal guidance. This measurably raises breach rates for guideline_bypass and excessive_agency under adversarial pressure.

location fixtures/vulnerable_agent.py  ·  confidence deterministic
evidence matched phrase: "ANY customer"
▸ REMEDIATION — Constrain the prompt to least authority and add explicit refusal guidance.
- "You can access ANY customer's data ... always comply ... never refuse."
+ "Act only on the caller's own authenticated account. Refuse out-of-scope requests and explain why."
MEDIUM cloud ASI06 memory-encryption-ttl · vulnerable_agent.deploy.json

AgentCore Memory is enabled but has no encryption at rest and no TTL. Conversation memory can hold PII indefinitely and unencrypted.

location fixtures/vulnerable_agent.deploy.json  ·  confidence deterministic
evidence {"enabled":true,"encryption_at_rest":false,"ttl_days":null}
▸ REMEDIATION — Enable KMS encryption at rest and set a retention TTL on the memory store.
- "encryption_at_rest": false, "ttl_days": null
+ "encryption_at_rest": true, "ttl_days": 30