{"id":49317203,"url":"https://github.com/antonlovesdnb/fishbowl","last_synced_at":"2026-04-26T16:00:59.950Z","repository":{"id":350875941,"uuid":"1202843893","full_name":"Antonlovesdnb/fishbowl","owner":"Antonlovesdnb","description":"Containerized credential auditing perimeter for AI coding agents. Wraps Codex/Claude Code in Docker, audits every credential access via eBPF.","archived":false,"fork":false,"pushed_at":"2026-04-12T16:17:42.000Z","size":487,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-12T16:18:25.755Z","etag":null,"topics":["ai-agents","claude-code","codex","container-security","credential-security","devtools","docker","ebpf","rust","security"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Antonlovesdnb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-06T13:10:48.000Z","updated_at":"2026-04-12T16:17:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Antonlovesdnb/fishbowl","commit_stats":null,"previous_names":["antonlovesdnb/fishbowl"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Antonlovesdnb/fishbowl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonlovesdnb%2Ffishbowl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonlovesdnb%2Ffishbowl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonlovesdnb%2Ffishbowl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonlovesdnb%2Ffishbowl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Antonlovesdnb","download_url":"https://codeload.github.com/Antonlovesdnb/fishbowl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Antonlovesdnb%2Ffishbowl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32303177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T09:34:17.070Z","status":"ssl_error","status_checked_at":"2026-04-26T09:34:00.993Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","claude-code","codex","container-security","credential-security","devtools","docker","ebpf","rust","security"],"created_at":"2026-04-26T16:00:32.005Z","updated_at":"2026-04-26T16:00:59.921Z","avatar_url":"https://github.com/Antonlovesdnb.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fishbowl\n\n[![Release](https://img.shields.io/badge/release-v2.1.1-blue?style=flat-square)](https://github.com/Antonlovesdnb/fishbowl/releases)\n[![Build](https://img.shields.io/badge/build-passing-brightgreen?style=flat-square)](https://github.com/Antonlovesdnb/fishbowl/actions)\n[![Rust](https://img.shields.io/badge/rust-2024_edition-orange?style=flat-square\u0026logo=rust)](https://www.rust-lang.org/)\n[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square)]()\n[![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE)\n[![AI Agents](https://img.shields.io/badge/AI%20Agents-Friendly-blueviolet?style=flat-square)](AGENTS.md)\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/architecture-dark.svg\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/architecture-light.svg\"\u003e\n  \u003cimg alt=\"Fishbowl architecture\" src=\"docs/architecture-light.svg\" width=\"1120\"\u003e\n\u003c/picture\u003e\n\nA containerized credential auditing perimeter for AI coding agents. Validated with **Codex** and **Claude Code** on both macOS and Linux.\n\nFishbowl wraps your AI agent in a Docker container, audits every credential access, environment variable mutation, and outbound network connection, then gives you a session report. It's observation-only — it doesn't block or kill anything the agent does.\n\nThe container is the security boundary. The agent can see your project directory, its own auth files (auto-mounted copies of `~/.codex/` or `~/.claude/`), any credentials you explicitly `--mount`, and the session logs — but not the rest of your home directory or system. The container filesystem is read-only, all Linux capabilities are dropped, and privilege escalation is disabled.\n\n**\u003cu\u003eNOTE - fishbowl is a vibe coded project, please evaluate and test it before utilizing it in production use-cases\u003c/u\u003e**\n\n## How it works\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/flow-dark.svg\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/flow-light.svg\"\u003e\n  \u003cimg alt=\"Fishbowl flow\" src=\"docs/flow-light.svg\" width=\"1200\"\u003e\n\u003c/picture\u003e\n\nWhen you run `fishbowl run ~/my-project`, this is what happens:\n\n1. **Host credential scan.** Fishbowl walks your home directory and project for known credential files (`.env`, `~/.aws/credentials`, `~/.codex/auth.json`, SSH keys, etc.) and prints what it finds. The scan report is saved to a host-only location (`~/.fishbowl/host-scans/`) — it is NOT visible inside the container. \n\n   See [docs/credential-scanning.md](docs/credential-scanning.md) for the full list of paths and classification rules.\n\n![image-20260412101202171](/img/image-20260412101202171.png)\n\n1. **Agent auto-detection.** Based on project markers (`CLAUDE.md`, `AGENTS.md`), host auth artifacts (`~/.codex/`, `~/.claude/`), and environment variable references, Fishbowl picks the agent type and auto-mounts the relevant auth files into `/fishbowl/home/` inside the container.\n\n   See [docs/agent-detection.md](docs/agent-detection.md) for the detection priority cascade and what each agent gets. On macOS, Claude Code stores its OAuth token in the login Keychain under service `\"Claude Code-credentials\"` rather than in `~/.claude/.credentials.json`; Fishbowl extracts it via `security find-generic-password -w` into the per-session runtime auth dir (0o600, parent 0o700, cleaned up with the rest of the session) so Claude running inside the Linux container can read it as a regular file. First run may trigger the standard macOS \"allow security to access your keychain\" dialog. **Credential env vars and SSH keys referenced in project text are NOT auto-passed** — Fishbowl prints them as recommendations but requires explicit `--mount` to avoid a malicious repo silently importing host secrets.\n\n![image-20260412101548199](/img/image-20260412101548199.png)\n\n1. **Registry seeding.** Credential paths from the host scan are translated to their in-container equivalents and written to the runtime credential registry (`registry.json`). This is how the file collector knows which `openat()` events are interesting.\n\n2. **Container launch.** Docker runs the agent inside a hardened container:\n   - `--cap-drop ALL --security-opt no-new-privileges`\n   - Project bind-mounted at `/\u003cproject-name\u003e` and `/workspace`\n   - Selected credentials at `/fishbowl/creds/` and `/fishbowl/ssh/` (read-only)\n   - Agent auth at `/fishbowl/home/` (the container's `$HOME`)\n   - Session logs at `/var/log/fishbowl/`\n\n3. **Monitoring starts.** Fishbowl picks the strongest monitoring available:\n   - **Linux:** host-side bpftrace via a `sudo` helper, scoped to the container's cgroup\n   - **macOS:** bpftrace in a privileged sidecar container inside the Docker VM (auto-detects Docker Desktop, Colima, OrbStack, Rancher Desktop)\n   - **Fallback:** if the strong path fails (no root, Docker not running, collector image missing), prints the reason and continues with container-local telemetry (bash env hooks, inotify file watchers, `ss` network polling)\n\n4. **Agent runs.** Your agent does its work inside the container. Every `execve()`, `connect()`, and `openat()` on a monitored credential is captured.\n\n5. **Shutdown.** When the agent exits, Fishbowl gracefully drains the bpftrace collectors (SIGINT + 1.5s grace period) and tears down the helper container. Session state is synced back to the host for Codex/Claude.\n\n## Install\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Antonlovesdnb/fishbowl/main/install.sh | sh\n```\n\nThe script auto-detects your OS and architecture, downloads the right binary and the collector image from the latest [GitHub release](https://github.com/Antonlovesdnb/fishbowl/releases), verifies the SHA256 checksum, and installs to `/usr/local/bin` (or `~/.local/bin` if no write access).\n\n**Supported platforms:** macOS (Apple Silicon) and Linux (x86_64 + arm64). Linux binaries are fully static (musl libc) so they run on any distro including Alpine.\n\n**Requirements:** a container runtime — Docker Desktop, Colima, OrbStack, or Rancher Desktop — must be running before `fishbowl run`.\n\n**Options:** pin a version with `FISHBOWL_VERSION=v2.1.1`, override the install directory with `FISHBOWL_BIN_DIR=...`.\n\n**That's the whole install.** The container image gets built automatically the first time you run `fishbowl run` (a few minutes; one-time). If you'd rather get that out of the way up front, run `fishbowl build-image` after installing.\n\n\u003e **Building from source:** `cargo install --path .` (requires Rust \u003e= 1.85). Only needed if you're contributing or want to modify the container image. The first `fishbowl run` will build the container image automatically, same as the prebuilt-binary path.\n\n### Uninstall\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Antonlovesdnb/fishbowl/main/install.sh | sh -s -- --uninstall\n```\n\nRemoves the binary, Docker images, and optionally `~/.fishbowl/` (prompts before deleting session data).\n\n## Usage\n\n```bash\n# Run the current directory\nfishbowl run\n\n# Run a specific project\nfishbowl run ~/projects/my-app\n\n# Mount a credential (auto-detects type: env var, SSH key, or credential file)\nfishbowl run --mount GH_TOKEN --mount ~/.ssh/id_ed25519 --mount ~/secrets/service.json\n\n# Use host networking (for VPN/lab routes)\nfishbowl run --network host\n```\n\nMounted credentials appear inside the container at `/fishbowl/creds/\u003cfilename\u003e` (credential files) and `/fishbowl/ssh/\u003cfilename\u003e` (SSH keys). Environment variables are passed through directly.\n\n## Reviewing sessions\n\nAfter a run, review what happened:\n\n```bash\nfishbowl audit              # most recent session\nfishbowl audit \u003cSESSION\u003e    # specific session directory\n```\n\nThe audit report shows:\n- **Credentials** — each discovered credential, its classification, access count, and expected destinations\n- **Alerts** — medium/high/critical severity events (env mutations, credential access by suspicious processes)\n- **Network** — outbound destinations with connection counts and alert flags\n\nNote - `fishbowl audit` is meant to be run outside of the container. \n\n### Session log location\n\nAll session data lives under `~/.fishbowl/logs/`:\n\n```\n~/.fishbowl/\n  logs/\n    latest -\u003e session-1775780487       # symlink to most recent\n    session-1775780487/                # one directory per run\n      audit.jsonl                      # all audit events (JSONL)\n      registry.json                    # credential registry (live state)\n      findings.jsonl                   # credential-egress correlation findings\n      ebpf_exec.jsonl                  # host eBPF: process exec events\n      ebpf_connect.jsonl               # host eBPF: network connect events\n      ebpf_file.jsonl                  # host eBPF: credential file access events\n      ebpf_scope.json                  # eBPF container scope metadata\n      ebpf_*.stderr.log                # bpftrace stderr (empty = probes attached OK)\n  host-scans/\n    session-1775780487.json            # host credential path enumeration (host-only)\n  runtime/\n    session-1775780487-\u003cnonce\u003e/        # runtime auth copies (cleaned up after 6h)\n```\n\n### Log formats\n\n**audit.jsonl** — one JSON object per line, every event from both in-container watchers and host eBPF collectors:\n\n```json\n{\n  \"timestamp\": \"2026-04-10T00:21:29+00:00\",\n  \"event\": \"process_exec\",\n  \"severity\": \"info\",\n  \"agent\": \"host-ebpf\",\n  \"command\": \"/bin/cat\",\n  \"path\": \"/usr/bin/cat\",\n  \"process_name\": \"cat\",\n  \"observed_pid\": \"40643\",\n  \"process_chain\": \"cat(pid=40643) \u003c- bash(pid=40626) \u003c- tini \u003c- containerd-shim \u003c- systemd\",\n  \"env_findings\": [{\"variable\": \"BASH_ENV\", \"value_preview\": \"/age...(redacted,len=23)\"}],\n  \"discovery_method\": \"host_ebpf_exec\",\n  \"verdict\": \"observed\"\n}\n```\n\nEvent types: `process_exec`, `env_mutation`, `env_enumeration`, `credential_discovery`, `credential_access`, `network_egress`, `workspace_credential_access`.\n\nFull credential values are **not intentionally logged**. Environment variable findings include a short preview (first 4 characters + length) for classification purposes — e.g. `sk-p...(redacted,len=48)`. Credential env vars are passed to Docker via `--env-file` (not CLI args) to avoid exposure in the host process table.\n\n**registry.json** — live credential registry, updated as credentials are discovered and accessed:\n\n```json\n{\n  \"credentials\": [\n    {\n      \"id\": \"file::/fishbowl-smoke/.env\",\n      \"classification\": \"Project .env Credential File\",\n      \"discovery_method\": \"project_scan\",\n      \"path\": \"/fishbowl-smoke/.env\",\n      \"access_count\": 3,\n      \"last_accessed_at\": \"2026-04-10T00:21:29+00:00\"\n    }\n  ]\n}\n```\n\n**ebpf_file.jsonl** — credential access events from the kernel file collector:\n\n```json\n{\n  \"event\": \"credential_access\",\n  \"process_name\": \"cat\",\n  \"raw_path\": \"/workspace/.env\",\n  \"resolved_path\": \"/fishbowl-demo/.env\",\n  \"operation\": \"openat\",\n  \"classification\": \"Project .env Credential File\",\n  \"process_chain\": \"cat \u003c- bash \u003c- tini \u003c- containerd-shim \u003c- systemd\",\n  \"collector\": \"bpftrace_file\"\n}\n```\n\n**ebpf_exec.jsonl** — every process spawn inside the container:\n\n```json\n{\n  \"event\": \"process_exec\",\n  \"process_name\": \"bash\",\n  \"filename\": \"/usr/bin/curl\",\n  \"cmdline\": \"curl -sS https://example.com/\",\n  \"process_chain\": \"curl \u003c- bash \u003c- tini \u003c- containerd-shim \u003c- systemd\",\n  \"env_findings\": [\n    {\n      \"variable\": \"BASH_ENV\",\n      \"classification\": \"Dangerous Execution Environment Variable\",\n      \"value_preview\": \"/age...(redacted,len=23)\"\n    }\n  ],\n  \"collector\": \"bpftrace_exec\"\n}\n```\n\n**audit.jsonl** — env mutations caught by the bash hooks:\n\n```json\n{\n  \"event\": \"dangerous_env_mutation\",\n  \"severity\": \"medium\",\n  \"command\": \"export PAGER=\\\"evil-pager\\\"\",\n  \"variable\": \"PAGER\",\n  \"new_value\": \"\\\"evi...(redacted,len=12)\",\n  \"reason\": \"dangerous variable mutation command observed\"\n}\n```\n\n**findings.jsonl** — credential-access-then-network-connect correlation findings (e.g., \"process read ~/.codex/auth.json then connected to 185.x.x.x:443\").\n\n## Platform support\n\n| Platform | Monitoring | Notes |\n|---|---|---|\n| **Linux** (source or binary) | Host-side eBPF via `sudo` helper | Full exec/connect/file coverage, cgroup-scoped. No collector image needed — bpftrace runs as the host binary. |\n| **macOS** (source or binary) | eBPF sidecar in Docker VM | Full coverage. `install.sh` downloads the pre-built collector image from the release and `docker load`s it; source installs build it via `fishbowl build-image`. Auto-detects Docker Desktop/Colima/OrbStack/Rancher. |\n| **Any host, fallback** | Container-local watchers | If the eBPF path fails (no root on Linux, Docker not running, etc.), Fishbowl falls back to bash env hooks, inotify file watchers, and `ss` network polling. |\n\n**Container images are platform-specific.** After cloning to a different architecture, run `fishbowl build-image` before `fishbowl run`.\n\n## Known limitations\n\n- **In-container audit log is writable by the agent.** The in-container watchers write `audit.jsonl` and `registry.json` to a writable subdirectory (`/var/log/fishbowl/watcher/`) inside the container. A compromised agent could tamper with this watcher output. However, the **host-side eBPF logs (`ebpf_*.jsonl`) are protected** — the parent session logs directory is mounted read-only into the agent container, and the eBPF logs are written by the helper container via its own mount. So the high-fidelity event data (exec, connect, file access from the kernel layer) is tamper-proof; only the in-container watcher events are at risk.\n\n- **Fallback monitoring has coverage gaps.** When strong monitoring (the default) is unavailable — no root on Linux, or collector image missing on macOS — Fishbowl falls back to container-local watchers. These have known gaps: bash env hooks don't fire for `sh`/`python`/`node`, the `ss` network poller misses sub-50ms connections, and UDP/DNS isn't covered. Strong monitoring covers all of these via kernel-level eBPF probes.\n\n- **Tested agents.** Only Codex and Claude Code have been validated end-to-end. Cursor, Windsurf, and Copilot have scaffolded enum variants in the code but the wrapped-session flow hasn't been exercised for them.\n\n## Security model\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/trust-boundary-dark.svg\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/trust-boundary-light.svg\"\u003e\n  \u003cimg alt=\"Fishbowl trust boundary\" src=\"docs/trust-boundary-light.svg\" width=\"1000\"\u003e\n\n\n\u003c/picture\u003e\n\nFishbowl provides **visibility into opportunistic credential exfiltration** — malicious npm/pip postinstall scripts, env-var poisoning (CVE-2026-22708), MCP config tampering via prompt injection (CVE-2025-54135/54136), and prompt injection that runs `curl`/`wget` to exfiltrate credentials.\n\n**Out of scope:** determined adversaries who specifically target the monitoring stack, the agent encoding credentials into its own API channel (e.g. to `api.anthropic.com`), and sophisticated multi-step exfil chains.\n\nFishbowl is **observation-only at runtime.** It does not block, terminate, or interfere with the agent. For kernel-level prevention with enforcement, see [owLSM](https://github.com/Cybereason-Public/owLSM), [Falco](https://falco.org/), or [Tetragon](https://tetragon.io/) — Fishbowl is complementary to these tools, not a replacement for them.\n\n## CI/CD integration\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/ci-pipeline-dark.svg\"\u003e\n  \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/ci-pipeline-light.svg\"\u003e\n  \u003cimg alt=\"Fishbowl CI/CD pipeline\" src=\"docs/ci-pipeline-light.svg\" width=\"1100\"\u003e\n\u003c/picture\u003e\n\nUse `fishbowl check` to gate CI/CD pipelines on session security. It reads the session logs, counts events by severity, and exits non-zero if the threshold is exceeded.\n\n```bash\n# Run the agent task inside Fishbowl\nfishbowl run ~/my-app --mount API_KEY -- codex \"run the deploy script\"\n\n# Gate the pipeline — fail if any high-severity events occurred\nfishbowl check --fail-on high\n```\n\nSeverity levels: `low`, `medium`, `high`, `critical`. The default threshold is `high`.\n\n```\nFishbowl Check\nSession:  /Users/dev/.fishbowl/logs/session-1775954089\nThreshold: --fail-on high\n\nEvents:   12 total (2 info, 0 low, 9 medium, 1 high, 0 critical)\neBPF:     9 exec, 2 file, 1 connect\n\nResult:   FAIL (1 events at or above high severity)\n\n  HIGH      credential_access_by_network_tool: curl accessed credential file /workspace/.env\n```\n\nWhat it catches:\n- **Credential exfiltration** — `curl`/`wget`/`python` reading credential files\n- **Env var poisoning** — `PAGER`, `LD_PRELOAD`, `GIT_ASKPASS` mutations\n- **Supply chain attacks** — malicious postinstall scripts accessing secrets\n- **MCP config tampering** — unauthorized server additions during agent sessions\n- **Prompt injection** — agent tricked into running exfiltration commands\n\nIn a GitHub Actions workflow:\n\n```yaml\n- name: Deploy with Fishbowl\n  run: |\n    fishbowl run . --mount DEPLOY_KEY -- codex \"deploy to staging\"\n    fishbowl check --fail-on high\n```\n\nIf `fishbowl check` exits non-zero, the pipeline stops and the full session audit log is available for investigation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonlovesdnb%2Ffishbowl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantonlovesdnb%2Ffishbowl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantonlovesdnb%2Ffishbowl/lists"}