Introduction to Deputy

Secure dependency management at scale, from diff to fix.

Deputy is a tool to help enable secure dependency management at scale. It brings the full dependency lifecycle workflow together in one place: vulnerability scans, diffs, fixes, SBOMs, sandboxed executions, download-time enforcement, and policy as code. It works across repos, directories, container images, VM disk images, SBOMs, one-off PURLs, and more. This project is still early and experimental. Expect sharp edges, rapid iteration, and continuous feedback loops.

The core idea

Dependencies are most of modern software. They live in repos, images, registries, and SBOMs, each with its own identity rules and edge cases. Most tooling solves a slice. You end up doing the reconciliation by hand.

Deputy flips that. It reconciles everything into a high-fidelity, inspectable dependency inventory, and when the input has relationship data, a dependency graph on demand. Then it keeps the rest of the workflow close to that foundation: scan for known issues, diff for reviews, sbom for sharing and archiving, graph why for paths, fix for reviewable remediation plans, exec for sandboxed installs and builds, and proxy for enforcement at download time.

Deputy helps answer questions you need answered fast:

  • What's in here? (repo, dir, image, Dockerfile, SBOM)
  • What changed? (between two git refs, container images, etc)
  • Why is it here? (the dependency path)
  • Is it allowed? (policy, everywhere)
  • What do we do next? (a reviewable remediation plan)

Policy keeps the guardrails consistent

Policies are authored in YAML using CEL, a small expression language for writing readable, deterministic checks over structured data. That keeps policies easy to review, and the exact same expressions run in local scans, CI, and proxy enforcement.

Check them into your repo, lint them in CI, and reuse the same bundle across scan, diff, and proxy. They can warn or deny. The point is that the same rule shows up in review, in CI, and at download time. If you want to borrow patterns, start with the policy examples or the policy cookbook for easy recipes.

Example 1

Block copyleft licenses

A simple compliance guardrail: deny packages with disallowed licenses and flag missing metadata. Works anywhere policy is evaluated (scan, diff, proxy, or SBOM analysis).

CEL policy snippet
policies:
  - name: allow-sans-copyleft
    vars:
      forbidden:
        - SSPL-1.0
        - AGPL-3.0-only
        - GPL-3.0
    rules:
      - action: deny
        when: pkg.licenses.exists(l, l in forbidden)
        reason: package carries a forbidden license
$ deputy scan --policy policy/examples/license-allowlist.yaml
Example 2

Require review for new dependencies

PR guardrail: allow known prefixes and force review for anything else. This plugs straight into deputy diff so reviewers see the policy decision next to the change.

CEL policy snippet
policies:
  - name: new-dependency-review
    vars:
      approvedPrefixes:
        - github.com/acme
        - npm:@acme
      isAddition: 'change.type == "added"'
      name: 'pkg.name'
      matchesPrefix: 'approvedPrefixes.exists(p, name.lowerAscii().startsWith(p.lowerAscii()))'
    rules:
      - action: deny
        when: env.entrypoint == "diff_dependency_change" && isAddition && !matchesPrefix
        reason: New dependency requires review or is outside the approved allowlist
$ deputy diff --policy policy/examples/new-dependency-review.yaml main WORKING
Example 3

Block high/critical vulns in CI

A hard gate for release pipelines: fail scans when critical or high vulnerabilities are still present. This is the classic “don’t ship with known fires burning” policy.

CEL policy snippet
policies:
  - name: deny-critical-and-high
    entrypoints: ["scan_report"]
    rules:
      - action: deny
        when: |
          vulnerabilities.orValue([]).exists(v,
            v.advisory.severity.level in [severity.critical, severity.high])
        reason: dependency has unresolved high-severity vulnerabilities
$ deputy scan --policy policy/examples/severity-guardrail.yaml
CI example

GitHub Actions: policy gate in one job

Same policy bundle, now enforced in CI. Deputy’s GitHub Actions can scan on every push or PR and upload SARIF to Code Scanning. See the GitHub Actions guide for options.

GitHub Actions (scan + policy)
name: Security Scan
on: [push, pull_request]

permissions:
  security-events: write
  contents: read

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: picatz/deputy/actions/setup@main
      - uses: picatz/deputy/actions/scan@main
        with:
          upload-sarif: true
          policy: policy/ci/security-gate.yaml
One policy, everywhere
# Sanity check a policy bundle
$ deputy policy lint policy/ci/security-gate.yaml

# Enforce policy during scan
$ deputy scan --policy policy/ci/security-gate.yaml

# Require review for new dependencies in diffs
$ deputy diff --policy policy/ci/pr-review.yaml main WORKING

# Block risky downloads at install time
$ deputy proxy go --policy policy/ci/security-gate.yaml -- go get github.com/example/pkg@latest

The command compass

Deputy has enough subcommands that it's worth a map. The full list is in the command docs.

Hover a command to preview a real invocation.

CommandPurposeWhy it matters
Find known vulnerabilities across repos, dirs, images, VM images, SBOMs, and PURLsBaseline risk and gate releases
Explore dependency paths (e.g. why a package exists)Answer "why is this here?" quickly
Compare dependency changes between Git refs or container imagesReview what actually changed
Generate CycloneDX, SPDX, or Protobom SBOMsInventory you can share, sign, or archive
List dependencies as PURLs for scriptingFast inventory without a full SBOM
Generate or apply remediation plansTurn findings into action
Prioritize findings (optional agent help)Focus effort where it matters
Run commands in a sandbox with supply chain protectionsSafer installs and builds
Enforce policies at download timeBlock risky dependencies early
Lint, test, bundle, and evaluate policiesTreat guardrails as code
Command preview deputy scan
$ deputy scan --policy policy/ci/security-gate.yaml

Add --format json when you want machine-readable output.

Where to start

A few on-ramps, depending on what you're trying to solve:

  • Developers / PR reviewers: start with deputy diff. If something looks weird, follow with deputy scan and deputy graph why.
  • Security: start with a policy bundle, run it in CI, then consider rolling out deputy proxy.
  • Platform/CI: generate an SBOM with deputy sbom and keep it as a build artifact, then scan or diff it later.
  • New to Deputy: run deputy init to generate a starter .deputy.yaml and policy bundle, then iterate from there.

If you want the guided tour: getting started, the cheat sheet, workflows, and the CI guide.

Targets everywhere

Deputy commands take a target, meaning the thing you want to analyze. A target can be a repo, a directory, a container image, a VM disk image, an SBOM document, a Dockerfile, or a single package identified by a PURL.

Deputy inspects the target you pass in and applies the right semantics for that artifact. That might mean extracting packages from a filesystem view, importing an SBOM document, or parsing a Dockerfile, then applying the same policies to the results. For repos and directories, Deputy can also resolve dependency relationships from manifests and lockfiles, which powers graph questions like deputy graph why. When relationship data is missing or partial, Deputy falls back to a flat inventory that is still useful for scans, diffs, and SBOMs.

SBOM files and one-off PURLs skip extraction and go straight to analysis. Dockerfiles are parsed for static dependency analysis rather than scanned like a filesystem. As Deputy grows, the intent is to keep this pipeline stable so new target kinds can slot in cleanly, including live targets like a running container or a cloud artifact like Lambda.

  • Repositories (local or remote Git) and plain directories
  • Container images (remote registries, local Docker daemon, tarballs)
  • VM disk images (qcow2, vmdk, vhd, vhdx, vdi, raw) and raw filesystem images (ext4)
  • SBOM files (CycloneDX / SPDX / Protobom JSON)
  • PURLs for one-off package queries
  • Dockerfiles for static dependency analysis

For VM disk images, Deputy stays unprivileged. It reads the disk image, parses the partition table (GPT or MBR), identifies a Linux partition, opens the filesystem (ext4), and walks it to build the inventory. No mounts and no root.

More details: targets and refs, container images, and Dockerfile scanning.

Scan anything
# Scan the current repo
$ deputy scan

# Scan a remote repo at a ref
$ deputy scan github.com/picatz/deputy --ref main

# Scan a container image
$ deputy scan image nginx:1.25

# Scan a VM disk image
$ deputy scan vm:///path/to/disk.qcow2

# Scan a rootfs filesystem image
$ deputy scan rootfs:///path/to/rootfs.ext4

# Scan an SBOM file from another tool
$ deputy scan sbom sbom.spdx.json

# Scan a single package by PURL
$ deputy scan purl pkg:golang/github.com/gin-gonic/gin@v1.9.0
Scan output (excerpt)
$ deputy scan

Scan Results:
  Target: /repo/deputy
  Ref: WORKING (b0f72a3)
  Origin: https://github.com/picatz/deputy.git

∴ Vulnerabilities Found:

github.com/google/osv-scalibr v0.3.3 [direct]:
  • CVE-2025-13425 [LOW] (0.3.4) [2 related]
    OSV-SCALIBR has NULL Pointer Dereference
    Aliases: GHSA-f786-75f3-74xj, GO-2025-4149
    Published: 2025-11-20
    Context:
      Sources:
        • go.mod
      Artifacts:
        • deputy (go)

Vulnerability Summary:
  ↑ 1 can be fixed by upgrading

Recommended Actions:
  1. Upgrade affected modules
       go.mod:
         › go get github.com/google/osv-scalibr@0.3.4
         ↻ go mod tidy

When something pops up, the next question is always: "why is this here?" deputy graph why answers with the dependency path. More in the graph docs.

Why is this dependency here?
$ deputy graph why github.com/google/osv-scalibr
github.com/google/osv-scalibr@0.3.3
[direct dependency]

deputy triage: turn findings into priorities

Scanning is cheap. Decision-making isn't. deputy triage helps you turn a raw report into priorities you can act on, and it can optionally pull in an agent for extra context. The triage docs cover report formats and flags.

Triage workflows
# Triage the current repo
$ deputy triage

# Delegate prioritization to an agent
$ deputy triage --agent claude

deputy diff: where reviews get real

For PR review or base-image bumps, deputy diff answers the only question that matters: "what actually changed?" It works on Git refs and container images, and it can include vulnerability deltas and policy findings so you can spot regressions before you merge them.

Diff repos and containers
# Git diff (default in a repo)
$ deputy diff main WORKING

# Compare container images
$ deputy diff nginx:1.24 nginx:1.25

# Use local Docker daemon images to avoid pull limits
$ deputy diff --source docker-daemon python:3.11 python:3.12
Diff summary (excerpt)
$ deputy diff
Comparing dependencies: main -> HEAD

Dependency Changes:
  ↑ github.com/google/osv-scalibr @ 0.3.2 -> 0.3.4 [direct]
  ↑ golang.org/x/crypto @ 0.44.0 -> 0.46.0 [direct]
  + cloud.google.com/go/compute/metadata @ 0.9.0 [indirect]
  ↓ osv.dev/bindings/go @ 0.0.0-20250905014459-96958296f6f2 → 0.0.0-20250808040635-c189436f8791 [direct]

Summary:
  + 1 packages
  ↑ 2 packages
  ↓ 1 package
  
✓ No vulnerabilities found

deputy proxy: enforce at download time

deputy proxy runs at download time. It supports Go, npm, PyPI, RubyGems, and OCI, and it uses the same policy bundles you run in scan and diff, so the decision stays consistent. This is where your toolchain reaches out to the internet. It's a great place to enforce guardrails before a dependency lands in a lockfile or image layer.

Proxy in practice
Local dev
Developer
Deputy proxy
Registry

Policies live in the repo and enforce guardrails on every download while you work.

$ deputy proxy go --policy policy/ci/security-gate.yaml -- go get github.com/example/pkg@latest
CI job
CI runner
Deputy proxy
Registry

The exact same policies run inside the pipeline during installs and image pulls.

$ deputy proxy npm --policy policy/ci/license-allowlist.yaml -- npm ci
Remote proxy with auth
Dev or CI
Auth and policy
Deputy proxy
Registry

Central proxy with JWT or OIDC auth and shared policy bundles for teams.

$ deputy proxy serve --config proxy.yaml

Policy decisions stay the same. Local and CI use repo policies. Remote adds auth and shared bundles.

If you're thinking about rolling it out beyond a single repo, the proxy rollout guide covers the operational rollout details. The proxy docs cover config and auth.

Proxy in front of registries
# Run commands through the proxy with policies
$ deputy proxy go --policy policy/ci/security-gate.yaml -- go get github.com/example/pkg@latest
$ deputy proxy npm --policy policy/ci/license-allowlist.yaml -- npm install

# Run a standalone proxy service
$ deputy proxy template > proxy.yaml
$ deputy proxy serve --config proxy.yaml

Composable workflows

Most commands can emit JSON, so you can treat them like building blocks: pipe outputs around or save them as artifacts. A scan can feed triage or a fix plan. An sbom can be generated once and scanned later. In CI, you can stash the JSON output and SBOM alongside build artifacts, which keeps "what did we know then?" conversations short.

From scan to fix
# Generate a JSON scan report
$ deputy scan --format json --output scan.json

# Turn that report into a remediation plan
$ deputy fix --report scan.json

# Or stream the report directly into fix
$ deputy scan --format json | deputy fix --report -
SBOM as a stable artifact
# Generate an SPDX SBOM
$ deputy sbom --format spdx-json --output sbom.spdx.json

# Scan the SBOM later
$ deputy scan sbom sbom.spdx.json

deputy fix: remediation plans

deputy fix prints an explicit plan first, because dependency changes deserve the same review as code changes. You can review it, export it, or apply it. If you run an agent, keep it behind a sandbox and keep review checkpoints in place. More in the fix docs.

Fix workflows
# Generate a plan
$ deputy fix

# Save a plan for review or approval
$ deputy fix --format json --output fix-plan.json

# Apply fixes automatically
$ deputy fix --apply .

# Ask an agent to help implement upgrades
$ deputy fix --agent claude --agent-full-auto
Agent remediation (excerpt)
$ deputy fix --agent claude --agent-full-auto 
Remediation Plan:
  Target: /repo/deputy
  Commit: b0f72a33a9a74cf1d19c869971c342871f5630ab
  • Apply dependency upgrades (1 total, 2 runnable)
       go.mod:
         › go get github.com/google/osv-scalibr@0.3.4
         ↻ go mod tidy

Agent Remediation

WARNING: Full-auto mode enabled: commands and file writes will execute without approval

  ## Summary                                                                  
                                                                              
  Successfully executed the remediation plan:                                 
                                                                              
  Completed actions:                                                          
                                                                              
  1. ✓ Updated  github.com/google/osv-scalibr  from v0.3.3 to v0.3.4          
  2. ✓ Ran  go mod tidy  to clean up dependencies (added  github.com/tink-    
  crypto/tink-go/v2 v2.4.0  as a new transitive dependency)                   
  3. ✓ All tests passed (113 test packages executed successfully)             
                                                                              
  The dependency update has been applied and verified. The repository is now  
  using the patched version of osv-scalibr.                                   

✦ claude · 58.9s

Full-auto mode executes commands and writes files without approval. Treat it like a power tool (it can be dangerous if misused). Use it in controlled environments, and pair it with builtin review safeguards like --review, or normal branch protections, pre-merge checks, etc.

deputy exec: safer installs and builds

If you don't fully trust npm install, you're not being paranoid. deputy exec runs package manager commands and build scripts inside a sandbox with workspace isolation, file masking, network allowlists, and resource limits. The point isn't magic safety. It's about shrinking the blast radius. Use --review to inspect changes before syncing them back. Full flag details are in the exec docs.

Sandboxed execution
# Isolate workspace and mask common secrets during npm install
$ deputy exec --runtime docker \
  --workspace-isolation snapshot \
  --mask-preset supply-chain \
  --image node:22-alpine \
  -- npm install

# Review changes before syncing them back
$ deputy exec --workspace-isolation snapshot --review -- npm audit fix

# VM-based isolation on macOS (VZ plugin)
$ deputy exec --runtime plugin --plugin vz \
  --workspace-isolation snapshot \
  --review \
  -- npm install
Sandbox runtimes
RuntimePlatformNotes
dockerAllContainer isolation (default).
gvisorLinuxStronger isolation via gVisor.
plugin (vz)macOS ARM64VM-based isolation.
sandbox-execmacOSHost sandboxing (more limited).
noneAllNo sandboxing (trusted only).
Workspace isolation
ModeWhat it does
directMount workspace directly (fastest).
snapshotCopy workspace to a temp dir.
overlayCopy‑on‑write overlay (Linux).
tmpfsMemory‑backed workspace (ephemeral).
git-worktreeIsolate via a Git worktree.

Use --review to inspect and confirm workspace changes before they sync back to your repo. For a manual audit, add --preserve-workspace to keep the isolated workspace around. Filesystem mode (--mode) is separate from workspace isolation: the default is workspace-write; for analysis‑only runs, use --mode read-only.

VM isolation helps prevent host-level compromise, but it doesn't make supply chain risk go away. A malicious package can still poison the build output or artifact you ship. Use defense-in-depth, keep blast radius small, and treat sandboxing as one (useful) layer, not a guarantee.

VZ plugin (macOS)

VZ is a sandbox runtime plugin that boots a lightweight Linux VM on macOS Apple Silicon using the Apple Virtualization.framework (via Code‑Hex/vz). It's a good fit when you want VM-level isolation without even needing Docker, and it's a concrete example of how customizable sandbox runtime plugins work in Deputy. What's really cool: boot time is very fast in the example plugin (≈1-2s), but it depends on the rootfs and what's installed inside the VM.

How it fits together
macOS host
deputy CLI Unix socket RPC deputy-sandbox-vz Virtualization.framework VZ VM (Linux)
Filesystem + isolation
Workspace (host → VM)

Isolation mode decides how the workspace is staged before it is mounted.

direct snapshot overlay tmpfs git-worktree
Mount path
host workspace /workspace (virtio‑fs)
Rootfs + kernel (VM base)

Boot assets live under ~/.deputy/vz/* and attach via virtio‑blk.

rootfs.img vmlinuz initrd
Mount path
~/.deputy/vz/* / (virtio‑blk)
Inside the VM
/deputy-init (PID 1) runs your command.
Lifecycle: boot → init → run → poweroff.
Device map (virtio):
virtio‑fs → /workspace
virtio‑blk → / (rootfs)
virtio‑net → NAT / allowlist
virtio‑console → stdout/stderr
The VM only sees / and /workspace—nothing else from the host.
Example rootfs:
Go 1.23.5 Node.js 22 build tools
Applies to the bundled Alpine rootfs used by the VZ example plugin.
Boot sequence (simplified):
load kernel mount rootfs load virtio net/DNS mount /workspace decode command exec

The hypervisor is the security boundary: the host treats everything inside the VM (kernel, userspace, your command) as untrusted.

Setup details: VZ plugin example.

Agents, but with guardrails

Agents are optional. If you pass --agent, Deputy can hand the context to an assistant for explanations, triage, or help applying a plan. Different agents run in different sandboxes (and may or may not have network access), so test the one you plan to use. Keep them on a leash: use read-only for analysis, and pair workspace-write with review checkpoints. The agents guide covers sandboxes and tradeoffs.

Agent-assisted workflows
# Explain a vulnerability
$ deputy explain --agent codex CVE-2021-44228

# Prioritize findings
$ deputy triage --agent claude

# Apply fixes with an agent in a safer sandbox
$ deputy fix --agent claude --agent-sandbox workspace-write

Plugins: extend the inventory

If Deputy doesn't understand your ecosystem, you can teach it. Plugins are external executables (write them in Go or anything) that return packages to include in the inventory.

The plugins guide walks through packaging, discovery, and testing.

Minimal extractor plugin (Go)
package main

import (
    "strings"

    "github.com/picatz/deputy/sdk/plugin"
)

func main() {
    plugin.Main(&myExtractor{})
}

type myExtractor struct{}

func (e *myExtractor) Name() string           { return "custom/myformat" }
func (e *myExtractor) DisplayName() string    { return "My Custom Format" }
func (e *myExtractor) Ecosystem() string      { return "custom" }
func (e *myExtractor) Version() int           { return 1 }
func (e *myExtractor) Description() string    { return "Extracts packages from .myformat files" }
func (e *myExtractor) FilePatterns() []string { return []string{"*.myformat"} }

func (e *myExtractor) FileRequired(path string, isDir bool, mode uint32, size int64) bool {
    return !isDir && strings.HasSuffix(path, ".myformat")
}

func (e *myExtractor) Extract(path string, contents []byte, root string) ([]*plugin.Package, error) {
    return []*plugin.Package{plugin.NewPackage("example-pkg", "1.0.0", "custom")}, nil
}

Where Deputy Is Headed

I want dependency work to feel like a system, not a scavenger hunt: one inventory, one set of policies, and a straight line from "we have a problem" to "it's fixed". Predictable. Reviewable. Routine. Tractable. The goal is fewer fire drills and more reliable, explainable outcomes: every change is traceable, every finding is actionable, and the guardrails stay consistent no matter where the software lives. If Deputy does its job, the inventory becomes quiet infrastructure in the background, not another dashboard to babysit, just a source of truth that makes reviews faster and fixes repeatable. If you want to kick the tires, the repo is here. Bug reports, sharp-edge notes, and ideas are all welcome.


Continue Reading