24 KiB
description, tags, last_updated, priority
| description | tags | last_updated | priority | |||||
|---|---|---|---|---|---|---|---|---|
| Supply chain security assessment and comprehensive defense guide for March 2026 incidents (Trivy/TeamPCP, CanisterWorm, GlassWorm). |
|
2026-03-23 | 10 |
Supply Chain Security Assessment (March 2026)
Scan Results
| Check | Status | Detail |
|---|---|---|
| CanisterWorm persistence | CLEAN | No pgmon service, no ~/.local/share/pgmon/ |
| TeamPCP C2 connection | CLEAN | No active connection to 45.148.10.212 |
| TeamPCP exfil repos | CLEAN | No tpcp-docs-* repos found |
| ICP C2 connection | CLEAN | No active connection to ICP endpoint |
| DNS resolution (C2) | NOTES | scan.aquasecurtiy.org still resolves to 45.148.10.212 (not yet sinkholed) |
| DNS resolution (ICP) | NOTES | ICP endpoint resolves via Dfinity boundary nodes (expected - decentralized) |
Note on DNS Resolution
C2 domains resolving is expected - they have not been fully sinkholed. The critical check is active connections, which are absent.
Threat Landscape Summary
Trivy/TeamPCP (March 19-22)
- Tag poisoning on
aquasecurity/trivy-action(75 tags) - Memory-based credential theft via
/proc/<pid>/mem - Exfiltration via typo-squatted domain + victim's own GitHub repos
- Safe version:
aquasec/trivy:0.69.3@sha256:0d52a20836526e387157a55f9368d4001c95b77c3855a9010049448106a6b5e0
CanisterWorm (March 20)
- 66+ malicious npm packages via stolen tokens
- Self-propagating: harvests npm tokens, republishes victim's packages
- Persistence: systemd user service (
pgmon) - C2: ICP blockchain (resistant to takedown)
- Preceded by "Shai Hulud" worm (2025) - now a recurring pattern
- Related 2026 campaigns: Shai-Hulud 2.0 (preinstall phase, destructive fallback), SANDWORM_MODE (MCP injection, multi-channel exfil)
GlassWorm (January-March 2026)
- Invisible Unicode chars (U+FE00-FE0F, U+E0100-E01EF) encode payloads
- 151+ GitHub repos, 72+ VS Code/Open VSX extensions, 400+ total affected packages
- Commits blend in with realistic surrounding changes (AI-generated)
- Cannot be detected by visual code review or standard linting
- Decoder pattern: maps codepoints to byte values, passes to
eval(Buffer.from(...))
Defense Technologies (Deep Research Findings)
Layered Detection Architecture
Layer 1 (Preventive): Workflow Policy + SHA Pinning + OIDC
Layer 2 (Preventive): npm Hardening + Token Scoping + Script Sandboxing
Layer 3 (Detective): Runtime Monitoring (eBPF: Falco/Tetragon/Tracee)
Layer 4 (Detective): Unicode Scanning + Pre-commit Hooks
Layer 5 (Detective): SBOM + Vuln Scanning (syft/grype) + Nuclei
Layer 6 (Detective): DNS/Network Monitoring + Threat Intel Feeds
Layer 7 (Response): Credential Rotation + Incident Playbook
1. GitHub Actions Hardening (Trivy/TeamPCP Defense)
SHA256 Digest Pinning
All Actions MUST be pinned to full commit SHA, never tags or branches:
# WRONG - vulnerable to tag poisoning
- uses: aquasecurity/trivy-action@0.69.3
# CORRECT - immutable
- uses: aquasecurity/trivy-action@a12a3943b4151b629d348435e26e7cb35bc0db20
OIDC Token Hardening
- Enforce short-lived tokens (5-15 min expiration)
- Restrict token audience to specific cloud providers and repositories
- Use conditional access policies tied to repository, branch, and environment
- Disable all long-lived PATs; enforce OIDC for machine accounts
Environment Protection Rules
environments:
production:
protection_rules:
- type: required_reviewers
reviewers: 2
- type: wait_timer
minutes: 30
- type: restrict_to_deployment_keys
Reusable Workflow Security
- Store in private repos with explicit access controls
- Require signed commits for workflow updates
- Pin calls to specific commits, not branches
- Audit log all workflow invocations
Action Allowlist Enforcement
# Branch protection or custom CI check
approved_actions:
- actions/checkout@a1b2c3d4e5f6
- actions/setup-node@f1b2c3d4e5f6
- org/internal-action@abcdef123456
enforcement: reject_if_not_in_list
Tag Poisoning Detection
Verify action repos for suspicious signals:
- Tag has been force-pushed (multiple SHAs for same tag)
- Repo created < 90 days ago
- Zero stars and zero forks
- Unknown or compromised maintainers
Tools: Snyk GitHub Actions Security (auto-scans workflows), SLSA provenance verification.
2. Memory Forensics (TeamPCP /proc/pid/mem Defense)
Attack Vector
/proc/[pid]/mem access allows reading running process memory to extract credentials, tokens, and secrets. TeamPCP targets CI/CD runners holding temporary credentials during workflow execution.
Detection Techniques
Volatility 3 - Post-incident memory analysis:
vol3 -f memory.dump linux.psaux # Process list with env vars
vol3 -f memory.dump linux.bash # Command history
# Scan for credential patterns: ghp_*, ghu_*, AWS_*, npm_*
eBPF Real-Time Monitoring - Detect access at syscall level:
// Trace openat syscall for /proc/*/mem access
TRACEPOINT_PROBE(syscalls, sys_enter_openat) {
char path[256];
bpf_probe_read_user_str(&path, sizeof(path), (void *)ctx->args[1]);
if (strstr(&path, "/proc") && strstr(&path, "mem")) {
events.perf_submit(ctx, &event, sizeof(event));
}
return 0;
}
memdump - Capture CI/CD runner memory post-execution, scan for credential remnants before process termination.
Defensive Countermeasures
# Container runtime - deny ptrace capability
securityContext:
capabilities:
drop: [SYS_PTRACE]
readOnlyRootFilesystem: true
- Deploy runners in VM isolation (not container-only)
- Enable hardware memory encryption (Intel TME / AMD SEV-SNP)
- Rotate all credentials immediately post-workflow execution
3. eBPF Runtime Security (Cross-Vector Detection)
Tool Comparison
| Tool | Strength | Best For | Enforcement |
|---|---|---|---|
| Falco | Rules-based alerting | Container environments, broad adoption | Alert only (SIGSTOP可选) |
| Tetragon | Kubernetes-native, policy enforcement | K8s clusters, tracing policies | Kill/block via policy |
| Tracee | Deep syscall tracing, eBPF-only | Bare-metal, CI/CD runners | Alert + filter |
Falco Rules for Supply Chain Attacks
- rule: Unauthorized Process Memory Access
condition: >
open and container and
fd.name glob "/proc/*/mem" or syscall == ptrace
output: "Unauthorized memory access (user=%user.name cmd=%proc.name file=%fd.name)"
priority: CRITICAL
tags: [supply-chain, memory_access]
- rule: Suspicious Env Variable Exfiltration
condition: >
syscall == execve and container and
proc.name in (curl, wget, nc, python) and
(proc.args contains "GITHUB_TOKEN" or proc.args contains "ghp_" or proc.args contains "npm_")
priority: CRITICAL
tags: [supply-chain, credential_exfil]
- rule: Suspicious Systemd Service Creation
condition: >
open_write and fd.name glob "*/.config/systemd/user/*" and
not proc.name in (systemctl, systemd, ansible)
priority: HIGH
tags: [supply-chain, persistence]
Tetragon TracingPolicy (Enforcement)
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: block-proc-mem-access
spec:
kprobes:
- call: "__fput"
selectors:
- matchArgs:
- index: 0
operator: "Equal"
values: ["/proc/*/mem"]
action: SIGKILL
Tracee for CI/CD Runner Monitoring
tracee \
--events=open,openat,ptrace,execve \
--filter='container and (event.args.pathname contains "/proc" and event.args.pathname contains "mem")' \
--output json \
--output-file events.json
Workstation Note: eBPF tools require kernel support. For this Arch Linux workstation, bpf package provides headers. Falco can run in agent mode for local monitoring.
4. npm Supply Chain Defense (CanisterWorm Defense)
Lifecycle Script Mitigation
# Global disable (already applied)
npm config set ignore-scripts true
# Per-project override when needed
npm ci --ignore-scripts=false --only=dev # explicit opt-in
# Node.js policy-based sandboxing (experimental)
node --experimental-policy=policy.json install
Limitation: Shai-Hulud 2.0 uses preinstall phase, bypassing postinstall-focused defenses. --ignore-scripts blocks both, but some legitimate packages require scripts. Use allowlists:
// policy.json - allow specific packages only
{
"resources": {
"node_modules/.package-lock.json": {
"integrity": true
}
},
"dependencies": {
"package:lodash": true,
"package:express": true
}
}
Token Security
| Practice | Implementation |
|---|---|
| Granular Access Tokens (GAT) | Scope to specific repos + read-only or automation permissions |
| Token expiration | Set max 90 days; automate rotation |
Never store in .npmrc |
Use environment variables or OIDC |
| Audit tokens quarterly | npm token list, revoke unused |
| MFA enforcement | Required for package publish (prevents bypass token abuse) |
npm OIDC (Long-Term Fix)
npm OIDC integration is "very hard to compromise" and would "almost completely erase supply-chain attack surface" (Chainguard, 2026). Enables tokenless authentication for package operations.
Systemd Service Monitoring (CanisterWorm pgmon)
# Detect unknown user services
systemctl --user list-units --type=service --all
# Audit service files for anomalies
ls -la ~/.config/systemd/user/
# Check for services with suspicious exec paths
grep -r "ExecStart" ~/.config/systemd/user/ | grep -v "/usr/bin\|/usr/local/bin"
# Monitor service file creation (eBPF Falco rule above)
ICP Blockchain C2 Detection
- Decentralized C2 via ICP canisters is resistant to traditional takedown
- Monitor for outbound connections to Dfinity boundary nodes from unexpected processes
- Network baseline: CI/CD runners should never connect to ICP endpoints
- Block ICP boundary node IPs at firewall level for non-ICP workloads
Worm Containment
- socket.dev: Detects malicious packages within 2-5 minutes of publication via differential analysis
- Monitor all npm packages under org control for unauthorized version publishes
- Alert on rapid-succession version publishes (1-3 day windows = suspicious)
- Revoke and rotate tokens immediately if compromise suspected
5. Unicode Attack Defense (GlassWorm Defense)
Understanding the Decoder Pattern
GlassWorm uses this exact decoder signature:
const s = v => [...v].map(w => (
w = w.codePointAt(0),
w >= 0xFE00 && w <= 0xFE0F ? w - 0xFE00 :
w >= 0xE0100 && w <= 0xE01EF ? w - 0xE0100 + 16 : null
)).filter(n => n !== null);
eval(Buffer.from(s(``)).toString('utf-8'));
Detection must target: eval() + Buffer.from() + codepoint arithmetic + apparent empty/whitespace strings.
Detection Tools
# Variation Selectors (VS1-VS16)
grep -rP "[\x{FE00}-\x{FE0F}]" . --exclude-dir=node_modules --exclude-dir=.git
# Ideographic Variation Selectors
grep -rP "[\x{E0100}-\x{E01EF}]" . --exclude-dir=node_modules --exclude-dir=.git
# Combined with decoder pattern
grep -rP "(codePointAt|fromCharCode|Buffer\.from)" . \
--include="*.js" --include="*.ts" --exclude-dir=node_modules
Python scanner for byte-level detection:
#!/usr/bin/env python3
"""Scan files for GlassWorm Unicode injection patterns."""
import sys, pathlib
VS_RANGES = [(0xFE00, 0xFE0F), (0xE0100, 0xE01EF)]
def scan_file(path):
content = path.read_bytes()
hits = []
for i, b in enumerate(content):
# Check for multi-byte UTF-8 sequences in VS ranges
if b >= 0xEF: # Start of 3-byte UTF-8 (VS range)
if i + 2 < len(content):
cp = int.from_bytes(content[i:i+3], 'big')
for start, end in VS_RANGES:
if start <= cp <= end:
hits.append((i, hex(cp)))
return hits
for p in pathlib.Path(sys.argv[1]).rglob('*'):
if p.suffix in ('.js', '.ts', '.mjs', '.cjs', '.py', '.json'):
hits = scan_file(p)
if hits:
print(f"{p}: {len(hits)} invisible chars found")
for offset, cp in hits[:5]:
print(f" offset {offset}: {cp}")
Git Pre-commit Hook
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: no-invisible-unicode
name: Block invisible Unicode (GlassWorm defense)
entry: python3 scripts/unicode-scanner.py
language: script
types: [text]
IDE Visibility Configuration
VS Code - Show invisible characters:
{
"editor.renderWhitespace": "all",
"editor.unicodeHighlight.ambiguousCharacters": true,
"editor.unicodeHighlight.invisibleCharacters": true
}
Vim/Neovim:
set list
set listchars+=trail:~,nbsp:␣
" Conceal off for PUA/VS ranges
syntax match GlassWorm "[\uFE00-\uFE0F\uE0100-\uE01EF]" cchar=█
VS Code Extension Security
- Verify extension publisher identity before install
- Audit extension update history for suspicious version jumps
- Known malicious:
quartz.quartz-markdown-editorv0.3.0 (March 12, 2026) - 72+ malicious Open VSX extensions in coordinated campaign
- Prefer extensions with signed commits and provenance
- Review extension permissions (workspace access, terminal access = high risk)
Semgrep Rules
rules:
- id: glassworm-decoder-pattern
patterns:
- pattern-either:
- pattern: |
eval(Buffer.from($FN(...)).toString(...))
- pattern: |
$VAR = $V => [...$V].map($W => ($W = $W.codePointAt(0), ...))
message: "Potential GlassWorm Unicode decoder pattern detected"
severity: ERROR
languages: [javascript, typescript]
6. SLSA Provenance & Sigstore Ecosystem
SLSA Framework (v1.2 Current)
| Level | Requirement | This Workstation |
|---|---|---|
| L1 | Documented build process | Partial (Ansible) |
| L2 | Hosted build platform | N/A (local dev) |
| L3 | Hardened build + provenance | N/A |
| L4 | Hermetic + reproducible | N/A |
Applicability: SLSA primarily targets CI/CD builds. For this local workstation, focus on L1 (documented process) and verifying SLSA provenance of consumed packages/artifacts.
Sigstore Components
| Component | Purpose | Tool |
|---|---|---|
| cosign | Container image signing/verification | pacman -S cosign |
| fulcio | Certificate authority (issues certs based on OIDC) | Service |
| rekor | Transparency log (immutable record) | Service |
| sigstore-js | Node.js client library | pnpm add @sigstore/sign |
# Verify a container image was signed
cosign verify --certificate-identity=action@github.com \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
aquasec/trivy:0.69.3
# Sign an artifact
cosign sign --key env://COSIGN_PRIVATE_KEY my-image:latest
in-toto Attestations
in-toto provides a framework for verifying the integrity of the software supply chain. Key concepts:
- Layout: Defines expected steps, authorized functionaries, and inspection rules
- Attestation: Metadata about a supply chain step (linked to SLSA provenance)
- Verification: Client-side check that all steps match the layout
Integration with GitHub Actions via slsa-github-generator:
- uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
attestation-name: my-attestation
7. SBOM Management
Toolchain
# Generate SBOM (CycloneDX format - preferred for security)
syft packages dir:./src -o cyclonedx-json > sbom.json
# Alternative: SPDX format (preferred for license compliance)
syft packages dir:./src -o spdx-json > sbom.spdx.json
# Scan SBOM for vulnerabilities
grype sbom:sbom.json --fail-on high
Format Choice
| Format | Best For | Tooling |
|---|---|---|
| CycloneDX | Security scanning, vulnerability matching | grype, Dependency-Track |
| SPDX | License compliance, legal | SPDX tools, FOSSA |
Dependency-Track (SBOM Repository)
Centralized SBOM storage and continuous monitoring:
- Upload SBOMs via API on each build
- Continuous vulnerability scanning (NVD, OSV, GitHub Advisories)
- Policy evaluation (fail builds on critical vulns)
- This workstation: run via Docker for local SBOM management
8. Nuclei Template-Based Scanning
# Install
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Scan for known supply chain C2 indicators
nuclei -t supply-chain/ -u scan.aquasecurtiy.org
# Scan local git repos for TeamPCP exfil patterns
nuclei -t custom/teampcp-exfil.yaml -target https://github.com/org/repo
# Custom template for C2 domain detection
cat <<'EOF' > nuclei-teampcp.yaml
id: teampcp-c2-detection
info:
name: TeamPCP C2 Domain
severity: critical
dns:
- name: "{{Hostname}}"
matchers:
- type: word
words:
- "45.148.10.212"
EOF
9. ML-Based & Behavioral Detection (Emerging)
ML Detection Approaches for Supply Chain Attacks
| Technique | Application | Tools/Platforms |
|---|---|---|
| Package behavior modeling | Detect anomalous postinstall behavior vs. historical baseline | socket.dev (differential analysis), EndorLabs |
| Dependency graph anomaly detection | Flag sudden dependency additions, version spikes, or transitive changes | Snyk Advisor, Socket.dev risk scores |
| Code embedding similarity | Detect AI-generated commits that mimic project style (GlassWorm evasion) | CodeQL ML models, GitHub Advanced Security |
| Network behavior profiling | Detect C2 beacon patterns, unusual outbound from build runners | Elastic ML, CrowdStrike Falcon |
| File system entropy analysis | Detect encoded payloads (Unicode steganography, encrypted strings) | YARA rules + ML classifiers |
Behavioral Detection Patterns
Anomalous Package Behavior (CanisterWorm/Shai-Hulud):
- Package executes network calls during install (baseline: most packages don't)
- Package writes files outside
node_modulesduring install - Package reads environment variables or
.npmrcduring install - Package spawns child processes during install
- Detection: compare current package behavior against historical behavior profile
Anomalous Commit Patterns (GlassWorm):
- Commit modifies only whitespace/Unicode but diff shows zero visual changes
- Commit timestamp clusters with other suspicious commits across repos (coordinated campaign signal)
- Commit author style differs from project norm (AI-generated code detection)
- Detection:
git diff --word-diffto catch invisible char changes; commit metadata clustering
Anomalous Build Behavior (TeamPCP):
- Build process accesses
/proc/*/mem(never legitimate in CI) - Build process resolves non-whitelisted DNS (tag poisoning C2 indicator)
- Build process creates outbound connections to non-cdn endpoints
- Detection: Falco rules (above), DNS query logging, network egress allowlists
Implementation for This Workstation
# Socket.dev CLI - local package risk scoring
npm install -g socket-cli
socket view express # Risk profile before installing
socket scan ./src # Scan dependencies for behavioral anomalies
# YARA rule for GlassWorm encoded payloads (high entropy strings)
# Install: pacman -S yara
cat <<'EOF' > rules/glassworm.yar
rule GlassWorm_Unicode_Payload {
strings:
$decoder = /codePointAt.*0xFE00|codePointAt.*0xE0100/ ascii
$eval_buf = /eval\(Buffer\.from/ ascii
$vs_range = /\xEF\xB8[\x80-\x8F]/ // UTF-8 for U+FE00-FE0F
condition:
2 of them
}
EOF
yara -r rules/glassworm.yar ./src
10. DNS & Network Monitoring
Passive DNS Integration
- Subscribe to Farsight Security or RiskIQ passive DNS feeds
- Query historical DNS data for infrastructure domains during IR
- Detect CNAME chains pointing to attacker-controlled servers
Threat Intelligence Feeds
| Feed | Coverage | Integration |
|---|---|---|
| URLhaus | Known C2 domains | API, daily dump |
| Abuse.ch SSLBL | Malicious SSL certificates | IP blocklists |
| VirusTotal | Domain reputation | API |
| Custom | Typo variants of internal domains | Local script |
DNS Monitoring for CI/CD
- Log all DNS queries from runners to SIEM
- Block non-whitelisted external domains from runners
- Alert on edit distance < 2 from known infrastructure domains
Hardening Actions
Immediate (Already Applied)
npm config set ignore-scripts true
Recommended for This Workstation
# 1. Install supply chain security tools
pacman -S syft grype cosign # SBOM, vuln scan, artifact verify
uv tool install semgrep # SAST with Unicode rules
# 2. Set up Unicode scanning as pre-commit hook
# (see GlassWorm section above for hook config)
# 3. Configure IDE visibility
# (see GlassWorm section for VS Code/Vim settings)
# 4. Audit systemd user services monthly
systemctl --user list-units --type=service --all
# 5. Network-level C2 blocking (optional)
# Block 45.148.10.212 and ICP boundary nodes if not needed
CI/CD Hardening Checklist
- Pin all Actions to SHA256 (never tags)
- Use OIDC over long-lived secrets
- Block outbound to non-essential domains from runners
- Run sensitive builds in ephemeral environments
- Generate and verify SLSA provenance for built artifacts
- Sign container images with cosign
- Generate SBOM on every build (syft + grype)
- Run Nuclei templates against dependencies
- Enable Unicode scanning in CI pipeline
Detection Patterns
File System IOCs
# CanisterWorm
find ~ -name "pgmon*" -o -name "uv-proxy" -o -name "pglog" 2>/dev/null
# Unknown systemd user services
ls ~/.config/systemd/user/ | grep -v "default.target"
Unicode IOCs (GlassWorm)
# VS1-VS16 range
grep -rP "[\x{FE00}-\x{FE0F}]" . --exclude-dir=node_modules --exclude-dir=.git
# Ideographic Variation Selectors
grep -rP "[\x{E0100}-\x{E01EF}]" . --exclude-dir=node_modules --exclude-dir=.git
# Decoder function pattern
grep -rP "codePointAt.*0xFE00|codePointAt.*0xE0100" . --include="*.js" --include="*.ts"
Network IOCs
# TeamPCP C2
ss -ntu | grep -E "45.148.10.212"
getent hosts scan.aquasecurtiy.org
# ICP C2 (unexpected connections to Dfinity)
ss -ntu | grep -E "icp0\.io|dfinity"
Workstation-Specific Implementation Priority
| Priority | Action | Effort | Impact |
|---|---|---|---|
| P0 | Pre-commit hook for Unicode scanning | 30min | Blocks GlassWorm |
| P0 | VS Code invisible char visibility | 5min | Visual detection |
| P1 | Monthly systemd service audit | 10min | Detects CanisterWorm |
| P1 | npm config set ignore-scripts true verify |
1min | Blocks script-based attacks |
| P2 | syft/grype SBOM scanning | 1hr setup | Continuous vuln monitoring |
| P2 | Nuclei C2 template scanning | 30min | Network IOC validation |
| P3 | Falco local agent setup | 2hr | Runtime memory/process monitoring |
| P3 | cosign artifact verification workflow | 1hr | Build integrity |
References
- Upwind Security: Trivy/TeamPCP analysis
- EndorLabs: CanisterWorm + SANDWORM_MODE technical analysis
- Aikido Security: GlassWorm Unicode campaign tracking
- Unit42 (Palo Alto): Shai-Hulud 2.0 npm supply chain attack
- Chainguard: npm supply chain hardening and OIDC future
- CISA: GitHub Actions Security Hardening Guidance
- Cilium/Tetragon: eBPF Security Enforcement documentation
- Falco: Runtime security rules and best practices
- SLSA v1.2: slsa.dev/spec/v1.2
- Sigstore: sigstore.dev
- NIST SSDF v1.1: Secure Software Development Framework