How to Enforce Chainsaw Org-Wide

Advanced 45 minutes Security Engineers / Platform Engineers Monitoring & Compliance

Layer CI checks, MDM policy, and network egress controls so every package install across the org routes through Chainsaw — without Chainsaw trying to be an endpoint agent.

Where Chainsaw Stops and the Rest of Your Security Stack Starts

Chainsaw is a supply chain security tool, not a system-control tool. It inspects, scores, and gates packages at the proxy layer. It does not — and will not — ship an endpoint agent, an MDM profile daemon, or a firewall driver. Those jobs belong to tools already purpose-built for them.

That split is deliberate. Customers already run MDM (Intune, Jamf, Kandji), SWG/SASE (Zscaler, Netskope, Cloudflare Gateway, Palo Alto), and CI platforms (GitHub Actions, GitLab CI, Jenkins) in production. A proxy that tries to duplicate those controls competes poorly and adds attack surface. A proxy that integrates with them cleanly is strictly additive.

So Chainsaw-wide enforcement is not “install the Chainsaw daemon everywhere.” It’s:

Internal controls inside Chainsaw (policy evaluation, client guides, attestation API, config-drift checks in the CLI) combined with third-party enforcement tools (MDM for endpoint config, network firewall/SASE for egress, CI for repo-level preflight).

This tutorial walks through the combination.

The three enforcement layers

LayerWhat it blocksWhat enforces itChainsaw’s role
CI-enforcedPRs that commit bypass files (project .npmrc, pinned public --index-url, etc.), runners whose config driftedYour CI platform + branch protectionThe chainsaw/enforcement GitHub Action runs chainsaw scan-repo + chainsaw doctor --strict --attest and exits non-zero on drift.
Endpoint-managedDrift on fleet-managed laptops and build agents between attestationsYour MDM (Intune, Jamf, Kandji, Workspace ONE, etc.)Chainsaw ships reference MDM scripts; the MDM owns provisioning and compliance reporting to the device fleet.
Network-mandatoryAny direct connection to public registries from anywhere on your networkYour SWG/SASE/firewallChainsaw publishes an authoritative upstream-host deny-list at GET /api/compliance/egress-allowlist; your SWG owns the block.

No single layer is sufficient on its own. An admin who deploys only the CI layer stops bypass files in PRs but can’t catch a developer running npm install --registry=https://registry.npmjs.org on their laptop. An admin who deploys only MDM misses BYOD and any runner outside their fleet. An admin who deploys only the network layer covers everything on the network but is blind to which endpoints are drifting inside the tunnel.

Combine at least two. Regulated environments combine all three.

Prerequisites

  • A running Chainsaw deployment accessible from both your CI and your endpoints.
  • A client credential or API key with the client-setup scope for CLI attestation, and an admin with compliance:read to view the dashboard.
  • Optional but recommended: your MDM (Intune, Jamf, Kandji) and SASE vendor (Zscaler, Netskope, Cloudflare Gateway) accessible for the endpoint / network steps.
If you only have bandwidth for one layer this week, start with CI-enforced. It requires no fleet-management integration, lands as a required status check on main, and gives you immediate bypass-file detection across every repo.

Layer 1 — CI-enforced

CI is the fastest layer to stand up because it requires no vendor integration beyond your existing CI platform and no endpoint changes. It catches two of the most common bypass patterns:

  1. A committed project-scope config override (a .npmrc in the repo, a --index-url directive in requirements.txt, a <repository> block in pom.xml).
  2. Drift on the CI runner itself — for example, a self-hosted runner where a prior job left PIP_INDEX_URL exported.

Step 1: add the Chainsaw GitHub Action

Copy the reference workflow into your repo:

# .github/workflows/chainsaw.yml
name: Chainsaw enforcement

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  chainsaw-preflight:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Chainsaw enforcement
        uses: ./enforcement/github-actions
        with:
          server: ${{ secrets.CHAINSAW_SERVER }}
          token: ${{ secrets.CHAINSAW_TOKEN }}
          attest: true
          scan-repo: true

Set CHAINSAW_SERVER and CHAINSAW_TOKEN as repository (or organization) secrets.

CHAINSAW_TOKEN here is a management-API key — the c305_pa_… value minted at Access → API keys, or a session JWT. The action exports it to chainsaw doctor, which sends Authorization: Bearer $CHAINSAW_TOKEN to /api/attestations, and that endpoint accepts only an API key or a JWT. It is a different credential from the CHAINSAW_CLIENT_ID / CHAINSAW_CLIENT_SECRET pair used further down this page to pull packages through the registry proxy: a client-credential pair never authenticates against /api/…, and an API key never authenticates a package fetch. Never base64-encode either one. The full map is in configuration reference section B30.

Step 2: mark it as a required status check

On the branch protection rule for main:

  1. Enable Require status checks to pass before merging.
  2. Add chainsaw-preflight to the required list.
  3. Enable Require branches to be up to date before merging so the check always runs against current code.

From this point, any PR that commits a bypass file or whose runner fails the doctor --strict check blocks merge until it’s resolved.

Step 3 (optional): add GitLab CI / Jenkins equivalents

For GitLab:

chainsaw-preflight:
  stage: test
  image: alpine:3.20
  before_script:
    - apk add --no-cache curl bash
    - curl -fsSL https://chain305.com/install.sh | bash
  script:
    - chainsaw scan-repo .
    - chainsaw doctor --strict --attest
  variables:
    CHAINSAW_SERVER: "$CHAINSAW_SERVER"
    CHAINSAW_TOKEN: "$CHAINSAW_TOKEN"

For Jenkins, wrap the same two commands in a sh stage. The exit codes (0, 10, 30, 40) are stable across platforms so the job failure surfaces cleanly.

Layer 2 — Endpoint-managed (MDM)

CI enforcement catches repo-level drift but cannot see what happens on a developer laptop between CI runs. That is the MDM layer’s job.

Chainsaw does not ship an endpoint agent. Instead, we publish reference scripts that your MDM runs on enrollment. The MDM owns the lifecycle; Chainsaw provides the payload.

What the MDM script does

The reference scripts (enforcement/mdm/jamf/macos.sh, enforcement/mdm/intune/macos.sh, .../linux.sh, .../windows.ps1) are the same scripts chainsaw harden generates, with nothing baked in: you supply the server URL and org slug at run time. A hardening bundle (tutorial 54) gives you copies with both pre-filled. Each script:

  1. Installs the CLI with install.sh / install.ps1 when it is not already on the machine (checksum-verified over TLS; no signature check runs on the client), and drops a marker beside the binary so the uninstall script knows it may remove it.
  2. Runs chainsaw install-hook <manager> --scope user for all eleven managers (npm, yarn, bun, pip, cargo, maven, gradle, sbt, nuget, go, docker), as the signed-in user. --scope takes only user or project, so the config lands in that user’s home directory (~/.npmrc, ~/.cargo/config.toml, …).
    • macOS (Jamf, Intune): run as root; the script finds the console user and runs the CLI as them with sudo -u.
    • Linux (Intune): there is no console-user convention, so a root run needs CHAINSAW_TARGET_USER, or run the policy as the user.
    • Windows (Intune): set the script policy to Run this script using the logged on credentials. The script refuses to run as SYSTEM, because it would wire the SYSTEM profile and protect nobody.
    • Only that one account is wired. Other accounts on the machine, and users who sign in later, are not covered until the policy runs for them. A developer can delete the block from their own config; the next chainsaw doctor --strict run reports that as drift.
  3. Exits non-zero, without attesting, if install-hook failed for any manager, or if the server URL, client ID, client secret or org slug is missing.
  4. Runs chainsaw doctor --strict --attest once, which POSTs the endpoint’s posture to /api/attestations (a bundle-generated copy adds --bundle-id=<id>, which reports the bundle as applied). It does not schedule a recurring check; schedule one through your MDM if you want it.

On Linux build hosts running dockerd, the user-scope docker wiring (~/.docker/daemon.json) is not read by the daemon; add the mirror to /etc/docker/daemon.json by hand and restart dockerd.

Step 1: deploy the enrollment script

Intune (Linux/macOS/Windows): upload enforcement/mdm/intune/<platform>.{sh,ps1} as an Intune script policy and assign it to the developer-device group. Supply CHAINSAW_SERVER, CHAINSAW_CLIENT_ID, CHAINSAW_CLIENT_SECRET and CHAINSAW_ORG (your org slug, e.g. acme-corp) through the script environment. On Windows, set Run this script using the logged on credentials to Yes.

Jamf (macOS): upload enforcement/mdm/jamf/macos.sh as a policy script. Set $4/$5/$6/$7 to your Chainsaw server, client ID, client secret and org slug (Jamf’s standard parameter convention).

Kandji / Workspace ONE / other MDMs: the scripts are plain shell / PowerShell. Use your MDM’s custom-script payload; inject CHAINSAW_SERVER, CHAINSAW_CLIENT_ID, CHAINSAW_CLIENT_SECRET and CHAINSAW_ORG via its secret store.

Step 2: verify attestation arrives

Open Admin → Enforcement in the dashboard. The table populates as endpoints run their initial attestation — usually within minutes of the MDM policy running. Each row shows:

  • device_id (defaults to hostname/USER; add --device-id to the script’s doctor call if you need stable IDs across re-enrollment).
  • Per-ecosystem status (compliant / drifted / unconfigured / unsupported).
  • direct_registry_egress — whether the endpoint can still reach registry.npmjs.org / pypi.org / repo.maven.apache.org directly. This is the signal for Layer 3 below.

Step 3: alert on drift

/api/compliance/overview exposes drifted_count and recent_bypass_events_24h. Wire those into whatever you already use for alerting (Slack incoming webhook, PagerDuty, your existing monitoring stack) — Chainsaw does not ship an alert router.

Layer 3 — Network-mandatory

The only layer that prevents bypass rather than detecting it. It’s also the one that requires cooperation from your network team.

Chainsaw does not terminate, route, or firewall traffic. We publish the upstream registry host list; your SWG/SASE/firewall owns the block.

Step 1: generate the deny-list

Either download it from your running deployment:

curl -H "Authorization: Bearer $CHAINSAW_TOKEN" \
     https://chain305.com/chainproxy/api/compliance/egress-allowlist \
     | jq -r '.entries[].host'

Or generate it statically from the seed config (useful for air-gapped review):

go run ./tools/egress-allowlist-gen \
    -config configs/seed.yaml \
    -out enforcement/network

Both produce the same host list. The enforcement/network/ output contains vendor-formatted files:

  • zscaler-urls.txt — .domain.tld format, paste as a Zscaler URL Category.
  • netskope-urls.txt — bare hosts, paste as a Netskope URL List.
  • generic-deny.txt — bare hosts with comments, for any firewall.

Step 2: load it into your SASE / firewall

Zscaler Internet Access: Administration → URL Categories → add a custom category from the zscaler-urls.txt list → add a URL Filtering rule that blocks that category for all users except Chainsaw’s outbound IP.

Netskope SWG: Policies → Real-time Protection → URL List → import netskope-urls.txt. Create a block rule for everyone except Chainsaw’s egress identity.

Palo Alto NGFW: import generic-deny.txt as an External Dynamic List → deny in an outbound security policy with Chainsaw’s host as the only exception.

AWS / Cloud egress: add the hosts to your VPC egress security group deny rules; keep your Chainsaw NAT IP on the allow path.

Kubernetes NetworkPolicy: for build-pod subnets, a egress: rule that only permits to: Chainsaw achieves the same result.

Step 3: verify

From a developer workstation (not Chainsaw), curl -I https://registry.npmjs.org/ should time out or be explicitly refused. The dashboard’s direct_registry_egress column flips to blocked on every endpoint that re-attests after the network change lands.

Phased rollout

Flipping everything at once causes breakage. We recommend five weeks:

  1. Week 0 — Monitor. Install the CI Action with attest: true but without adding it to branch protection. Compliance data starts flowing. Nothing is blocked.
  2. Week 1 — CI required. Flip the GitHub Action to a required status check on main. Drifted PRs stop merging.
  3. Week 2 — Endpoint pilot. Roll the MDM script to a 5–10 person pilot cohort. Confirm the compliance dashboard populates.
  4. Week 3 — Fleet-wide MDM. Expand MDM coverage to all managed hosts. Direct-egress reachability on the dashboard is still “reachable” at this point — that’s expected until Week 4.
  5. Week 4 — Network mandatory. Load the firewall deny-list. The dashboard shows direct_registry_egress: blocked on every compliant endpoint. Chainsaw is now the only path.

Why the split matters

The friction point customers raise early is: “Why doesn’t Chainsaw just enforce this itself?”

The honest answer is that it can’t, and any proxy claiming otherwise is over-promising. A proxy knows only about requests that reach it. The moment a developer’s shell resolves registry.npmjs.org directly, the proxy is out of the loop regardless of how careful its policy engine is. Closing that gap requires either:

  • MDM-level configuration lock (endpoint can’t reconfigure without root, and the policy is re-enforced on every boot), or
  • Network-level egress control (the DNS / TCP connection fails before any HTTP conversation starts).

Both already exist as mature products. Rebuilding them inside Chainsaw would make the proxy worse at its actual job (supply-chain inspection and policy) while being worse at endpoint / network control than the vendors who specialize there.

So the story we tell customers is the story this tutorial walks through: use Chainsaw for what it’s good at — supply-chain decisions on every package request — and use your existing security stack for the layers it already enforces.

  • Integrate Chainsaw with CI/CD pipelines (tutorial 21) — the deeper CI integration guide beyond the enforcement action.
  • Manage Policy Precedence and Exceptions (tutorial 19) — once traffic is fully captured, tighten the policies that run against it.
  • Use Audit Logs to Track Consumption and Policy Changes (tutorial 12) — compliance_reported, compliance_drift, and bypass_attempted events show up in the same audit stream.