Guides

Tutorial: Securing an Express API

End-to-end walkthrough — initialize a project, configure SCAL-P policy, run guarded installs, catch violations in CI, and handle a fork PR.

This tutorial walks through a realistic scenario: you're maintaining a public Express API and want to protect it against supply chain attacks with SCAL-P.

You'll initialize the project, configure policy, catch a malicious dependency, set up CI enforcement, and handle a fork PR — all with concrete commands and outputs.

Prerequisites

  • Node.js 18+ and npm installed
  • SCAL-P installed (scalp version works)
  • A GitHub repository to push to (for the CI section)

1. Initialize the project

mkdir express-api && cd express-api
npm init -y
npm install express

Now bootstrap SCAL-P's policy and directory structure:

scalp init

This creates .scalp/policy.json with safe defaults — audit-only mode, no blocking.

2. Run your first scalp ci

scalp ci

This creates .scalp/ with policy.json, policy.schema.json, lockfile.json, audit.log, and cache/.

Inspect what was recorded:

scalp audit

Output:

audit ok

3. Add a suspicious dependency

A contributor opens a PR adding colors (or any package on the denylist) to the project:

npm install colors

Run scalp ci again with enforcement:

scalp ci

This time the evaluation finds violations and blocks the install:

ci failed: [{"PackageID":"colors@1.4.2","Reason":"denylist","Rule":"denylist"}]

The install step never ran — no tarballs were downloaded.

Without SCAL-P, npm install colors would silently succeed. The malicious node_modules/colors would be deployed to production. SCAL-P caught it before it reached disk.

4. Create a real policy

Now let's create a proper policy for the Express API. If you want a head start, use scalp init --strict — it writes a production-hardened policy with denylist, trust scoring, and block enforcement. Then customize from there.

To manually replace .scalp/policy.json with a custom policy:

{
  "$schema": ".scalp/policy.schema.json",
  "trust": {
    "mode": "audit-only",
    "min_score": 50,
    "require_hash": true
  },
  "packages": {
    "allow": [
      { "name": "express", "versions": "^4.18.0" },
      { "name": "body-parser", "versions": "^1.20.0" }
    ],
    "deny": ["colors", "faker", "node-ipc"]
  },
  "transitive": {
    "max_depth": 3,
    "strategy": "warn"
  },
  "ci": {
    "pr-context": "fork",
    "sarif": true,
    "sarif-file": ".scalp/sarif-report.sarif"
  }
}

Commit everything:

git add .scalp/policy.json .scalp/policy.schema.json .scalp/lockfile.json
git commit -m "chore: add SCAL-P policy and initial lockfile"

Committing .scalp/lockfile.json gives you a versioned integrity baseline. See Security Best Practices for the tradeoffs.

5. Review the audit log

SCAL-P logs every operation to .scalp/audit.log. Check what happened during the blocked install:

grep '"status":"blocked"' .scalp/audit.log
{
  "time": "2026-05-22T10:05:00Z",
  "action": "policy_evaluate",
  "status": "blocked",
  "violations": 2,
  "pkg": "colors@1.4.2",
  "reason": "denylist + no_hash"
}

Search for specific packages:

grep '"pkg":"body-parser"' .scalp/audit.log
{
  "time": "2026-05-22T10:00:00Z",
  "action": "hash_sync",
  "status": "ok",
  "pkg": "body-parser@1.20.3",
  "hash_match": true
}

Full compliance trail — no external service required.

6. Set up CI with GitHub Actions

Create .github/workflows/scalp-ci.yml:

name: SCAL-P

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

jobs:
  scalp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: scal-p-labs/scalp-action@0b29e6acb5c358a7c6b6d470c6ab8cb6e86fc217
        with:
          pr-context: ${{ github.event.pull_request.head.repo.fork == 'true' && 'fork' || 'internal' }}
          sarif: true

      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: .scalp/sarif-report.sarif

Push and open a PR. The action will:

  1. Run scalp ci with the appropriate PR context
  2. Annotate the PR with any violations
  3. Upload SARIF results to GitHub Security tab
  4. Fail the check if violations are found
alt [Has violations] [Clean] Push PR branch Trigger workflow scalp ci --pr-context fork Load policy + resolve Evaluate violations Exit 1 + SARIF Annotate PR + upload SARIF PR check ❌ Exit 0 + SARIF Upload SARIF PR check ✅ Developer GitHub scalp-action SCAL-P CLI

7. Simulate a fork PR attack

Fork PRs are the most common supply chain entry point. An attacker submits a PR from a fork with a modified .scalp/lockfile.json pointing to a tampered package.

Without SCAL-P, the modified lockfile could slip through review. With SCAL-P in fork context:

pr-context: fork

The action:

  1. Ignores the incoming lockfile — re-computes hashes from scratch
  2. Blocks install scripts — no postinstall malware can execute
  3. Verifies every package — any hash mismatch produces a violation

The PR check fails with annotations pointing to the tampered package — before any code reaches main.

8. Verify the lockfile baseline

After merging a clean PR, run scalp audit to compare the current lockfile against the committed baseline:

scalp audit --ci

This checks every hash in .scalp/lockfile.json against node_modules. If all packages match:

audit ok

If any package was tampered with (malicious commit, compromised CI runner, manual edit), the audit returns violations:

Recap

StepWhat you didSCAL-P command
1Initialize project + install depsnpm install, scalp init
2First guarded runscalp ci
3Catch malicious dependencyscalp ci (blocked)
4Configure policyEdit .scalp/policy.json
5Review audit loggrep '"status":"blocked"' .scalp/audit.log
6Set up CI.github/workflows/scalp-ci.yml
7Fork PR simulationpr-context: fork
8Verify integrity baselinescalp audit --ci

On this page