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.
Files changed (150) hide show
  1. package/package.json +1 -1
  2. package/.gitattributes +0 -18
  3. package/datasets/adversarial/README.md +0 -23
  4. package/datasets/adversarial/ai-agent-weaponization/index.js +0 -5
  5. package/datasets/adversarial/ai-agent-weaponization/package.json +0 -9
  6. package/datasets/adversarial/ai-agent-weaponization/setup.js +0 -83
  7. package/datasets/adversarial/ai-config-injection/.cursorrules +0 -36
  8. package/datasets/adversarial/ai-config-injection/index.js +0 -16
  9. package/datasets/adversarial/ai-config-injection/package.json +0 -8
  10. package/datasets/adversarial/browser-api-hook/index.js +0 -66
  11. package/datasets/adversarial/browser-api-hook/package.json +0 -6
  12. package/datasets/adversarial/bun-runtime-evasion/bun_environment.js +0 -23
  13. package/datasets/adversarial/bun-runtime-evasion/package.json +0 -9
  14. package/datasets/adversarial/bun-runtime-evasion/setup.js +0 -10
  15. package/datasets/adversarial/ci-trigger-exfil/index.js +0 -17
  16. package/datasets/adversarial/ci-trigger-exfil/package.json +0 -9
  17. package/datasets/adversarial/conditional-chain/index.js +0 -14
  18. package/datasets/adversarial/conditional-chain/package.json +0 -9
  19. package/datasets/adversarial/crypto-wallet-harvest/index.js +0 -44
  20. package/datasets/adversarial/crypto-wallet-harvest/package.json +0 -6
  21. package/datasets/adversarial/dead-mans-switch/index.js +0 -35
  22. package/datasets/adversarial/dead-mans-switch/package.json +0 -9
  23. package/datasets/adversarial/delayed-exfil/index.js +0 -6
  24. package/datasets/adversarial/delayed-exfil/package.json +0 -6
  25. package/datasets/adversarial/detached-background/launcher.js +0 -11
  26. package/datasets/adversarial/detached-background/package.json +0 -9
  27. package/datasets/adversarial/detached-background/worker.js +0 -26
  28. package/datasets/adversarial/discord-webhook-exfil/index.js +0 -95
  29. package/datasets/adversarial/discord-webhook-exfil/package.json +0 -9
  30. package/datasets/adversarial/dns-chunk-exfil/index.js +0 -10
  31. package/datasets/adversarial/dns-chunk-exfil/package.json +0 -6
  32. package/datasets/adversarial/docker-aware/index.js +0 -10
  33. package/datasets/adversarial/docker-aware/package.json +0 -6
  34. package/datasets/adversarial/double-base64-exfil/index.js +0 -11
  35. package/datasets/adversarial/double-base64-exfil/package.json +0 -9
  36. package/datasets/adversarial/dynamic-import/index.js +0 -21
  37. package/datasets/adversarial/dynamic-import/package.json +0 -9
  38. package/datasets/adversarial/dynamic-require/index.js +0 -3
  39. package/datasets/adversarial/dynamic-require/package.json +0 -9
  40. package/datasets/adversarial/fake-captcha-fingerprint/index.js +0 -64
  41. package/datasets/adversarial/fake-captcha-fingerprint/package.json +0 -9
  42. package/datasets/adversarial/gh-cli-token-steal/index.js +0 -31
  43. package/datasets/adversarial/gh-cli-token-steal/package.json +0 -6
  44. package/datasets/adversarial/github-exfil/index.js +0 -33
  45. package/datasets/adversarial/github-exfil/package.json +0 -9
  46. package/datasets/adversarial/iife-exfil/index.js +0 -17
  47. package/datasets/adversarial/iife-exfil/package.json +0 -9
  48. package/datasets/adversarial/indirect-eval-bypass/index.js +0 -27
  49. package/datasets/adversarial/indirect-eval-bypass/package.json +0 -5
  50. package/datasets/adversarial/mjs-extension-bypass/package.json +0 -6
  51. package/datasets/adversarial/mjs-extension-bypass/stealer.mjs +0 -39
  52. package/datasets/adversarial/muaddib-ignore-bypass/index.js +0 -47
  53. package/datasets/adversarial/muaddib-ignore-bypass/package.json +0 -5
  54. package/datasets/adversarial/nested-payload/index.js +0 -3
  55. package/datasets/adversarial/nested-payload/package.json +0 -9
  56. package/datasets/adversarial/nested-payload/utils/helper.js +0 -6
  57. package/datasets/adversarial/nested-payload/utils/lib/format.js +0 -23
  58. package/datasets/adversarial/postinstall-download/package.json +0 -8
  59. package/datasets/adversarial/preinstall-background-fork/bootstrap.js +0 -16
  60. package/datasets/adversarial/preinstall-background-fork/index.js +0 -2
  61. package/datasets/adversarial/preinstall-background-fork/package.json +0 -9
  62. package/datasets/adversarial/preinstall-background-fork/stealer.js +0 -67
  63. package/datasets/adversarial/preinstall-exec/package.json +0 -9
  64. package/datasets/adversarial/preinstall-exec/steal.js +0 -24
  65. package/datasets/adversarial/proxy-env-intercept/index.js +0 -33
  66. package/datasets/adversarial/proxy-env-intercept/package.json +0 -9
  67. package/datasets/adversarial/pyinstaller-dropper/index.js +0 -25
  68. package/datasets/adversarial/pyinstaller-dropper/package.json +0 -9
  69. package/datasets/adversarial/rdd-zero-deps/index.js +0 -32
  70. package/datasets/adversarial/rdd-zero-deps/init.js +0 -15
  71. package/datasets/adversarial/rdd-zero-deps/package.json +0 -11
  72. package/datasets/adversarial/remote-dynamic-dependency/index.js +0 -15
  73. package/datasets/adversarial/remote-dynamic-dependency/package.json +0 -7
  74. package/datasets/adversarial/self-hosted-runner-backdoor/index.js +0 -28
  75. package/datasets/adversarial/self-hosted-runner-backdoor/package.json +0 -9
  76. package/datasets/adversarial/silent-error-swallow/index.js +0 -32
  77. package/datasets/adversarial/silent-error-swallow/package.json +0 -6
  78. package/datasets/adversarial/staged-fetch/index.js +0 -9
  79. package/datasets/adversarial/staged-fetch/package.json +0 -6
  80. package/datasets/adversarial/string-concat-obfuscation/index.js +0 -6
  81. package/datasets/adversarial/string-concat-obfuscation/package.json +0 -6
  82. package/datasets/adversarial/template-literal-obfuscation/index.js +0 -4
  83. package/datasets/adversarial/template-literal-obfuscation/package.json +0 -9
  84. package/datasets/adversarial/triple-base64-github-push/index.js +0 -38
  85. package/datasets/adversarial/triple-base64-github-push/package.json +0 -9
  86. package/datasets/adversarial/websocket-exfil/index.js +0 -34
  87. package/datasets/adversarial/websocket-exfil/package.json +0 -9
  88. package/datasets/benign/README.md +0 -20
  89. package/datasets/benign/packages-npm.txt +0 -597
  90. package/datasets/benign/packages-pypi.txt +0 -164
  91. package/datasets/ground-truth/README.md +0 -54
  92. package/datasets/ground-truth/known-malware.json +0 -622
  93. package/datasets/holdout-v4/atob-eval/index.js +0 -2
  94. package/datasets/holdout-v4/atob-eval/package.json +0 -5
  95. package/datasets/holdout-v4/base64-require/index.js +0 -3
  96. package/datasets/holdout-v4/base64-require/package.json +0 -5
  97. package/datasets/holdout-v4/charcode-fetch/index.js +0 -3
  98. package/datasets/holdout-v4/charcode-fetch/package.json +0 -5
  99. package/datasets/holdout-v4/charcode-spread-homedir/index.js +0 -5
  100. package/datasets/holdout-v4/charcode-spread-homedir/package.json +0 -5
  101. package/datasets/holdout-v4/concat-env-steal/index.js +0 -4
  102. package/datasets/holdout-v4/concat-env-steal/package.json +0 -5
  103. package/datasets/holdout-v4/double-decode-exfil/index.js +0 -4
  104. package/datasets/holdout-v4/double-decode-exfil/package.json +0 -5
  105. package/datasets/holdout-v4/hex-array-exec/index.js +0 -3
  106. package/datasets/holdout-v4/hex-array-exec/package.json +0 -5
  107. package/datasets/holdout-v4/mixed-obfuscation-stealer/index.js +0 -10
  108. package/datasets/holdout-v4/mixed-obfuscation-stealer/package.json +0 -5
  109. package/datasets/holdout-v4/nested-base64-concat/index.js +0 -4
  110. package/datasets/holdout-v4/nested-base64-concat/package.json +0 -5
  111. package/datasets/holdout-v4/template-literal-hide/index.js +0 -3
  112. package/datasets/holdout-v4/template-literal-hide/package.json +0 -5
  113. package/datasets/holdout-v5/callback-exfil/main.js +0 -8
  114. package/datasets/holdout-v5/callback-exfil/package.json +0 -5
  115. package/datasets/holdout-v5/callback-exfil/reader.js +0 -10
  116. package/datasets/holdout-v5/class-method-exfil/collector.js +0 -10
  117. package/datasets/holdout-v5/class-method-exfil/main.js +0 -7
  118. package/datasets/holdout-v5/class-method-exfil/package.json +0 -5
  119. package/datasets/holdout-v5/conditional-split/detector.js +0 -2
  120. package/datasets/holdout-v5/conditional-split/package.json +0 -5
  121. package/datasets/holdout-v5/conditional-split/stealer.js +0 -16
  122. package/datasets/holdout-v5/event-emitter-flow/listener.js +0 -12
  123. package/datasets/holdout-v5/event-emitter-flow/package.json +0 -5
  124. package/datasets/holdout-v5/event-emitter-flow/scanner.js +0 -11
  125. package/datasets/holdout-v5/mixed-inline-split/index.js +0 -6
  126. package/datasets/holdout-v5/mixed-inline-split/package.json +0 -5
  127. package/datasets/holdout-v5/mixed-inline-split/reader.js +0 -3
  128. package/datasets/holdout-v5/mixed-inline-split/sender.js +0 -6
  129. package/datasets/holdout-v5/named-export-steal/main.js +0 -6
  130. package/datasets/holdout-v5/named-export-steal/package.json +0 -5
  131. package/datasets/holdout-v5/named-export-steal/utils.js +0 -1
  132. package/datasets/holdout-v5/reexport-chain/a.js +0 -2
  133. package/datasets/holdout-v5/reexport-chain/b.js +0 -1
  134. package/datasets/holdout-v5/reexport-chain/c.js +0 -11
  135. package/datasets/holdout-v5/reexport-chain/package.json +0 -5
  136. package/datasets/holdout-v5/split-env-exfil/env.js +0 -2
  137. package/datasets/holdout-v5/split-env-exfil/exfil.js +0 -5
  138. package/datasets/holdout-v5/split-env-exfil/package.json +0 -5
  139. package/datasets/holdout-v5/split-npmrc-steal/index.js +0 -2
  140. package/datasets/holdout-v5/split-npmrc-steal/package.json +0 -5
  141. package/datasets/holdout-v5/split-npmrc-steal/reader.js +0 -8
  142. package/datasets/holdout-v5/split-npmrc-steal/sender.js +0 -17
  143. package/datasets/holdout-v5/three-hop-chain/package.json +0 -5
  144. package/datasets/holdout-v5/three-hop-chain/reader.js +0 -8
  145. package/datasets/holdout-v5/three-hop-chain/sender.js +0 -11
  146. package/datasets/holdout-v5/three-hop-chain/transform.js +0 -3
  147. package/docker/Dockerfile +0 -20
  148. package/docker/sandbox-runner.sh +0 -313
  149. package/testssampleslink-deppackage.json +0 -1
  150. package/tmp-test-pack.js +0 -66
@@ -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
- }