unh0lymos3s/Pinkeye

GitHub: unh0lymos3s/Pinkeye

一个以 LLM 为核心的网络安全测试与红队工具 AI 编排平台,通过统一控制平面串联多种安全工具并自动执行侦察与测试任务。

Stars: 1 | Forks: 0

# Pinkeye 阅读以下说明,这只是我设计的一个包装器,代码由 Claude 编写。AI 生成的垃圾内容从此处开始: 有关完整的架构和设计,请参阅 [`IMPLEMENTATION_REPORT.md`](IMPLEMENTATION_REPORT.md)。 ## 布局 | 路径 | 内容 | |------|------| | `control-plane/` | FastAPI 服务:scope guard、审计日志、仓库、查询/KPI/关联/报告、RBAC、作业队列、worker | | `agent-runtime/` | LLM 提供商 + 规划循环、工具注册表、沙箱运行器、工具 + 标准化器 | | `web/` | Next.js UI:网络地图、仪表板、查询 | | `deploy/` | docker-compose + Dockerfiles | | `graph/` | Neo4j schema(约束/索引) | ## 前置条件 - **Docker + Docker Compose**(最快的路径 — 启动整个技术栈)。 - 对于本地(非 Docker)开发:**Python 3.11+**、**Node 20+**,以及可访问的 **Postgres 16** 和 **Neo4j 5** 实例。 ## 快速开始 应用栈在容器中运行,agent 直接与 **Ollama Cloud** 通信 (`https://ollama.com/v1`)— 无需主机 Ollama,也无需 GPU。将你的 Ollama API 密钥(来自 https://ollama.com/settings/keys)放入 `deploy/.env` 中,然后启动技术栈: ``` echo 'OLLAMA_API_KEY=' > deploy/.env # gitignored; compose loads it automatically cd deploy docker compose up --build ``` Compose 会读取 `deploy/.env` 并将密钥注入 `api`/`worker`(它们已预先配置为 `EYE_LLM_PROVIDER=openai`、`EYE_LLM_BASE_URL=https://ollama.com/v1`、 `EYE_LLM_MODEL=minimax-m3:cloud`)。这将启动五个服务: | 服务 | URL / 端口 | 用途 | |---------|-----------|---------| | `web` | http://localhost:3000 | UI:网络地图、仪表板、查询、`/agent` 聊天 | | `api` | http://localhost:9000 (`/docs` 为 OpenAPI) | 控制平面 | | `worker` | — | 消耗作业队列(使用 `--scale worker=N` 进行扩容) | | `neo4j` | http://localhost:7474 (bolt 7687) | 知识图谱 | | `postgres` | 5432 | 持久化存储 | API 在启动时会自动运行数据库迁移并应用 Neo4j schema。**然后播种 CVE 数据库**,以便 `cve_lookup` 和 `/cve` 正常工作: ``` docker compose exec api python -m app.cve_seed ``` 打开 http://localhost:3000,创建一个限定在实验室网络范围内的 engagement,并启动扫描(或者使用 `/agent` 聊天进行由 LLM 规划的运行)。任何 Ollama Cloud 模型均可工作 — 在 `api`+`worker` 服务上设置 `EYE_LLM_MODEL`(例如 `gemma4:31b`、`gpt-oss:120b`)。如果想要 **完全在本地主机上运行且不使用云**,请指向主机/容器化的 Ollama(`EYE_LLM_PROVIDER=ollama`,`EYE_LLM_BASE_URL` 指向它), 并使用本地拉取的模型(如 `llama3.1`) — 参见“连接 LLM 提供商”部分。 ## 构建与部署 ### 构建镜像 ``` # from repo root docker compose -f deploy/docker-compose.yml build # all images docker build -f deploy/api.Dockerfile -t eye-api . # API + worker (same image) docker build -f deploy/web.Dockerfile -t eye-web ./web # UI ``` API 镜像打包了两个 Python 包(`control-plane` + `agent-runtime`),因此 API 可以在进程内运行 orchestrator,并且 worker 可以运行相同的代码路径。 ### 部署清单(超出本地实验室范围) 1. **设置真实的密钥/配置**(参见下表):强 `EYE_SCOPE_SIGNING_KEY`、数据库 凭据,以及用于开启身份验证的 `EYE_API_KEYS`(否则 API 将处于开放的开发模式 管理员状态)。 2. **运行迁移**(幂等;也会在 API 启动时自动运行): docker compose exec api python -m app.db.migrate 3. **播种/刷新 CVE 数据库**(内置的入门集,或 NVD JSON 导出): docker compose exec api python -m app.cve_seed # bundled set docker compose exec api python -m app.cve_seed /path/nvd.json 4. **挂载密钥**以用于可选集成(参见“启用可选集成”)。 5. **扩展 worker** 以提高吞吐量:`docker compose up -d --scale worker=4`。 6. **针对真实目标强化沙箱**:设置 `EYE_SANDBOX_RUNTIME=runsc` (gVisor) 并接入 `EYE_EGRESS_ENFORCER`。请注意,Docker socket 挂载会授予 daemon 访问权限 — 对于共享主机,请替换为代理的 runner。 ### 配置(环境变量) | 变量 | 默认值 | 用途 | |----------|---------|---------| | `EYE_NEO4J_URI` | `bolt://localhost:7687` | Neo4j bolt endpoint | | `EYE_NEO4J_USER` / `EYE_NEO4J_PASSWORD` | `neo4j` / `eye-dev-password` | Neo4j 身份验证 | | `EYE_POSTGRES_DSN` | `postgresql://eye:eye@localhost:5432/eye` | Postgres DSN | | `EYE_SCOPE_SIGNING_KEY` | `dev-insecure-signing-key` | **用于签名范围的 HMAC 密钥 — 请务必设置此项** | | `EYE_API_KEYS` | _(未设置 → 开放开发模式)_ | RBAC 密钥:`key:tenant:role,...` (role = viewer\|operator\|admin) | | `EYE_LLM_PROVIDER` | `ollama` | `claude` \| `openai` \| `ollama` (默认指向位于 `localhost:11434` 的本地 Ollama) | | `EYE_LLM_MODEL` | 提供商默认值 | 模型 ID;`EYE_LLM__MODEL` 可按角色覆盖 | | `EYE_LLM_BASE_URL` | — | `openai`/`ollama` 适配器的 base URL(Ollama、vLLM/LM Studio 或 LiteLLM 代理) | | `EYE_LLM_API_KEY` | — | `openai` 适配器的 API 密钥;回退到 `OPENAI_API_KEY`。Claude 读取 `ANTHROPIC_API_KEY`;Ollama 不需要 | | `EYE_LLM_CONFIG` | — | JSON 路由文件的路径,每次运行都会重新读取 — 编辑它即可热切换模型/回退而无需重启 | | `EYE_LLM_FALLBACK_MODELS` | — | 逗号分隔的 `provider:model`(或纯 `model`)列表,在主模型拒绝时按顺序尝试 | | `EYE_UPLOAD_ROOT` | `/eye-uploads` | 解压 SAST 上传文件的位置;必须在主机 + api/worker + 任何 MCP SAST 同级组件上保持相同的绝对路径 | | `EYE_SANDBOX_RUNTIME` | daemon 默认值 (`runc`) | 例如用于 gVisor 隔离的 `runsc` | | `EYE_EGRESS_ENFORCER` | — | 应用每个作业 egress 防火墙规则的外部命令 | | `EYE_SECRETS_DIR` | `/run/secrets` | 扫描机密文件的目录 | | `EYE_MCP_SERVERS` | — | 将工具映射到 MCP server 后端的内联 JSON(见下文) | | `EYE_MCP_CONFIG` | — | 具有与 `EYE_MCP_SERVERS` 相同结构的 JSON 文件路径 | 机密(通过 `EYE_SECRETS_DIR` 文件或环境变量):`VT_API_KEY` (VirusTotal)、`MSF_RPC_PASSWORD`、 `MSF_RPC_HOST`、`MSF_RPC_PORT` (Metasploit RPC)。 ### MCP 工具后端(可选) 注册的工具可以通过外部 **MCP server**(semgrep、trivy、nmap、ZAP、nuclei、 VirusTotal、Metasploit 等)执行,而不是在本地沙箱中。这是按工具选择的:没有配置时,每个 工具都像以前一样在沙箱中运行。至关重要的是,harness 仅充当 MCP **client**,并且 由 MCP 支持的工具会被*包装*,因此 **scope guard、offensive-flag 门控和审计仍会优先执行** — 模型永远不会获得原始的 MCP 访问权限,并且范围外的目标永远不会到达服务器。 ``` # Run the SAST slot via Snyk Code — pooled in its own hardened container (verified live) — trivy via # its plugin, nmap via a community one. Snyk needs SNYK_TOKEN (free-tier works); it's forwarded by # name into the sibling. Build the Snyk MCP image first: `docker compose build mcp-snyk`. export SNYK_TOKEN= export EYE_MCP_SERVERS='{ "semgrep": {"pooled": true, "image": "eye-mcp-snyk:latest", "tool": "snyk_code_scan", "target_arg": "path", "env": {"SNYK_TOKEN": "'"$SNYK_TOKEN"'"}, "mounts": ["'"$PWD"'/deploy/samples:/samples:ro"]}, "trivy": {"command": "trivy", "args": ["mcp", "-t", "stdio"], "tool": "scan_filesystem", "target_arg": "path"}, "nmap": {"command": "npx", "args": ["-y", "nmap-mcp-server"], "tool": "run_nmap_scan", "target_arg": "target"} }' ``` `pooled: true` 在一个**预热的、锁定的容器**(`docker run --rm -i`、cap-drop-all / read-only / no-new-privileges、无 Docker socket)中运行服务器,进程范围的池跨调用复用该容器,并在空闲 `EYE_MCP_IDLE_TTL`(默认 300s)后将其 驱逐 — 这比每次调用生成进程更轻量,**并且**与 api 进程隔离。将其用于只读分析器;将 offensive/network 工具保留在用完即弃的 单次运行沙箱中。更简单的每次调用生成形式(`command`/`args`,无 `pooled`)如 `trivy`/`nmap` 所示。默认的 `target_mode: "value"` 将经过范围检查的目标作为纯 字符串传递(`snyk_code_scan` 的源路径、nmap 的主机、ZAP 的 URL);`target_mode: "path_list"` 也可用。有关可用性表和完整规范字段,请参见 [`list.md`](list.md#mcp-integration)。 ### 连接 LLM 提供商 模型层是模型无关的(`agent-runtime/runtime/llm/`):三个环境变量用于选择 提供商、模型以及(在需要时)endpoint/密钥。在运行 agent 的任何进程上设置它们 — 对于本地开发是 `uvicorn` API 和 `worker`,或者是 Compose 中的 `api` 和 `worker` 服务 (**两者**都要设置 — 它们中的任何一个都可以启动 agent 运行)。每次运行都会重新进行选择,因此更改后的 环境变量/配置将在下次运行时生效。 **Ollama Cloud — 默认提供商。** Compose 将 `api`/`worker` 指向通过 OpenAI 兼容 endpoint 的 Ollama 云,因此无需主机 Ollama 也无需 GPU。提供来自 https://ollama.com/settings/keys 的 API 密钥(在 `deploy/.env` 中作为 `OLLAMA_API_KEY`,或通过 export 导出): ``` export EYE_LLM_PROVIDER=openai export EYE_LLM_MODEL=minimax-m3:cloud # any cloud model: gemma4:31b, gpt-oss:120b, deepseek-v3.1:671b, … export EYE_LLM_BASE_URL=https://ollama.com/v1 export EYE_LLM_API_KEY=... # your Ollama key (compose reads OLLAMA_API_KEY from deploy/.env) ``` **本地/自托管 Ollama(完全在本地主机上,无云)— 内置默认值。** 如果没有设置 `EYE_LLM_*`,harness 将使用位于 `http://localhost:11434/v1` 的本地 Ollama(模型为 `minimax-m3:cloud`),因此纯粹的本地运行根本不需要 LLM 环境变量。自行运行 Ollama 并指向它; 从容器中使用 `http://host.docker.internal:11434/v1`(需要主机可从 Docker 访问 — 将 Ollama 绑定到 `0.0.0.0` 并允许 Docker 子网通过任何主机防火墙),或者将 Ollama 作为 同一网络上的 compose 服务运行: ``` ollama serve & ollama pull llama3.1 # The provider/base_url already default to local Ollama; set these only to override the model/endpoint. export EYE_LLM_PROVIDER=ollama EYE_LLM_MODEL=llama3.1 EYE_LLM_BASE_URL=http://localhost:11434/v1 ``` **任何 OpenAI 兼容的 API** — OpenAI、Groq、Together、OpenRouter、vLLM、LM Studio 或 **LiteLLM 代理** — 只需将 `base_url` 指向它并提供密钥: ``` export EYE_LLM_PROVIDER=openai export EYE_LLM_MODEL=gpt-4o # whatever id the endpoint serves export EYE_LLM_BASE_URL=https://api.groq.com/openai/v1 # omit for OpenAI itself export EYE_LLM_API_KEY=sk-... # or set OPENAI_API_KEY ``` **Anthropic Claude:** ``` export EYE_LLM_PROVIDER=claude export EYE_LLM_MODEL=claude-fable-5 export ANTHROPIC_API_KEY=sk-ant-... ``` 在启动运行之前验证 runtime 是否能看到提供商: ``` python -c "from runtime.llm.config import get_provider; print(type(get_provider()).__name__)" ``` **热切换(无需重启)。** `get_provider()` 在每次运行开始时都会全新执行,因此 `EYE_LLM_CONFIG` JSON 文件每次都会被重新读取 — 编辑它,下次运行即会应用 更改。LiteLLM *proxy* 也可以通过 OpenAI 兼容适配器作为 `EYE_LLM_BASE_URL` 目标运行(无进程内依赖)。 ``` { "provider": "openai", "model": "minimax-m3:cloud", "base_url": "https://ollama.com/v1", "fallbacks": ["openai:gpt-oss:120b", "claude:claude-fable-5"] } ``` **拒绝。** 经过安全调优的本地模型有时会拒绝已授权的侦测步骤, 返回道歉文本且没有工具调用 — 这通常看起来与“已完成”完全相同,从而静默结束运行。当配置了任何 `fallbacks` 时,提供商将被包装,以便在检测到拒绝时,它会 (1) 重新声明 engagement 的签名 授权并重试同一模型一次,然后 (2) 遍历回退链,直到 有模型配合,并在每次转换时向转录发出 `refusal` 事件。 **模型选择。** 经过重度 RLHF 的聊天模型通常会过度拒绝安全相关的措辞 对于已授权的 engagement,请优先选择适合安全操作的本地模型,并将更强的 托管模型放在回退链的最后作为后盾。 ## 本地开发(不使用 Docker) ``` # Control plane + agent runtime (editable installs into one venv) python -m venv .venv && source .venv/bin/activate pip install -e './control-plane[dev]' -e './agent-runtime[dev]' # Point at running Postgres/Neo4j, then migrate + seed export EYE_POSTGRES_DSN=postgresql://eye:eye@localhost:5432/eye python -m app.db.migrate python -m app.cve_seed # Run the API and (separately) a worker uvicorn app.main:app --reload --port 9000 python -m app.worker # Web UI cd web && npm install && npm run dev # http://localhost:3000 ``` ### 运行测试 ``` cd control-plane && pytest # 31 tests: scope guard, query, scoring, correlation, tenancy, hardening cd agent-runtime && pytest # 36 tests: normalizers, agent loop, tools, exploitation/creds gating cd web && npm run build # type-check + production build ``` ## 用法 ### 创建 engagement(定义已签名的范围) ``` curl -X POST localhost:9000/engagements -H 'content-type: application/json' -d '{ "name": "lab", "allowed_cidrs": ["10.0.0.0/24"], "allowed_domains": ["lab.example.com"], "allowed_artifacts": ["/repos/app"], "max_intensity": "normal" }' ``` **仅**在那些已获得授权的 engagement 中添加 `"allow_exploit": true` 和/或 `"allow_credential_attacks": true` — 它们会被固化到 scope 签名中。 ### 启动运行 ``` # deterministic single tool curl -X POST localhost:9000/engagements//runs -H 'content-type: application/json' \ -d '{"target":"10.0.0.5","tool":"nmap","intensity":"light"}' # LLM agent plans multi-step recon/scan across all authorized tools curl -X POST localhost:9000/engagements//runs -H 'content-type: application/json' \ -d '{"target":"10.0.0.5","mode":"agent"}' # authenticated DAST with ZAP (credential resolved from a secret named "app-token") curl -X POST localhost:9000/engagements//runs -H 'content-type: application/json' \ -d '{"target":"https://lab.example.com","tool":"zap", "auth":{"header_name":"Authorization","value_ref":"app-token"}}' ``` 如果设置了 `EYE_API_KEYS`,请传入 `-H "X-API-Key: "`;写入需要 `operator` 角色,读取需要 `viewer` 角色。 ### SAST:扫描上传的代码库 **SAST** 标签页(`/sast`)允许操作员上传代码库 — `.zip` / `.tar` / `.tar.gz` 或 单个源文件 — 而不是预挂载仓库路径。harness 会将其解压,**通过将解压后的目录添加到 engagement 的签名 scope**(`allowed_artifacts`,重新签名)来对其进行**授权**, 然后使用所选的分析器在其上运行 `sast` 专家,并实时流式传输:**Snyk Code**(连接 Snyk MCP 后端时的 `semgrep` 插槽,否则为 Semgrep)、**gitleaks**(机密)和 **trivy**(依赖项 CVE)。上传即代表授权决定 — 它受操作员控制、可审计, 并且永远只能添加本地源路径;它永远不会授予网络或 offensive 权限。 ``` # Raw file as the request body; the server extracts it and returns the path a SAST run targets. curl -X POST "localhost:9000/engagements//sast/upload?filename=app.zip" \ --data-binary @app.zip -H 'content-type: application/octet-stream' # -> {"path":"/eye-uploads//","file_count":123,"kind":"zip", ...} # Then scan it (agent `sast` profile over the enabled analyzers): curl -X POST localhost:9000/engagements//runs -H 'content-type: application/json' \ -d '{"target":"/eye-uploads//","mode":"agent","profile":"sast", "enabled_tools":["semgrep","gitleaks","trivy"]}' ``` ### 使用 LLM agent 运行 engagement(端到端) Agent 模式(`"mode":"agent"`)将规划交给配置的 LLM:它提出工具调用, harness 针对签名的 scope 验证每个调用,在沙箱中执行它,并反馈 简短摘要。步骤: ``` # 1. Put your Ollama key in deploy/.env and bring up the stack — the provider is already wired # (Ollama Cloud direct). Skip to step 3 once it's running. echo 'OLLAMA_API_KEY=' > deploy/.env docker compose -f deploy/docker-compose.yml up --build -d # --- OR run the agent locally instead of in Compose (see "Connecting an LLM provider"): --- # export EYE_LLM_PROVIDER=openai EYE_LLM_MODEL=minimax-m3:cloud EYE_LLM_BASE_URL=https://ollama.com/v1 EYE_LLM_API_KEY=... # export EYE_LLM_FALLBACK_MODELS="claude:claude-fable-5" # optional backstop, needs ANTHROPIC_API_KEY # uvicorn app.main:app --port 9000 & # python -m app.worker & # 3. Create an engagement (defines the signed scope the agent can never exceed): EID=$(curl -s -X POST localhost:9000/engagements -H 'content-type: application/json' \ -d '{"name":"lab","allowed_cidrs":["10.0.0.0/24"],"max_intensity":"normal"}' | jq -r .id) # 4. Launch an agent run. `objective` is free-text guidance (never authorization): RID=$(curl -s -X POST localhost:9000/engagements/$EID/runs -H 'content-type: application/json' \ -d '{"target":"10.0.0.5","mode":"agent","objective":"map exposed services and known CVEs"}' | jq -r .id) # 5. Watch it think and act live (SSE), or replay the full transcript: curl -N localhost:9000/runs/$RID/events # streaming: plan, thinking, tool_call, finding, refusal… curl -s localhost:9000/runs/$RID/transcript # full JSON transcript ``` 或者使用 Web UI:打开 **`/agent`** (http://localhost:3000/agent),选择 engagement,输入 目标,并观察相同的事件流 — 工具调用、发现以及任何 `refusal`/回退 转换都会内联呈现。拒绝已授权步骤的模型将作为 `refusal` 事件(以及 `stop_reason: "model refused"`)显现,而不是静默完成。 ### 关键 API 端点 | 方法 / 路径 | 用途 | |---------------|---------| | `POST /engagements`, `GET /engagements`, `GET /engagements/{id}` | 管理 engagement | | `POST /engagements/{id}/runs`, `GET /runs/{id}` | 启动 / 检查运行 | | `POST /engagements/{id}/sast/upload?filename=` | 上传代码库 (zip/tar/file) 以进行静态分析;解压 + 授权路径 | | `GET /runs/{id}/events` (SSE), `GET /runs/{id}/transcript` | 实时 agent 事件流 / 完整转录 | | `GET /map`, `GET /engagements/{id}/graph` | 网络地图(受限) | | `POST /engagements/{id}/graph/query` | 对图谱进行只读 Cypher 查询 | | `GET /engagements/{id}/findings` | 筛选后的发现(严重性/类别/cve/状态/文本) | | `GET /engagements/{id}/entities` | 主机/服务实体搜索 | | `GET /engagements/{id}/metrics` | 仪表板 KPI | | `GET /engagements/{id}/chains` | 关联的攻击链 | | `POST /engagements/{id}/validate` | 将相互印证的发现提升为已确认 | | `GET /engagements/{id}/report` | Markdown 报告 (CVSS + ATT&CK) | | `GET /cve?product=&version=` | 离线 CVE 查找 | ## 启用可选集成 - **VirusTotal (`virustotal` 工具):** 提供作为机密/环境变量的 `VT_API_KEY`。 - **利用 / 后渗透 (`exploit`, `post_exploit`):** 运行 `msfrpcd` 并设置 `MSF_RPC_PASSWORD`(+ `MSF_RPC_HOST`/`MSF_RPC_PORT`);在 engagement 上设置 `"allow_exploit": true`。 利用操作默认为非破坏性的 `check`。 - **凭据攻击 (`credential_attack`):** 设置 `"allow_credential_attacks": true`;hydra 运行时会进行硬性限制 (≤4 个线程,遇到第一个即停止),以避免锁定/DoS。 ## 验证端到端(推荐的冒烟测试) 在沙箱网络内**故意搭建一个易受攻击的目标**(Web 使用 OWASP Juice Shop, 网络使用 Metasploitable/DVWA),并**仅**针对它运行: 1. 确认 scope guard:针对范围外 IP 的扫描会被硬拒绝并记录在案。 2. 运行 nmap,然后进行 agent 运行;确认主机/端口/发现作为连接的节点出现在图谱 和仪表板 KPI 中。 3. 确认已知植入的漏洞显示为标准化、去重的发现,并附带 CVSS + ATT&CK 映射。 4. 生成报告(`GET /engagements/{id}/report`)并重新运行,以确认去重(`times_seen`)和 重放完整性。
标签:AI智能体, AI风险缓解, AV绕过, FastAPI, Neo4j, 二进制模式, 插件系统, 测试用例, 版权保护, 网络侦查, 请求拦截, 逆向工具