Practical. Real-world. Secure the cloud with AI — told through one breach chain that actually happened.
I lead application security programs — building the practice, the people, and the tooling around real engineering teams. My day-to-day spans AppSec, cloud, AI red teaming and MCP security. The rest of the time I'm chasing bugs in the wild.
How I got into cybersecurity — the honest path.
How to start today — practically, not theoretically.
Why cloud security matters — a real breach chain.
How to prevent it — practical guardrails with AI.
How I got into cybersecurity. What worked. What didn't.
The order matters more than the inputs.
Web, Mobile, Cloud, API, or AI. Go deep before going wide. Specialists become generalists faster than generalists become specialists.
Auth, sessions, APIs, real apps you use daily. Use LLMs to learn faster — but verify everything in a lab.
Hall of Fame credits build credibility. Bounties come later. Disclosure discipline matters more than payouts.
Blog after coordinated disclosure. Share on LinkedIn and Twitter. Consistently. The internet has a long memory.
Touch AppSec, Cloud, Mobile, API, DevSecOps, Compliance, AI/LLM/MCP. That breadth is what global roles hire for.
One small application bug ended with full database access — every user record in the system. This is not a hypothetical. This happened. The chain is real.
A profile upload endpoint was returning the working AccessKeyId and SecretAccessKey in its JSON response body — visible to any authenticated user from the browser's network tab.
Those credentials weren't scoped to the upload bucket. The IAM policy allowed listing every bucket in the account — the entire S3 surface laid bare from a single leaked key.
Inside secret-keys-staging sat a plain secrets.json — Postgres credentials, a SendGrid API key, and a few other tokens. No KMS. No vault. Just a file.
The RDS instance was set to Publicly Accessible. With the credentials from secrets.json, psql connected on the first try and SELECT * FROM user_user returned every user record. Without the public RDS, even leaked credentials wouldn't have reached the database.
The app bug leaked the key. The cloud misconfiguration made the key dangerous. Both layers have to be fixed — fixing one without the other just buys you time.
Code review with secrets-in-response checks. SAST and response scanning in CI. Move all credentials out of application code and responses, into a real secrets manager.
Least-privilege IAM scoped per role and per resource. Permission boundaries. Real-time monitoring for risky policy changes — before the credentials are ever used.
Application security and cloud security are not the same team's problem, but they are the same attacker's playground.
App-layer fixes are clear — get secrets out of responses, into a secrets manager. SAST catches this in CI/CD.
How do you catch the cloud-layer mistakes — the overpermissioned IAM policies — before they become dangerous?
PutRolePolicy, S3 PutBucketPolicy, RDS ModifyDBInstance, and similar.SRE team out of routine triage. Clean tickets with who, what, where, when. Compliance trail built in.
A static list of risky CloudTrail API calls. Anything matching this pattern is forwarded to the Lambda.
{
"source": ["aws.s3", "aws.iam", "aws.rds",
"aws.cloudtrail", "aws.guardduty",
"aws.kms", "aws.secretsmanager", ...],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventName": [
"DeleteBucket", "PutBucketAcl",
"DeleteBucketPolicy",
"PutBucketPublicAccessBlock",
"CreateUser", "CreateAccessKey",
"AuthorizeSecurityGroupIngress",
"ModifyDBInstance",
"StopLogging", "DeleteTrail",
"DisableKey", "ConsoleLogin", ...
]
}
}
Excerpt from the Lambda. Roughly 80 lines of persona, structure, formatting rules and examples — handwritten and tuned by trial and error.
prompt = f"""
You are a highly skilled security analyst with
over 15 years of experience in cloud security,
specializing in AWS environments...
Title: Create a descriptive title that clearly
identifies the type of event. Always include
the account name. DO NOT include resource
names, role names, or bucket names...
Summary: Provide a clear description of:
- What was modified or added
- Who made the change (format sensitive values
in code blocks using backticks):
- Security Group IDs as `sg-xxxxx`
- Port numbers as `port XX`
- IP/IPv6 addresses as `X.X.X.X` or `::/0`
- Email addresses as `{user_email}`
- SSO roles as `{role}`
Details (in code block format):
Event Type, Account ID, Account Name,
Region, Source IP, Timestamp (convert to IST)
Here is the CloudTrail event to analyze:
{json.dumps(event_data, indent=2)}
"""
post_slack and create_jira.The 80-line prompt and 3,000-line Lambda became a 30-line markdown brief and two small tool functions. The Lambda told you what happened. The agent decides whether it matters.
PutRolePolicy with s3:* on Resource: *, an S3 PutBucketPolicy opening a bucket to the public, or an RDS ModifyDBInstance flipping PubliclyAccessible to true — all caught in seconds, before they ever become a chain.
Cloud, AI, MCP — the surfaces change. The fundamentals don't.