Supply chain security scanner for Node.js, Bun, Python, Go, and Rust projects.
OpenSentinel scans your full dependency tree — including transitive dependencies — for known CVEs, malicious code patterns, and supply chain risks. Results appear in an interactive terminal UI or can be exported as CycloneDX SBOM, JSON, or HTML.
- Multi-ecosystem — Node.js, Bun, Python, Go, Rust. Ecosystem auto-detected from project files
- Full dependency tree — parses lock files (
package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lock,Cargo.lock,go.sum,poetry.lock,Pipfile.lock) - Multi-source advisories — queries OSV, GitHub Security Advisories, and NVD in parallel
- Advisory source URL — each vulnerability links to its canonical page (OSV, NVD, GitHub Advisories)
- Credential harvesting detection — env var access, hardcoded secrets, SSH keys, obfuscated
eval/require - Behavioral analysis (regex) — 20 patterns for filesystem access (
/etc/passwd,~/.ssh), network connections (reverse shells, hardcoded IPs, suspicious ports), and syscalls (execve,LD_PRELOAD, privilege escalation) - Behavioral analysis (AST) — Tree-sitter semantic inspection of JS, TypeScript, Python, and Go source: detects actual function calls (
fs.readFileSync,subprocess.run,exec.Command) while ignoring identical patterns inside comments - MITRE ATT&CK mapping — all detections linked to techniques (T1005, T1057, T1059, T1071, T1083, T1106, …)
- Install script detection — flags packages with
preinstall/postinstallhooks - Typosquatting detection — catches packages with names similar to popular libraries
- Version behavior analysis — detects unexpected changes between package releases
- Maintainer reputation scoring — GitHub activity, commit cadence, open issues
- Interactive TUI — dependency tree view, drill into each CVE, view code snippets, export on the fly
- Scan history — every scan persisted to PostgreSQL; re-open any past scan with
opse view - Watch mode — re-scans automatically when lockfiles change
- CI/CD ready — structured exit codes, non-interactive formats, auto-generated GitHub Actions workflow
- Rust 1.75+
- PostgreSQL (optional, for advisory caching and scan history) — the scanner works fully without a database
GITHUB_TOKEN— recommended for higher GitHub Advisory API rate limitsNVD_API_KEY— recommended to avoid NVD rate limiting
(I Recommend fork it first)
git clone https://github.com/yourname/opensentinel
cd opensentinel
cargo install --path .The opse binary will be available in your $PATH after installation.
# Scan current directory — ecosystem auto-detected, opens interactive TUI
opse scan
# Scan a specific project
opse scan ~/projects/my-app
# Force a specific ecosystem
opse scan --ecosystem rust
opse scan --ecosystem golang
opse scan --ecosystem python
opse scan --ecosystem nodejs,bun
# Non-interactive — print table to stdout
opse scan --format table
# Save CycloneDX SBOM to file
opse scan --format sbom --output sbom.cdx.json
# Only report high and critical issues
opse scan --severity high,critical
# Skip dev dependencies
opse scan --exclude devDependencies
# Watch mode — re-scan when lockfiles change
opse scan --watch# Initialize config in your project (ecosystem auto-detected)
opse init
# Also generate a GitHub Actions workflow
opse init --ciThe wizard detects your project's ecosystem automatically and offers it as the default option. Choosing Auto-detect omits the ecosystems key from the config so detection runs on every scan.
Each scan is saved to PostgreSQL automatically. Review previous results without re-scanning:
# List scans for the current project
opse history
# List scans across all projects
opse history --all
# Limit results
opse history --all --limit 50
# Re-open a previous scan in the TUI
opse view 3f2a1b4cThe scan ID is shown at the end of every scan. Use the first 8 characters or the full UUID.
Generate a Shields.io badge reflecting the worst severity of the last scan:
opse badge # prints markdown
opse badge --style flat-square # custom badge style
opse badge --output badge.md # save to filePaste the output into your README:
[](https://github.com/yourname/opensentinel)| Key | Action |
|---|---|
↑ ↓ |
Navigate package list or vulnerability list |
Enter |
Open vulnerability list for selected package; then open detail |
Esc |
Go back one panel |
Tab |
Cycle between panels |
? |
Toggle help overlay |
I |
Ignore / restore selected package (dimmed at bottom of list) |
/ |
Search packages by name |
D |
Toggle direct dependencies only |
G |
Group by severity |
E |
Export current filtered results to opensentinel-{timestamp}.json |
C |
Copy selected vulnerability ID + fix info to clipboard |
Q |
Quit |
Switch between arrows (default) and vim (hjkl) keybindings via opse init or the --keybindings flag.
The scanning screen shows real-time database connection status. If the database is unreachable, OpenSentinel continues without cache and scan results are not persisted.
| Panel | Content |
|---|---|
| Left (30%) | Dependency tree with severity labels. Direct deps at root, transitive deps indented with └─. Filterable by search, direct-only, severity group. |
| Top-right (30%) | Vulnerability list for the selected package — advisories ([OSV], [GitHub], [NVD]) and code detections ([CODE]). |
| Bottom-right (70%) | Full detail: description, CVSS score, source URL, affected/patched versions, publish date, references, code snippet, MITRE mapping, recommendations. |
opse scan --format sbom # CycloneDX (default for --output)
opse scan --format json # Raw JSON — full risk data
opse scan --format table # ASCII table in terminal
opse scan --format html # HTML report
# Re-render a saved JSON report into another format
opse report --source scan.json --format html --output report.htmlConfig is read from ./opensentinel.json (project-level) or ~/.opensentinel/config.json (global). Run opse init to generate it interactively.
If neither --ecosystem nor an "ecosystems" key in any config file is present, OpenSentinel detects ecosystems automatically by looking for manifest files in the project root:
| File found | Ecosystem activated |
|---|---|
package.json |
nodejs |
bun.lockb, bunfig.toml |
bun |
Cargo.toml |
rust |
go.mod |
golang |
requirements.txt, pyproject.toml, poetry.lock, Pipfile.lock |
python |
To pin the ecosystems explicitly, add the key to opensentinel.json:
{ "ecosystems": ["rust"] }{
"version": "1.0",
"database": {
"engine": "postgresql",
"url": "${DATABASE_URL}",
"poolSize": 5
},
"sourceAnalysis": {
"enabled": true,
"downloadSource": false,
"analyzeAst": true,
"cacheDir": ".opensentinel/cache",
"cacheTtl": 86400,
"maxSourceSizeMb": 100
},
"parallelism": {
"packageConcurrency": 4,
"apiConcurrency": 3,
"osv": { "limit": 10, "delayMs": 50 },
"github": { "limit": 3, "delayMs": 100 },
"nvd": { "limit": 3, "delayMs": 100 },
"mitre": { "limit": 3, "delayMs": 300 }
},
"credentials": {
"githubToken": "${GITHUB_TOKEN}",
"nvdApiKey": "${NVD_API_KEY}",
"storage": "env",
"keyringSupport": false
},
"ecosystems": ["nodejs", "bun"],
"severity": ["high", "critical"],
"excludeDevDeps": false,
"keybindings": "arrows",
"outputFormat": "sbom"
}For local PostgreSQL (individual connection fields):
"database": {
"engine": "postgresql",
"host": "localhost",
"port": 5432,
"database": "opensentinel",
"user": "postgres",
"password": "${DB_PASSWORD}",
"ssl": false,
"poolSize": 5
}Credential values starting with
${...}are read from environment variables at runtime. Setstorage: "keyring"to use the OS keyring instead.
Use the url field for managed PostgreSQL providers:
{
"database": {
"url": "${DATABASE_URL}"
}
}When url is set it takes precedence over host, port, user, and password.
By default, OpenSentinel only checks advisory databases (no source download). To enable full code analysis:
"sourceAnalysis": {
"downloadSource": true,
"analyzeAst": true
}With downloadSource: true, OpenSentinel fetches package tarballs and scans the actual source code. Results appear as [CODE] entries in the TUI with file paths, line numbers, and code snippets.
analyzeAst: true activates both detection layers:
| Layer | What it does |
|---|---|
| Regex (Phase 2A) | Line-by-line pattern matching across all supported file types. Fast, works on shell scripts, YAML, and any text file. |
| AST semantic (Phase 2B) | Tree-sitter parses JS/TS/Python/Go source into an AST. Detects actual function calls with suspicious arguments. Code in comments never triggers a finding. AST findings carry higher confidence than regex findings for the same pattern. |
Both layers run in every scan; AST is additive. A finding from AST and regex for the same line appears once, with the higher confidence score taking precedence.
- OSV — Open Source Vulnerabilities (covers npm, PyPI, Go, Rust, …)
- GitHub Security Advisories — curated advisories from the GitHub Advisory Database
- NVD — National Vulnerability Database (NIST CVE feed)
Each advisory shows its canonical URL (osv.dev, nvd.nist.gov, github.com/advisories) directly in the TUI detail panel.
Detections appear as [CODE] entries in the TUI with the exact file path, line number, and a 120-character code snippet.
| Category | Examples | Confidence |
|---|---|---|
| Credential harvesting | process.env["API_KEY"], hardcoded secrets, SSH key patterns, AWS access keys |
0.60–0.99 |
| Crypto mining | Mining pool connections (stratum+tcp://), CoinHive, XMRig |
0.75–0.98 |
| Network exfiltration | HTTP POST to external hosts, DNS lookup with env var | 0.65–0.90 |
| Obfuscated code | eval(Buffer.from(..., 'base64')), dynamic require via encoded strings |
0.92–0.98 |
| Category | Examples | Confidence |
|---|---|---|
| Filesystem access | /etc/passwd, /etc/shadow, /proc/self, ~/.ssh, ~/.aws, writes to /etc///boot/ |
0.70–0.95 |
| Network connection | bash -i >& /dev/tcp/, nc -e /bin/bash, hardcoded IPs, ports 4444/6667/31337 |
0.50–0.98 |
| System call | execve /bin/bash, sudo chmod 4000, setuid, LD_PRELOAD, ptrace, fork() |
0.70–0.95 |
AST detection uses Tree-sitter to parse source files. Code inside comments is ignored; only actual function calls are evaluated. AST findings carry higher confidence than regex findings for the same pattern.
| Language | Filesystem | System call | Network |
|---|---|---|---|
| JavaScript / TypeScript | fs.readFile/Sync(), fs.open(), fs.createReadStream() with sensitive path |
child_process.exec/spawn/execFile[Sync]() |
net.createConnection(), http.request() on suspicious port |
| Python | open(), os.open() with sensitive path |
subprocess.run/Popen/call(), os.system/popen/execv() |
socket.connect() on suspicious port |
| Go | os.Open/ReadFile(), ioutil.ReadFile() with sensitive path |
exec.Command/CommandContext() |
net.Dial/DialTCP() on suspicious port |
// This comment is ignored by AST analysis:
// fs.readFileSync('/etc/passwd')
// This is detected (actual call):
const data = fs.readFileSync('/etc/passwd', 'utf8'); // confidence 0.96- Install scripts —
preinstall/postinstall/preparehooks - Typosquatting — names suspiciously similar to well-known packages
- Version behavior — unexpected changes between releases (file removals, license changes, new dependencies)
- Maintainer reputation — GitHub commit cadence, open issues, contributor count
Every detection is automatically mapped to one or more MITRE ATT&CK techniques. Technique IDs and names appear in the TUI detail panel alongside each finding, with a direct link to attack.mitre.org.
| Detection category | Tactic | Techniques |
|---|---|---|
| Credential Harvesting | Credential Access | T1552 · T1552.001 Credentials In Files |
| Crypto Mining | Impact | T1496 Resource Hijacking |
| Network Exfiltration | Exfiltration / C2 | T1041 · T1071.001 Web Protocols |
| Obfuscated Code | Defense Evasion | T1027 Obfuscated Files or Information |
| Install Hook | Initial Access | T1195.001 Compromise Software Dependencies |
| Typosquatting | Initial Access | T1195 Supply Chain Compromise |
| Reverse Shell | Execution / C2 | T1059 · T1105 Ingress Tool Transfer |
| Filesystem Access | Discovery / Exfiltration | T1005 Data Staging · T1057 Process Discovery · T1083 File & Directory Discovery |
| Network Connection | Command and Control | T1071 App Layer Protocol · T1090 Proxy · T1095 Non-App Layer Protocol |
| System Call | Execution | T1059 Command Interpreter · T1106 Native API |
OpenSentinel returns structured exit codes suitable for use in pipelines:
| Exit code | Meaning |
|---|---|
0 |
No risks, or only LOW severity |
1 |
MEDIUM severity risks found |
2 |
HIGH severity risks found |
3 |
CRITICAL severity risks found |
opse init --ciThis creates .github/workflows/opensentinel.yml with:
- Scan on every push and pull request to
main/master - Weekly scheduled scan (Mondays 06:00 UTC)
- JSON report uploaded as a build artifact
- Pipeline failure on CRITICAL vulnerabilities
Add these secrets to your repository:
| Secret | Required | Notes |
|---|---|---|
GITHUB_TOKEN |
No | Already available in Actions |
DATABASE_URL |
No | Enables scan history persistence |
NVD_API_KEY |
No | Faster NVD lookups, avoids rate limits |
- name: Security scan
run: opse analyze --format json --output report.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- name: Upload report
uses: actions/upload-artifact@v4
with:
name: opensentinel-report
path: report.json| Ecosystem | Manifest / lock files parsed | OSV registry name |
|---|---|---|
| Node.js | package.json, package-lock.json (v1/v2/v3), yarn.lock (classic + berry), pnpm-lock.yaml |
npm |
| Bun | bun.lock, bunfig.toml |
npm |
| Rust | Cargo.toml, Cargo.lock |
crates.io |
| Go | go.mod, go.sum |
Go |
| Python | requirements.txt, pyproject.toml (PEP 621 + Poetry), poetry.lock, Pipfile.lock |
PyPI |
| Component | Library |
|---|---|
| Language | Rust |
| Async runtime | Tokio |
| Terminal UI | Ratatui + Crossterm |
| HTTP client | Reqwest |
| Database | SQLx (PostgreSQL) |
| AST parsing | Tree-sitter |
| Serialization | Serde |
| SBOM output | CycloneDX |
| File watching | Notify |
Contributions are welcome. Please open an issue before submitting large changes.
# Run all tests (208 tests — 158 unit + 50 integration)
cargo test
# Run only unit tests
cargo test --lib
# Run a specific integration suite
cargo test --test behavioral_integration_test
cargo test --test behavioral_ast_integration_test
# Check for warnings (treated as errors)
cargo clippy -- -D warnings
# Format
cargo fmtApache 2.0 — see LICENSE.
