August 27, 2026
I Put Fake AWS Identities in the Cloud to Catch Hackers
Building GhostIam — an open-source deception framework that turns attacker reconnaissance into a high-confidence alert

By Kakashi
7 min read
Building GhostIam — an open-source deception framework that turns attacker reconnaissance into a high-confidence alert
GitHub: github.com/kakashi-kx/ghostiam· Release: [v1.0.0]
Imagine you're an attacker.
You find an AWS access key. It works. The identity looks interesting:
aws sts get-caller-identity
# ghost-prod-db-read-a7f3c2aws sts get-caller-identity
# ghost-prod-db-read-a7f3c2So you start looking around. What can this account access? What roles exist? What S3 buckets are available? Are there backups? Can you create another access key?
You think you've found something useful.
You haven't. Someone left that identity there hoping you'd find it. And the moment you touch it, they know you're there.
That's the idea behind GhostIam.
The problem with cloud detection
Cloud environments generate an enormous amount of activity. Employees log in. Applications call APIs. CI/CD pipelines deploy infrastructure. Monitoring systems query resources. Developers inspect permissions. Automation creates and deletes things.
So when a security team sees something unusual, they have to answer a difficult question: Is this actually malicious?
That usually means building detection rules around unusual login locations, abnormal API activity, impossible travel, strange user agents, access patterns, behavioral baselines, thresholds, and anomaly scores.
These systems are useful. But they also produce noise.
GhostIam starts from a different question:
Why would anyone legitimate ever touch this identity?
A GhostIam identity has no employee, no application, no CI job, no scheduled task, and no legitimate owner. So if someone starts using it, the event is already interesting.
GhostIam flips the traditional detection model. Instead of trying to determine whether a legitimate identity is behaving maliciously, it creates identities where interaction itself is the signal.
Meet the ghosts
A fake AWS user named test-honeypot-user isn't going to fool anyone. An attacker sees that and immediately starts wondering why it exists.
So GhostIam takes a different approach. The identities are designed to look like something that could actually exist in a production environment:
ghost-prod-db-read-a7f3c2
ghost-prod-admin-c2b8
ghost-prod-backup-91deghost-prod-db-read-a7f3c2
ghost-prod-admin-c2b8
ghost-prod-backup-91deThe policies follow the same idea:
Here is the information formatted cleanly into a table:
- ProdDatabaseReadAccess — Production database access
- CloudInfrastructureViewer — Infrastructure visibility
- S3BackupOperator — Backup bucket access
- IAMSecurityAuditor — IAM audit capabilities
- CrossAccountAccessRole — Cross-account bridge
The names sound valuable. The permissions aren't.
- `ProdDatabaseReadAccess` only provides RDS/DynamoDB `Describe` calls
- `S3BackupOperator` can inspect bucket metadata but doesn’t get object access
- `IAMSecurityAuditor` can inspect IAM but cannot create users or access keys- `ProdDatabaseReadAccess` only provides RDS/DynamoDB `Describe` calls
- `S3BackupOperator` can inspect bucket metadata but doesn’t get object access
- `IAMSecurityAuditor` can inspect IAM but cannot create users or access keysThe deception is in the gap between what the identity looks like and what it can actually do.
The trap needs a trigger
Creating fake users is only half the problem. The real question is: How do you know when somebody touches one?
GhostIam uses AWS's native event pipeline:
Ghost IAM User
↓
AWS CloudTrail
↓
Amazon EventBridge
↓
Go AWS Lambda
↙ ↘
Slack Dashboard
Ghost IAM User
↓
AWS CloudTrail
↓
Amazon EventBridge
↓
Go AWS Lambda
↙ ↘
Slack Dashboard
The Terraform deployment creates the detection infrastructure: a Go Lambda detector, an EventBridge rule, the Lambda invocation permission, and a minimal IAM role.
The EventBridge rule filters CloudTrail activity associated with GhostIam identities. When the event reaches Lambda, GhostIam parses and enriches it before sending the alert to Slack and the dashboard.
More than an alert
A notification saying "GHOST USER ACTIVATED" is useful. But it doesn't tell me enough.
What happened before that? Did the attacker simply check the identity? Did they start enumerating the environment? Did they look for storage? Did they attempt data access? Did they try to establish persistence?
That's why GhostIam doesn't stop at detection. It reconstructs the activity as an attacker journey:
RECONNAISSANCE
↓
sts:GetCallerIdentity
↓
PRIVILEGE / DISCOVERY
iam:ListRoles
↓
DISCOVERY
s3:ListBuckets
↓
DATA ACCESS
s3:GetObject
↓
LATERAL-MOVEMENT / INFRA DISCOVERY
ec2:DescribeInstances
RECONNAISSANCE
↓
sts:GetCallerIdentity
↓
PRIVILEGE / DISCOVERY
iam:ListRoles
↓
DISCOVERY
s3:ListBuckets
↓
DATA ACCESS
s3:GetObject
↓
LATERAL-MOVEMENT / INFRA DISCOVERY
ec2:DescribeInstances
Five actions, mapped to MITRE ATT&CK techniques, calculated risk score: 10/10 Critical.
This changes the investigation. Instead of "someone used a suspicious AWS key," you start with "the identity was checked, roles were enumerated, buckets were discovered, an object was accessed, and infrastructure was queried."
No AWS required
If someone wants to experiment with GhostIam, I don't want them to first configure an AWS account, CloudTrail, Terraform, EventBridge, Lambda, IAM permissions, and Slack just to see the project work.
So GhostIam has a local mode:
git clone https://github.com/kakashi-kx/ghostiam
cd ghostiam
make build
./build/ghostiam deploy - local - count 5 - with-keys
./build/ghostiam simulate - local - username ghost-prod-db-read-a7f3c2 - journey
./build/ghostiam dashboard - port 8080 - api-key demo-keygit clone https://github.com/kakashi-kx/ghostiam
cd ghostiam
make build
./build/ghostiam deploy - local - count 5 - with-keys
./build/ghostiam simulate - local - username ghost-prod-db-read-a7f3c2 - journey
./build/ghostiam dashboard - port 8080 - api-key demo-keyThen open [http://localhost:8080](http://localhost:8080`). No AWS account required. The local mode uses a JSON store for ghosts while the dashboard persists operational data in SQLite and streams live updates over SSE.
The dashboard
I didn't want GhostIam to become another security tool where everything interesting lives inside terminal output. So I built an operations console around it.
Built with Go, HTMX, SQLite, Chart.js, and Server-Sent Events. Includes:
- Dashboard — live statistics, alert traffic, severity distribution, top actions, recent alerts
- Ghosts — deploy and archive decoys, inspect keys and attached policies
- Alerts— review triggered activity, source IPs, severity, simulate alerts
- Journeys — visualize attack kill chains, timelines, MITRE ATT&CK mappings, Mermaid graphs
- Mesh — see correlated identities across AWS, GitHub, and Okta
- Seeds — track where ghost credentials have been planted
What if nobody finds the ghost?
Creating a fake credential doesn't guarantee an attacker will ever see it.
So GhostIam includes a token seeder. Put ghost credentials somewhere an attacker might realistically discover them:
ghostiam seed github
ghostiam seed s3
ghostiam seed pastebin
ghostiam seed all
ghostiam seed github
ghostiam seed s3
ghostiam seed pastebin
ghostiam seed allThe project supports bait locations including GitHub, S3, and a local Pastebin-style simulation. The seed records can then be tracked from the Seeds page.
The attack path becomes:
Ghost credential
↓
Realistic-looking bait
↓
Attacker discovers it
↓
Attacker tests credential
↓
CloudTrail
↓
GhostIam detector
↓
🚨 AlertGhost credential
↓
Realistic-looking bait
↓
Attacker discovers it
↓
Attacker tests credential
↓
CloudTrail
↓
GhostIam detector
↓
🚨 AlertNow the deception isn't just waiting. You're deliberately creating breadcrumbs.
One ghost. Many platforms.
Attackers don't think in terms of individual platforms. They think in terms of identities.
If they find an employee's credentials, they don't care whether those credentials belong to AWS, GitHub, or Okta. They care about what that identity gives them.
GhostIam has a concept called Ghost Mesh:
ghost-prod-admin-a7f3
│
┌──────────┼──────────┐
▼ ▼ ▼
AWS GitHub Okta
IAM User Employee
│ │ │
└──────────┼──────────┘
▼
Correlated Alert
ghost-prod-admin-a7f3
│
┌──────────┼──────────┐
▼ ▼ ▼
AWS GitHub Okta
IAM User Employee
│ │ │
└──────────┼──────────┘
▼
Correlated Alert
The project models a synthetic persona across AWS IAM, GitHub, and Okta. If one platform is triggered, the identity can be correlated across the other platform legs. One attacker interaction becomes a much larger piece of context.
The whole system from an attacker's perspective
ATTACKER
│
▼
Finds ghost credential
│
▼
Tests the identity
│
▼
Starts enumeration
│
┌─────────┴─────────┐
▼ ▼
AWS IAM GitHub
│ │
└─────────┬─────────┘
▼
Ghost activated
│
▼
CloudTrail
│
▼
EventBridge
│
▼
Lambda
┌─────┴─────┐
▼ ▼
Slack Dashboard
│
▼
Attack Journey
│
▼
MITRE ATT&CK
ATTACKER
│
▼
Finds ghost credential
│
▼
Tests the identity
│
▼
Starts enumeration
│
┌─────────┴─────────┐
▼ ▼
AWS IAM GitHub
│ │
└─────────┬─────────┘
▼
Ghost activated
│
▼
CloudTrail
│
▼
EventBridge
│
▼
Lambda
┌─────┴─────┐
▼ ▼
Slack Dashboard
│
▼
Attack Journey
│
▼
MITRE ATT&CK
A synthetic identity becomes the tripwire. The cloud logs become the sensor. The dashboard becomes the investigation surface. The attack journey becomes the context.
Why the decoy permissions matter
One of the easiest mistakes in a deception system is making the bait too powerful. If the fake account actually has production access, you've created a liability instead of a sensor.
GhostIam deliberately keeps the policies limited:
Decoy policy | Looks like | Actually allows
---------------------------------------------------------------------------------------------
ProdDatabaseReadAccess | Production DB access | RDS/DynamoDB Describe only
CloudInfrastructureViewer | Infrastructure visibility | EC2/VPC/Lambda List/Describe
S3BackupOperator | Backup access | Bucket listing/metadata only
IAMSecurityAuditor | IAM audit access | IAM List/Get; no user/key creation
CrossAccountAccessRole | Cross-account bridge | Identity/org structure readsDecoy policy | Looks like | Actually allows
---------------------------------------------------------------------------------------------
ProdDatabaseReadAccess | Production DB access | RDS/DynamoDB Describe only
CloudInfrastructureViewer | Infrastructure visibility | EC2/VPC/Lambda List/Describe
S3BackupOperator | Backup access | Bucket listing/metadata only
IAMSecurityAuditor | IAM audit access | IAM List/Get; no user/key creation
CrossAccountAccessRole | Cross-account bridge | Identity/org structure readsThe goal: make the identity interesting without making it dangerous.
The CLI is the control plane
ghostiam deploy - count 20 - prefix prod
ghostiam simulate - username ghost-prod-db-read-a7f3c2
ghostiam journey - username ghost-prod-db-read-a7f3c2
ghostiam replay - file attack-journey-2026–08–07.json
ghostiam seed all - ghost-user ghost-prod-db-read-a7f3c2
ghostiam mesh deploy - count 2 - platforms all
ghostiam dashboard - port 8080
ghostiam deploy - count 20 - prefix prod
ghostiam simulate - username ghost-prod-db-read-a7f3c2
ghostiam journey - username ghost-prod-db-read-a7f3c2
ghostiam replay - file attack-journey-2026–08–07.json
ghostiam seed all - ghost-user ghost-prod-db-read-a7f3c2
ghostiam mesh deploy - count 2 - platforms all
ghostiam dashboard - port 8080
From local mode to AWS
Once the local demo makes sense, GhostIam can be deployed into an actual AWS account:
export SLACK_WEBHOOK_URL="https://hooks.slack.com/..."
make deploy-lambda
./build/ghostiam deploy - count 20 - prefix prodexport SLACK_WEBHOOK_URL="https://hooks.slack.com/..."
make deploy-lambda
./build/ghostiam deploy - count 20 - prefix prodThe AWS pipeline: IAM → CloudTrail → EventBridge → Lambda → Slack + Dashboard.
Security note
GhostIam handles realistic-looking credentials and exposes live security telemetry through its dashboard. Treat it like a security-sensitive system.
The dashboard supports API-key authentication. Do not run the dashboard with authentication disabled anywhere except localhost.
The ghosts are supposed to be exposed to attackers. The GhostIam control plane isn't.
Why I built this
The original question was pretty simple: Can I create an identity where any interaction with it is suspicious by definition?
That question turned into something much bigger. First, fake IAM users. Then realistic policies. Then CloudTrail detection. Then EventBridge and Lambda. Then Slack alerts. Then attack simulation. Then MITRE ATT&CK mapping. Then credential seeding. Then a dashboard. Then cross-platform identity correlation.
At some point, GhostIam stopped feeling like a simple IAM honeypot.
It became an experiment in identity-based deception.
You don't always need a more complicated detection model. Sometimes you need a better signal.
What's next
- Azure support
- GCP support
- Real Okta organization provisioning
- Security Hub integration
- Behavioral analysis engine for attacker intent scoring
The direction I'd like to explore: a synthetic identity that exists across an entire organization.
FAKE EMPLOYEE
│
┌──────────────┼──────────────┐
▼ ▼ ▼
AWS GitHub Okta
│ │ │
▼ ▼ ▼
Azure CI/CD GCP
│ │ │
└──────────────┼──────────────┘
▼
Identity triggered
│
▼
Correlated detection
FAKE EMPLOYEE
│
┌──────────────┼──────────────┐
▼ ▼ ▼
AWS GitHub Okta
│ │ │
▼ ▼ ▼
Azure CI/CD GCP
│ │ │
└──────────────┼──────────────┘
▼
Identity triggered
│
▼
Correlated detection
The attacker sees one identity. The defender sees the entire graph.
Try it yourself
You don't need an AWS account to start:
git clone https://github.com/kakashi-kx/ghostiam
cd ghostiam
make build
./build/ghostiam deploy - local - count 5 - with-keys
./build/ghostiam simulate - local - username ghost-prod-db-read-a7f3c2 - journey
./build/ghostiam dashboard - port 8080 - api-key demo-keygit clone https://github.com/kakashi-kx/ghostiam
cd ghostiam
make build
./build/ghostiam deploy - local - count 5 - with-keys
./build/ghostiam simulate - local - username ghost-prod-db-read-a7f3c2 - journey
./build/ghostiam dashboard - port 8080 - api-key demo-keyThen open [http://localhost:8080](http://localhost:8080`).
Create a ghost. Trigger it. Watch the alert. Open the journey. Then break something. Add a new decoy policy. Create another seed location. Add another platform. Improve the detection logic. Build something on top of it.
That's what I want from an open-source security project. Not just people starring it. People messing with it.
GitHub: github.com/kakashi-kx/ghostiam
Final thought
Security detection usually asks: Does this behavior look malicious?
GhostIam asks: Why did anyone touch this identity at all?
That is a much smaller question. And sometimes, smaller questions produce much stronger signals.
Create an identity that nobody should ever use. Make it look interesting. Let an attacker find it. Then wait.
If the ghost moves, you know someone is there. 👻
Let's connect! I'm actively looking for opportunities in cybersecurity, red teaming, and tool development. You can find me on GitHub or reach out on LinkedIn.
If you found this breakdown helpful, give it a clap 👏 and share it with your network!