Automatic malware scanning and vulnerability auditing for package installs, git operations, downloads, and archive extraction. Sentinel wraps common shell commands so every operation that brings new code onto your machine is scanned transparently — no change to your workflow required.
On-access scanning (e.g. ClamAV's clamd with OnAccessScanning, or antivirus kernel modules) scans every file the moment it is read or written — including every file your build tools, editors, and package managers touch during normal work. The performance cost is significant:
- A typical
node_modulestree has 50,000–200,000 files. On-access scanning means everywebpack,tsc, oreslintrun triggers tens of thousands of scan events, adding seconds or minutes to each build. vendor/in a PHP project,.gradle/caches, and similar directories have the same problem.- On Linux, on-access scanning requires
fanotify, which needs root orCAP_SYS_ADMIN— not suitable for developer workstations. - A background daemon consumes CPU and memory continuously, even when nothing security-relevant is happening.
Sentinel takes a different approach: scan once, at the moment new code arrives. The threat window is npm install or git clone, not npm run build. By intercepting only the operations that fetch external code, sentinel:
- Adds scan time only when something is actually downloaded or installed — normal builds, tests, and editor operations have zero overhead.
- Requires no root privileges, no kernel modules, and no background daemon.
- Knows exactly what changed (the freshly installed or extracted files), so it scans a precise target rather than the entire filesystem.
- Works transparently inside existing workflows — no wrapper scripts or CI config changes needed beyond installation.
The trade-off is that sentinel is not a substitute for full system protection. It covers the highest-risk moment in a developer workflow (pulling untrusted code from the internet) while keeping day-to-day performance unaffected.
Shell wrappers intercept specific subcommands and route them through the sentinel binary, which runs the original command and then scans what was fetched. Non-intercepted subcommands (e.g. git status, npm run build, tar cf ...) pass straight through to the real binary with zero overhead.
ClamAV is used for malware detection, running with both signature matching and heuristic analysis (packed/obfuscated executables, phishing content, potentially unwanted programs). For npm and composer, a vulnerability audit is also run after every install.
Infected files are moved to a quarantine directory (not deleted). Malware detections exit with code 2, failing the operation. Audit warnings are non-fatal.
| Command | Intercepts | ClamAV scan | Audit |
|---|---|---|---|
npm install / i / ci / update / up |
install operations only | node_modules/ + project tree¹ |
npm audit |
composer install / update / require |
install operations only | vendor/ + script files² + project tree³ |
composer audit |
git clone <url> |
clone only | cloned directory | — |
git pull / git fetch |
pull/fetch only | current directory | — |
curl -o file … / curl -O <url> |
file-writing invocations only | downloaded file | — |
wget <url> |
file-writing invocations only | downloaded file or directory | — |
tar xf … / tar -xzf … / tar --extract |
extract operations only | extraction directory | — |
unzip archive.zip |
all invocations | extraction directory | — |
7z x … / 7za e … |
extract operations (x/e) only |
extraction directory | — |
¹ npm project tree — after install, any file written outside node_modules/ and .git/ during the run (e.g. by a postinstall script dropping files into dist/ or public/) is detected via timestamp comparison and scanned.
² composer script files — file-path entries in the scripts section of composer.json and vendor/composer/installed.json are scanned individually after install. PHP callables (Vendor\Pkg::method) and @-aliases are covered by the vendor/ scan. Requires jq.
³ composer project tree — same timestamp approach as npm: any file written outside vendor/ and .git/ during the install run is scanned. This catches vendor:publish output (config/, resources/views/vendor/, lang/, public/, database/migrations/, etc.) regardless of which command produced it.
curl/wget note: invocations that stream to stdout (curl without -o/-O, wget -O -) pass through unwrapped — there is no file to scan.
tar/7z note: create and list operations pass through directly; only extract operations are intercepted.
ClamAV (required for malware scanning):
# Debian / Ubuntu
sudo apt install clamav clamav-daemon
sudo freshclam
# Arch / CachyOS
sudo pacman -S clamav
sudo freshclam
# Fedora / RHEL
sudo dnf install clamav clamd
sudo freshclamnpm audit — included with Node.js / npm (no extra install needed).
composer audit — included with Composer 2.4+:
composer --version # must be >= 2.4jq (required for composer script scanning) — enables scanning of file-path entries in composer scripts:
# Debian / Ubuntu
sudo apt install jq
# Arch / CachyOS
sudo pacman -S jq
# Fedora / RHEL
sudo dnf install jqClamAV via Homebrew:
brew install clamav
cp /opt/homebrew/etc/clamav/freshclam.conf.sample /opt/homebrew/etc/clamav/freshclam.conf
sed -i '' 's/^Example$//' /opt/homebrew/etc/clamav/freshclam.conf
freshclamnpm — install via Node.js or brew install node.
composer — install via brew install composer (must be >= 2.4 for composer audit).
jq (required for composer script scanning) — brew install jq.
bash -c "$(curl -fsSL https://raw.githubusercontent.com/geodro/sentinel/main/install.sh)"bash -c "$(command wget -qO- https://raw.githubusercontent.com/geodro/sentinel/main/install.sh)"Note:
command wgetbypasses sentinel's shell wrapper. Without it, if sentinel is already installed, the wrapper intercepts the call and may fail if~/.local/binis not yet onPATH.
git clone https://github.com/geodro/sentinel.git
cd sentinel
bash install.shThe installer:
- Copies
sentinelto~/.local/bin/(configurable viaSENTINEL_INSTALL_DIR) - Detects your shell and installs the appropriate wrappers automatically
- Adds
~/.local/bintoPATHin your shell config if it isn't there already
Fish — the installer splits shell/sentinel.fish into individual function files in ~/.config/fish/functions/. To do it manually, extract each function … end block into its own file named after the function (e.g. git.fish).
Zsh — add to ~/.zshrc:
source /path/to/sentinel/shell/sentinel.zshBash — add to ~/.bashrc:
source /path/to/sentinel/shell/sentinel.bashOnce installed the wrappers are transparent — just use your tools normally:
npm install
npm i express
composer install
composer require guzzlehttp/guzzle
git clone https://github.com/example/repo.git
git pull
curl -O https://example.com/tool.tar.gz
wget https://example.com/tool.tar.gz
tar xf tool.tar.gz
unzip archive.zip
7z x archive.7zYou can also call sentinel directly (useful in CI or scripts):
sentinel npm install
sentinel composer install
sentinel git clone https://github.com/example/repo.git
sentinel curl -O https://example.com/tool.tar.gz
sentinel wget https://example.com/tool.tar.gz
sentinel tar xf tool.tar.gz
sentinel unzip archive.zip
sentinel 7z x archive.7zAll options are set via environment variables:
| Variable | Default | Description |
|---|---|---|
SENTINEL_QUARANTINE |
/tmp/sentinel-quarantine |
Directory infected files are moved into |
SENTINEL_CLAM_OPTS |
see below | Flags passed to clamscan (overrides all defaults) |
SENTINEL_SKIP_CLAM |
0 |
Set to 1 to skip ClamAV scan |
SENTINEL_SKIP_AUDIT |
0 |
Set to 1 to skip npm/composer audit |
Default clamscan flags:
--infected --suppress-ok-results
--heuristic-alerts --heuristic-scan-precedence
--alert-phishing-cloak --alert-phishing-ssl
--detect-pua
--heuristic-scan-precedence means a heuristic hit is reported immediately without waiting to finish all signature checks — this can make detections faster. The other heuristic flags extend coverage to packed/obfuscated binaries, phishing payloads, and potentially unwanted programs beyond the core signature database.
Setting SENTINEL_CLAM_OPTS replaces all of these defaults, so include --infected --suppress-ok-results in your override if you still want clean output.
# Skip ClamAV for a one-off install (e.g. known-safe internal package)
SENTINEL_SKIP_CLAM=1 npm install
# Use a custom quarantine location
SENTINEL_QUARANTINE=/var/quarantine git clone https://github.com/example/repo.git
# Skip audit warnings
SENTINEL_SKIP_AUDIT=1 composer install| Code | Meaning |
|---|---|
0 |
Clean — no threats found |
1 |
The wrapped command itself failed |
2 |
Malware detected — infected files quarantined |
Audit warnings (npm audit, composer audit) do not affect the exit code.
In CI there is no interactive shell to source wrappers into — call sentinel directly. No shell wrappers or install.sh are needed; just copy the binary and add it to PATH.
# .github/workflows/security-scan.yml
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Fetch sentinel
run: |
git clone https://github.com/geodro/sentinel.git /tmp/sentinel
mkdir -p "$HOME/.local/bin"
cp /tmp/sentinel/sentinel "$HOME/.local/bin/sentinel"
chmod +x "$HOME/.local/bin/sentinel"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Cache ClamAV signatures
uses: actions/cache@v4
with:
path: /var/lib/clamav
key: clamav-${{ runner.os }}-${{ hashFiles('/var/lib/clamav/main.cvd') }}
restore-keys: clamav-${{ runner.os }}-
- name: Install ClamAV and update signatures
run: |
sudo apt-get install -y clamav
sudo systemctl stop clamav-freshclam
sudo freshclam
- name: Install dependencies (with scan)
run: sentinel npm install
# composer: sentinel composer install$GITHUB_PATH is the GitHub Actions mechanism for adding to PATH — equivalent to export PATH= in a local shell.
Tip: If sentinel lives in the same repo as your project, replace the
Fetch sentinelstep withcp sentinel "$HOME/.local/bin/sentinel"— no clone needed.
# bitbucket-pipelines.yml
pipelines:
default:
- step:
name: Install and scan dependencies
image: node:20
caches:
- clamav
script:
- apt-get update && apt-get install -y clamav git
- freshclam
- git clone https://github.com/geodro/sentinel.git /tmp/sentinel
- mkdir -p "$HOME/.local/bin"
- cp /tmp/sentinel/sentinel "$HOME/.local/bin/sentinel"
- chmod +x "$HOME/.local/bin/sentinel"
- export PATH="$HOME/.local/bin:$PATH"
- sentinel npm install
# composer: sentinel composer install
definitions:
caches:
clamav: /var/lib/clamav| Topic | Detail |
|---|---|
| Signature update time | freshclam adds ~1–2 min on a cold cache. Cache /var/lib/clamav between runs to avoid this. |
| Scan time | Large node_modules/vendor trees can be slow. Tune with SENTINEL_CLAM_OPTS. Project-tree scans only cover files written during the install run, so they are typically fast. |
| Audit exit codes | npm audit / composer audit are non-fatal (warnings only). |
| Malware detection | Exit code 2 — the build fails automatically. |
| Skipping checks | SENTINEL_SKIP_CLAM=1 or SENTINEL_SKIP_AUDIT=1 to bypass individual steps. |
ClamAV signatures must be updated regularly to detect new threats:
# Linux (run as root or via cron)
sudo freshclam
# macOS
freshclamRecommended: set up a daily cron job:
# /etc/cron.daily/freshclam (Linux)
#!/bin/sh
freshclam --quietsentinel uninstallThis removes the binary, shell wrappers, and the ~/.local/share/sentinel directory, and cleans up any lines sentinel added to your shell config.