muaddib-scanner 2.2.22 → 2.2.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/.gitattributes +0 -18
- package/datasets/adversarial/README.md +0 -23
- package/datasets/adversarial/ai-agent-weaponization/index.js +0 -5
- package/datasets/adversarial/ai-agent-weaponization/package.json +0 -9
- package/datasets/adversarial/ai-agent-weaponization/setup.js +0 -83
- package/datasets/adversarial/ai-config-injection/.cursorrules +0 -36
- package/datasets/adversarial/ai-config-injection/index.js +0 -16
- package/datasets/adversarial/ai-config-injection/package.json +0 -8
- package/datasets/adversarial/browser-api-hook/index.js +0 -66
- package/datasets/adversarial/browser-api-hook/package.json +0 -6
- package/datasets/adversarial/bun-runtime-evasion/bun_environment.js +0 -23
- package/datasets/adversarial/bun-runtime-evasion/package.json +0 -9
- package/datasets/adversarial/bun-runtime-evasion/setup.js +0 -10
- package/datasets/adversarial/ci-trigger-exfil/index.js +0 -17
- package/datasets/adversarial/ci-trigger-exfil/package.json +0 -9
- package/datasets/adversarial/conditional-chain/index.js +0 -14
- package/datasets/adversarial/conditional-chain/package.json +0 -9
- package/datasets/adversarial/crypto-wallet-harvest/index.js +0 -44
- package/datasets/adversarial/crypto-wallet-harvest/package.json +0 -6
- package/datasets/adversarial/dead-mans-switch/index.js +0 -35
- package/datasets/adversarial/dead-mans-switch/package.json +0 -9
- package/datasets/adversarial/delayed-exfil/index.js +0 -6
- package/datasets/adversarial/delayed-exfil/package.json +0 -6
- package/datasets/adversarial/detached-background/launcher.js +0 -11
- package/datasets/adversarial/detached-background/package.json +0 -9
- package/datasets/adversarial/detached-background/worker.js +0 -26
- package/datasets/adversarial/discord-webhook-exfil/index.js +0 -95
- package/datasets/adversarial/discord-webhook-exfil/package.json +0 -9
- package/datasets/adversarial/dns-chunk-exfil/index.js +0 -10
- package/datasets/adversarial/dns-chunk-exfil/package.json +0 -6
- package/datasets/adversarial/docker-aware/index.js +0 -10
- package/datasets/adversarial/docker-aware/package.json +0 -6
- package/datasets/adversarial/double-base64-exfil/index.js +0 -11
- package/datasets/adversarial/double-base64-exfil/package.json +0 -9
- package/datasets/adversarial/dynamic-import/index.js +0 -21
- package/datasets/adversarial/dynamic-import/package.json +0 -9
- package/datasets/adversarial/dynamic-require/index.js +0 -3
- package/datasets/adversarial/dynamic-require/package.json +0 -9
- package/datasets/adversarial/fake-captcha-fingerprint/index.js +0 -64
- package/datasets/adversarial/fake-captcha-fingerprint/package.json +0 -9
- package/datasets/adversarial/gh-cli-token-steal/index.js +0 -31
- package/datasets/adversarial/gh-cli-token-steal/package.json +0 -6
- package/datasets/adversarial/github-exfil/index.js +0 -33
- package/datasets/adversarial/github-exfil/package.json +0 -9
- package/datasets/adversarial/iife-exfil/index.js +0 -17
- package/datasets/adversarial/iife-exfil/package.json +0 -9
- package/datasets/adversarial/indirect-eval-bypass/index.js +0 -27
- package/datasets/adversarial/indirect-eval-bypass/package.json +0 -5
- package/datasets/adversarial/mjs-extension-bypass/package.json +0 -6
- package/datasets/adversarial/mjs-extension-bypass/stealer.mjs +0 -39
- package/datasets/adversarial/muaddib-ignore-bypass/index.js +0 -47
- package/datasets/adversarial/muaddib-ignore-bypass/package.json +0 -5
- package/datasets/adversarial/nested-payload/index.js +0 -3
- package/datasets/adversarial/nested-payload/package.json +0 -9
- package/datasets/adversarial/nested-payload/utils/helper.js +0 -6
- package/datasets/adversarial/nested-payload/utils/lib/format.js +0 -23
- package/datasets/adversarial/postinstall-download/package.json +0 -8
- package/datasets/adversarial/preinstall-background-fork/bootstrap.js +0 -16
- package/datasets/adversarial/preinstall-background-fork/index.js +0 -2
- package/datasets/adversarial/preinstall-background-fork/package.json +0 -9
- package/datasets/adversarial/preinstall-background-fork/stealer.js +0 -67
- package/datasets/adversarial/preinstall-exec/package.json +0 -9
- package/datasets/adversarial/preinstall-exec/steal.js +0 -24
- package/datasets/adversarial/proxy-env-intercept/index.js +0 -33
- package/datasets/adversarial/proxy-env-intercept/package.json +0 -9
- package/datasets/adversarial/pyinstaller-dropper/index.js +0 -25
- package/datasets/adversarial/pyinstaller-dropper/package.json +0 -9
- package/datasets/adversarial/rdd-zero-deps/index.js +0 -32
- package/datasets/adversarial/rdd-zero-deps/init.js +0 -15
- package/datasets/adversarial/rdd-zero-deps/package.json +0 -11
- package/datasets/adversarial/remote-dynamic-dependency/index.js +0 -15
- package/datasets/adversarial/remote-dynamic-dependency/package.json +0 -7
- package/datasets/adversarial/self-hosted-runner-backdoor/index.js +0 -28
- package/datasets/adversarial/self-hosted-runner-backdoor/package.json +0 -9
- package/datasets/adversarial/silent-error-swallow/index.js +0 -32
- package/datasets/adversarial/silent-error-swallow/package.json +0 -6
- package/datasets/adversarial/staged-fetch/index.js +0 -9
- package/datasets/adversarial/staged-fetch/package.json +0 -6
- package/datasets/adversarial/string-concat-obfuscation/index.js +0 -6
- package/datasets/adversarial/string-concat-obfuscation/package.json +0 -6
- package/datasets/adversarial/template-literal-obfuscation/index.js +0 -4
- package/datasets/adversarial/template-literal-obfuscation/package.json +0 -9
- package/datasets/adversarial/triple-base64-github-push/index.js +0 -38
- package/datasets/adversarial/triple-base64-github-push/package.json +0 -9
- package/datasets/adversarial/websocket-exfil/index.js +0 -34
- package/datasets/adversarial/websocket-exfil/package.json +0 -9
- package/datasets/benign/README.md +0 -20
- package/datasets/benign/packages-npm.txt +0 -597
- package/datasets/benign/packages-pypi.txt +0 -164
- package/datasets/ground-truth/README.md +0 -54
- package/datasets/ground-truth/known-malware.json +0 -622
- package/datasets/holdout-v4/atob-eval/index.js +0 -2
- package/datasets/holdout-v4/atob-eval/package.json +0 -5
- package/datasets/holdout-v4/base64-require/index.js +0 -3
- package/datasets/holdout-v4/base64-require/package.json +0 -5
- package/datasets/holdout-v4/charcode-fetch/index.js +0 -3
- package/datasets/holdout-v4/charcode-fetch/package.json +0 -5
- package/datasets/holdout-v4/charcode-spread-homedir/index.js +0 -5
- package/datasets/holdout-v4/charcode-spread-homedir/package.json +0 -5
- package/datasets/holdout-v4/concat-env-steal/index.js +0 -4
- package/datasets/holdout-v4/concat-env-steal/package.json +0 -5
- package/datasets/holdout-v4/double-decode-exfil/index.js +0 -4
- package/datasets/holdout-v4/double-decode-exfil/package.json +0 -5
- package/datasets/holdout-v4/hex-array-exec/index.js +0 -3
- package/datasets/holdout-v4/hex-array-exec/package.json +0 -5
- package/datasets/holdout-v4/mixed-obfuscation-stealer/index.js +0 -10
- package/datasets/holdout-v4/mixed-obfuscation-stealer/package.json +0 -5
- package/datasets/holdout-v4/nested-base64-concat/index.js +0 -4
- package/datasets/holdout-v4/nested-base64-concat/package.json +0 -5
- package/datasets/holdout-v4/template-literal-hide/index.js +0 -3
- package/datasets/holdout-v4/template-literal-hide/package.json +0 -5
- package/datasets/holdout-v5/callback-exfil/main.js +0 -8
- package/datasets/holdout-v5/callback-exfil/package.json +0 -5
- package/datasets/holdout-v5/callback-exfil/reader.js +0 -10
- package/datasets/holdout-v5/class-method-exfil/collector.js +0 -10
- package/datasets/holdout-v5/class-method-exfil/main.js +0 -7
- package/datasets/holdout-v5/class-method-exfil/package.json +0 -5
- package/datasets/holdout-v5/conditional-split/detector.js +0 -2
- package/datasets/holdout-v5/conditional-split/package.json +0 -5
- package/datasets/holdout-v5/conditional-split/stealer.js +0 -16
- package/datasets/holdout-v5/event-emitter-flow/listener.js +0 -12
- package/datasets/holdout-v5/event-emitter-flow/package.json +0 -5
- package/datasets/holdout-v5/event-emitter-flow/scanner.js +0 -11
- package/datasets/holdout-v5/mixed-inline-split/index.js +0 -6
- package/datasets/holdout-v5/mixed-inline-split/package.json +0 -5
- package/datasets/holdout-v5/mixed-inline-split/reader.js +0 -3
- package/datasets/holdout-v5/mixed-inline-split/sender.js +0 -6
- package/datasets/holdout-v5/named-export-steal/main.js +0 -6
- package/datasets/holdout-v5/named-export-steal/package.json +0 -5
- package/datasets/holdout-v5/named-export-steal/utils.js +0 -1
- package/datasets/holdout-v5/reexport-chain/a.js +0 -2
- package/datasets/holdout-v5/reexport-chain/b.js +0 -1
- package/datasets/holdout-v5/reexport-chain/c.js +0 -11
- package/datasets/holdout-v5/reexport-chain/package.json +0 -5
- package/datasets/holdout-v5/split-env-exfil/env.js +0 -2
- package/datasets/holdout-v5/split-env-exfil/exfil.js +0 -5
- package/datasets/holdout-v5/split-env-exfil/package.json +0 -5
- package/datasets/holdout-v5/split-npmrc-steal/index.js +0 -2
- package/datasets/holdout-v5/split-npmrc-steal/package.json +0 -5
- package/datasets/holdout-v5/split-npmrc-steal/reader.js +0 -8
- package/datasets/holdout-v5/split-npmrc-steal/sender.js +0 -17
- package/datasets/holdout-v5/three-hop-chain/package.json +0 -5
- package/datasets/holdout-v5/three-hop-chain/reader.js +0 -8
- package/datasets/holdout-v5/three-hop-chain/sender.js +0 -11
- package/datasets/holdout-v5/three-hop-chain/transform.js +0 -3
- package/docker/Dockerfile +0 -20
- package/docker/sandbox-runner.sh +0 -313
- package/testssampleslink-deppackage.json +0 -1
- package/tmp-test-pack.js +0 -66
package/docker/sandbox-runner.sh
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
PACKAGE="$1"
|
|
3
|
-
MODE="${2:-permissive}"
|
|
4
|
-
|
|
5
|
-
if [ -z "$PACKAGE" ]; then
|
|
6
|
-
echo "Usage: sandbox-runner.sh <package-name> [permissive|strict]" >&2
|
|
7
|
-
exit 1
|
|
8
|
-
fi
|
|
9
|
-
|
|
10
|
-
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
11
|
-
START_MS=$(date +%s%3N 2>/dev/null || echo 0)
|
|
12
|
-
|
|
13
|
-
# ══════════════════════════════════════════════════════════════
|
|
14
|
-
# PHASE 1: Root-privileged setup (iptables, tcpdump, filesystem snapshot)
|
|
15
|
-
# Runs as root to access raw sockets and kernel netfilter.
|
|
16
|
-
# ══════════════════════════════════════════════════════════════
|
|
17
|
-
|
|
18
|
-
# ── 0. Strict mode: iptables rules (requires root + NET_ADMIN) ──
|
|
19
|
-
if [ "$MODE" = "strict" ]; then
|
|
20
|
-
echo "[SANDBOX] STRICT MODE — blocking non-essential network..." >&2
|
|
21
|
-
# Allow loopback
|
|
22
|
-
iptables -A OUTPUT -o lo -j ACCEPT
|
|
23
|
-
# Allow DNS (UDP 53)
|
|
24
|
-
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
|
25
|
-
# Allow registry.npmjs.org (resolve + allow)
|
|
26
|
-
REGISTRY_IPS=$(nslookup registry.npmjs.org 2>/dev/null | grep -E '^Address:' | tail -n+2 | awk '{print $2}')
|
|
27
|
-
for ip in $REGISTRY_IPS; do
|
|
28
|
-
iptables -A OUTPUT -d "$ip" -p tcp --dport 443 -j ACCEPT
|
|
29
|
-
done
|
|
30
|
-
# Log + reject everything else
|
|
31
|
-
iptables -A OUTPUT -j LOG --log-prefix "BLOCKED: "
|
|
32
|
-
iptables -A OUTPUT -j REJECT
|
|
33
|
-
echo "[SANDBOX] iptables rules applied." >&2
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# ── 1. Filesystem snapshot BEFORE install ──
|
|
37
|
-
echo "[SANDBOX] Snapshot filesystem before install..." >&2
|
|
38
|
-
find / -type f 2>/dev/null | sort > /tmp/fs-before.txt
|
|
39
|
-
|
|
40
|
-
# ── 2. tcpdump: separate captures for DNS, HTTP, TLS (requires root + NET_RAW) ──
|
|
41
|
-
echo "[SANDBOX] Starting network capture..." >&2
|
|
42
|
-
tcpdump -i any -nn 'port 53' -l > /tmp/dns.log 2>/dev/null &
|
|
43
|
-
DNS_PID=$!
|
|
44
|
-
tcpdump -i any -nn -A 'tcp port 80' -l > /tmp/http.log 2>/dev/null &
|
|
45
|
-
HTTP_PID=$!
|
|
46
|
-
tcpdump -i any -nn 'tcp port 443' -l > /tmp/tls.log 2>/dev/null &
|
|
47
|
-
TLS_PID=$!
|
|
48
|
-
tcpdump -i any -nn 'not port 53 and not port 80 and not port 443' -l > /tmp/other.log 2>/dev/null &
|
|
49
|
-
OTHER_PID=$!
|
|
50
|
-
sleep 1
|
|
51
|
-
|
|
52
|
-
# ══════════════════════════════════════════════════════════════
|
|
53
|
-
# PHASE 2: Unprivileged install (su sandboxuser)
|
|
54
|
-
# npm install runs as sandboxuser with strace for syscall tracing.
|
|
55
|
-
# strace can trace child processes without SYS_PTRACE (parent→child).
|
|
56
|
-
# ══════════════════════════════════════════════════════════════
|
|
57
|
-
|
|
58
|
-
# ── 2b. CI environment simulation ──
|
|
59
|
-
# Simulate CI to trigger CI-aware malware that checks for these env vars
|
|
60
|
-
echo "[SANDBOX] Simulating CI environment..." >&2
|
|
61
|
-
export CI=true
|
|
62
|
-
export GITHUB_ACTIONS=true
|
|
63
|
-
export GITLAB_CI=true
|
|
64
|
-
export TRAVIS=true
|
|
65
|
-
export CIRCLECI=true
|
|
66
|
-
export JENKINS_URL=http://localhost:8080
|
|
67
|
-
|
|
68
|
-
# ── 2c. Canary tokens (honeypots) ──
|
|
69
|
-
# Use Docker-injected dynamic tokens if available, otherwise static fallbacks.
|
|
70
|
-
# If exfiltrated via network/DNS/files, sandbox.js detects the theft.
|
|
71
|
-
export GITHUB_TOKEN="${GITHUB_TOKEN:-MUADDIB_CANARY_GITHUB_f8k3t0k3n}"
|
|
72
|
-
export NPM_TOKEN="${NPM_TOKEN:-MUADDIB_CANARY_NPM_s3cr3tt0k3n}"
|
|
73
|
-
export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-MUADDIB_CANARY_AKIAIOSFODNN7EXAMPLE}"
|
|
74
|
-
export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-MUADDIB_CANARY_wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY}"
|
|
75
|
-
export SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-https://hooks.slack.com/MUADDIB_CANARY_SLACK}"
|
|
76
|
-
export DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-https://discord.com/api/webhooks/MUADDIB_CANARY_DISCORD}"
|
|
77
|
-
|
|
78
|
-
# ── 3. npm install with strace — as sandboxuser ──
|
|
79
|
-
echo "[SANDBOX] Installing $PACKAGE as sandboxuser..." >&2
|
|
80
|
-
cd /sandbox/install
|
|
81
|
-
# Ensure sandboxuser owns the install directory
|
|
82
|
-
chown sandboxuser:sandboxuser /sandbox/install
|
|
83
|
-
# Run npm install as sandboxuser via su, wrapped in strace for syscall tracing
|
|
84
|
-
su sandboxuser -s /bin/sh -c "
|
|
85
|
-
strace -f -e trace=network,process,open,openat,connect,execve,sendto,recvfrom \
|
|
86
|
-
-o /tmp/strace.log \
|
|
87
|
-
npm install \"$PACKAGE\" --ignore-scripts=false > /tmp/install.log 2>&1
|
|
88
|
-
"
|
|
89
|
-
EXIT_CODE=$?
|
|
90
|
-
|
|
91
|
-
# ══════════════════════════════════════════════════════════════
|
|
92
|
-
# PHASE 3: Post-install analysis (back as root for full access)
|
|
93
|
-
# ══════════════════════════════════════════════════════════════
|
|
94
|
-
|
|
95
|
-
# ── 4. Filesystem snapshot AFTER install ──
|
|
96
|
-
echo "[SANDBOX] Snapshot filesystem after install..." >&2
|
|
97
|
-
find / -type f 2>/dev/null | sort > /tmp/fs-after.txt
|
|
98
|
-
|
|
99
|
-
# Stop tcpdump
|
|
100
|
-
kill "$DNS_PID" "$HTTP_PID" "$TLS_PID" "$OTHER_PID" 2>/dev/null
|
|
101
|
-
wait "$DNS_PID" "$HTTP_PID" "$TLS_PID" "$OTHER_PID" 2>/dev/null
|
|
102
|
-
|
|
103
|
-
END_MS=$(date +%s%3N 2>/dev/null || echo 0)
|
|
104
|
-
DURATION_MS=$((END_MS - START_MS))
|
|
105
|
-
[ "$DURATION_MS" -lt 0 ] 2>/dev/null && DURATION_MS=0
|
|
106
|
-
|
|
107
|
-
# ── 5. Filesystem diff ──
|
|
108
|
-
echo "[SANDBOX] Analyzing filesystem changes..." >&2
|
|
109
|
-
comm -13 /tmp/fs-before.txt /tmp/fs-after.txt | grep -v '^/sandbox/install/' | grep -v '^/tmp/' > /tmp/fs-created.txt
|
|
110
|
-
comm -23 /tmp/fs-before.txt /tmp/fs-after.txt | grep -v '^/sandbox/install/' > /tmp/fs-deleted.txt
|
|
111
|
-
|
|
112
|
-
# ── 6. Parse strace ──
|
|
113
|
-
echo "[SANDBOX] Parsing strace..." >&2
|
|
114
|
-
|
|
115
|
-
SENSITIVE='\.npmrc|\.ssh/|\.aws/|\.env|/etc/passwd|/etc/shadow|\.gitconfig|\.bash_history'
|
|
116
|
-
|
|
117
|
-
# 6a. Sensitive file access (read)
|
|
118
|
-
grep -E 'openat\(' /tmp/strace.log 2>/dev/null | \
|
|
119
|
-
grep -E "$SENSITIVE" | \
|
|
120
|
-
grep 'O_RDONLY' | \
|
|
121
|
-
sed 's/.*openat([^,]*, "\([^"]*\)".*/\1/' | \
|
|
122
|
-
sort -u > /tmp/sensitive-read.txt
|
|
123
|
-
|
|
124
|
-
# 6b. Sensitive file access (write)
|
|
125
|
-
grep -E 'openat\(' /tmp/strace.log 2>/dev/null | \
|
|
126
|
-
grep -E "$SENSITIVE" | \
|
|
127
|
-
grep -E 'O_WRONLY|O_RDWR|O_CREAT' | \
|
|
128
|
-
sed 's/.*openat([^,]*, "\([^"]*\)".*/\1/' | \
|
|
129
|
-
sort -u > /tmp/sensitive-written.txt
|
|
130
|
-
|
|
131
|
-
# 6c. Suspicious execve
|
|
132
|
-
grep 'execve(' /tmp/strace.log 2>/dev/null | \
|
|
133
|
-
grep '= 0' | \
|
|
134
|
-
grep -vE 'execve\("[^"]*/(node|npm|npx|sh|git)"' | \
|
|
135
|
-
sed -n 's/.*\[pid \([0-9]*\)\].*execve("\([^"]*\)".*/\1\t\2/p' > /tmp/suspicious-cmds.txt
|
|
136
|
-
|
|
137
|
-
grep 'execve(' /tmp/strace.log 2>/dev/null | \
|
|
138
|
-
grep '= 0' | \
|
|
139
|
-
grep -vE 'execve\("[^"]*/(node|npm|npx|sh|git)"' | \
|
|
140
|
-
grep -v '\[pid' | \
|
|
141
|
-
sed -n 's/.*execve("\([^"]*\)".*/0\t\1/p' >> /tmp/suspicious-cmds.txt
|
|
142
|
-
|
|
143
|
-
# 6d. Outgoing connections (AF_INET, successful)
|
|
144
|
-
grep 'connect(' /tmp/strace.log 2>/dev/null | \
|
|
145
|
-
grep 'AF_INET' | grep -v 'AF_INET6' | \
|
|
146
|
-
grep '= 0' | \
|
|
147
|
-
sed -n 's/.*sin_port=htons(\([0-9]*\)).*sin_addr=inet_addr("\([^"]*\)").*/\2\t\1/p' | \
|
|
148
|
-
grep -v ' 65535$' | \
|
|
149
|
-
grep -v '^127\.' | \
|
|
150
|
-
sort -u > /tmp/connections.txt
|
|
151
|
-
|
|
152
|
-
# ── 7. Parse DNS resolutions (query → answer pairs) ──
|
|
153
|
-
echo "[SANDBOX] Parsing DNS resolutions..." >&2
|
|
154
|
-
|
|
155
|
-
# Extract DNS queries and answers from dns.log
|
|
156
|
-
# Format: "domain query_type answer_ip"
|
|
157
|
-
awk '
|
|
158
|
-
/A\?/ { domain=$0; sub(/.*A\? /,"",domain); sub(/ .*$/,"",domain); gsub(/\.$/,"",domain); pending=domain }
|
|
159
|
-
/A [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/ && pending {
|
|
160
|
-
ip=$0; sub(/.*A /,"",ip); sub(/ .*$/,"",ip);
|
|
161
|
-
print pending "\t" ip;
|
|
162
|
-
pending=""
|
|
163
|
-
}
|
|
164
|
-
' /tmp/dns.log 2>/dev/null | sort -u > /tmp/dns-resolutions.txt
|
|
165
|
-
|
|
166
|
-
# Plain DNS query list (for backward compat)
|
|
167
|
-
grep -oE '(A|AAAA)\? [^ ]+' /tmp/dns.log 2>/dev/null | \
|
|
168
|
-
awk '{print $2}' | sed 's/\.$//' | sort -u > /tmp/dns-queries.txt
|
|
169
|
-
|
|
170
|
-
# ── 8. Parse HTTP requests ──
|
|
171
|
-
echo "[SANDBOX] Parsing HTTP requests..." >&2
|
|
172
|
-
|
|
173
|
-
# Extract HTTP method, host, path from http.log
|
|
174
|
-
awk '
|
|
175
|
-
/^[0-9].*length [0-9]/ { next }
|
|
176
|
-
/(GET|POST|PUT|DELETE|PATCH) \// {
|
|
177
|
-
method=$0; sub(/.*((GET|POST|PUT|DELETE|PATCH)) /,"",method);
|
|
178
|
-
path=method; sub(/ .*$/,"",path);
|
|
179
|
-
meth=$0; match(meth,/(GET|POST|PUT|DELETE|PATCH)/); meth=substr(meth,RSTART,RLENGTH);
|
|
180
|
-
pending_method=meth; pending_path=path; pending_host=""; pending_body=""
|
|
181
|
-
}
|
|
182
|
-
/^Host:/ && pending_method { pending_host=$2; gsub(/\r/,"",pending_host) }
|
|
183
|
-
/^\r?$/ && pending_method && pending_host {
|
|
184
|
-
print pending_method "\t" pending_host "\t" pending_path;
|
|
185
|
-
pending_method=""; pending_host=""; pending_path=""
|
|
186
|
-
}
|
|
187
|
-
' /tmp/http.log 2>/dev/null > /tmp/http-requests.txt
|
|
188
|
-
|
|
189
|
-
# Extract HTTP body snippets for exfiltration detection
|
|
190
|
-
awk '
|
|
191
|
-
/^(POST|PUT|PATCH) / { capturing=1; body="" }
|
|
192
|
-
capturing && /^[^\t]/ && !/^(GET|POST|PUT|DELETE|PATCH|HTTP|Host:|Content|Accept|User-Agent|Connection)/ {
|
|
193
|
-
if (length(body) < 500) body = body $0
|
|
194
|
-
}
|
|
195
|
-
/^\r?$/ && capturing { if (length(body)>0) print body; capturing=0; body="" }
|
|
196
|
-
' /tmp/http.log 2>/dev/null > /tmp/http-bodies.txt
|
|
197
|
-
|
|
198
|
-
# ── 9. Parse TLS connections (SNI via IP correlation) ──
|
|
199
|
-
echo "[SANDBOX] Parsing TLS connections..." >&2
|
|
200
|
-
|
|
201
|
-
# Map IPs to domains from DNS resolutions, then correlate with TLS IPs
|
|
202
|
-
awk '{print $1}' /tmp/connections.txt 2>/dev/null | sort -u > /tmp/tls-ips.txt
|
|
203
|
-
|
|
204
|
-
# Build domain→IP map, then find TLS connections
|
|
205
|
-
awk -F'\t' '
|
|
206
|
-
NR==FNR { ip_domain[$2]=$1; next }
|
|
207
|
-
{ if ($1 in ip_domain) print ip_domain[$1] "\t" $1 "\t" $2 }
|
|
208
|
-
' /tmp/dns-resolutions.txt /tmp/connections.txt 2>/dev/null | \
|
|
209
|
-
grep ' 443$' | sort -u > /tmp/tls-connections.txt
|
|
210
|
-
|
|
211
|
-
# ── 10. Blocked connections (strict mode — requires root for dmesg) ──
|
|
212
|
-
if [ "$MODE" = "strict" ]; then
|
|
213
|
-
dmesg 2>/dev/null | grep 'BLOCKED:' | \
|
|
214
|
-
sed -n 's/.*DST=\([^ ]*\).*DPT=\([^ ]*\).*/\1\t\2/p' | \
|
|
215
|
-
sort -u > /tmp/blocked.txt
|
|
216
|
-
else
|
|
217
|
-
touch /tmp/blocked.txt
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
# ── 11. Build JSON with jq ──
|
|
221
|
-
echo "[SANDBOX] Building report..." >&2
|
|
222
|
-
|
|
223
|
-
# Ensure all temp files exist
|
|
224
|
-
touch /tmp/fs-created.txt /tmp/fs-deleted.txt /tmp/dns-queries.txt \
|
|
225
|
-
/tmp/sensitive-read.txt /tmp/sensitive-written.txt \
|
|
226
|
-
/tmp/connections.txt /tmp/suspicious-cmds.txt /tmp/install.log \
|
|
227
|
-
/tmp/dns-resolutions.txt /tmp/http-requests.txt /tmp/http-bodies.txt \
|
|
228
|
-
/tmp/tls-connections.txt /tmp/blocked.txt
|
|
229
|
-
|
|
230
|
-
INSTALL_OUTPUT=$(head -c 5000 /tmp/install.log)
|
|
231
|
-
|
|
232
|
-
FS_CREATED=$(jq -R -s 'split("\n") | map(select(length > 0))' < /tmp/fs-created.txt)
|
|
233
|
-
FS_DELETED=$(jq -R -s 'split("\n") | map(select(length > 0))' < /tmp/fs-deleted.txt)
|
|
234
|
-
DNS=$(jq -R -s 'split("\n") | map(select(length > 0))' < /tmp/dns-queries.txt)
|
|
235
|
-
SENS_READ=$(jq -R -s 'split("\n") | map(select(length > 0))' < /tmp/sensitive-read.txt)
|
|
236
|
-
SENS_WRITTEN=$(jq -R -s 'split("\n") | map(select(length > 0))' < /tmp/sensitive-written.txt)
|
|
237
|
-
|
|
238
|
-
CONNS=$(jq -R -s 'split("\n") | map(select(length > 0)) | map(
|
|
239
|
-
split("\t") | {host: .[0], port: (.[1] | tonumber), protocol: "TCP"}
|
|
240
|
-
)' < /tmp/connections.txt)
|
|
241
|
-
|
|
242
|
-
PROCS=$(jq -R -s 'split("\n") | map(select(length > 0)) | map(
|
|
243
|
-
split("\t") | {command: .[1], pid: (.[0] | tonumber)}
|
|
244
|
-
)' < /tmp/suspicious-cmds.txt)
|
|
245
|
-
|
|
246
|
-
DNS_RESOLUTIONS=$(jq -R -s 'split("\n") | map(select(length > 0)) | map(
|
|
247
|
-
split("\t") | {domain: .[0], ip: .[1]}
|
|
248
|
-
)' < /tmp/dns-resolutions.txt)
|
|
249
|
-
|
|
250
|
-
HTTP_REQUESTS=$(jq -R -s 'split("\n") | map(select(length > 0)) | map(
|
|
251
|
-
split("\t") | {method: .[0], host: .[1], path: .[2]}
|
|
252
|
-
)' < /tmp/http-requests.txt)
|
|
253
|
-
|
|
254
|
-
HTTP_BODIES=$(jq -R -s 'split("\n") | map(select(length > 0))' < /tmp/http-bodies.txt)
|
|
255
|
-
|
|
256
|
-
TLS_CONNS=$(jq -R -s 'split("\n") | map(select(length > 0)) | map(
|
|
257
|
-
split("\t") | {domain: .[0], ip: .[1], port: (.[2] | tonumber)}
|
|
258
|
-
)' < /tmp/tls-connections.txt)
|
|
259
|
-
|
|
260
|
-
BLOCKED=$(jq -R -s 'split("\n") | map(select(length > 0)) | map(
|
|
261
|
-
split("\t") | {ip: .[0], port: (.[1] | tonumber)}
|
|
262
|
-
)' < /tmp/blocked.txt)
|
|
263
|
-
|
|
264
|
-
# ── Final JSON (prefixed with delimiter for reliable parsing) ──
|
|
265
|
-
echo "---MUADDIB-REPORT-START---"
|
|
266
|
-
jq -n \
|
|
267
|
-
--arg package "$PACKAGE" \
|
|
268
|
-
--arg timestamp "$TIMESTAMP" \
|
|
269
|
-
--arg mode "$MODE" \
|
|
270
|
-
--argjson duration "${DURATION_MS:-0}" \
|
|
271
|
-
--argjson fs_created "$FS_CREATED" \
|
|
272
|
-
--argjson fs_deleted "$FS_DELETED" \
|
|
273
|
-
--argjson dns "$DNS" \
|
|
274
|
-
--argjson connections "$CONNS" \
|
|
275
|
-
--argjson processes "$PROCS" \
|
|
276
|
-
--argjson sensitive_read "$SENS_READ" \
|
|
277
|
-
--argjson sensitive_written "$SENS_WRITTEN" \
|
|
278
|
-
--argjson dns_resolutions "$DNS_RESOLUTIONS" \
|
|
279
|
-
--argjson http_requests "$HTTP_REQUESTS" \
|
|
280
|
-
--argjson http_bodies "$HTTP_BODIES" \
|
|
281
|
-
--argjson tls_connections "$TLS_CONNS" \
|
|
282
|
-
--argjson blocked_connections "$BLOCKED" \
|
|
283
|
-
--arg install_output "$INSTALL_OUTPUT" \
|
|
284
|
-
--argjson exit_code "${EXIT_CODE:-1}" \
|
|
285
|
-
'{
|
|
286
|
-
package: $package,
|
|
287
|
-
timestamp: $timestamp,
|
|
288
|
-
mode: $mode,
|
|
289
|
-
duration_ms: $duration,
|
|
290
|
-
filesystem: {
|
|
291
|
-
created: $fs_created,
|
|
292
|
-
deleted: $fs_deleted,
|
|
293
|
-
modified: []
|
|
294
|
-
},
|
|
295
|
-
network: {
|
|
296
|
-
dns_queries: $dns,
|
|
297
|
-
dns_resolutions: $dns_resolutions,
|
|
298
|
-
http_connections: $connections,
|
|
299
|
-
http_requests: $http_requests,
|
|
300
|
-
http_bodies: $http_bodies,
|
|
301
|
-
tls_connections: $tls_connections,
|
|
302
|
-
blocked_connections: $blocked_connections
|
|
303
|
-
},
|
|
304
|
-
processes: {
|
|
305
|
-
spawned: $processes
|
|
306
|
-
},
|
|
307
|
-
sensitive_files: {
|
|
308
|
-
read: $sensitive_read,
|
|
309
|
-
written: $sensitive_written
|
|
310
|
-
},
|
|
311
|
-
install_output: $install_output,
|
|
312
|
-
exit_code: $exit_code
|
|
313
|
-
}'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"name":"test-link","dependencies":{"eslint-plugin-react-internal":"link:./scripts/eslint-rules","lodash":"^4.17.21"}}
|
package/tmp-test-pack.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
const { execSync } = require('child_process');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const zlib = require('zlib');
|
|
5
|
-
|
|
6
|
-
const testDir = path.join(__dirname, '.muaddib-cache', 'benign-tarballs', '_test_express');
|
|
7
|
-
fs.mkdirSync(testDir, { recursive: true });
|
|
8
|
-
|
|
9
|
-
// Step 1: npm pack with cwd
|
|
10
|
-
console.log('Step 1: npm pack express (cwd)...');
|
|
11
|
-
try {
|
|
12
|
-
const out = execSync('npm pack express', { cwd: testDir, encoding: 'utf8', timeout: 30000 });
|
|
13
|
-
const tgzFile = out.trim().split('\n').pop().trim();
|
|
14
|
-
console.log(' OK:', tgzFile);
|
|
15
|
-
|
|
16
|
-
// Step 2: extract with native Node.js
|
|
17
|
-
const tgzPath = path.join(testDir, tgzFile);
|
|
18
|
-
console.log('Step 2: native extraction...');
|
|
19
|
-
extractTgz(tgzPath, testDir);
|
|
20
|
-
|
|
21
|
-
const pkgDir = path.join(testDir, 'package');
|
|
22
|
-
const files = fs.readdirSync(pkgDir);
|
|
23
|
-
console.log(' Extracted files:', files.join(', '));
|
|
24
|
-
console.log(' SUCCESS');
|
|
25
|
-
} catch (e) {
|
|
26
|
-
console.log(' FAIL:', e.message.slice(0, 300));
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Cleanup
|
|
30
|
-
fs.rmSync(testDir, { recursive: true, force: true });
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Extract a .tgz file using Node.js built-in zlib + minimal tar parser.
|
|
34
|
-
* Only extracts regular files (type '0' or NUL).
|
|
35
|
-
*/
|
|
36
|
-
function extractTgz(tgzPath, destDir) {
|
|
37
|
-
const compressed = fs.readFileSync(tgzPath);
|
|
38
|
-
const tarData = zlib.gunzipSync(compressed);
|
|
39
|
-
|
|
40
|
-
let offset = 0;
|
|
41
|
-
while (offset + 512 <= tarData.length) {
|
|
42
|
-
const header = tarData.subarray(offset, offset + 512);
|
|
43
|
-
|
|
44
|
-
// Check for end-of-archive (two zero blocks)
|
|
45
|
-
if (header.every(b => b === 0)) break;
|
|
46
|
-
|
|
47
|
-
// Parse tar header
|
|
48
|
-
const name = header.subarray(0, 100).toString('utf8').replace(/\0+$/, '');
|
|
49
|
-
const sizeOctal = header.subarray(124, 136).toString('utf8').replace(/\0+$/, '').trim();
|
|
50
|
-
const size = parseInt(sizeOctal, 8) || 0;
|
|
51
|
-
const typeFlag = String.fromCharCode(header[156]);
|
|
52
|
-
|
|
53
|
-
offset += 512; // move past header
|
|
54
|
-
|
|
55
|
-
if (name && (typeFlag === '0' || typeFlag === '\0') && size > 0) {
|
|
56
|
-
// Regular file — extract it
|
|
57
|
-
const filePath = path.join(destDir, name);
|
|
58
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
59
|
-
const fileData = tarData.subarray(offset, offset + size);
|
|
60
|
-
fs.writeFileSync(filePath, fileData);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Advance past data blocks (512-byte aligned)
|
|
64
|
-
offset += Math.ceil(size / 512) * 512;
|
|
65
|
-
}
|
|
66
|
-
}
|