Skip to main content

Real-time security monitor for Python, Node.js, and PHP package installations

Project description

package-alert

package-alert monitors Python, Node.js, and PHP package installations in real time and alerts developers when a malicious or suspicious package is detected. It can also run package manager commands inside an isolated bubblewrap sandbox with pre-flight and post-install OSV checks, scan project lock files or installed environments on demand, and schedule automatic daily or weekly scans across multiple projects.

Features

  • Real-time process monitoring — detects pip install, uv add, uv sync, pipenv install, npm install, composer require, and more as they happen
  • Lock file scanning — after a lock-file-based install finishes, reads the lock file for exact package versions and scans all of them in a single OSV batch call
  • Cache monitoring — watches pip/uv/npm cache directories for newly downloaded packages
  • OSV.dev integration — checks every package against the Open Source Vulnerabilities database
  • Heuristic risk scoring — detects suspicious packages even without a known advisory
  • Typosquatting detection — flags packages that closely resemble popular libraries (Levenshtein distance)
  • Popularity signal — queries deps.dev to flag packages with very few versions or dependents
  • Low latency alerts — Rich terminal panel + notify-send desktop notifications
  • Alert history — all alerts persisted in SQLite with package name, version, advisory, and project path
  • Sandboxed installspackage-alert run wraps any package manager command in a bubblewrap sandbox with pre-flight and post-install OSV checks

Supported Ecosystems

Ecosystem Package managers monitored Lock files scanned
PyPI pip, python -m pip, uv add, uv sync, uv lock, pipenv install uv.lock, Pipfile.lock, requirements.txt
npm npm install, npm add, npm ci package-lock.json
Packagist composer install, composer update, composer require, php composer.phar … composer.lock, composer.json

Installation

Recommended — pipx (isolated environment, package-alert available system-wide):

pipx install package-alert

Development install:

pipx install -e .

Quick Start

# Start the background daemon
package-alert daemon

# Check daemon status
package-alert status

# Run a package manager command in a sandbox
package-alert run uv sync
package-alert run npm install

# Scan the current project's lock files for vulnerabilities
package-alert scan-project

# Scan an explicit requirements file (e.g. a pinned CI lockfile)
package-alert scan-project -r requirements-lock.txt

# Query a specific package
package-alert query requests 2.31.0

# View recent alerts
package-alert alerts

Commands

Global options

Option Description
--verbose / -v Print log output to the console. Without this flag log output is written only to the configured log file.
--config / -c Path to a TOML config file (overrides the default ~/.config/package-alert/config.toml).

daemon

Start the monitoring daemon. Only one instance may run at a time (enforced via a PID file at ~/.local/share/package-alert/daemon.pid).

package-alert daemon [--config PATH]

The daemon:

  1. Polls running processes every second for package manager invocations
  2. Watches pip/uv/npm cache directories with inotify for newly downloaded wheels/tarballs
  3. Dynamically registers site-packages directories when a venv install is detected
  4. Waits for lock-file-based installs (npm, uv sync/lock, pipenv, composer) to finish, then reads the lock file and sends all packages to OSV in a single batch call
  5. Checks each package against OSV; fires alerts for malicious packages or those exceeding the heuristic risk threshold

run

Run a package manager command inside a bubblewrap sandbox.

package-alert run [--no-network] <command> [args...]

Examples:

package-alert run uv sync
package-alert run uv add httpx
package-alert run pip install requests flask==3.0.0
package-alert run npm install
package-alert run npm install lodash@4.17.21
package-alert run composer install
package-alert run --no-network uv sync   # fully offline; uv cache must be warm

What it does:

  1. Pre-flight check — identifies what will be installed (from the command arguments or the project lock file) and batch-queries OSV before anything runs. Blocks immediately if a known-malicious package is found.
  2. Sandboxed execution — runs the command inside a bubblewrap namespace with layered filesystem isolation (see below). Network access is allowed by default so package managers can reach their registries; use --no-network only when all packages are already cached locally.
  3. Post-install scan — diffs the install targets against a pre-run snapshot, identifies new packages by their metadata files (.dist-info, package.json, composer.json), and runs another OSV check on everything that appeared.
Option Description
--no-network Block all outbound network inside the sandbox. Use only when all packages are already in the local cache.
--env VAR Pass an additional environment variable through into the sandbox. Repeatable: --env MY_TOKEN --env CUSTOM_URL.
--expose-ssh-keys Expose ~/.ssh read-only inside the sandbox. Required when installing packages with git+ssh:// or scp-style (git@host:org/repo) VCS dependencies. package-alert detects these automatically and suggests the flag if it is not passed.
--config PATH Path to config TOML file.

Filesystem isolation:

The sandbox uses a layered mount strategy to prevent install-time scripts from reading credentials or secrets outside the project:

Layer What happens
/ read-only The entire filesystem is mounted read-only — install scripts cannot modify system files or other projects.
$HOME hidden A fresh empty tmpfs is overlaid on the home directory, hiding ~/.ssh, ~/.aws, ~/.gnupg, .env files in sibling projects, and any other secrets stored there.
Safe home paths re-exposed A curated allowlist of home subdirectories is re-mounted read-only inside the tmpfs so package managers can function normally (see table below).
Install targets writable The project directory, site-packages/node_modules/vendor, and package manager caches are bound writable on top of the above.

Paths re-exposed inside the home tmpfs (read-only):

Path Purpose
$PYENV_ROOT (~/.pyenv) pyenv-managed Python installations
$NVM_DIR (~/.nvm) nvm-managed Node.js installations
~/.local/bin User-local binaries (uv, pip-installed scripts, etc.)
~/.local/share/uv uv-managed Python installations and tool environments
~/.local/pipx pipx-managed tool environments (shebangs in ~/.local/bin may point here)
~/.config/pip, ~/.pip pip configuration (index URLs, proxy settings)
~/.config/uv uv configuration
~/.npmrc npm registry and auth configuration
~/.cache/pip, ~/.cache/uv, ~/.npm Package manager caches (writable)
~/.config/composer Composer home (writable, when present)

Paths that are not accessible inside the sandbox by default: ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gcloud, ~/.netrc, ~/.git-credentials, and everything else in $HOME not listed above. Pass --expose-ssh-keys to re-expose ~/.ssh read-only when SSH-authenticated VCS dependencies are needed.

Environment isolation:

The sandbox process also starts with a stripped environment. A curated set of variables is forwarded (PATH, HOME, locale, proxy settings, registry URLs for pip/uv/npm/composer, SSL certificates, pyenv/nvm locations). Variables not in this allowlist are removed. Use --env VAR on the command line or sandbox.extra_env in the config file to forward additional variables.

Requirements: bwrap (bubblewrap) must be installed.

# Ubuntu/Debian
sudo apt install bubblewrap

# Fedora/RHEL
sudo dnf install bubblewrap

# Arch
sudo pacman -S bubblewrap

Virtual environment detection: for Python commands, package-alert automatically detects the target site-packages directory by checking (in order) the executable path in the command, VIRTUAL_ENV (pip/pipenv only — uv always uses the project-local .venv), and .venv/venv directories in the current working directory.

status

Show the current state of the daemon and related paths.

package-alert status [--json] [--config PATH]

Displays:

  • Daemon running/stopped, PID, uptime, and whether it was started by systemd
  • Config file path in use
  • Daemon log file path and whether it exists
  • CLI log file path and whether it exists

Use --json for machine-readable output.

scan-project

Scan a project directory for vulnerable or malicious packages.

package-alert scan-project [PATH] [OPTIONS]
Option Default Description
PATH . Project directory
--scan-unpinned off Also query OSV for unpinned/range-constrained dependencies
--scan-installed off Scan venv/.venv site-packages or node_modules instead of lock files
--requirements / -r Explicit requirements file to scan instead of auto-detecting lock files (mutually exclusive with --scan-installed)
--details / -d off Show full advisory details and URL
--format / -f text Output format: text, json, html, browser

Formats:

  • text — colour-coded terminal output; severity badge on the advisory line ([HIGH] GHSA-…)
  • json — machine-readable JSON with all findings, unpinned packages, and sources
  • html — self-contained HTML report printed to stdout
  • browser — writes HTML to /tmp/package-alert-*.html and opens it in the default browser

--requirements accepts a path relative to PATH (the project directory) or an absolute path. Nested -r/--requirement includes within the file are followed recursively. --requirements and --scan-installed are mutually exclusive.

Auto-detected lock files (in order of precedence):

  • package-lock.json → npm
  • uv.lock → PyPI (uv)
  • Pipfile.lock → PyPI (pipenv)
  • requirements.txt / requirements/base.txt / requirements/prod.txt → PyPI (only when no uv/pipenv lock found)
  • composer.lock → Packagist
  • composer.json (fallback when no lock file) → Packagist

scan-cache

Scan pip and uv cache directories for wheels that have known malicious advisories.

package-alert scan-cache [--config PATH]

query

Query OSV for a specific package, with full advisory details.

package-alert query PACKAGE [VERSION] [--ecosystem pypi|npm] [--config PATH]

alerts

Show recent alerts stored in the database.

package-alert alerts [--limit N] [--config PATH]

Displays a table with: package name, ecosystem, version, advisory ID or risk score, project path, and timestamp.

clear-cache

Clear the OSV query cache.

package-alert clear-cache [--ecosystem pypi|npm] [--config PATH]

Omit --ecosystem to clear all ecosystems.

config-show

Print the resolved configuration as JSON (useful for verifying config file is being read).

package-alert config-show [--config PATH]

Scheduled Scans

Register projects for automatic daily or weekly scans run by the daemon. Each path can be registered for multiple scan types independently.

# Register the current project for daily lock-file scans
package-alert schedule add --daily

# Also register for weekly installed-packages scans (both coexist independently)
package-alert schedule add --weekly --installed

# Register a specific path
package-alert schedule add /path/to/project --daily

# List all registered projects (shows all path/scan_type pairs)
package-alert schedule list

# Remove only the installed-packages scan entry
package-alert schedule remove --installed

# Remove all scan entries for the current project
package-alert schedule remove

# List completed scans for the current project (newest first)
package-alert scans list
package-alert scans list /path/to/project --limit 10

# Show findings from a specific scan
package-alert scans show 42
package-alert scans show 42 --format json
package-alert scans show 42 --format html
package-alert scans show 42 --format browser
package-alert scans show 42 --details

Configuration

Config is loaded from ~/.config/package-alert/config.toml automatically if it exists. Override with --config PATH on any command.

# Logging for the long-running daemon process.
[log]
level = "INFO"                                      # DEBUG, INFO, WARNING, ERROR, CRITICAL
file = "~/.local/share/package-alert/daemon.log"    # set file = "" to disable file logging
# max_bytes = 10485760    # 10 MB per file before rotation
# backup_count = 3

# Logging for short-lived CLI commands (scan-project, query, alerts, etc.).
[cli_log]
level = "INFO"
file = "~/.local/share/package-alert/cli.log"       # set file = "" to disable file logging

[watch]
enable_cache_monitoring = true
enable_process_monitoring = true
pip_cache_dir = "~/.cache/pip"
uv_cache_dir = "~/.cache/uv"
npm_cache_dir = "~/.npm/_cacache"
site_packages_dirs = []                             # extra site-packages to watch
process_poll_interval_seconds = 1.0

[osv]
base_url = "https://api.osv.dev/v1"
cache_ttl_hours = 24
timeout_seconds = 10.0
max_retries = 3

[alerts]
desktop_notifications = true
terminal_notifications = true
min_severity_for_desktop = "MEDIUM"

[heuristics]
enabled = true
warning_threshold = 40
critical_threshold = 70

[sandbox]
# Additional environment variable names to forward into the sandbox beyond
# the built-in allowlist (PATH, HOME, proxy vars, registry URLs, etc.).
extra_env = []
# Example: extra_env = ["MY_PRIVATE_REGISTRY_TOKEN", "CUSTOM_CERT_PATH"]

# Additional paths to mount as empty tmpfs inside the sandbox.
# Use this on systems where other root-owned paths cause tool failures inside
# bwrap's user namespace (e.g. SSH proxy config files owned by root).
extra_tmpfs = []
# Example: extra_tmpfs = ["/etc/ssh/other_config.d"]

[scheduler]
enabled = true
daily_hour = 2          # hour of day (0–23) to run daily scans
weekly_day = 6          # day of week to run weekly scans (0=Mon … 6=Sun)
weekly_hour = 2         # hour of day to run weekly scans
max_scan_history = 5    # completed scan records to keep per project per scan type

Scan types:

  • project (default) — scans lock files (requirements.txt, uv.lock, package-lock.json, composer.lock). Reproducible; works offline with cached OSV results.
  • installed — enumerates packages actually installed in the project's virtual environment (pip list, npm ls, composer show). Catches drift between lock file and real environment.

Data Storage

All persistent data lives in ~/.local/share/package-alert/:

File Purpose
package-alert.db SQLite database: OSV cache, alert history, popularity cache
daemon.log Rotating daemon log file (10 MB × 3 backups)
cli.log Rotating CLI command log file (10 MB × 3 backups)
daemon.pid PID file used to prevent duplicate daemon instances

systemd (Linux)

mkdir -p ~/.config/systemd/user
cp package-alert.service ~/.config/systemd/user/
systemctl --user enable --now package-alert

Architecture

See ARCHITECTURE.md.

Security

See THREAT_MODEL.md.

Roadmap

See ROADMAP.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

package_alert-0.1.0.tar.gz (145.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

package_alert-0.1.0-py3-none-any.whl (75.8 kB view details)

Uploaded Python 3

File details

Details for the file package_alert-0.1.0.tar.gz.

File metadata

  • Download URL: package_alert-0.1.0.tar.gz
  • Upload date:
  • Size: 145.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for package_alert-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e6e84e9f7363350ca4609c4b4e7ce499f2594b099eded5f4a29ebcaa7b4a54e5
MD5 ddf471df70e0016eb08c598422e391e6
BLAKE2b-256 1e1c31f2218fb5cc1c96fb0daf687417378d9fe4d94a3ec46c58d5a08a3edf45

See more details on using hashes here.

File details

Details for the file package_alert-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: package_alert-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 75.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for package_alert-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 234791712b90530ad1be48898c8b20fd5c3aa4362e13eda370a8035c0679d457
MD5 5f88fb3404322767303185956bf729a3
BLAKE2b-256 da4ab77c9276c2a4066a977ca0b651c7eaf49cf97245e6a6168eda951aec6dc7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page