Antonlovesdnb/fishbowl

GitHub: Antonlovesdnb/fishbowl

一个容器化凭证审计系统,为 AI 编码代理提供基于 eBPF 的全链路观测与审计。

Stars: 9 | Forks: 1

# Fishbowl [![发布](https://img.shields.io/badge/release-v2.1.1-blue?style=flat-square)](https://github.com/Antonlovesdnb/fishbowl/releases) [![构建](https://img.shields.io/badge/build-passing-brightgreen?style=flat-square)](https://github.com/Antonlovesdnb/fishbowl/actions) [![Rust](https://img.shields.io/badge/rust-2024_edition-orange?style=flat-square&logo=rust)](https://www.rust-lang.org/) [![平台](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey?style=flat-square)]() [![许可证](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE) [![AI Agents](https://img.shields.io/badge/AI%20Agents-Friendly-blueviolet?style=flat-square)](AGENTS.md) Fishbowl architecture 一个为 AI 编程 agent 打造的容器化凭证审计边界。已通过 **Codex** 和 **Claude Code** 在 macOS 和 Linux 上验证。 Fishbowl 将你的 AI agent 封装在 Docker container 中,审计每一次凭证访问、环境变量变更以及出站网络连接,然后为你生成一份会话报告。它是纯观察性的——它不会阻止或终止 agent 的任何操作。 Container 本身就是安全边界。Agent 可以看到你的项目目录、它自己的认证文件(自动挂载的 `~/.codex/` 或 `~/.claude/` 副本)、你通过 `--mount` 显式挂载的任何凭证,以及会话日志——但它无法访问你的主目录或系统的其余部分。Container 的文件系统是只读的,所有的 Linux capabilities 都被丢弃,并且禁用了权限提升。 **注意 - fishbowl 是一个 vibe coded 项目,在生产环境中使用之前,请对其进行评估和测试** ## 工作原理 Fishbowl flow 当你运行 `fishbowl run ~/my-project` 时,会发生以下过程: 1. **宿主机凭证扫描。** Fishbowl 会遍历你的主目录和项目,查找已知的凭证文件(`.env`、`~/.aws/credentials`、`~/.codex/auth.json`、SSH keys 等)并打印出找到的内容。扫描报告会保存到一个仅限宿主机访问的位置(`~/.fishbowl/host-scans/`)——它在 container 内部是不可见的。 有关路径和分类规则的完整列表,请参见 [docs/credential-scanning.md](docs/credential-scanning.md)。 ![image-20260412101202171](/img/image-20260412101202171.png) 1. **Agent 自动检测。** 基于项目标记(`CLAUDE.md`、`AGENTS.md`)、宿主机认证工件(`~/.codex/`、`~/.claude/`)以及环境变量引用,Fishbowl 会选择 agent 类型,并自动将相关的认证文件挂载到 container 内部的 `/fishbowl/home/` 目录下。 有关检测优先级级联和每个 agent 获取的内容,请参见 [docs/agent-detection.md](docs/agent-detection.md)。在 macOS 上,Claude Code 将其 OAuth token 存储在服务 `"Claude Code-credentials"` 下的登录 Keychain 中,而不是在 `~/.claude/.credentials.json` 中;Fishbowl 通过 `security find-generic-password -w` 将其提取到按会话划分的运行时认证目录中(权限 0o600,父目录 0o700,与会话的其余部分一起清理),以便在 Linux container 内运行的 Claude 可以像读取常规文件一样读取它。首次运行可能会触发标准的 macOS “允许安全访问你的钥匙串”对话框。**项目文本中引用的凭证环境变量和 SSH keys 不会自动传递** —— Fishbowl 会将它们作为建议打印出来,但需要显式使用 `--mount` 以避免恶意 repo 静默导入宿主机机密。 ![image-20260412101548199](/img/image-20260412101548199.png) 1. **注册表播种。** 来自宿主机扫描的凭证路径会被转换为 container 内部的等效路径,并写入运行时凭证注册表(`registry.json`)中。这就是文件收集器如何知道哪些 `openat()` 事件值得关注的方式。 2. **Container 启动。** Docker 在一个经过安全加固的 container 中运行 agent: - `--cap-drop ALL --security-opt no-new-privileges` - 项目目录绑定挂载到 `/` 和 `/workspace` - 选定的凭证挂载到 `/fishbowl/creds/` 和 `/fishbowl/ssh/`(只读) - Agent 认证挂载到 `/fishbowl/home/`(即 container 的 `$HOME`) - 会话日志位于 `/var/log/fishbowl/` 3. **监控启动。** Fishbowl 会选择可用的最强监控方式: - **Linux:** 通过 `sudo` 辅助程序进行宿主机端的 bpftrace 监控,范围限定在 container 的 cgroup 内 - **macOS:** 在 Docker VM 内部的特权 sidecar container 中运行 bpftrace(自动检测 Docker Desktop、Colima、OrbStack、Rancher Desktop) - **降级方案:** 如果强监控路径失败(无 root 权限、Docker 未运行、缺少收集器镜像),则打印原因并继续使用 container 本地遥测(bash env hooks、inotify 文件监视器、`ss` 网络轮询) 4. **Agent 运行。** 你的 agent 在 container 内部执行其工作。每一个 `execve()`、`connect()` 以及对受监控凭证的 `openat()` 操作都会被捕获。 5. **关闭。** 当 agent 退出时,Fishbowl 会正常耗尽 bpftrace 收集器(SIGINT + 1.5 秒宽限期)并销毁辅助 container。会话状态会同步回宿主机以供 Codex/Claude 使用。 ## 安装说明 ``` curl -fsSL https://raw.githubusercontent.com/Antonlovesdnb/fishbowl/main/install.sh | sh ``` 该脚本会自动检测你的操作系统和架构,从最新的 [GitHub 发布](https://github.com/Antonlovesdnb/fishbowl/releases)中下载正确的二进制文件和收集器镜像,验证 SHA256 校验和,并安装到 `/usr/local/bin`(如果没有写权限,则安装到 `~/.local/bin`)。 **支持的平台:** macOS (Apple Silicon) 和 Linux (x86_64 + arm64)。Linux 二进制文件是完全静态链接的(musl libc),因此它们可以在包括 Alpine 在内的任何发行版上运行。 **要求:** 在执行 `fishbowl run` 之前,必须有一个正在运行的容器运行时——Docker Desktop、Colima、OrbStack 或 Rancher Desktop。 **选项:** 使用 `FISHBOWL_VERSION=v2.1.1` 固定版本,使用 `FISHBOWL_BIN_DIR=...` 覆盖安装目录。 **这就是安装的全部内容。** Container 镜像会在你第一次运行 `fishbowl run` 时自动构建(需要几分钟时间;仅此一次)。如果你想提前完成这一步,请在安装后运行 `fishbowl build-image`。 ### 卸载 ``` curl -fsSL https://raw.githubusercontent.com/Antonlovesdnb/fishbowl/main/install.sh | sh -s -- --uninstall ``` 移除二进制文件、Docker 镜像,并可选择移除 `~/.fishbowl/`(在删除会话数据之前会进行提示)。 ## 用法 ``` # 运行当前目录 fishbowl run # 运行特定项目 fishbowl run ~/projects/my-app # 挂载 credential(自动检测类型:env var、SSH key 或 credential file) fishbowl run --mount GH_TOKEN --mount ~/.ssh/id_ed25519 --mount ~/secrets/service.json # 使用 host networking(用于 VPN/lab routes) fishbowl run --network host ``` 挂载的凭证将显示在 container 内部的 `/fishbowl/creds/`(凭证文件)和 `/fishbowl/ssh/`(SSH keys)路径下。环境变量将直接传递。 ## 查看会话 运行结束后,查看发生了什么: ``` fishbowl audit # most recent session fishbowl audit # specific session directory ``` 审计报告显示: - **凭证** — 每个发现的凭证、其分类、访问次数和预期的目标地址 - **警报** — 中/高/严重级别的安全事件(环境变量变更、可疑进程访问凭证) - **网络** — 出站目标地址及其连接次数和警报标志 注意 - `fishbowl audit` 应在 container 外部运行。 ### 会话日志位置 所有会话数据都位于 `~/.fishbowl/logs/` 目录下: ``` ~/.fishbowl/ logs/ latest -> session-1775780487 # symlink to most recent session-1775780487/ # one directory per run audit.jsonl # all audit events (JSONL) registry.json # credential registry (live state) findings.jsonl # credential-egress correlation findings ebpf_exec.jsonl # host eBPF: process exec events ebpf_connect.jsonl # host eBPF: network connect events ebpf_file.jsonl # host eBPF: credential file access events ebpf_scope.json # eBPF container scope metadata ebpf_*.stderr.log # bpftrace stderr (empty = probes attached OK) host-scans/ session-1775780487.json # host credential path enumeration (host-only) runtime/ session-1775780487-/ # runtime auth copies (cleaned up after 6h) ``` ### 日志格式 **audit.jsonl** — 每行一个 JSON object,包含来自 container 内部监视器和宿主机 eBPF 收集器的所有事件: ``` { "timestamp": "2026-04-10T00:21:29+00:00", "event": "process_exec", "severity": "info", "agent": "host-ebpf", "command": "/bin/cat", "path": "/usr/bin/cat", "process_name": "cat", "observed_pid": "40643", "process_chain": "cat(pid=40643) <- bash(pid=40626) <- tini <- containerd-shim <- systemd", "env_findings": [{"variable": "BASH_ENV", "value_preview": "/age...(redacted,len=23)"}], "discovery_method": "host_ebpf_exec", "verdict": "observed" } ``` 事件类型:`process_exec`、`env_mutation`、`env_enumeration`、`credential_discovery`、`credential_access`、`network_egress`、`workspace_credential_access`。 **不会故意记录**完整的凭证值。环境变量的发现结果包含一个简短的预览(前 4 个字符 + 长度)用于分类目的——例如 `sk-p...(redacted,len=48)`。凭证环境变量通过 `--env-file`(而不是 CLI 参数)传递给 Docker,以避免在宿主机进程表中暴露。 **registry.json** — 实时凭证注册表,随着凭证的发现和访问而更新: ``` { "credentials": [ { "id": "file::/fishbowl-smoke/.env", "classification": "Project .env Credential File", "discovery_method": "project_scan", "path": "/fishbowl-smoke/.env", "access_count": 3, "last_accessed_at": "2026-04-10T00:21:29+00:00" } ] } ``` **ebpf_file.jsonl** — 来自内核文件收集器的凭证访问事件: ``` { "event": "credential_access", "process_name": "cat", "raw_path": "/workspace/.env", "resolved_path": "/fishbowl-demo/.env", "operation": "openat", "classification": "Project .env Credential File", "process_chain": "cat <- bash <- tini <- containerd-shim <- systemd", "collector": "bpftrace_file" } ``` **ebpf_exec.jsonl** — container 内部的每一个进程生成: ``` { "event": "process_exec", "process_name": "bash", "filename": "/usr/bin/curl", "cmdline": "curl -sS https://example.com/", "process_chain": "curl <- bash <- tini <- containerd-shim <- systemd", "env_findings": [ { "variable": "BASH_ENV", "classification": "Dangerous Execution Environment Variable", "value_preview": "/age...(redacted,len=23)" } ], "collector": "bpftrace_exec" } ``` **audit.jsonl** — bash hooks 捕获的环境变量变更: ``` { "event": "dangerous_env_mutation", "severity": "medium", "command": "export PAGER=\"evil-pager\"", "variable": "PAGER", "new_value": "\"evi...(redacted,len=12)", "reason": "dangerous variable mutation command observed" } ``` **findings.jsonl** — 凭证访问后发起网络连接的相关性发现结果(例如,“进程读取了 ~/.codex/auth.json,然后连接到了 185.x.x.x:443”)。 ## 平台支持 | 平台 | 监控方式 | 说明 | |---|---|---| | **Linux** (源码或二进制文件) | 通过 `sudo` 辅助程序进行宿主机端 eBPF 监控 | 完整的 exec/connect/file 覆盖,cgroup 范围限定。无需收集器镜像 — bpftrace 作为宿主机二进制文件运行。 | | **macOS** (源码或二进制文件) | Docker VM 中的 eBPF sidecar | 完整覆盖。`install.sh` 从 release 下载预构建的收集器镜像并 `docker load` 它;源码安装通过 `fishbowl build-image` 构建它。自动检测 Docker Desktop/Colima/OrbStack/Rancher。 | | **任何宿主机,降级方案** | Container 本地监视器 | 如果 eBPF 路径失败(Linux 上无 root 权限、Docker 未运行等),Fishbowl 将降级为 bash env hooks、inotify 文件监视器和 `ss` 网络轮询。 | **Container 镜像是特定于平台的。** 克隆到不同的架构后,在运行 `fishbowl run` 之前,请先运行 `fishbowl build-image`。 ## 已知限制 - **Container 内的审计日志可被 agent 写入。** Container 内部的监视器将 `audit.jsonl` 和 `registry.json` 写入 container 内部的一个可写子目录(`/var/log/fishbowl/watcher/`)。被入侵的 agent 可能会篡改此监视器的输出。但是,**宿主机端的 eBPF 日志(`ebpf_*.jsonl`)是受保护的** —— 父会话日志目录以只读方式挂载到 agent container 中,并且 eBPF 日志由辅助 container 通过其自己的挂载点写入。因此,高保真度的事件数据(exec、connect、来自内核层的文件访问)是防篡改的;只有 container 内部的监视器事件存在风险。 - **降级监控存在覆盖盲区。** 当强监控(默认方式)不可用时——Linux 上没有 root 权限或 macOS 上缺少收集器镜像——Fishbowl 会降级为 container 本地监视器。这些监视器存在已知的盲区:bash env hooks 不会为 `sh`/`python`/`node` 触发,`ss` 网络轮询器会错过低于 50 毫秒的连接,并且不覆盖 UDP/DNS。强监控通过内核级 eBPF probes 覆盖了所有这些情况。 - **已测试的 agents。** 只有 Codex 和 Claude Code 进行了端到端的验证。Cursor、Windsurf 和 Copilot 在代码中拥有脚手架式的枚举变体,但尚未针对它们执行封装会话的流程。 ## 安全模型 Fishbowl trust boundary Fishbowl 提供**对机会性凭证窃取的可见性** —— 恶意的 npm/pip postinstall 脚本、环境变量投毒(CVE-2026-22708)、通过 prompt injection 进行的 MCP 配置篡改(CVE-2025-54135/54136),以及运行 `curl`/`wget` 来窃取凭证的 prompt injection。 **不在范围内:** 专门针对监控栈的顽固攻击者、agent 将凭证编码到其自身的 API 通道中(例如发送至 `api.anthropic.com`),以及复杂的多步骤窃取链条。 Fishbowl **在 runtime 期间是纯观察性的。** 它不会阻止、终止或干扰 agent。有关具有执行力且基于内核防护的预防措施,请参见 [owLSM](https://github.com/Cybereason-Public/owLSM)、[Falco](https://falco.org/) 或 [Tetragon](https://tetragon.io/) —— Fishbowl 是对这些工具的补充,而不是它们的替代品。 ## CI/CD 集成 Fishbowl CI/CD pipeline 使用 `fishbowl check` 据会话安全性为 CI/CD pipelines 设置门禁。它会读取会话日志,按严重程度对事件进行计数,如果超过阈值则以非零状态退出。 ``` # 在 Fishbowl 内运行 agent task fishbowl run ~/my-app --mount API_KEY -- codex "run the deploy script" # 门控 pipeline — 如果发生任何 high-severity 事件则失败 fishbowl check --fail-on high ``` 严重程度级别:`low`、`medium`、`high`、`critical`。默认阈值为 `high`。 ``` Fishbowl Check Session: /Users/dev/.fishbowl/logs/session-1775954089 Threshold: --fail-on high Events: 12 total (2 info, 0 low, 9 medium, 1 high, 0 critical) eBPF: 9 exec, 2 file, 1 connect Result: FAIL (1 events at or above high severity) HIGH credential_access_by_network_tool: curl accessed credential file /workspace/.env ``` 它能捕获的内容: - **凭证窃取** — `curl`/`wget`/`python` 读取凭证文件 - **环境变量投毒** — `PAGER`、`LD_PRELOAD`、`GIT_ASKPASS` 变更 - **供应链攻击** — 恶意的 postinstall 脚本访问机密 - **MCP 配置篡改** — 在 agent 会话期间未经授权添加服务器 - **Prompt injection** — agent 被诱骗执行窃取命令 在 GitHub Actions workflow 中: ``` - name: Deploy with Fishbowl run: | fishbowl run . --mount DEPLOY_KEY -- codex "deploy to staging" fishbowl check --fail-on high ``` 如果 `fishbowl check` 以非零状态退出,pipeline 将停止,并提供完整的会话审计日志以供调查。
标签:AI编程助手, Docker, eBPF, Rust, 凭证审计, 安全审计, 安全防御评估, 容器化隔离, 网络流量审计, 通知系统