Skip to content

Commit 860b6a1

Browse files
authored
Upgrade supply chain demo to scoped capabilities with active defense (#76)
* feat: upgrade supply chain demo to scoped capabilities with active defense Replace custom kernel/rootfs build with pyhl for the Hyperlight sandbox. The guest now has real capabilities (--mount for workspace, --net-allow for example.com) and performs legitimate work (read input, fetch HTTP, write output) while every malicious action is still actively blocked by policy enforcement: loopback denied, link-local denied, scoped mount, no env forwarding. Add cloud metadata probing phase (IMDS) to stealer. Update graphic and README to reflect active defense model. Signed-off-by: danbugs <danilochiarlone@gmail.com> * fix: address Copilot review feedback on supply chain demo - Fix pyhl install path in README (host crate, not tools/) - Fix run-minimal description (mount only, not total isolation) - Fix README wording (mounted directory, not workspace) - Fix graphic: correct mount flag, correct workspace paths - Add metadata to attack success condition - Use structured errno checks instead of string matching - Fix docstring dash Signed-off-by: danbugs <danilochiarlone@gmail.com> --------- Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent 0009274 commit 860b6a1

10 files changed

Lines changed: 454 additions & 191 deletions

File tree

demos/supply-chain/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
reqeusts/__pycache__/
2+
hl-unikraft/guest/
3+
hl-unikraft/workspace/

demos/supply-chain/README.md

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
A safe, educational demonstration of a supply chain attack modeled on
44
[Mini Shai-Hulud](https://thehackernews.com/2026/05/mini-shai-hulud-worm-compromises.html)
5-
(TeamPCP, May 2026), showing how Hyperlight micro-VM isolation contains it.
5+
(TeamPCP, May 2026), showing how Hyperlight micro-VM isolation contains it
6+
**even when the guest has realistic capabilities**.
67

78
## Background
89

@@ -11,37 +12,54 @@ TanStack, Mistral AI, Guardrails AI, and AntV — affecting 518M+ cumulative
1112
downloads. The attack used typosquatted/hijacked packages to:
1213

1314
1. **Steal credentials** — SSH keys, AWS creds, `.env` files, 80+ env vars
14-
2. **Exfiltrate data** — triple-redundant C2 (custom domain, Session Protocol, GitHub dead drops)
15-
3. **Install persistence** — Claude Code `SessionStart` hooks, VS Code `runOn`, LaunchAgents
16-
4. **Self-propagate** — used stolen npm tokens to poison other packages
15+
2. **Probe cloud metadata** — AWS IMDS, Azure IMDS, GCP metadata (169.254.169.254)
16+
3. **Exfiltrate data** — triple-redundant C2 (custom domain, Session Protocol, GitHub dead drops)
17+
4. **Install persistence** — Claude Code `SessionStart` hooks, VS Code `runOn`, LaunchAgents
18+
5. **Self-propagate** — used stolen npm tokens to poison other packages
1719

1820
## What this demo does
1921

2022
A fake typosquatted package (`reqeusts` instead of `requests`) simulates the
2123
attack payload. A victim application imports it, triggering the malicious code.
22-
23-
**Bare metal** — the attack succeeds: secrets are stolen, exfiltrated, persistence installed.
24-
25-
**Hyperlight sandbox** — every malicious action is blocked by the micro-VM's
26-
default-deny security model:
27-
- **Filesystem is isolated** — the guest runs on its own ramfs (from the CPIO
28-
initrd). Host directories are only visible if explicitly mounted with
29-
`--mount`, and even then access is scoped to that directory with path-escape
30-
prevention. The attacker's `~/.ssh/id_rsa`, `~/.aws/credentials`, etc. simply
31-
don't exist inside the VM.
32-
- **Environment variables are compile-time only** — the guest kernel only has
33-
`PATH` and `LD_LIBRARY_PATH` (set in `kraft.yaml`). There is no `--env` flag;
34-
the host's environment is never forwarded. `AWS_ACCESS_KEY_ID`, `GITHUB_TOKEN`,
35-
etc. are all absent.
36-
- **Networking is opt-in** — without `--net`, the guest has zero network access
37-
(`socket()` returns "Function not implemented"). Even with `--net`, outbound
38-
connections can be restricted to specific hosts via `--net-allow`.
39-
- **Persistence is impossible** — the guest's ramfs is destroyed when the VM
40-
exits. There is no way to write to the host's `~/.claude/settings.json` or
41-
`~/.bashrc` unless the host explicitly mounts those paths.
24+
The victim app also does legitimate work: reads input from the workspace,
25+
fetches data from `example.com`, and writes output.
26+
27+
**Bare metal** — the attack succeeds: secrets stolen, exfiltrated, persistence
28+
installed. Legitimate work also succeeds.
29+
30+
**Hyperlight sandbox (scoped)** — the guest has real capabilities (`--mount`
31+
for directory access, `--net-allow example.com` for network). Legitimate work
32+
succeeds, but every malicious action is blocked by Hyperlight's security model:
33+
34+
- **Credential theft → BLOCKED**`~/.ssh/id_rsa`, `~/.aws/credentials`, etc.
35+
are outside the mounted directory. The guest has filesystem access, but only
36+
to the scoped directory (no HOME, no `/etc/passwd`).
37+
- **Environment variables → NOT SET** — host environment is never forwarded to
38+
the guest. `AWS_ACCESS_KEY_ID`, `GITHUB_TOKEN`, etc. are absent.
39+
- **C2 exfiltration → BLOCKED** — loopback addresses (127.0.0.0/8) are
40+
**always denied** regardless of network policy. The C2 server at
41+
`127.0.0.1:8080` is unreachable.
42+
- **Cloud metadata → BLOCKED** — link-local addresses (169.254.0.0/16) are
43+
**always denied**. AWS IMDS, Azure IMDS, and GCP metadata at 169.254.169.254
44+
are all blocked — a hardcoded safety net, not a user-configurable policy.
45+
- **Persistence → BLOCKED** — host dotfiles (`~/.claude/settings.json`,
46+
`~/.bashrc`) are not mounted. The guest's ramfs is destroyed on exit.
47+
48+
This is **active defense**, not just absence of resources. An empty Docker
49+
container blocks the same attacks, but only because it has nothing to attack.
50+
Hyperlight blocks them because its network policy engine and filesystem sandbox
51+
enforce scoped access even when capabilities are granted.
4252

4353
## Running the demo
4454

55+
### Prerequisites
56+
57+
Install `pyhl` (the Hyperlight Python runtime):
58+
59+
```bash
60+
cargo install --path host --bin pyhl
61+
```
62+
4563
### Bare metal (attack succeeds)
4664

4765
The script creates a temporary HOME with planted fake secrets, starts a C2
@@ -57,12 +75,14 @@ cd demos/supply-chain
5775
```bash
5876
cd demos/supply-chain/hl-unikraft
5977

60-
# One-time setup
61-
just build # Build or pull Unikraft kernel
62-
just rootfs # Build rootfs with Python + malicious package
78+
# One-time setup — installs kernel + rootfs, warms Python snapshot
79+
just setup
80+
81+
# Scoped sandbox: mount + network, attack still contained
82+
just run
6383

64-
# Run
65-
just run # Execute inside the sandbox
84+
# Minimal sandbox: mount only, no network
85+
just run-minimal
6686
```
6787

6888
### C2 server (standalone)
@@ -80,15 +100,13 @@ supply-chain/
80100
├── reqeusts/ # Typosquatted package
81101
│ ├── __init__.py # Triggers payload on import
82102
│ ├── api.py # Fake requests-like API surface
83-
│ └── stealer.py # Attack payload (6 phases)
84-
├── victim_app.py # Legitimate-looking app that imports reqeusts
103+
│ └── stealer.py # Attack payload (7 phases)
104+
├── victim_app.py # App that imports reqeusts + does legitimate work
85105
├── c2_server.py # Fake C2 server (receives exfiltrated data)
86106
├── bare-metal/
87107
│ └── run.sh # Bare-metal demo (plants secrets, runs attack)
88108
└── hl-unikraft/
89-
├── Dockerfile # Rootfs with Python + malicious package
90-
├── kraft.yaml # Unikraft kernel config
91-
└── Justfile # Build + run commands
109+
└── Justfile # pyhl-based setup + run commands
92110
```
93111

94112
## Safety

demos/supply-chain/bare-metal/run.sh

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
#
44
# This script:
55
# 1. Creates a temporary HOME with planted fake secrets
6-
# 2. Starts the C2 server in the background
7-
# 3. Runs the victim app (stealer triggers on import)
8-
# 4. Shows the modified persistence files
9-
# 5. Cleans everything up
6+
# 2. Creates a workspace with input data (legitimate workload)
7+
# 3. Starts the C2 server in the background
8+
# 4. Runs the victim app (stealer triggers on import)
9+
# 5. Shows the modified persistence files and workspace output
10+
# 6. Cleans everything up
1011
set -e
1112

1213
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
@@ -53,6 +54,11 @@ EOF
5354

5455
touch "$DEMO_HOME/.bashrc"
5556

57+
# ── Create workspace with input data ─────────────────────────────────
58+
WORKSPACE="$DEMO_HOME/workspace"
59+
mkdir -p "$WORKSPACE"
60+
echo "Hello from the host filesystem" > "$WORKSPACE/input.txt"
61+
5662
# ── Start C2 server ─────────────────────────────────────────────────
5763
python3 "$DEMO_DIR/c2_server.py" &
5864
C2_PID=$!
@@ -66,6 +72,7 @@ echo "========================================"
6672
echo ""
6773

6874
HOME="$DEMO_HOME" \
75+
WORKSPACE="$WORKSPACE" \
6976
SUPPLY_CHAIN_DEMO=1 \
7077
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE \
7178
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY \
@@ -83,3 +90,9 @@ echo ""
8390
echo "── Post-attack: ~/.bashrc (last 3 lines) ────────────────────"
8491
tail -3 "$DEMO_HOME/.bashrc"
8592
echo ""
93+
94+
# ── Show workspace output ────────────────────────────────────────────
95+
echo ""
96+
echo "── Workspace output ─────────────────────────────────────────"
97+
cat "$WORKSPACE/output.txt" 2>/dev/null || echo "(no output written)"
98+
echo ""

0 commit comments

Comments
 (0)