Respond
03 · Investigate: was a leaked secret used?
Window: from the first public push (see metadata.json from the snapshot, and the earliest pushed commit) until the moment each secret was revoked. A copied key keeps working until it is revoked, even after the repo is deleted.
Look for: unfamiliar IPs or countries · new resources (especially large VMs or GPU nodes) · new users, keys or role assignments · large reads or exports · cost spikes.
Free early warnings
Before querying anything, check the inboxes of your cloud and SaaS admins. GitHub’s secret scanning partner programme forwards keys found in public repos to their issuers (Microsoft, AWS, Google, Slack, Stripe and many more), and some revoke them automatically. An alert email confirms the key was seen publicly and gives you a timestamp.
Azure
Service principal sign-ins (Log Analytics, Entra diagnostic logs enabled)
AADServicePrincipalSignInLogs
| where TimeGenerated between (datetime(<first-push-utc>) .. datetime(<revoked-at-utc>))
| where AppId == "<app-id>"
| summarize SignIns = count(), Failures = countif(ResultType != "0")
by IPAddress, Location, ResourceDisplayName
| order by SignIns desc
Any IP that is not your pipelines, servers or office is a finding.
What the identity changed (Activity Log)
AzureActivity
| where TimeGenerated between (datetime(<first-push-utc>) .. now())
| where Caller == "<app-id-or-object-id>"
| where OperationNameValue endswith "/WRITE" or OperationNameValue endswith "/DELETE"
| project TimeGenerated, OperationNameValue, ResourceGroup, _ResourceId, CallerIpAddress, ActivityStatusValue
| order by TimeGenerated asc
Without Log Analytics, the CLI covers the last 90 days:
az monitor activity-log list --start-time <first-push-utc> --caller <app-id-or-object-id> \
--query "[].{time:eventTimestamp, op:operationName.value, rg:resourceGroupName, ip:claims.ipaddr}" -o table
New role assignments anywhere in the subscription
az role assignment list --all \
--query "[?createdOn>='<first-push-utc>'].{who:principalName, role:roleDefinitionName, scope:scope, at:createdOn}" -o table
Storage accounts accessed with the account key
StorageBlobLogs
| where TimeGenerated between (datetime(<first-push-utc>) .. datetime(<revoked-at-utc>))
| where AuthenticationType in ("AccountKey", "SAS")
| summarize Requests = count(), Bytes = sum(ResponseBodySize) by CallerIpAddress, OperationName, AccountName
| order by Bytes desc
Cost
Cost Management → Cost analysis, daily granularity, grouped by resource, for the window. Mining shows up as compute you did not create.
AWS
# Everything the leaked key did (CloudTrail event history covers 90 days)
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=AccessKeyId,AttributeValue=<AKIA-key-id> \
--start-time <first-push-utc> \
--query "Events[].{time:EventTime, event:EventName, source:EventSource}" --output table
Also check every region for new EC2 instances, IAM users and access keys created in the window, and Billing → Cost Explorer by service.
Google Cloud
# Everything the leaked service account did (Admin Activity audit logs are on by default)
gcloud logging read \
'protoPayload.authenticationInfo.principalEmail="<sa>@<project>.iam.gserviceaccount.com"
AND timestamp>="<first-push-utc>"' \
--project=<project> --limit=500 \
--format='table(timestamp, protoPayload.methodName, protoPayload.requestMetadata.callerIp)'
Data reads are only logged if Data Access audit logs were enabled before the leak; say so in the findings if they were not. Check Billing → Reports by service for the window.
Databases
| Engine | Where logins show up |
|---|---|
| PostgreSQL | log_connections = on server logs; Azure: PostgreSQLLogs / AzureDiagnostics |
| Azure SQL / SQL Server | Auditing logs (SQLSecurityAuditEvents); failed and successful logins with client IP |
| MySQL | General or audit log; Azure: AzureDiagnostics with Category == "MySqlAuditLogs" |
Look for client IPs outside your network and for unusually large SELECT volumes.
GitHub-side facts
| Fact | Where |
|---|---|
| When it became public | Snapshot metadata.json (created_at, pushed_at), and the owner’s account security log |
| Who cloned it | Insights → Traffic (owner only, last 14 days, gone when the repo is deleted) |
| Who copied it | Forks (list-forks.sh); stars and watchers in the snapshot |
Write it down
For each secret: seen used? yes/no · by which IPs · what was done · evidence location. “No evidence of use” is a valid finding, but only for the logs you actually had. Say which logs were not available.