Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sentinel

Python License Status Tests

Stop supply chain attacks before they reach your pipeline.

Sentinel audits npm package updates using three independent security validators and a Byzantine fault-tolerant consensus engine — the same class of algorithm that keeps distributed systems honest even when individual nodes lie.


The Problem

The event-stream attack (2018) compromised 2 million projects. A trusted package was transferred to a malicious maintainer, who published a patch update containing an encrypted cryptocurrency wallet stealer. It passed every existing security check because it looked like a routine update.

The attack vector is almost always the same:

  1. Attacker gains control of a trusted package
  2. Publishes a patch or minor update — low scrutiny
  3. The new version introduces network calls, shell execution, or dynamic eval
  4. The payload runs silently during npm install or at runtime

You can't trust any single signal. A package can have a clean reputation, pass a malware scanner, and still phone home your environment variables.


The Approach: Byzantine Consensus

Sentinel runs three independent validators on every update. No single validator is trusted absolutely. The final verdict requires a supermajority — at least 2 out of 3 validators must agree.

This is Byzantine fault tolerance applied to dependency security: even if one validator is fooled or bypassed, two honest validators can still reach the correct verdict.

Package Update (old → new)
         │
         ├──────────────────────────────────────────────────────┐
         │                         │                            │
         ▼                         ▼                            ▼
 ┌───────────────┐       ┌──────────────────┐        ┌──────────────────┐
 │   Validator 1 │       │   Validator 2    │        │   Validator 3    │
 │   ENTROPY     │       │   BEHAVIORAL     │        │   METADATA       │
 │               │       │                 │        │                  │
 │ Shannon bits  │       │ Dangerous API   │        │ Install scripts  │
 │ per byte of   │       │ fingerprinting  │        │ Maintainer count │
 │ source code   │       │                 │        │ Deprecation      │
 │               │       │ exec, eval,     │        │ Native binaries  │
 │ Obfuscation   │       │ net, fs,        │        │                  │
 │ = entropy     │       │ process.env     │        │                  │
 │   spike       │       │                 │        │                  │
 └───────┬───────┘       └────────┬────────┘        └────────┬─────────┘
         │                        │                          │
         └────────────────────────┼──────────────────────────┘
                                  │
                                  ▼
                    ┌─────────────────────────┐
                    │  BYZANTINE CONSENSUS    │
                    │                         │
                    │  2+ REJECT  → REJECT    │
                    │  1  REJECT  → WARN      │
                    │  2+ WARN    → WARN      │
                    │  3  APPROVE → APPROVE   │
                    └─────────────────────────┘

Validator 1 — Entropy Analysis

Computes Shannon entropy of source code before and after the update. A patch bump that suddenly introduces high-entropy content (obfuscated strings, encoded payloads, compressed blobs) is flagged. Thresholds scale by version bump type: a patch is held to stricter entropy stability than a major rewrite.

Validator 2 — Behavioral Fingerprint

Scans for new dangerous API categories introduced by the update:

Category Severity Pattern
exec_access critical child_process, exec, spawn
eval_usage critical eval(), Function(), vm.runIn
net_access high https, fetch, XMLHttpRequest
install_scripts high preinstall, postinstall
fs_access medium fs.readFileSync, fs.promises
env_access medium process.env, process.argv
crypto_access low crypto.*

The key insight: Sentinel flags APIs that are new to this update, not just present. A package that has always used fs is not suspicious. A package that suddenly gains child_process in a patch update is.

Validator 3 — Metadata Integrity

Inspects the npm registry metadata for the new version: lifecycle scripts that run on install, deprecation flags, native compilation requirements (node-gyp), and single-maintainer packages with elevated risk.


Quick Start

Requires Python 3.10+. No external dependencies — uses only the standard library.

# Clone
git clone https://github.com/amadoalvarez/sentinel.git
cd sentinel

# Run directly
python3 sentinel.py lodash 4.17.20 4.17.21

# Or install as a CLI tool
pip install -e .
sentinel lodash 4.17.20 4.17.21

# Verbose mode shows entropy numbers and escalated capabilities
sentinel express 4.18.0 4.19.0 -v

Example Output

Clean Update — APPROVED

$ sentinel lodash 4.17.20 4.17.21

============================================================
  SENTINEL — Analyzing lodash
  4.17.20 → 4.17.21
============================================================

[*] Fetching package metadata...
[*] Downloading 4.17.20...
[*] Downloading 4.17.21...
[*] Extracting code files...
    Old: 12 files, New: 12 files

[*] Running validators...

  [+] Entropy:    APPROVE — Entropy delta 0.0104 within patch threshold
  [+] Behavioral: APPROVE — No new dangerous API categories introduced
  [+] Metadata:   APPROVE — Metadata checks passed

============================================================
  [+] CONSENSUS: APPROVED (supermajority consensus)
============================================================

Exit code: 0 — safe to proceed.


Attack Detected — REJECTED

The following simulates the event-stream pattern: an innocent formatting package where the new version adds a hidden file that collects environment variables, runs shell commands, encrypts the output, and exfiltrates it over HTTPS.

  === FULL ATTACK SIMULATION ===
  Entropy:    REJECT — Entropy delta 0.3277 exceeds patch threshold 0.3
  Behavioral: REJECT — CRITICAL: New exec_access capability detected
    NEW: crypto_access (low)
    NEW: exec_access (critical)
    NEW: env_access (medium)
    NEW: net_access (high)
  Metadata:   APPROVE

  CONSENSUS: REJECT
  === ATTACK CORRECTLY DETECTED ===

Exit code: 1 — block the update.


What the consensus table looks like

Validators          →  Consensus
─────────────────────────────────
3x APPROVE          →  APPROVE
2x APPROVE, 1x WARN →  APPROVE
2x WARN, 1x APPROVE →  WARN
1x REJECT, 2x OK    →  WARN
2x REJECT, 1x OK    →  REJECT
3x REJECT           →  REJECT

A single compromised or noisy validator cannot produce a false REJECT or slip a malicious package through as APPROVE. Two validators must agree.


Test Suite

python3 test_sentinel.py

Full output from the test suite:

Sentinel Test Suite
========================================

--- Entropy Validator ---
  [OK] Clean patch: APPROVE — delta=0.0104
  [OK] Suspicious patch: WARN — delta=0.1461
       entropy: 4.1325 → 3.9864

--- Behavioral Validator ---
  [OK] Clean behavioral: APPROVE
  [OK] New network detected: WARN — HIGH: New net_access capability detected
       Escalated: ['net_access']
  [OK] Exec injection detected: REJECT — CRITICAL: New exec_access capability detected
  [OK] Eval injection detected: REJECT — CRITICAL: New eval_usage capability detected

--- Byzantine Consensus ---
  [OK] Consensus: 3 approve → APPROVE
  [OK] Consensus: 1 reject + 2 approve → WARN
  [OK] Consensus: 2 reject + 1 approve → REJECT
  [OK] Consensus: 2 warn + 1 approve → WARN

--- Full Attack Simulation ---

  === FULL ATTACK SIMULATION ===
  Entropy:    REJECT — Entropy delta 0.3277 exceeds patch threshold 0.3
  Behavioral: REJECT — CRITICAL: New exec_access capability detected
    NEW: crypto_access (low)
    NEW: exec_access (critical)
    NEW: env_access (medium)
    NEW: net_access (high)
  Metadata:   APPROVE

  CONSENSUS: REJECT
  === ATTACK CORRECTLY DETECTED ===

========================================
ALL TESTS PASSED

Use in CI/CD

GitHub Actions

Add this step to your workflow to audit npm dependency updates before they land:

name: Dependency Security Audit

on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'

jobs:
  sentinel:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Clone Sentinel
        run: git clone https://github.com/amadoalvarez/sentinel.git /tmp/sentinel

      - name: Detect changed packages
        id: changed
        run: |
          # Extract changed package name and versions from package-lock.json diff
          # Adapt this to your lockfile format (npm/yarn/pnpm)
          git diff HEAD~1 HEAD -- package-lock.json | grep '"version"' | head -20

      - name: Run Sentinel audit
        run: |
          # Example: audit a specific package update detected in the PR
          python3 /tmp/sentinel/sentinel.py ${{ env.PACKAGE_NAME }} \
            ${{ env.OLD_VERSION }} \
            ${{ env.NEW_VERSION }}
        # Exit code 1 = REJECT or WARN → blocks merge

Pre-commit Hook

#!/bin/bash
# .git/hooks/pre-commit
# Audit any npm packages bumped in this commit

SENTINEL_PATH="/path/to/sentinel/sentinel.py"

# Check if package.json changed
if git diff --cached --name-only | grep -q "package.json"; then
    echo "[Sentinel] package.json changed — run sentinel audit manually:"
    echo "  python3 $SENTINEL_PATH <package> <old> <new>"
fi

Makefile Target

.PHONY: audit-dep
audit-dep:
	@python3 path/to/sentinel.py $(PKG) $(OLD) $(NEW)
	# Usage: make audit-dep PKG=lodash OLD=4.17.20 NEW=4.17.21

Background: Why Byzantine Consensus?

Byzantine fault tolerance is a property of distributed systems: the ability to reach correct consensus even when some participants are faulty, slow, or actively malicious. The name comes from the Byzantine Generals Problem (Lamport, Shostak, Pease — 1982).

Supply chain security has the same structure. Each validator is an independent observer with different blind spots:

  • The entropy validator can be fooled by well-formatted malicious code
  • The behavioral scanner can be fooled by obfuscated API names
  • The metadata validator only sees registry data, not code

No single validator is reliable in isolation. Requiring supermajority agreement raises the cost of a successful bypass: an attacker must simultaneously defeat multiple independent detection mechanisms.

Further reading:


Roadmap

  • Automatic lockfile diffing (detect which packages changed)
  • PyPI and Go module support
  • Maintainer change detection (compares across versions)
  • SHA256 integrity verification against npm registry dist hash
  • JSON output mode for programmatic consumption
  • Parallel validator execution

Contributing

Issues and pull requests are welcome. Run the test suite before submitting:

python3 test_sentinel.py

All tests must pass. New validators should include at least two tests: one clean case and one that demonstrates the attack pattern it catches.


License

MIT — see LICENSE.


Created by Amado Alvarez Sueiras.

About

Multi-validator dependency security with Byzantine consensus

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages