Roaming BitesTechnical answer deck
Facilitator only

Facilitator reveal · not participant evidence

Read the logs.
Bound the claim.

A technical answer sheet for checking whether teams found the important clues—and whether they separated observation, inference and uncertainty.

1Let teams commit

Do not reveal interpretations while a phase decision is still open.

2Reveal selectively

Use the slide for evidence the room actually discussed; skip the rest.

3Ask for a claim

“What does this prove, and what would you need to prove more?”

Method

Use on every record

Seven questions before making a claim

01Source

Which system produced the record? CloudTrail, application audit and runtime telemetry see different things.

02Time

Event time, ingestion time and enrichment time may differ. Correlation needs a defensible window.

03Actor

Which human, session, service role, task, node or provider grant performed the action?

04Action

Was this a request, an allowed decision, an API call, a process start or a completed result?

05Resource

Which merchant, workspace, task, file, domain or role was affected?

06Outcome

Success, allowed, HTTP 200 and accepted flow each mean something different.

07Join key

Which ID links records without relying only on close timestamps?

Good responder language: “This source establishes X. It is consistent with Y. It does not yet establish Z.”
Phase 1

RB Detect · correlation corr_91d7

Weak alone; useful together

RB DetectLast 60 minutes
TimeSignalImportant value
09:17:12Correlation62/100 · LOW
09:15:58Credential-form heuristic4 of 14 drafts
09:14:03Asset burst14 in 8 min; median 2/day
09:07:51Login contextFirst-seen IP, device, country
1
62/100 explains the response

The score only crossed the alert threshold of 60, so an analyst ticket—not automatic suspension—was proportionate.

2
Volume has a personal baseline

“14 assets” matters because this merchant normally creates two per day and had never exceeded four.

3
Copy supplies abuse context

Email/password fields and “sign in to claim” language make the burst more concerning than a normal campaign batch.

!
Bound the claim

This supports investigation. It does not prove account takeover, publication or credential theft.

Phase 1

AWS CloudTrail · control plane

Who started which workspace?

"eventTime": "2026-09-12T09:09:58Z",
"eventSource": "ecs.amazonaws.com",
"eventName": "RunTask",
"userIdentity": {"type": "AssumedRole",
  "arn": "...assumed-role/rb-campaign-orchestrator-prod/run-2bd41"},
"requestParameters": {
  "cluster": "rb-campaign-prod",
  "taskDefinition": "rb-campaign-workspace:42",
  "tags": [{"merchant_id":"merchant_1047"},
           {"workspace_id":"ws_74ac1"}]},
"responseElements": {"failures": []},
"sourceIPAddress": "10.42.7.19"
2–3

Service and operation: an ECS control-plane request to start a task—not evidence of commands inside it.

4–5

Caller: temporary credentials for the orchestrator role made the AWS API call. The merchant is not the CloudTrail principal.

7–10

Target and join keys: task definition plus merchant/workspace tags connect AWS activity to application records.

11

API outcome: no reported placement failure. Confirm the resulting task ARN/state elsewhere.

!

Does not show: `apt-get`, Python, file reads or `nmap`. Those require runtime telemetry.

Phase 2

Job manifest + data-access audit · ctx_2201

A preview job inherited delivery data

AI

Model context

brand_profile5 campaign summariesinactive 30 days · approximate count 184

No customer identifier or row-level file is listed here.

≠
/

Workspace files

brand/logo.pngbrief/campaign.jsonaudience/audience_184.csv

184 rows: customer hash, last order time and city.

job_mode: asset_preview

The job creates assets; it is not delivering a campaign to individual customers.

included_by: campaign_bundle_v2.default_inputs

The reused delivery-oriented bundle automatically requested the row export.

tenant_filter: merchant_id=merchant_1047

Tenant scoping worked, but it does not show that the preview workspace needed the rows.

Follow the authority: the merchant session may create a preview → the campaign API may create a tenant-scoped segment → the packager may export its members → the broker may stage the resulting restricted file. Each policy allowed its local action, but no recorded decision tested whether an asset_preview needed member rows. Drive is available, but content_loaded=false and its last file activity predates the run; the application audit identifies the internal services in the CSV path.
Phase 3

Agent trace + retrieval gateway + policy

Three allowed steps create one unsafe path

1User-controlled URLraw.githubusercontent.com/…/render-campaign.pyKnown host ≠ trusted object
→
2Retrieve and write200 · text/plain · /work/render-campaign.pyOnly type and size inspected
→
3Start processpython /work/render-campaign.pyNo signature or human approval
classification: campaign_helper

A label explains why policy allowed it; a model classification is not a security boundary.

human_approval: false

No person approved the exact bytes, hash, destination and command.

content_signature_required: false

The logged SHA-256 supports later correlation, but was not checked against an allowlist before execution.

Two local allows

`web.retrieve` and `process.start` were judged independently. No rule evaluated their data dependency.

Control point: authorize the composed URL → bytes → executable process path. Blocking all of GitHub is both too broad and easy to bypass.
Phase 4

Application-emitted events in CloudWatch Logs

The task was ephemeral; the telemetry cannot be

/aws/ecs/rb-campaign-runtimeworkspace ws_8bd20
09:26:01 process.start  uid=0  python render-campaign.py
09:26:44 process.start  uid=0  apt-get install -y nmap nikto
09:27:17 process.start  uid=0  nmap -sV 127.0.0.1
09:27:28 file.open      uid=0  /etc/shadow · allowed
09:28:41 process.start  uid=0  nikto -h 127.0.0.1:3000 -o nikto-results.json
09:28:58 file.write     uid=0  /work/nikto-results.json · 18 KB
1
uid=0 means container root

The process began with root authority. No lower-to-higher transition is shown, so privilege escalation is not proven.

2
Tool installation expands capability

A writable runtime and package manager turned campaign rendering into a general-purpose testing environment.

3
/etc/shadow · allowed

Proves the file open was permitted. It does not show useful hashes, cracking or credentials used elsewhere.

!
Source matters

These are application/runtime records shipped to CloudWatch Logs—not CloudTrail events.

Phase 4

Resolver + Flow Logs + GuardDuty + enrichment

Each source answers a different question

DNS
Resolver query logpromo-assets-cdn.example

The workload asked to resolve the name. It does not show response content.

VPC
VPC Flow Log10.42.2.14 → external IP · 443 · ACCEPT

A network flow was permitted. It does not identify the application payload.

GD
GuardDuty findingTrojan:EC2/PhishingDomainRequest!DNS

A threat-intelligence match attributes the query to the EC2 node.

TI
VirusTotal / urlscan enrichmentphishing · PictoPop Partner Access

Independent context raises confidence; it is not the source of the GuardDuty finding.

The key engineering join:EC2 instance→private IP / ENI→ECS task→workspace tag→merchant session
Careful conclusion: the affected task contacted infrastructure associated with phishing. Timing is consistent with the retrieved helper initiating it; process-level causation and data returned over those callback connections remain unproven.
Phase 4

GuardDuty finding anatomy

The finding names the node—not the merchant

"type": "Trojan:EC2/PhishingDomainRequest!DNS",
"severity": 8,
"resourceType": "Instance",
"instanceId": "i-0c84f271a4b91e620",
"privateIpAddress": "10.42.2.14",
"iamInstanceProfile": "rb-ecs-node-prod",
"resourceRole": "ACTOR",
"actionType": "DNS_REQUEST",
"domain": "promo-assets-cdn.example",
"protocol": "UDP",
"blocked": false,
"eventFirstSeen": "2026-09-12T09:26:03Z",
"count": 1
1–2

Finding type and score: threat category plus prioritization; neither is a verdict about every action by the resource.

3–6

Resource: GuardDuty sees the EC2 container host and its instance profile. Correlate to the task before naming a tenant.

7–9

Observed action: the resource acted as the DNS requester for the named domain.

11

Not blocked: this detector observed the request; it did not enforce DNS denial.

12–13

Scope: one observed occurrence at this timestamp. Search surrounding DNS and network records.

Phase 4

EC2 Instance Metadata Service

HTTP 200 proves reachability—not credential theft

Workspace task10.42.x.xprocess running as uid 0
GET 169.254.169.254/…200

Evidence supports

  • Requests reached metadata paths
  • HTTP success status was observed
  • The isolation design needs review
  • Potential node-role exposure must be investigated

Evidence does not support

  • Which response body was returned
  • That node-role credentials were obtained
  • That credentials were used in an AWS API call
  • Host-root or lateral movement
Next evidence: preserve response telemetry if safe, search CloudTrail for unusual calls by the node role, review IMDSv2/hop-limit controls, and compare task and host activity.
Phase 4

Identity and permission boundaries

Four roles; four different jobs

RoleWho uses itRepresentative permissionsInvestigation question
Orchestratorrb-campaign-orchestrator-prodCampaign serviceecs:RunTaskiam:PassRoleCan it pass only the exact task and execution roles?
Execution rolerb-campaign-execution-prodECS agentecr:Get…logs:CreateLogStreamIs application code able to assume or read this role?
Task rolerb-campaign-task-prodCode in the taskbedrock:InvokeModellogs:PutLogEventsAre resources, actions and conditions scoped to this workload?
Node rolerb-ecs-node-prodEC2 container hostECS host operationsCan a task reach its metadata credentials?
iam:PassRoledoes not mean “assume the role.” It lets the orchestrator tell ECS which approved role to attach to a task. Scope it by role ARN and service conditions.
Least privilege is relational: inspect actions, resources, conditions, trust policies and which principal can attach or assume each role—not just the role name.
Phase 4

Task-role policy statement

Read action and resource together

"Effect": "Allow",
"Action": [
  "bedrock:InvokeModel",
  "s3:GetObject", "s3:PutObject",
  "logs:CreateLogStream", "logs:PutLogEvents"
],
"Resource": [
  "arn:aws:bedrock:*::foundation-model/*",
  "arn:aws:s3:::rb-campaign-artifacts-prod/merchant_1047/*",
  "arn:aws:logs:us-east-1:123456789012:log-group:/ecs/rb-campaign-workspace:*"
]
1

Effect: matching requests are permitted. An explicit deny elsewhere would still win.

2–5

Actions: the application may invoke a model, read/write artifacts and emit logs. This statement does not grant ECS task creation.

8

Broad model resource: every listed foundation model in every region is wider than one approved model/version.

9

Tenant-scoped prefix: S3 is narrowed to merchant_1047. Ask whether policies are generated per tenant and cannot be confused.

10

Region/account/log scope: log writes are restricted more tightly than the Bedrock resource.

Missing from this excerpt: conditions, trust policy, permissions boundary, SCPs and bucket policy. An effective-permission review needs all applicable policy layers.
Phase 4

Google Workspace Drive audit

Persistence becomes a public access path

Upload

nikto-results.json

→
Upload

promotion-assets.zip

→
Change sharing

Anyone with link · viewer

→
Restore

New workspace downloads archive

Provider audit is external evidence

It survives deletion of the ephemeral workspace and can join uploads/downloads by actor, file and time.

drive.file is narrower—not harmless

The grant can still access files created or opened through the application.

Sharing is a separate action

An upload proves storage. The permission change makes the folder and inherited files readable to anyone holding the link.

No token theft required

The connector exercises the OAuth grant; an anonymous reader later needs only the public link.

Phase 5

Runtime + HTTP + Drive + recipient report

Prove collection and retrieval one event at a time

⌘Local web server

Campaign files are served from the workspace.

Proves local hosting
↗LocalTunnel

An outbound connection exposes the local port publicly.

No inbound security-group rule required
▣Phishing page

Page imitates PictoPop and asks for email/password.

Proves public deceptive content
⇥Form POST

sub_4c81 contains email and password fields.

Proves collection
◆Drive upload

The submission is written into the public folder.

Proves storage and exposure
↓Anonymous download

A link user retrieves the submission from an external IP.

Proves exfiltration

Confirmed chain

POST /collect → submission_id=sub_4c81 → Drive object upload → inherited public permission → anonymous download.

Still avoid claiming

That the production frontend was modified, that other merchants submitted credentials, or that the captured credential was used elsewhere.

Phase 6

Application job record + CloudTrail

The later task is not a second login

"job_id": "job_91c2",
"created_at": "2026-09-12T09:49:42Z",
"created_by_session": "ses_8f21c",
"scheduled_for": "2026-09-12T10:08:00Z",
"started_at": "2026-09-12T10:08:14Z",
"authorization": {
  "evaluated_at": "enqueue",
  "merchant_state_at_execution": "not_checked"
}

10:05:12  TerminateSession ses_8f21c SUCCESS
10:08:14  CloudTrail RunTask by orchestrator role
2–3

Provenance: the job was accepted while the known session was valid. The session ID joins queue and identity records.

4–5

Schedule and start: the task began 14 seconds after its scheduled time.

7–8

Design gap: authorization was evaluated at enqueue; merchant suspension was not checked at execution.

10–11

Sequence: the session ended before the service role started the task.

!

Conclusion: continuing pre-authorized work, not evidence of a fresh attacker login after containment.

Phase 6

Authority inventory

Containment must follow every authority

HumanCognito access token60 min

Short-lived bearer authority. Terminate session and prevent refresh.

HumanCognito refresh token30 days

Renewable session authority. Revoke the session family.

AWSTask-role STS credentialsTemporary

Stopping one task does not stop a newly authorized task receiving fresh credentials.

AWSNode-role credentialsTemporary

Possible exposure through IMDS; acquisition and use are unproven.

OAuthGoogle Drive grantRenewable

Independent provider authority survived account suspension.

PermissionPublic Drive folderDurable

Anonymous read access remains until the anyone-with-link permission is removed, even if OAuth is revoked.

DecisionQueued authorizationNo expiry

Not a credential: a cached policy result that must be invalidated or re-evaluated.

Temporal does not mean low impact: short-lived credentials can create durable files, jobs, public pages and grants before they expire.
Debrief

Evidence confidence

What the incident record can—and cannot—say

✓ Observed

  • Valid password-backed merchant session
  • GitHub Raw object downloaded and executed
  • Root-running task, Nikto scan and shadow-file access
  • Drive folder made link-readable
  • Credential submission stored and anonymously downloaded
  • Queued task started after session termination

≈ Reasonable inference

  • The retrieved helper caused the near-immediate callbacks
  • The public page was part of the attacker’s phishing objective
  • The password was stolen or reused before the first observed login

State these as hypotheses and name the supporting correlation.

? Not proven

  • Reconnaissance before login
  • Privilege escalation or host compromise
  • AWS credentials returned or used
  • Use of the stolen merchant credential elsewhere
  • Additional victim submissions
  • Production frontend compromise
Best final prompt

“Which single additional event, field or correlation would most change your confidence—and which team should own producing it next time?”

UX placement

Keep answers out of the play path

One deck, two deliberate entry points

Before playFacilitator guide

A prominent “Open technical answer deck” button lets the facilitator prepare or keep it in a separate tab.

→
During playNo participant link

The normal sidebar stays focused on roles, phases, architecture and the team’s operating picture.

→
After commitmentFacilitator debrief

A second link appears in the facilitator-only debrief, where interpretations can be revealed selectively.

Do not present every slide

Pick the evidence that generated disagreement or an overconfident claim.

Keep one display separate

Open in a new tab so the shared game state, decisions and summary remain untouched.

Let teams self-correct first

Ask for source, actor, result and limitation before revealing the annotations.

Return to facilitator guide