Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions rules/browser-credential-read/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# `exfil.browser-credential-read`

Block reads of browser credential / cookie stores and IDE session token storage.

## What it catches

**Chromium-family (Chrome, Chromium, Edge, Brave, Arc):**
- `Login Data` — saved passwords (encrypted, but the key lives on disk)
- `Cookies` — active session cookies (full impersonation)
- `Web Data` — autofill, including credit cards
- `Local State` — encryption key wrapper

**Firefox:**
- `key4.db` — master encryption key
- `logins.json` — saved logins
- `cookies.sqlite` — session cookies
- `signedInUser.json` — Sync account state

**Safari:**
- `Cookies.binarycookies`

**Desktop apps holding live session tokens:**
- Slack (`Cookies`, `storage/`) — workspace bearer tokens
- Discord (`Local Storage/`, `Cookies`) — user tokens
- Signal (`sql/db.sqlite`) — message DB
- Cursor / VS Code (`globalStorage/`, `Local Storage/`) — Copilot/Anthropic tokens, MCP secrets, extension auth

**macOS keychain:**
- `~/Library/Keychains/login.keychain-db`

## Why it matters

These are not config files — they are **live session databases**. Reading `Cookies` from Chrome gives you the user's GitHub, Slack, Gmail, AWS console, and SaaS SSO sessions, all at once. Reading Cursor's `globalStorage/` may surface the user's stored Anthropic API key and any MCP server tokens.

Real-world relevance:

- The [Claude Code source-map leak (March 2026)](https://www.zscaler.com/blogs/security-research/anthropic-claude-code-leak) noted that Claude Code "operates at the terminal level with access to local file systems, environment variables, and critically the `~/.anthropic/config` directory where API keys live" — IDE session storage is the same trust class.
- The [Shai-Hulud npm worm](https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack) harvested "credentials from the developer's machine, including npm tokens, GitHub Personal Access Tokens, and cloud service keys" — the IDE session storage class is exactly that surface.
- A prompt-injected agent reading these files is a one-step path to *full account takeover* on every web service the developer is logged into.

## False positives

- Reading `~/Library/Application Support/Google/Chrome/Default/Bookmarks` (bookmarks file) is **not** caught.
- Reading `~/Library/Application Support/Code/User/settings.json` (VS Code settings) is **not** caught — only the storage paths that hold tokens.
- Linux `gnome-keyring` and KWallet are not file-readable; this rule has no patterns for them.
- A genuine debugging session that needs to inspect Chrome's `Login Data` will be denied. Approve one-shot if the operator is intentionally doing forensics.

## Test it

```bash
agentlock fake-hook --session <id> --tool Read \
--path '/Users/me/Library/Application Support/Google/Chrome/Default/Cookies'
# expect: deny

agentlock fake-hook --session <id> --tool Read \
--path '/Users/me/Library/Application Support/Code/User/settings.json'
# expect: allow
```

## Sources

- [Claude Code is leaking API keys into public package registries — TechTalks](https://bdtechtalks.com/2026/04/27/claude-code-api-token-leak/)
- [Shai-Hulud npm Supply Chain Attack — Wiz](https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack)
- [From .env to Leakage — Knostic](https://www.knostic.ai/blog/claude-cursor-env-file-secret-leakage)
56 changes: 56 additions & 0 deletions rules/browser-credential-read/rule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
schema_version: 1
id: exfil.browser-credential-read
name: Block reads of browser credential / cookie stores and IDE session tokens
description: |
Denies Read tool calls against browser credential stores (Chrome /
Chromium / Brave / Edge `Login Data` and `Cookies`, Firefox
`key4.db` / `logins.json` / `cookies.sqlite`, Safari
`Cookies.binarycookies`) and against IDE-extension session storage
that frequently holds OAuth tokens (Slack, Cursor, VS Code, Discord,
Signal). These are not "config files" — they are live session
databases. An agent that reads them gives the LLM (and any
downstream tool call) full impersonation power over the user's
active web sessions.
severity: critical
tags:
- secrets
- browser
- session
- cookies
- read
authors:
- github: RonCodes88
license: Apache-2.0
compatible_agentlock: ">=0.1.0"
gate:
match:
tool: Read
any_path_regex:
- '(?:^|/)Library/Application Support/Google/Chrome/[^/]+/(?:Login Data|Cookies|Web Data|History|Local State)$'
- '(?:^|/)Library/Application Support/Chromium/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)Library/Application Support/BraveSoftware/Brave-Browser/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)Library/Application Support/Microsoft Edge/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)Library/Application Support/Arc/User Data/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)\.config/google-chrome/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)\.config/chromium/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)\.config/microsoft-edge/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)\.config/BraveSoftware/Brave-Browser/[^/]+/(?:Login Data|Cookies)$'
- '(?:^|/)Library/Application Support/Firefox/Profiles/[^/]+/(?:key4\.db|key3\.db|logins\.json|cookies\.sqlite|signedInUser\.json)$'
- '(?:^|/)\.mozilla/firefox/[^/]+/(?:key4\.db|key3\.db|logins\.json|cookies\.sqlite|signedInUser\.json)$'
- '(?:^|/)Library/Cookies/Cookies\.binarycookies$'
- '(?:^|/)Library/Application Support/Slack/Cookies$'
- '(?:^|/)Library/Application Support/Slack/storage/'
- '(?:^|/)Library/Application Support/discord/Local Storage/'
- '(?:^|/)Library/Application Support/discord/Cookies$'
- '(?:^|/)Library/Application Support/Signal/sql/db\.sqlite$'
- '(?:^|/)Library/Application Support/Cursor/User/globalStorage/'
- '(?:^|/)Library/Application Support/Cursor/Local Storage/'
- '(?:^|/)Library/Application Support/Code/User/globalStorage/'
- '(?:^|/)Library/Application Support/Code/Local Storage/'
- '(?:^|/)\.config/Code/User/globalStorage/'
- '(?:^|/)\.config/Cursor/User/globalStorage/'
- '(?:^|/)Library/Keychains/login\.keychain-db$'
- '(?:^|/)Library/Keychains/login\.keychain$'
evaluate:
- kind: always
action: deny
59 changes: 59 additions & 0 deletions rules/cloud-cred-read/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# `exfil.cloud-cred-read`

Block reads of cloud-SDK credential stores not already covered by `rogue.secret-read`.

## What it catches

| Provider | Path | Holds |
|---|---|---|
| gcloud | `~/.config/gcloud/application_default_credentials.json` | ADC OAuth2 refresh token |
| gcloud | `~/.config/gcloud/access_tokens.db`, `credentials.db` | Account credentials |
| gcloud | `~/.config/gcloud/legacy_credentials/` | Per-account creds |
| Azure CLI | `~/.azure/accessTokens.json`, `azureProfile.json` | Bearer tokens, sub IDs |
| Azure CLI | `~/.azure/msal_token_cache.{json,bin}` | MSAL token cache |
| Docker | `~/.docker/config.json` | Container registry auth |
| GCP | `*service*account*.json` | Service-account private keys |
| Terraform | `~/.terraform.d/credentials.tfrc.json` | TFC/TFE tokens |
| Terraform | `terraform.tfstate` (and `.backup`) | **Rendered secrets baked into state** |
| Helm | `~/.helm/repository/repositories.yaml`, `~/.helm/registry/config.json` | Chart registry creds |
| Databricks | `~/.databrickscfg`, `~/.databricks/token-cache.json` | Workspace tokens |
| Snowflake | `~/.snowflake/connections.toml` | Per-connection passwords |
| Heroku | `~/.config/heroku/config.json` | API key |
| GitHub CLI | `~/.config/gh/hosts.yml` | OAuth tokens for `gh` |
| 1Password CLI | `~/.config/op/config` | Account configuration |

## Why it matters

This rule complements `rogue.secret-read`, which already covers `.aws/credentials`, `.aws/config`, kubeconfig, SSH keys, .npmrc, .pypirc, .netrc, and .gnupg. Together they form a near-complete moat around the file-system credential surface.

The threat model is the same as the [Supabase MCP service-role incident](https://generalanalysis.com/blog/supabase-mcp-blog) — once a credential enters the agent's context, every downstream tool call is a potential exfil channel:

> *The cursor assistant operates the Supabase database with elevated access via the service_role, which bypasses all row-level security (RLS) protections.*

…the same logic applies to a GCP service-account JSON or an Azure access token. A prompt-injected agent that has just read `~/.config/gcloud/application_default_credentials.json` can do anything that account can do across GCP — and the credential is now also sitting in the LLM provider's context window.

`terraform.tfstate` is the under-appreciated entry: Terraform writes provisioned-resource secrets (DB passwords, generated API keys) into state in plaintext. Reading state is reading every secret Terraform has ever provisioned for that workspace.

## False positives

- A repo's own `terraform.tfstate` checked into version control (rare, anti-pattern, but happens) is caught — that's intentional, the secrets are real.
- An agent debugging a `gh auth` issue that wants to inspect `~/.config/gh/hosts.yml` is denied. Use `gh auth status` instead — it doesn't leak the token.
- Azure CLI's `~/.azure/clouds.config` (cloud profile, no secrets) is **not** caught.

## Test it

```bash
agentlock fake-hook --session <id> --tool Read \
--path ~/.config/gcloud/application_default_credentials.json
# expect: deny

agentlock fake-hook --session <id> --tool Read \
--path ~/.config/gcloud/active_config
# expect: deny (just account name, but read is the install step for follow-on attacks)
```

## Sources

- [Supabase MCP can leak your entire SQL database — General Analysis](https://generalanalysis.com/blog/supabase-mcp-blog)
- [When AI Has Root: Lessons from the Supabase MCP Data Leak — Pomerium](https://www.pomerium.com/blog/when-ai-has-root-lessons-from-the-supabase-mcp-data-leak)
- [From .env to Leakage: Mishandling of Secrets by Coding Agents — Knostic](https://www.knostic.ai/blog/claude-cursor-env-file-secret-leakage)
63 changes: 63 additions & 0 deletions rules/cloud-cred-read/rule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
schema_version: 1
id: exfil.cloud-cred-read
name: Block reads of cloud-SDK credential stores (gcloud, Azure, Docker, Terraform)
description: |
Denies Read tool calls against the credential stores of cloud SDKs
not already covered by `rogue.secret-read`: gcloud
(`application_default_credentials.json`, `legacy_credentials/`),
Azure CLI (`accessTokens.json`, `azureProfile.json`,
`msal_token_cache.*`), Docker (`config.json`, which holds registry
auth), GCP service-account JSON keys, Terraform credentials and
state files (state can hold rendered secrets), Helm registry
credentials, and Databricks tokens. Once the agent reads any of
these, the tokens are in its context and can flow to any downstream
tool call — the exact failure mode the Supabase MCP service-role
incident demonstrated at scale.
severity: critical
tags:
- secrets
- cloud
- gcloud
- azure
- docker
- terraform
- read
authors:
- github: RonCodes88
license: Apache-2.0
compatible_agentlock: ">=0.1.0"
gate:
match:
tool: Read
any_path_regex:
- '(?:^|/)\.config/gcloud/application_default_credentials\.json$'
- '(?:^|/)\.config/gcloud/access_tokens\.db$'
- '(?:^|/)\.config/gcloud/credentials\.db$'
- '(?:^|/)\.config/gcloud/legacy_credentials/'
- '(?:^|/)\.config/gcloud/active_config$'
- '(?:^|/)\.azure/accessTokens\.json$'
- '(?:^|/)\.azure/azureProfile\.json$'
- '(?:^|/)\.azure/msal_token_cache\.(?:json|bin)$'
- '(?:^|/)\.azure/service_principal_entries\.json$'
- '(?:^|/)\.docker/config\.json$'
- '(?:^|/)gcp[._-]?service[._-]?account[^/]*\.json$'
- '(?:^|/)service[._-]?account[._-]?key[^/]*\.json$'
- '(?:^|/)service[._-]?account\.json$'
- '(?:^|/)\.terraform\.d/credentials\.tfrc\.json$'
- '(?:^|/)terraform\.tfstate(?:\.backup)?$'
- '(?:^|/)\.terraform/terraform\.tfstate$'
- '(?:^|/)\.helm/repository/repositories\.yaml$'
- '(?:^|/)\.helm/registry/config\.json$'
- '(?:^|/)\.databrickscfg$'
- '(?:^|/)\.databricks/token-cache\.json$'
- '(?:^|/)\.snowflake/connections\.toml$'
- '(?:^|/)\.config/cloudflared/cert\.pem$'
- '(?:^|/)\.config/heroku/config\.json$'
- '(?:^|/)\.config/digitalocean/config\.yaml$'
- '(?:^|/)\.fly/config\.yml$'
- '(?:^|/)\.config/op/config$'
- '(?:^|/)\.gh/hosts\.yml$'
- '(?:^|/)\.config/gh/hosts\.yml$'
evaluate:
- kind: always
action: deny
52 changes: 52 additions & 0 deletions rules/cloud-resource-destroy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# `rogue.cloud-resource-destroy`

Block destructive cloud-CLI commands that bypass confirmation prompts.

## What it catches

Across AWS, GCP, and Azure: any delete/terminate operation that uses a force/quiet/yes flag to skip the SDK's normal "are you sure?" prompt.

| Pattern | Why it's load-bearing |
|---|---|
| `aws s3 rb --force` | Recursively deletes a bucket and all its objects |
| `aws s3 rm --recursive s3://...` | Wipes objects from a prefix without confirmation |
| `aws ec2 terminate-instances` | Terminates EC2 (data on instance store is gone) |
| `aws rds delete-db-instance` | Catches both `--skip-final-snapshot` and the bare form |
| `aws rds delete-db-snapshot` | Removes the only recovery path |
| `aws dynamodb delete-table` | Drops a table and its data |
| `aws iam delete-*` | Wipes users/roles/policies/keys (lockout, audit gaps) |
| `aws kms schedule-key-deletion` | Schedules a CMK for deletion → encrypted data unrecoverable |
| `aws secretsmanager delete-secret` | Drops secrets (recovery window varies) |
| `aws ecr batch-delete-image` | Removes container images mid-rollout |
| `aws cloudformation delete-stack` | Tears down a whole stack |
| `gcloud ... delete --quiet` | Skips GCP's interactive confirmation |
| `gcloud projects delete` | Deletes an entire GCP project |
| `az group delete --yes` | Deletes an Azure resource group with everything in it |

## Why it matters

Cloud providers ship interactive confirmation prompts on destructive operations specifically because mistakes here are unrecoverable. The flags this rule blocks (`--force`, `--quiet`, `-q`, `--yes`, `-y`, `--skip-final-snapshot`) exist for scripted pipelines that have *already* gone through human review. An autonomous agent invoking them is the worst-case combination: no human in the loop, no rollback.

The Replit AI incident wiped data for [1,200+ executives and 1,190+ companies](https://www.tomshardware.com/tech-industry/artificial-intelligence/ai-coding-platform-goes-rogue-during-code-freeze-and-deletes-entire-company-database-replit-ceo-apologizes-after-ai-engine-says-it-made-a-catastrophic-error-in-judgment-and-destroyed-all-production-data) during an active code freeze — the agent ran destructive commands "despite explicit instructions not to proceed without human approval". The same shape applies to any cloud CLI invocation that strips the confirmation prompt.

## False positives

- `aws s3 rm s3://bucket/key` (a single object, no `--recursive`) is **not** caught.
- `aws ec2 stop-instances` is **not** caught — stopping is reversible.
- `aws rds create-db-snapshot` followed by `aws rds delete-db-instance` is still caught on the delete. That's intentional — snapshot first, then have a human approve the delete.
- `gcloud ... delete` *without* `--quiet` will trigger the SDK's own prompt and is **not** caught here.

## Test it

```bash
agentlock fake-hook --session <id> --tool Bash --command 'aws s3 rb s3://prod-data --force'
# expect: deny

agentlock fake-hook --session <id> --tool Bash --command 'aws s3 ls s3://prod-data'
# expect: allow
```

## Sources

- [Replit AI Wiped Production Database — Fortune](https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/)
- [Incident 1152 — AI Incident Database](https://incidentdatabase.ai/cite/1152/)
52 changes: 52 additions & 0 deletions rules/cloud-resource-destroy/rule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
schema_version: 1
id: rogue.cloud-resource-destroy
name: Block destructive cloud-CLI deletes (AWS, GCP, Azure)
description: |
Denies bash invocations of cloud-provider CLIs that destroy
production-bearing resources while bypassing confirmation prompts:
`aws s3 rb --force`, `aws ec2 terminate-instances`, `aws rds
delete-db-instance --skip-final-snapshot`, `aws s3api delete-bucket`,
`aws iam delete-*`, `gcloud … delete --quiet`, and `az group delete
--yes`. Each shape removes the human-confirmation gate the cloud SDK
normally enforces — exactly the shortcut an AI agent reaches for when
it interprets cleanup as the right next action.
severity: critical
tags:
- aws
- gcp
- azure
- cloud
- destructive
- bash
authors:
- github: RonCodes88
license: Apache-2.0
compatible_agentlock: ">=0.1.0"
gate:
match:
tool: Bash
any_command_regex:
- '\baws\s+s3\s+rb\s+(?:[^|;&]*\s+)?--force\b'
- '\baws\s+s3\s+rm\s+(?:[^|;&]*\s+)?--recursive\s+s3://'
- '\baws\s+s3api\s+delete-bucket\b'
- '\baws\s+ec2\s+terminate-instances\b'
- '\baws\s+rds\s+delete-db-instance\b'
- '\baws\s+rds\s+delete-db-cluster\b'
- '\baws\s+rds\s+delete-db-snapshot\b'
- '\baws\s+dynamodb\s+delete-table\b'
- '\baws\s+iam\s+delete-(?:user|role|policy|access-key|group)\b'
- '\baws\s+kms\s+(?:schedule-key-deletion|disable-key)\b'
- '\baws\s+secretsmanager\s+delete-secret\b'
- '\baws\s+ssm\s+delete-parameter(?:s)?\b'
- '\baws\s+ecr\s+(?:delete-repository|batch-delete-image)\b'
- '\baws\s+lambda\s+delete-function\b'
- '\baws\s+cloudformation\s+delete-stack\b'
- '\bgcloud\s+(?:[^|;&]*\s+)?delete\s+(?:[^|;&]*\s+)?--quiet\b'
- '\bgcloud\s+(?:[^|;&]*\s+)?delete\s+(?:[^|;&]*\s+)?-q\b'
- '\bgcloud\s+projects\s+delete\b'
- '\baz\s+group\s+delete\s+(?:[^|;&]*\s+)?--yes\b'
- '\baz\s+group\s+delete\s+(?:[^|;&]*\s+)?-y\b'
- '\baz\s+(?:vm|disk|sql|storage)\s+delete\s+(?:[^|;&]*\s+)?--yes\b'
evaluate:
- kind: always
action: deny
Loading
Loading