Amazon GuardDuty: Catch Compromised Coding-Agent Credentials Early

0 views

A prompt injection convinced your coding agent to print its task-role credentials into a “debug gist,” and twelve minutes later someone was calling RunInstances in a region you do not use. Humans read Slack. Attackers read IMDS. Amazon GuardDuty is the unfair advantage for agent fleets: continuous threat detection on CloudTrail, VPC Flow, and DNS — including finding types aimed at stolen credentials and unusual API sequences. Pair with CloudTrail Lake for deep forensics and Secrets Manager rotation so leaked secrets die fast — this post is the early-warning layer.

⚡ TL;DR: Enable GuardDuty in every agent account/OU (Org auto-enable). Prioritize finding types for credential exfil, unusual AssumeRole, and anomalous S3/EC2 from agent roles. Auto-ticket HIGH/CRITICAL; revoke sessions and rotate secrets on match. Related: CloudTrail Lake IAM abuses, SCPs, IAM condition keys, WAF.

Why coding agents are credential candy

Agent runtimes concentrate power:

  1. Task roles that can write to S3, start CodeBuild, call Bedrock
  2. Long-lived forge tokens in Secrets Manager fetched every turn
  3. Logs/traces that accidentally capture Authorization headers
  4. Sandboxes that execute untrusted README / install scripts

Compromise modes you should assume: IMDS SSRF from a tool, secret printed into model output, stolen laptop CI role, or a dependency that exfils env. GuardDuty will not stop the first exfil packet — it will shorten the dwell time.

Signal source What GuardDuty sees Agent relevance
CloudTrail Unusual API / identity Stolen keys, privilege abuse
VPC Flow C2-like traffic patterns Sandbox exfil
DNS logs Algorithmic domains Malware in install scripts
S3 protection Anomalous object access Artifact bucket theft
Malware protection Infected objects / EBS Uploaded agent artifacts

Enable the right protections for agent OUs

Use Organizations delegated admin so every sandbox account inherits GuardDuty without tribal knowledge.

bash
# ✅ org-level: enable GuardDuty + S3 + malware (adjust features to your plan)
aws guardduty create-detector --enable --features '[
  {"Name":"S3_DATA_EVENTS","Status":"ENABLED"},
  {"Name":"EBS_MALWARE_PROTECTION","Status":"ENABLED"},
  {"Name":"RDS_LOGIN_EVENTS","Status":"ENABLED"},
  {"Name":"LAMBDA_NETWORK_LOGS","Status":"ENABLED"}
]'

# ✅ preferred: enable via Organizations / delegated admin console or CFN
aws organizations enable-aws-service-access --service-principal guardduty.amazonaws.com
typescript
// ✅ EventBridge → Lambda ticket on HIGH findings for agent roles
const rule = new events.Rule(this, "GdAgentHigh", {
  eventPattern: {
    source: ["aws.guardduty"],
    detailType: ["GuardDuty Finding"],
    detail: {
      severity: [{ numeric: [">=", 7] }],
      resource: {
        accessKeyDetails: {
          userName: [{ prefix: "coding-agent-" }],
        },
      },
    },
  },
});
rule.addTarget(new targets.LambdaFunction(triageFn));

Finding types that matter for agents

Focus playbooks on:

  • UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration.* — classic stolen task-role keys used outside AWS
  • PrivilegeEscalation / Persistence IAM findings — agent role trying to create users/keys
  • Stealth:IAMUser/CloudTrailLoggingDisabled — someone covering tracks
  • Impact / Crypto EC2 findings from sandbox accounts — mining after compromise
  • Exfiltration:S3/ — bulk reads from artifact buckets
python
# ✅ triage stub: revoke + rotate on credential exfil finding
import boto3, json

gd = boto3.client("guardduty")
iam = boto3.client("iam")
sm = boto3.client("secretsmanager")

def handle_finding(finding: dict):
    ftype = finding["type"]
    if "InstanceCredentialExfiltration" in ftype or "UnauthorizedAccess:IAMUser" in ftype:
        user = finding.get("resource", {}).get("accessKeyDetails", {}).get("userName")
        access_key = finding.get("resource", {}).get("accessKeyDetails", {}).get("accessKeyId")
        # ❌ Do not only "notify Slack" and wait for morning
        if access_key:
            iam.delete_access_key(UserName=user, AccessKeyId=access_key)
        # force secret rotation for forge tokens used by that tenant's tools
        sm.rotate_secret(SecretId=f"agent/{finding['accountId']}/github")
        return {"action": "revoked_and_rotating", "type": ftype}
    return {"action": "ticket_only", "type": ftype}

Correlate with CloudTrail Lake and IAM design

GuardDuty tells you something is wrong. CloudTrail Lake tells you every API the principal called. Standard response:

  1. GuardDuty HIGH → auto-create ticket + page
  2. Disable access keys / invalidate sessions (AWSRevokeOlderSessions on role trust)
  3. Query Lake for the principal’s 24h API set
  4. Confirm SCP / Cedar gaps; tighten IAM condition keys
sql
-- ✅ CloudTrail Lake: APIs by a suspect agent role after finding time
SELECT eventTime, eventName, sourceIPAddress, userAgent, errorCode
FROM agent_trail_lake
WHERE userIdentity.sessionContext.sessionIssuer.arn LIKE '%coding-agent-tool%'
  AND eventTime >= '2026-09-25T00:00:00Z'
ORDER BY eventTime ASC
LIMIT 500

Deny standing access keys for humans in agent accounts via SCPs. Prefer roles + short sessions. Sandbox egress should already be filtered (Network Firewall pattern in this batch’s companion post if published).

Suppress noise without suppressing compromise

Agent accounts generate unusual-but-benign API bursts (many AssumeRole, many S3 puts). Use suppression rules carefully:

  • ✅ Suppress known canary account mining tests
  • ❌ Suppress all UnauthorizedAccess for coding-agent-* roles
  • Tag findings with tenant_id from role session tags when present for Insights joins
Finding severity Auto response Human SLA
CRITICAL / HIGH credential Revoke + rotate + page 15 min
MEDIUM anomalous S3 Ticket + Lake query Same day
LOW recon Weekly digest Weekly

Production checklist

  • [ ] GuardDuty enabled Org-wide; delegated admin owned by security
  • [ ] S3 / Malware / Lambda network features on in agent accounts as licensed
  • [ ] EventBridge rules for HIGH+ findings involving agent role name prefixes
  • [ ] Playbook: revoke keys, AWSRevokeOlderSessions, rotate Secrets Manager, disable tool via AppConfig
  • [ ] CloudTrail Lake workbook for post-finding API reconstruction
  • [ ] No long-lived IAM users in agent OUs; SCP deny CreateAccessKey for agents
  • [ ] Findings exported to SIEM; retention ≥ 90 days
  • [ ] Quarterly tabletop: simulated credential exfil from a Fargate task role

FAQ

Q: Does GuardDuty replace WAF or Firewall?
A: No — WAF is north-south HTTP, Firewall is egress policy, GuardDuty is detection on AWS telemetry. You want all three.

Q: Will it catch a secret pasted into ChatGPT?
A: Not directly. It catches use of stolen AWS credentials and many post-exploitation patterns. Prevent printouts with guardrails and secret hygiene; detect misuse with GuardDuty.

Q: How is this different from CloudTrail Lake alone?
A: Lake is query-when-you-suspect. GuardDuty is always-on detection with scored findings — then you use Lake to go deep.

GuardDuty turns credential theft against coding-agent fleets from a silent multi-day incident into a paged, automatable revoke-and-rotate loop. Enable it everywhere agents run, wire HIGH findings to action — not just dashboards — and keep CloudTrail Lake ready for the autopsy.

Runtime hygiene that makes GuardDuty findings rare

Detection is mandatory; reducing credential surface is cheaper:

  • Prefer task roles over embedding cloud keys in sandbox env
  • Block instance metadata hop abuse (IMDSv2 required, hop limit 1 on EC2; Fargate tasks already constrained — still treat SSRF as real)
  • Never echo secrets into model responses — strip with Bedrock ApplyGuardrail on tool I/O
  • Scope S3 artifact prefixes per tenant (conditional writes) so a stolen role cannot list the whole bucket
  • Cap spend so a compromised role mining EC2 hits Budgets / Cost Anomaly within minutes even if detection lags
json
// ✅ role trust session policy fragment forced on AssumeRole
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "Action": ["iam:*", "organizations:*", "guardduty:DeleteDetector", "cloudtrail:StopLogging"],
    "Resource": "*"
  }]
}

When GuardDuty and Budgets fire together, assume compromise until Lake proves otherwise — dual-signal beats waiting for a perfect single finding.

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.