SafeDeps is a local dependency firewall.
It checks dependency changes before they are installed, committed, or accepted in CI. The goal is simple: make risky dependency changes harder to introduce by accident.
This is useful when developers, scripts, or AI coding agents can add packages to a project.
SafeDeps does not try to prove that every package is safe. It enforces your dependency policy before the change goes through.
SafeDeps 0.6.0 is a Beta Preview with production-grade evidence for its tested scope: Python/pip runtime guards are the strongest path, Poetry lockfile scanning is validated across a version matrix, npm has a limited first blocking runtime slice, and NuGet has strong scan/CI validation. Broader npm runtime protection and NuGet runtime interception are not claimed until their own dedicated evidence exists.
python -m pip install safedeps
cd your-project
safedeps init
safedeps setup .
source .safedeps/activate.shOn Windows PowerShell, use:
python -m pip install safedeps
cd your-project
safedeps init
safedeps setup .
. .\.safedeps\activate.ps1Then try the guard:
pip install requests
pip install requests==2.32.3
safedeps scan . --fail-on HIGHThe unpinned install should be blocked. The pinned install can pass if the project policy and scan allow it.
After installation, configure the guard before relying on protected installs:
safedeps setup .Alternatively, open the local UI with safedeps ui . and use the Setup Guard button.
With the guard active, this is blocked:
pip install requestsBlocked: unpinned runtime install is not allowed.
Use exact versions (example: package==1.2.3).
This can pass, if the project policy allows it:
pip install requests==2.32.3Before the install is allowed, SafeDeps scans the project and fails the operation if blocking findings are found.
SafeDeps is strongest today for Python and pip workflows. The current release line is a Beta Preview, not a broad stable guarantee across every package manager.
| Area | Status |
|---|---|
| Python project scanning | Supported |
pip runtime guard |
Tested |
python -m pip runtime guard |
Tested |
| Local web UI | Tested for guard toggles and scan flows |
| npm scanning | Supported |
| npm runtime guard | Limited first blocking slice; broader claim still experimental |
| NuGet/.NET scanning | Supported |
| NuGet/.NET runtime flows | Not claimed; scan/CI validation is supported |
| Git submodule checks | Supported |
The detailed support matrix and current non-guarantees are tracked in docs/ECOSYSTEM_SUPPORT.md and docs/KNOWN_LIMITATIONS.md. The threat model is documented in docs/THREAT_MODEL.md. For positioning against adjacent tools, see docs/COMPARISON.md.
PyPI publishing is available:
pip install safedepsThe npm wrapper and .NET tool wrapper exist in this repository, but they are not published to the public npm or NuGet registries in this release. npm runtime protection is validated only for a limited first slice. NuGet runtime interception is not claimed because SafeDeps does not yet install a dedicated dotnet command interceptor.
For normal use:
python -m pip install safedepsFor local development from this repository:
git clone https://github.com/jaydemks/SafeDeps.git
cd SafeDeps
python -m pip install -e .[dev]SafeDeps can be installed in more than one Python at the same time. This is common when you test the published PyPI package globally while developing the local repository inside a project .venv.
Use the Python executable explicitly when testing guard scope:
| Target | Use this interpreter | Typical source |
|---|---|---|
| Project scope | .\.venv-test\Scripts\python.exe or ./.venv-test/bin/python |
local editable repo install |
| Global scope from PyPI | system Python, for example py -3 on Windows or python3 on Linux/macOS |
pip install safedeps from PyPI |
| Global scope from repo | system Python, for example py -3 on Windows or python3 on Linux/macOS |
local editable repo install |
Avoid using bare python, pip, or safedeps while switching targets unless you have just verified what they resolve to.
Verify the active SafeDeps installation:
python -c "import pathlib, safedeps, sys; print(sys.executable); print(safedeps.__version__); print(pathlib.Path(safedeps.__file__).resolve())"On Windows, also check command resolution:
where.exe python
where.exe pip
where.exe safedepsUse these commands from the repository root (safedeps-Latest) and only one block at a time.
safedeps setup is safe to rerun: it clears stale guard hooks/wrappers for PowerShell, CMD, Bash, PATH, and the Python interpreter startup hook, then regenerates them for the selected target. If Auto Guard was already enabled, setup re-syncs it after the new wrappers are written.
SafeDeps also installs an interpreter-level guard hook into the protected Python runtime. This is what blocks direct calls such as C:\...\python.exe -m pip install six that bypass shell aliases, PowerShell functions, CMD AutoRun, or PATH wrappers.
deactivate
# 1) uninstall from project venv
& .\.venv-test\Scripts\python.exe -m pip uninstall -y safedeps
# 2) full local cleanup (removes previous .safedeps state)
.\scripts\reset-safedeps.ps1 -ProjectPath (Get-Location)
# 3) reinstall editable from this repo, then setup project guard
& .\.venv-test\Scripts\python.exe -m pip install -e .[dev]
& .\.venv-test\Scripts\python.exe -m safedeps.cli setup . --install-scope project
. .\.safedeps\activate.ps1For CMD after setup:
.safedeps\activate.batThese commands install SafeDeps at system level from this repository (explicit system Python install, not .venv pip).
deactivate
# choose the exact system interpreter explicitly
$SystemPython = py -3 -c "import sys; print(sys.executable)"
# 1) uninstall from system python
& $SystemPython -m pip uninstall -y safedeps
# 2) full global cleanup
.\scripts\reset-safedeps.ps1
# 3) reinstall editable from this repo, then setup global guard
& $SystemPython -m pip install -e .[dev]
& $SystemPython -m safedeps.cli setup . --install-scope system --protection-scope global
. .\.safedeps\activate.ps1For a PyPI/global smoke test instead of a local editable install, replace the reinstall command with:
& $SystemPython -m pip install --upgrade safedeps
& $SystemPython -m safedeps.cli setup . --install-scope system --protection-scope globalFor CMD after setup:
.safedeps\activate.batIf you installed only a single command earlier and want to know what target you are touching, verify explicitly:
python -c "import sys; print(sys.executable)"
where.exe python
where.exe pip
where.exe safedepsThe commands below install/refresh SafeDeps inside the project virtual environment and rebuild the project guard in project mode.
deactivate || true
# 1) uninstall from project venv
./.venv-test/bin/python -m pip uninstall -y safedeps
# 2) full local cleanup (removes previous .safedeps state)
./scripts/reset-safedeps.sh "$(pwd)"
# 3) reinstall editable from this repo, then setup project guard
./.venv-test/bin/python -m pip install -e .[dev]
./.venv-test/bin/python -m safedeps.cli setup . --install-scope project
source ./.safedeps/activate.shThese commands install SafeDeps at system level from this repository (explicit system Python install, not .venv pip).
deactivate || true
# choose the exact system interpreter explicitly
SYS_PY="$(command -v python3 || command -v python)"
# 1) uninstall from system python
"$SYS_PY" -m pip uninstall -y safedeps
# 2) full global cleanup
./scripts/reset-safedeps.sh
# 3) reinstall editable from this repo, then setup global guard
"$SYS_PY" -m pip install -e .[dev]
"$SYS_PY" -m safedeps.cli setup . --install-scope system --protection-scope global
source ./.safedeps/activate.shFor a PyPI/global smoke test instead of a local editable install, replace the reinstall command with:
"$SYS_PY" -m pip install --upgrade safedeps
"$SYS_PY" -m safedeps.cli setup . --install-scope system --protection-scope globalIf you installed only a single command earlier and want to know what target you are touching, verify explicitly:
python -c "import sys; print(sys.executable)"
python -m pip show safedepsUse these commands from the repository root after running one of the reinstall blocks above.
Project install UI test:
& .\.venv-test\Scripts\python.exe -m safedeps.cli ui . --install-scope project --port 5207Expected behavior:
- the
Globaltoggle is disabled - only project dependencies are shown
- package actions target the project virtual environment
System install UI test:
$SystemPython = py -3 -c "import sys; print(sys.executable)"
& $SystemPython -m safedeps.cli ui . --install-scope system --port 5207Expected behavior:
- the
ProjectandGlobaltoggles are both available Projectprotects only the selected project rootGlobalprotects guarded dependency commands globally in activated guard sessions
CMD activation test:
cd /d C:\path\to\safedeps-Latest
.safedeps\activate.bat
where pip
where python
pip install sixExpected behavior:
where pipshows.safedeps\bin\pip.cmdfirst- unpinned guarded installs are blocked according to the selected
ProjectorGlobalscope
Project install UI test:
./.venv-test/bin/python -m safedeps.cli ui . --install-scope project --port 5207System install UI test:
SYS_PY="$(command -v python3 || command -v python)"
"$SYS_PY" -m safedeps.cli ui . --install-scope system --port 5207six is useful for manual smoke tests because it is normally not required by pytest in this repo environment. Do not use colorama for uninstall tests on Windows because pytest requires it.
Project virtual environment test:
.\.venv-test\Scripts\Activate.ps1
python -c "import pathlib, safedeps, sys; print(sys.executable); print(pathlib.Path(safedeps.__file__).resolve())"
# expected: blocked because the version is not pinned
python -m pip install six
# expected: allowed if the scan passes, then cleaned up
python -m pip install six==1.17.0
python -m pip show six
python -m pip uninstall -y sixExplicit project interpreter test:
# expected: blocked because the version is not pinned
& .\.venv-test\Scripts\python.exe -m pip install six
# expected: allowed if the scan passes, then cleaned up
& .\.venv-test\Scripts\python.exe -m pip install six==1.17.0
& .\.venv-test\Scripts\python.exe -m pip show six
& .\.venv-test\Scripts\python.exe -m pip uninstall -y sixSystem interpreter test:
deactivate
$SystemPython = py -3 -c "import sys; print(sys.executable)"
& $SystemPython -c "import pathlib, safedeps, sys; print(sys.executable); print(pathlib.Path(safedeps.__file__).resolve())"
# expected: blocked because the version is not pinned
& $SystemPython -m pip install six
# expected: allowed if the scan passes, then cleaned up
& $SystemPython -m pip install six==1.17.0
& $SystemPython -m pip show six
& $SystemPython -m pip uninstall -y sixCMD guarded-session test:
cd /d C:\path\to\safedeps-Latest
.safedeps\activate.bat
where pip
where python
pip install six
python -m pip install sixExpected behavior:
where pipshows.safedeps\bin\pip.cmdfirstwhere pythonshows.safedeps\bin\python.cmdfirst- guarded unpinned installs are blocked according to the selected
ProjectorGlobalscope
Project virtual environment test:
source ./.venv-test/bin/activate
python -c "import pathlib, safedeps, sys; print(sys.executable); print(pathlib.Path(safedeps.__file__).resolve())"
# expected: blocked because the version is not pinned
python -m pip install six
# expected: allowed if the scan passes, then cleaned up
python -m pip install six==1.17.0
python -m pip show six
python -m pip uninstall -y sixSystem interpreter test:
deactivate || true
SYS_PY="$(command -v python3 || command -v python)"
"$SYS_PY" -c "import pathlib, safedeps, sys; print(sys.executable); print(pathlib.Path(safedeps.__file__).resolve())"
# expected: blocked because the version is not pinned
"$SYS_PY" -m pip install six
# expected: allowed if the scan passes, then cleaned up
"$SYS_PY" -m pip install six==1.17.0
"$SYS_PY" -m pip show six
"$SYS_PY" -m pip uninstall -y sixYou may see this warning during reinstall:
WARNING: Ignoring invalid distribution ~afedeps
If it appears, run a full uninstall + reset for the same target before reinstall (the two sections above already include that flow). After reset, rerun the matching reinstall block.
When uninstalling SafeDeps from a guarded shell, SafeDeps tries to run guard-cleanup automatically before uninstalling itself. For a deterministic clean uninstall, run the cleanup command first, then uninstall from the exact interpreter that owns the installation.
Project .venv uninstall:
cd C:\path\to\safedeps-Latest
& .\.venv-test\Scripts\python.exe -m safedeps.cli guard-cleanup .
& .\.venv-test\Scripts\python.exe -m pip uninstall -y safedeps
.\scripts\reset-safedeps.ps1 -ProjectPath (Get-Location)System uninstall:
cd C:\path\to\safedeps-Latest
$SystemPython = py -3 -c "import sys; print(sys.executable)"
& $SystemPython -m safedeps.cli guard-cleanup .
& $SystemPython -m pip uninstall -y safedeps
.\scripts\reset-safedeps.ps1After uninstall/reset, open a fresh PowerShell or CMD session.
Project .venv uninstall:
cd /d C:\path\to\safedeps-Latest
.\.venv-test\Scripts\python.exe -m safedeps.cli guard-cleanup .
.\.venv-test\Scripts\python.exe -m pip uninstall -y safedeps
scripts\reset-safedeps.bat %CD%System uninstall:
cd /d C:\path\to\safedeps-Latest
py -3 -m safedeps.cli guard-cleanup .
py -3 -m pip uninstall -y safedeps
scripts\reset-safedeps.batProject .venv uninstall:
cd /path/to/safedeps-Latest
./.venv-test/bin/python -m safedeps.cli guard-cleanup .
./.venv-test/bin/python -m pip uninstall -y safedeps
./scripts/reset-safedeps.sh "$(pwd)"System uninstall:
cd /path/to/safedeps-Latest
SYS_PY="$(command -v python3 || command -v python)"
"$SYS_PY" -m safedeps.cli guard-cleanup .
"$SYS_PY" -m pip uninstall -y safedeps
./scripts/reset-safedeps.shcolorama --version is not a valid package CLI. Use Python to read package versions, for example:
python -c "import colorama, sys; print(sys.executable, colorama.__version__)"python -c "import colorama, sys; print(sys.executable, colorama.__version__)"Run setup once inside the project you want to protect.
Project install from the local development virtual environment:
$ProjectPython = ".\.venv-test\Scripts\python.exe"
& $ProjectPython -m safedeps.cli setup . --install-scope project
. .\.safedeps\activate.ps1Global/system install:
$SystemPython = py -3 -c "import sys; print(sys.executable)"
& $SystemPython -m safedeps.cli setup . --install-scope system --protection-scope global
. .\.safedeps\activate.ps1CMD activation after either setup:
.safedeps\activate.batProject install from the local development virtual environment:
PROJECT_PY="./.venv-test/bin/python"
"$PROJECT_PY" -m safedeps.cli setup . --install-scope project
source ./.safedeps/activate.shGlobal/system install:
SYS_PY="$(command -v python3 || command -v python)"
"$SYS_PY" -m safedeps.cli setup . --install-scope system --protection-scope global
source ./.safedeps/activate.shAfter activation, guarded dependency operations are checked before they run.
On Windows, Auto Guard configures new PowerShell sessions through the PowerShell profile and new CMD sessions through the current-user Command Processor AutoRun hook. For an already open CMD session, run .safedeps\activate.bat.
If you get wrapper errors like:
C:\path\to\project\.safedeps\bin\pip.ps1 ... traceback ...
run the reset script for your platform.
Clean the global SafeDeps guard state:
.\scripts\reset-safedeps.ps1Clean the global state and a specific project guard directory:
.\scripts\reset-safedeps.ps1 -ProjectPath C:\path\to\projectAlternative for double-click/manual CMD use:
scripts\reset-safedeps.bat C:\path\to\projectAfter reset, open a fresh terminal and verify:
where.exe pip
where.exe safedepsClean the global SafeDeps guard state:
./scripts/reset-safedeps.shClean the global state and a specific project guard directory:
./scripts/reset-safedeps.sh /path/to/projectAfter reset, open a fresh terminal and verify:
which pip || true
which safedeps || trueWhat is cleaned:
.safedeps/binentries from user/process paths- SafeDeps Auto Guard blocks from shell profiles
- SafeDeps Python interpreter startup hook
- command wrappers/functions already active in current session
.safedepsdirectories under home and optional project path
Then reinstall SafeDeps in the desired environment using the matching Windows or Bash block in "Clean reinstall and target switching (repo-local)" above.
Run a local scan:
safedeps scan .Fail on high or critical findings:
safedeps scan . --fail-on HIGHWrite scan artifacts to a folder:
safedeps scan . --out security-artifactsOptional npm audit check:
safedeps scan . --online-auditSafeDeps also has a local web UI:
safedeps ui --open-browserThe UI runs locally on 127.0.0.1 and opens a browser window. If the requested port is busy, SafeDeps tries nearby ports.
The UI is useful for scans, dependency inventory, guard controls, approvals, policy edits, baselines, and local intelligence files.
On Windows, you can create a desktop launcher:
safedeps ui-shortcutSafeDeps can flag:
- unpinned Python dependencies
- floating npm versions such as
^,~,*, orlatest - floating or range-based NuGet versions
- untrusted registries or package sources
- denied packages
- missing lockfiles
- direct URL dependencies that need review
- npm install lifecycle scripts
- suspicious package name patterns
- insecure Git submodule URLs
- expired exceptions
- local vulnerability feed matches
- optional metadata risk signals, when a metadata cache is provided
| Ecosystem | Files |
|---|---|
| Python / pip | requirements*.txt, pyproject.toml, poetry.lock, uv.lock, Pipfile.lock |
| npm | package.json, package-lock.json, pnpm-lock.yaml, yarn.lock, .npmrc |
| NuGet / .NET | *.csproj, Directory.Packages.props, packages.config, packages.lock.json, NuGet.Config, nuget.config |
| Git | .gitmodules |
SafeDeps creates a default policy at:
.safedeps/policy.json
Minimal example:
{
"allowed_registries": {
"npm": ["https://registry.npmjs.org/"],
"pip": ["https://pypi.org/simple", "https://pypi.org/simple/"],
"nuget": ["https://api.nuget.org/v3/index.json"]
},
"deny_packages": ["malicious-demo-package"],
"allow_unpinned": false,
"require_lockfiles": true,
"advisory_severity_threshold": "LOW",
"metadata_risk_severity": "MEDIUM",
"require_expiring_exceptions": true,
"exceptions": [
{
"manager": "npm",
"package": "demo-package",
"rule": "FLOATING_VERSION",
"expires": "2026-12-31",
"reason": "Temporary migration exception"
}
]
}Keep policies small at first. Start with pinned versions, trusted registries, deny packages, and lockfiles.
safedeps explain FLOATING_VERSIONThis prints what the rule means and what SafeDeps expects you to change.
Create a baseline from a report:
safedeps baseline . \
--report security-artifacts/safedeps-report.json \
--output .safedeps/vuln-baseline.jsonAdd an expiring approval:
safedeps approve . \
--manager npm \
--rule FLOATING_VERSION \
--package lodash \
--file package.json \
--expires 2026-12-31Approvals should expire. Permanent suppressions are easy to forget.
SafeDeps can use local files for extra checks:
.safedeps/vuln-feed.json
.safedeps/metadata-cache.json
The vulnerability feed can contain local package advisories. The metadata cache can be used for age, churn, and maintainer-change signals.
Local advisories support a simple SafeDeps format and an OSV-style format with affected version ranges. Metadata risk checks are optional and policy-controlled; they can flag very new packages, publisher churn, maintainer changes, missing repository links, and unusually low download signals when the cache contains those fields.
These files can be edited directly or from the UI.
JSON report:
safedeps scan . --out security-artifactsSARIF:
safedeps scan . --sarif security-artifacts/safedeps.sarifCycloneDX:
safedeps scan . --cyclonedx security-artifacts/safedeps.cdx.jsonSPDX:
safedeps scan . --spdx security-artifacts/safedeps.spdx.jsonHTML:
safedeps scan . --html security-artifacts/safedeps-report.htmlMinimal GitHub Actions example:
name: SafeDeps
on:
pull_request:
push:
branches: [main]
jobs:
safedeps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- run: python -m pip install safedeps
- run: safedeps scan . --fail-on HIGH --out security-artifactsMore CI examples are in:
examples/ci/
The repository includes a pre-commit config:
pip install pre-commit
pre-commit installThe hook runs SafeDeps before commit.
safedeps doctor .This checks the local setup and warns about missing optional files or environment problems. It also reports inactive guard state, policy/cache shape issues, missing lockfiles, and missing npm or .NET tooling when the project uses those ecosystems.
If Auto Guard was enabled from the UI, SafeDeps tries to disable it automatically when uninstalling safedeps through a guarded pip or python -m pip command.
For the clean manual flow, run:
safedeps guard-cleanup .
python -m pip uninstall safedepsFor full reset after uninstall, use the matching reset script from "Recover from a broken guard installation". This removes generated wrappers, shell hooks, and stale .safedeps guard state.
Run tests:
python -m pytestSafeDeps is a preventive gate, not a guarantee that every package is safe.
Use it together with lockfiles, code review, vulnerability feeds, SBOM analysis, signed releases where supported, and CI policy enforcement.
SafeDeps is meant to add an early safety layer: before install, before commit, and before CI accepts a dependency change.
