v0.1.7 · Static analysis for the agent era

Security gatewayfor AI codingagents.

Argus scans packages before they land in your environment, blocking malicious install-time code, hardcoded secrets, and dangerous execution paths in packages installed by humans or AI agents.

Agent Integration
Runs locally · Standard-library Go binary · No source code leaves your machine
Runtime
Go 1.22+
Deps
Zero
Licence
AGPL-3.0
Platforms
macOS · Linux · Win
+ 41.2398° N
argus.panoptes / lock-on
Scroll
01 / The problem

Signatures verify authenticity. They don't verify intent.

Modern AI-agent and MCP ecosystems encourage installing packages from the internet with a single command. Manifest checks tell you the package is what its author published — they don't tell you what the code actually does.

What manifests check
ChecksumThe bytes match a hash
SignatureA keyholder signed it
ProvenanceIt came from npm / PyPI
All necessary. None sufficient. A legitimate-looking package can still exfiltrate credentials, open a reverse shell, or execute a remote payload at install time.
What Argus checks
Dangerous callsexec, eval, subprocess, pickle.loads
Hardcoded secretsAWS, GitHub PAT, OpenAI keys
Network beaconsRaw IPs, disabled TLS verification
ObfuscationHigh-entropy strings, dynamic imports
Path traversalZip-slip in archive extraction
Argus reads what the code does, not what it claims to be. Static AST analysis for Go; pattern + entropy for Python, JS/TS, Ruby, Rust, and shell.
02 / Live scan

Watch it catch a payload.

Argus extracts the package, walks every source file, and runs language-aware rules. Chain it with && in front of any package manager: critical findings exit 1, the install never runs.

argus — zsh — 80×24
ready
subprocess.Popen
Spawns curl to fetch a remote payload. Caught by the Python subprocess rule — CRITICAL, blocks the install.
exec(open(...).read())
Executes the just-downloaded payload. Caught by the eval/exec rule — second confirmation of remote code execution.
SECRET_KEY = "sk-…"
Hardcoded OpenAI key, likely a real one. Caught by the API-key entropy + format rule before the package can call out.
03 / Features

Built for the way packages actually get installed.

Seven primitives that compose into a single CLI. Each one earns its place — no telemetry, no cloud round-trip, no opinion about your package manager.

Feature
Go AST analysis
Precise call-site detection for dangerous Go APIs — no false positives from string matching. Import aliases resolve to full package paths, so renamed imports are caught identically.
H = 4.5
Feature
Pattern + entropy scanning
Python, JavaScript, TypeScript, Ruby, Rust and shell scripts run through language-aware rules plus a Shannon-entropy pass on assignment expressions — enough to catch hardcoded secrets without false positives on base64 fixtures.
WARNCRIT
Feature
Two severity tiers
CRITICAL forces exit 1 and blocks the install. WARNING is logged but non-blocking. Sane defaults for security work.
../
Feature
Archive protection
Archive extraction rejects path-traversal attempts. Nested .tar.gz and .zip files inside a package are auto-extracted up to two levels deep and scanned in place — findings reference the full archive path.
100 MiBcap
Feature
Extraction size cap
A 100 MiB per-file ceiling on archive extraction guards against zip-bomb payloads. Source files above 1 MiB are skipped during SAST scanning.
$&&
Feature
Composable with any installer
argus scan ./pkg && pip install ./pkg — exit 0 on clean, exit 1 on blocked. No prompts, no daemon, no opinion about your package manager. CI gets the same behaviour as your terminal.
Feature
Zero external dependencies
Standard library only. The whole tool is one Go binary — no postinstall hooks, no transitive surface, no supply-chain risk from the gatekeeper itself.
04 / Rule coverage

75+ rules. Documented, not hand-wavy.

Every rule has a defined pattern and severity. Add your own by extending the scanner — the rule sets are plain Go, not a DSL.

+ 1 universal rule · high-entropy strings in assignments (H ≥ 4.8, len ≥ 32)
Pattern-based, with API-key formats and entropy on top. getattr indirection and escape obfuscation added in v0.1.6.
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
getattr() indirection
getattr(obj, "dangerous_method")
CRITICAL
getattr() concatenation
getattr(obj, "met" + "hod")
CRITICAL
Hex/unicode obfuscation
\x65\x78\x65\x63 / \u0065\u0078\u0065\u0063
CRITICAL
Hardcoded secret
password/secret/api_key/token = "..."
CRITICAL
AWS access key
AKIA...
CRITICAL
GitHub PAT
ghp_...
CRITICAL
OpenAI API key
sk-...
CRITICAL
Dynamic import
importlib.import_module(
WARNING
SSL verification disabled
verify=False
WARNING
Raw IP address
Dotted-decimal (public ranges only)
WARNING
Private and loopback IP ranges (10.x, 127.x, 192.168.x, 172.16–31.x) are excluded from the raw-IP rule.
05 / Architecture

Five stages from scan to exit.

Argus is a thin layer in front of your package manager. Each stage is one file in pkg/scanner or cmd/argus — easy to audit, easy to extend.

01
Stage

Prepare work directory

prepareWorkDir

Argus receives a source — a directory, a .tar.gz, .tgz, .zip, .whl, .crate, or .gem — and stages it in a temp work directory.

Archive extraction runs sanitisePath() on every entry, rejecting zip-slip path traversal. A 100 MiB per-file cap stops zip bombs. Nested archives are auto-extracted up to two levels deep, so payloads hidden in vendored bundles still get scanned.

02
Stage

Static analysis

scanner.Scan

fs.WalkDir visits every recognised source file and dispatches to the right scanner. package.json lifecycle hooks are also inspected for malicious code.

Go files go to the AST scanner (parse → walk → match dangerous selectors). Python, JS/TS, Ruby, Rust and shell go through their regex rule packs. Every file also gets a Shannon-entropy pass on assignments to catch hidden secrets. package.json lifecycle scripts (install, postinstall, preinstall, and variants) are scanned for exec, eval, and network calls.

fs.WalkDir → for each source file:
.go
GoASTScanner · falls back to regex on parse error
.py
RegexScanner (pyRules)
.js / .ts / .mjs
RegexScanner (jsRules)
.rb / .rake / .gemspec
RegexScanner (rubyRules)
.rs
RegexScanner (rustRules · build.rs always warns)
.sh / .bash
RegexScanner (shellRules)
package.json
LifecycleScanner (install, postinstall, preinstall & variants)
+ Shannon-entropy pass on every assignment expression (H ≥ 4.8, len ≥ 32, hex-only strings excluded)
03
Stage

Print report

printReport

Findings are grouped by file and printed to stderr with snippets, ordered by severity.

CRITICAL is rendered with an amber badge; WARNING is dimmed. The line number is exact — for Go, it comes from the AST node; for the regex scanners, from the matching source line.

04
Stage

Prompt or auto-decline

isatty

On a TTY, CRITICAL findings prompt [y/N] before exit. Answering y exits 0. In CI (non-TTY), Argus auto-declines and exits 1.

Whether stdout is a terminal determines the behaviour. Interactive users can override a CRITICAL finding; CI cannot. The blocking default is consistent — a package with CRITICAL findings will not install unattended.

05
Stage

Exit code signals the decision

os.Exit

Clean → exit 0. CRITICAL + declined → exit 1. No interactive branch in CI.

The scan model is pipe-friendly. Your shell, Makefile, or CI step uses the exit code. The --json flag (v0.1.2) emits structured findings on stdout for programmatic consumers.

Key types · pkg/scanner
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 Scanner interface {
    Scan(path string, content []byte) ([]Finding, error)
}
06 / Install

One binary. No daemons.

Argus runs as a single statically-linked executable. Drop it on your PATH and chain it in front of your installs.

One tap, one install, on your PATH. macOS and Linuxbrew users.

brew tap argusgate/tap
brew install argus

# Start a temporary protected session
argus shell

# Or enable persistent shims
argus shim install
Usage
argus scan <source>          # exit 0 on clean, exit 1 on critical findings
Local directory
argus scan ./my-package
.tar.gz / .tgz
argus scan package-1.2.3.tar.gz
.zip
argus scan package-1.2.3.zip
.whl (Python)
argus scan package-1.0-cp311-linux.whl
.crate (Rust)
argus scan serde-1.0.193.crate
.gem (Ruby)
argus scan rails-7.1.3.gem
Compose with your installer
argus scan ./pkg && pip install ./pkg
argus scan ./pkg && npm install ./pkg
argus scan ./pkg.tar.gz && go install ./pkg/...
argus scan ./pkg --json | jq '.findings[]'   # programmatic output
07 / Agent integration

Protect agent install commands.

Coding agents do not always remember to run security tools before installing dependencies. Argus closes that gap by adding optional interception layers around normal package-manager commands.

01Temporary

Protected shell session

argus shell starts a protected subshell with Argus shims first on PATH. The safest way to try interception — it does not modify shell profiles.

argus shell
npm install lodash
pip install requests
02Persistent

Package-manager shims

Persistent shims place Argus wrappers earlier on PATH. Developers and agents keep using normal package-manager commands; Argus scans before delegating to the real binary.

argus shim install
Commands intercepted
pip installuv adduv pip installnpm installnpm cinpm inpm addpnpm addpnpm installpnpm dlxyarn addyarn installcargo addcargo installgo installgo getgem install
03Claude Code

PreToolUse hook

Adds a checkpoint before Bash commands. Uses Claude Code's PreToolUse hook for Bash and routes suspicious install commands through Argus.

argus hook install claude
04Verify

Confirm protection is active

argus doctor shows which package managers are protected, which real binaries the shims delegate to, and whether the Claude Code hook is installed.

argus doctor
08 / Limitations

What Argus does not catch — yet.

A documented evasion. Argus is honest about its coverage boundary so you can layer defences appropriately. Taint-flow analysis, planned for v2, would close this gap.

NOTEArgus interception is default-path protection, not a sandbox. It protects normal package-manager usage, but a user or agent with arbitrary shell access can still bypass local interception with absolute binary paths, python -m pip, direct downloads, curl | sh, custom install scripts, or deliberate PATH changes. For stronger enforcement, run Argus in CI as a backup gate and review dependency changes before merge.
Technique
Example
Why it slips · plan
Split secret across variables
k1="sk-abc"; k2="xyz"; key = k1 + k2
Each fragment falls below the entropy threshold. Catches require value tracking.
V2Taint-flow analysis is the priority for the next major release — it would unlock value-tracking evasions like the one above. Nested archive scanning (previously listed here) shipped in v0.1.3. Open an issue on GitHub if you have a concrete bypass you'd like tracked.
Get started

One install. One binary.
A hundred eyes on the gate.

Star on GitHub