vtk 1.0.0 → 1.2.0

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.
@@ -0,0 +1,531 @@
1
+ #!/bin/bash
2
+ #
3
+ # Shai-Hulud Machine Infection Checker
4
+ # =====================================
5
+ #
6
+ # Quick script to check if your machine shows signs of active Shai-Hulud infection.
7
+ # This is a FAST check (~5 seconds) for infection indicators only.
8
+ #
9
+ # WHAT THIS CHECKS:
10
+ #
11
+ # CRITICAL (Active Infection):
12
+ # - ~/.dev-env/ persistence folder (contains GitHub self-hosted runner)
13
+ # - ~/.truffler-cache/ malware cache (NOT legit Trufflehog which uses ~/.trufflehog/)
14
+ # - Running processes: Runner.Listener, SHA1HULUD, suspicious node/bun
15
+ # - Malware files: setup_bun.js, bun_environment.js
16
+ # - Backdoor workflows: .github/workflows/discussion.yaml
17
+ #
18
+ # HIGH (Exfiltration Likely Occurred):
19
+ # - Exfiltration artifacts: cloud.json, truffleSecrets.json, etc.
20
+ # - Unexpected ~/.bun/ installation
21
+ # - Unexpected Trufflehog binary
22
+ #
23
+ # INFO (Credentials to Rotate if Infected):
24
+ # - Lists credential files the malware targets
25
+ # - ~/.npmrc, ~/.aws/, ~/.config/gh/, etc.
26
+ #
27
+ # EXIT CODES:
28
+ # 0 - Clean (no infection indicators found)
29
+ # 1 - INFECTED (critical indicators found)
30
+ # 2 - WARNING (high-risk indicators, needs investigation)
31
+ #
32
+ # USAGE:
33
+ # ./shai-hulud-machine-check.sh # Compact output
34
+ # ./shai-hulud-machine-check.sh --verbose # Detailed output
35
+ # ./shai-hulud-machine-check.sh --quiet # Exit code only
36
+ # ./shai-hulud-machine-check.sh --json # JSON output
37
+ # ./shai-hulud-machine-check.sh --scan-dirs=~/repos,~/work # Additional dirs
38
+ #
39
+ # References:
40
+ # - Wiz.io: https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack
41
+ # - Datadog: https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/
42
+ #
43
+ # Author: Eric Boehs / EERT (with Claude Code)
44
+ # Version: 1.1.0
45
+ # Date: December 2025
46
+ #
47
+
48
+ set -e
49
+
50
+ # Parse arguments
51
+ QUIET=false
52
+ JSON=false
53
+ VERBOSE=false
54
+ EXTRA_SCAN_DIRS=""
55
+ for arg in "$@"; do
56
+ case $arg in
57
+ --quiet|-q) QUIET=true ;;
58
+ --json|-j) JSON=true ;;
59
+ --verbose|-v) VERBOSE=true ;;
60
+ --scan-dirs=*) EXTRA_SCAN_DIRS="${arg#*=}" ;;
61
+ --help|-h)
62
+ echo "Usage: $0 [--verbose|-v] [--quiet|-q] [--json|-j] [--scan-dirs=DIR1,DIR2,...]"
63
+ echo " --verbose Detailed output with all checks"
64
+ echo " --quiet Exit code only, no output"
65
+ echo " --json JSON output format"
66
+ echo " --scan-dirs Additional directories to scan for backdoor workflows (comma-separated)"
67
+ exit 0
68
+ ;;
69
+ esac
70
+ done
71
+
72
+ # Default directories to scan for backdoor workflows
73
+ DEFAULT_SCAN_DIRS=("$HOME/Code" "$HOME/Projects" "$HOME/src" "$HOME/dev" "$HOME/workspace")
74
+
75
+ # Build final scan dirs list
76
+ SCAN_DIRS=("${DEFAULT_SCAN_DIRS[@]}")
77
+ if [ -n "$EXTRA_SCAN_DIRS" ]; then
78
+ IFS=',' read -ra EXTRA_DIRS <<< "$EXTRA_SCAN_DIRS"
79
+ for dir in "${EXTRA_DIRS[@]}"; do
80
+ # Expand ~ to $HOME
81
+ expanded_dir="${dir/#\~/$HOME}"
82
+ SCAN_DIRS+=("$expanded_dir")
83
+ done
84
+ fi
85
+
86
+ # Results tracking
87
+ CRITICAL_FINDINGS=()
88
+ HIGH_FINDINGS=()
89
+ INFO_FINDINGS=()
90
+
91
+ # Colors (disabled in quiet/json mode)
92
+ if [ "$QUIET" = false ] && [ "$JSON" = false ] && [ -t 1 ]; then
93
+ RED='\033[0;31m'
94
+ YELLOW='\033[0;33m'
95
+ GREEN='\033[0;32m'
96
+ CYAN='\033[0;36m'
97
+ BOLD='\033[1m'
98
+ NC='\033[0m' # No Color
99
+ else
100
+ RED='' YELLOW='' GREEN='' CYAN='' BOLD='' NC=''
101
+ fi
102
+
103
+ # Logging functions
104
+ log() {
105
+ if [ "$QUIET" = false ] && [ "$JSON" = false ]; then
106
+ echo -e "$@"
107
+ fi
108
+ }
109
+
110
+ log_verbose() {
111
+ if [ "$VERBOSE" = true ] && [ "$QUIET" = false ] && [ "$JSON" = false ]; then
112
+ echo -e "$@"
113
+ fi
114
+ }
115
+
116
+ log_critical() {
117
+ CRITICAL_FINDINGS+=("$1")
118
+ }
119
+
120
+ log_high() {
121
+ HIGH_FINDINGS+=("$1")
122
+ }
123
+
124
+ log_info() {
125
+ INFO_FINDINGS+=("$1")
126
+ }
127
+
128
+ # Verbose header
129
+ log_verbose ""
130
+ log_verbose "${BOLD}========================================${NC}"
131
+ log_verbose "${BOLD} Shai-Hulud Machine Infection Check${NC}"
132
+ log_verbose "${BOLD}========================================${NC}"
133
+ log_verbose ""
134
+ log_verbose "Checking for active infection indicators..."
135
+ log_verbose ""
136
+
137
+ ###########################################
138
+ # CRITICAL CHECKS
139
+ ###########################################
140
+
141
+ log_verbose "${BOLD}=== Critical Checks ===${NC}"
142
+ log_verbose ""
143
+
144
+ # 1. Check for ~/.dev-env/ persistence folder
145
+ log_verbose "Checking for persistence folder (~/.dev-env/)..."
146
+ if [ -d "$HOME/.dev-env" ]; then
147
+ log_critical "~/.dev-env/ persistence folder found"
148
+ log_verbose " ${RED}[CRITICAL]${NC} PERSISTENCE FOLDER FOUND: ~/.dev-env/"
149
+ log_verbose " This folder contains the malware's self-hosted GitHub runner."
150
+ log_verbose " Contents:"
151
+ ls -la "$HOME/.dev-env" 2>/dev/null | head -10 | while read line; do log_verbose " $line"; done
152
+ else
153
+ log_verbose " ${GREEN}Not found${NC}"
154
+ fi
155
+
156
+ # 1b. Check for ~/.truffler-cache/ (malware-specific Trufflehog cache)
157
+ log_verbose ""
158
+ log_verbose "Checking for malware Trufflehog cache (~/.truffler-cache/)..."
159
+ if [ -d "$HOME/.truffler-cache" ]; then
160
+ log_critical "~/.truffler-cache/ malware cache found"
161
+ log_verbose " ${RED}[CRITICAL]${NC} MALWARE CACHE FOUND: ~/.truffler-cache/"
162
+ log_verbose " This is a MALWARE-SPECIFIC path (legit Trufflehog uses ~/.trufflehog/)."
163
+ log_verbose " The malware stores its Trufflehog binary here to scan for secrets."
164
+ log_verbose " Contents:"
165
+ ls -la "$HOME/.truffler-cache" 2>/dev/null | head -10 | while read line; do log_verbose " $line"; done
166
+ else
167
+ log_verbose " ${GREEN}Not found${NC}"
168
+ fi
169
+
170
+ # 2. Check for malicious running processes
171
+ log_verbose ""
172
+ log_verbose "Checking for malicious processes..."
173
+
174
+ # Runner.Listener (GitHub self-hosted runner - malware installs this)
175
+ if pgrep -f "Runner.Listener" > /dev/null 2>&1; then
176
+ log_critical "Runner.Listener process running"
177
+ log_verbose " ${RED}[CRITICAL]${NC} MALICIOUS PROCESS: Runner.Listener is running"
178
+ log_verbose " PIDs: $(pgrep -f 'Runner.Listener' | tr '\n' ' ')"
179
+ fi
180
+
181
+ # SHA1HULUD process
182
+ if pgrep -f "SHA1HULUD" > /dev/null 2>&1; then
183
+ log_critical "SHA1HULUD process running"
184
+ log_verbose " ${RED}[CRITICAL]${NC} MALICIOUS PROCESS: SHA1HULUD is running"
185
+ log_verbose " PIDs: $(pgrep -f 'SHA1HULUD' | tr '\n' ' ')"
186
+ fi
187
+
188
+ # Check for node processes running from ~/.dev-env
189
+ if pgrep -f "$HOME/.dev-env" > /dev/null 2>&1; then
190
+ log_critical "Process running from ~/.dev-env"
191
+ log_verbose " ${RED}[CRITICAL]${NC} MALICIOUS PROCESS: Process running from ~/.dev-env"
192
+ log_verbose " PIDs: $(pgrep -f "$HOME/.dev-env" | tr '\n' ' ')"
193
+ fi
194
+
195
+ # Check for suspicious bun processes (from ~/.bun if unexpected)
196
+ if pgrep -f "$HOME/.bun/bin/bun" > /dev/null 2>&1; then
197
+ log_high "bun process running from ~/.bun/bin/bun"
198
+ log_verbose " ${YELLOW}[HIGH]${NC} SUSPICIOUS PROCESS: bun running from ~/.bun/bin/bun"
199
+ log_verbose " This could be legitimate if you installed Bun intentionally."
200
+ log_verbose " PIDs: $(pgrep -f "$HOME/.bun/bin/bun" | tr '\n' ' ')"
201
+ fi
202
+
203
+ # If no malicious processes found
204
+ if ! pgrep -f "Runner.Listener" > /dev/null 2>&1 && \
205
+ ! pgrep -f "SHA1HULUD" > /dev/null 2>&1 && \
206
+ ! pgrep -f "$HOME/.dev-env" > /dev/null 2>&1; then
207
+ log_verbose " ${GREEN}No malicious processes detected${NC}"
208
+ fi
209
+
210
+ # 3. Check for malware payload files in home directory
211
+ log_verbose ""
212
+ log_verbose "Checking for malware files in home directory..."
213
+ MALWARE_FILES=("setup_bun.js" "bun_environment.js")
214
+ MALWARE_FOUND=false
215
+ for file in "${MALWARE_FILES[@]}"; do
216
+ if [ -f "$HOME/$file" ]; then
217
+ log_critical "Malware file: ~/$file"
218
+ log_verbose " ${RED}[CRITICAL]${NC} MALWARE FILE FOUND: ~/$file"
219
+ MALWARE_FOUND=true
220
+ fi
221
+ done
222
+
223
+ # Check in common locations
224
+ for dir in "$HOME" "$HOME/Desktop" "$HOME/Downloads" "/tmp"; do
225
+ if [ -d "$dir" ]; then
226
+ for file in "${MALWARE_FILES[@]}"; do
227
+ FOUND=$(find "$dir" -maxdepth 2 -name "$file" -type f 2>/dev/null | head -5)
228
+ if [ -n "$FOUND" ]; then
229
+ while IFS= read -r f; do
230
+ log_critical "Malware file: $f"
231
+ log_verbose " ${RED}[CRITICAL]${NC} MALWARE FILE FOUND: $f"
232
+ MALWARE_FOUND=true
233
+ done <<< "$FOUND"
234
+ fi
235
+ done
236
+ fi
237
+ done
238
+
239
+ if [ "$MALWARE_FOUND" = false ]; then
240
+ log_verbose " ${GREEN}No malware files detected${NC}"
241
+ fi
242
+
243
+ # 4. Check for backdoor workflow files
244
+ log_verbose ""
245
+ log_verbose "Checking for backdoor workflow files..."
246
+ log_verbose " Scanning directories:"
247
+ for dir in "${SCAN_DIRS[@]}"; do
248
+ if [ -d "$dir" ]; then
249
+ log_verbose " - $dir"
250
+ else
251
+ log_verbose " - $dir ${YELLOW}(not found)${NC}"
252
+ fi
253
+ done
254
+ log_verbose ""
255
+ BACKDOOR_FOUND=false
256
+ # Search for discussion.yaml in .github/workflows directories
257
+ # The malicious workflow contains: runs-on: self-hosted AND ${{ github.event.discussion.body }}
258
+ for base_dir in "${SCAN_DIRS[@]}"; do
259
+ if [ -d "$base_dir" ]; then
260
+ FOUND=$(find "$base_dir" -maxdepth 5 -path "*/.github/workflows/discussion.yaml" -type f 2>/dev/null | head -10)
261
+ if [ -n "$FOUND" ]; then
262
+ while IFS= read -r f; do
263
+ # Check if file contains the malicious pattern
264
+ if grep -q "self-hosted" "$f" 2>/dev/null && grep -q "github.event.discussion" "$f" 2>/dev/null; then
265
+ log_critical "Backdoor workflow: $f"
266
+ log_verbose " ${RED}[CRITICAL]${NC} BACKDOOR WORKFLOW FOUND: $f"
267
+ log_verbose " This workflow uses self-hosted runner with discussion body injection."
268
+ BACKDOOR_FOUND=true
269
+ else
270
+ log_high "Unverified discussion.yaml: $f"
271
+ log_verbose " ${YELLOW}[HIGH]${NC} UNVERIFIED WORKFLOW: $f"
272
+ log_verbose " Found discussion.yaml - please verify this is legitimate."
273
+ fi
274
+ done <<< "$FOUND"
275
+ fi
276
+ fi
277
+ done
278
+
279
+ if [ "$BACKDOOR_FOUND" = false ]; then
280
+ log_verbose " ${GREEN}No backdoor workflows detected${NC}"
281
+ fi
282
+
283
+ ###########################################
284
+ # HIGH-RISK CHECKS
285
+ ###########################################
286
+
287
+ log_verbose ""
288
+ log_verbose "${BOLD}=== High-Risk Checks ===${NC}"
289
+ log_verbose ""
290
+
291
+ # 4. Check for exfiltration artifacts
292
+ log_verbose "Checking for exfiltration artifacts..."
293
+ EXFIL_FILES=("cloud.json" "truffleSecrets.json" "environment.json" "actionsSecrets.json" "contents.json")
294
+ EXFIL_FOUND=false
295
+ for file in "${EXFIL_FILES[@]}"; do
296
+ # Check home directory and common locations
297
+ for dir in "$HOME" "/tmp" "$HOME/Desktop" "$HOME/Downloads"; do
298
+ if [ -f "$dir/$file" ]; then
299
+ log_high "Exfiltration artifact: $dir/$file"
300
+ log_verbose " ${YELLOW}[HIGH]${NC} EXFILTRATION ARTIFACT: $dir/$file"
301
+ EXFIL_FOUND=true
302
+ fi
303
+ done
304
+ done
305
+
306
+ if [ "$EXFIL_FOUND" = false ]; then
307
+ log_verbose " ${GREEN}No exfiltration artifacts detected${NC}"
308
+ fi
309
+
310
+ # 5. Check for unexpected Bun installation
311
+ log_verbose ""
312
+ log_verbose "Checking for unexpected Bun installation..."
313
+ if [ -d "$HOME/.bun" ]; then
314
+ log_high "~/.bun/ installation found"
315
+ log_verbose " ${YELLOW}[HIGH]${NC} BUN INSTALLATION FOUND: ~/.bun/"
316
+ log_verbose " If you did NOT install Bun intentionally, this is suspicious."
317
+ log_verbose " Bun version: $(~/.bun/bin/bun --version 2>/dev/null || echo 'unknown')"
318
+ elif command -v bun &> /dev/null; then
319
+ BUN_PATH=$(which bun)
320
+ log_high "Bun found: $BUN_PATH"
321
+ log_verbose " ${YELLOW}[HIGH]${NC} BUN FOUND IN PATH: $BUN_PATH"
322
+ log_verbose " If you did NOT install Bun intentionally, investigate."
323
+ else
324
+ log_verbose " ${GREEN}No unexpected Bun installation${NC}"
325
+ fi
326
+
327
+ # 6. Check for Trufflehog (malware downloads this to scan for secrets)
328
+ log_verbose ""
329
+ log_verbose "Checking for unexpected Trufflehog..."
330
+ if command -v trufflehog &> /dev/null; then
331
+ TH_PATH=$(which trufflehog)
332
+ log_high "Trufflehog found: $TH_PATH"
333
+ log_verbose " ${YELLOW}[HIGH]${NC} TRUFFLEHOG FOUND: $TH_PATH"
334
+ log_verbose " The malware uses Trufflehog to scan for secrets."
335
+ log_verbose " If you did NOT install this intentionally, investigate."
336
+ elif [ -f "$HOME/.local/bin/trufflehog" ] || [ -f "/tmp/trufflehog" ]; then
337
+ log_high "Trufflehog in suspicious location"
338
+ log_verbose " ${YELLOW}[HIGH]${NC} TRUFFLEHOG FOUND in suspicious location"
339
+ else
340
+ log_verbose " ${GREEN}No unexpected Trufflehog installation${NC}"
341
+ fi
342
+
343
+ ###########################################
344
+ # INFORMATIONAL - Credentials at Risk
345
+ ###########################################
346
+
347
+ log_verbose ""
348
+ log_verbose "${BOLD}=== Credential Files (Rotate if Infected) ===${NC}"
349
+ log_verbose ""
350
+ log_verbose "These are files the malware targets for exfiltration."
351
+ log_verbose "If your machine is infected, ROTATE ALL OF THESE:"
352
+ log_verbose ""
353
+
354
+ # NPM tokens
355
+ if [ -f "$HOME/.npmrc" ]; then
356
+ if grep -qi "authtoken\|_auth" "$HOME/.npmrc" 2>/dev/null; then
357
+ log_info "~/.npmrc contains auth tokens"
358
+ log_verbose " ${CYAN}[INFO]${NC} ~/.npmrc contains auth tokens - ROTATE NPM TOKENS"
359
+ else
360
+ log_verbose " ~/.npmrc exists (no tokens detected)"
361
+ fi
362
+ else
363
+ log_verbose " ~/.npmrc not found"
364
+ fi
365
+
366
+ # AWS credentials
367
+ if [ -d "$HOME/.aws" ]; then
368
+ log_info "~/.aws/ exists"
369
+ log_verbose " ${CYAN}[INFO]${NC} ~/.aws/ exists - ROTATE AWS ACCESS KEYS"
370
+ else
371
+ log_verbose " ~/.aws/ not found"
372
+ fi
373
+
374
+ # GCP credentials
375
+ if [ -f "$HOME/.config/gcloud/application_default_credentials.json" ]; then
376
+ log_info "GCP ADC exists"
377
+ log_verbose " ${CYAN}[INFO]${NC} GCP ADC exists - RE-AUTHENTICATE with 'gcloud auth application-default login'"
378
+ else
379
+ log_verbose " GCP ADC not found"
380
+ fi
381
+
382
+ # Azure credentials
383
+ if [ -d "$HOME/.azure" ]; then
384
+ log_info "~/.azure/ exists"
385
+ log_verbose " ${CYAN}[INFO]${NC} ~/.azure/ exists - RE-AUTHENTICATE with 'az login'"
386
+ else
387
+ log_verbose " ~/.azure/ not found"
388
+ fi
389
+
390
+ # GitHub CLI
391
+ if [ -f "$HOME/.config/gh/hosts.yml" ]; then
392
+ log_info "GitHub CLI authenticated"
393
+ log_verbose " ${CYAN}[INFO]${NC} GitHub CLI authenticated - ROTATE TOKEN with 'gh auth logout && gh auth login'"
394
+ else
395
+ log_verbose " GitHub CLI not authenticated"
396
+ fi
397
+
398
+ # SSH keys
399
+ if [ -d "$HOME/.ssh" ] && ls "$HOME/.ssh/"*.pub &> /dev/null; then
400
+ log_info "SSH keys exist"
401
+ log_verbose " ${CYAN}[INFO]${NC} SSH keys exist (~/.ssh/) - Consider rotating if infected"
402
+ fi
403
+
404
+ # Git credentials
405
+ if [ -f "$HOME/.git-credentials" ]; then
406
+ log_info "~/.git-credentials exists"
407
+ log_verbose " ${CYAN}[INFO]${NC} ~/.git-credentials exists - ROTATE stored credentials"
408
+ fi
409
+
410
+ # Environment variables with secrets
411
+ SENSITIVE_VARS=$(env | grep -iE "token|key|secret|password|credential|auth|api" 2>/dev/null | wc -l | tr -d ' ')
412
+ if [ "$SENSITIVE_VARS" -gt 0 ]; then
413
+ log_info "$SENSITIVE_VARS sensitive env vars"
414
+ log_verbose " ${CYAN}[INFO]${NC} $SENSITIVE_VARS sensitive environment variables detected"
415
+ fi
416
+
417
+ ###########################################
418
+ # SUMMARY
419
+ ###########################################
420
+
421
+ log_verbose ""
422
+ log_verbose "${BOLD}========================================${NC}"
423
+
424
+ # Determine final status
425
+ EXIT_CODE=0
426
+ if [ ${#CRITICAL_FINDINGS[@]} -gt 0 ]; then
427
+ EXIT_CODE=1
428
+ if [ "$VERBOSE" = true ]; then
429
+ log "${RED}${BOLD} STATUS: INFECTED${NC}"
430
+ log "${RED}${BOLD}========================================${NC}"
431
+ log ""
432
+ log "${RED}CRITICAL FINDINGS (${#CRITICAL_FINDINGS[@]}):${NC}"
433
+ for finding in "${CRITICAL_FINDINGS[@]}"; do
434
+ log " - $finding"
435
+ done
436
+ log ""
437
+ log "${BOLD}IMMEDIATE ACTIONS:${NC}"
438
+ log " 1. DISCONNECT from network immediately"
439
+ log " 2. Do NOT run npm/yarn/node commands"
440
+ log " 3. Follow the cleanup playbook"
441
+ log " 4. Rotate ALL credentials listed above"
442
+ else
443
+ log "${RED}${BOLD}Shai-Hulud Check: INFECTED${NC}"
444
+ for finding in "${CRITICAL_FINDINGS[@]}"; do
445
+ log " ${RED}-${NC} $finding"
446
+ done
447
+ log ""
448
+ log "Run with ${BOLD}--verbose${NC} for details and remediation steps"
449
+ fi
450
+ elif [ ${#HIGH_FINDINGS[@]} -gt 0 ]; then
451
+ EXIT_CODE=2
452
+ if [ "$VERBOSE" = true ]; then
453
+ log "${YELLOW}${BOLD} STATUS: WARNING - INVESTIGATE${NC}"
454
+ log "${YELLOW}${BOLD}========================================${NC}"
455
+ log ""
456
+ log "${YELLOW}HIGH-RISK FINDINGS (${#HIGH_FINDINGS[@]}):${NC}"
457
+ for finding in "${HIGH_FINDINGS[@]}"; do
458
+ log " - $finding"
459
+ done
460
+ log ""
461
+ log "${BOLD}RECOMMENDED ACTIONS:${NC}"
462
+ log " 1. Verify if Bun/Trufflehog were installed intentionally"
463
+ log " 2. If not intentional, treat as potentially infected"
464
+ log " 3. Investigate further before running any package manager commands"
465
+ else
466
+ log "${YELLOW}${BOLD}Shai-Hulud Check: WARNING${NC}"
467
+ for finding in "${HIGH_FINDINGS[@]}"; do
468
+ log " ${YELLOW}-${NC} $finding"
469
+ done
470
+ log ""
471
+ log "These may be legitimate if you installed them intentionally."
472
+ log "Run with ${BOLD}--verbose${NC} for details or investigate if unexpected."
473
+ fi
474
+ else
475
+ if [ "$VERBOSE" = true ]; then
476
+ log "${GREEN}${BOLD} STATUS: CLEAN${NC}"
477
+ log "${GREEN}${BOLD}========================================${NC}"
478
+ log ""
479
+ log "${GREEN}No active infection indicators detected.${NC}"
480
+ log ""
481
+ log "Next steps:"
482
+ log " - Verify your repos don't have compromised packages"
483
+ log " - Check your lockfiles for known malicious packages"
484
+ else
485
+ log "${GREEN}${BOLD}Shai-Hulud Check: CLEAN${NC}"
486
+ fi
487
+ fi
488
+
489
+ log ""
490
+
491
+ # JSON output mode
492
+ if [ "$JSON" = true ]; then
493
+ # Build JSON array from bash array (no jq dependency)
494
+ json_array() {
495
+ local arr=("$@")
496
+ if [ ${#arr[@]} -eq 0 ]; then
497
+ echo '[]'
498
+ return
499
+ fi
500
+ local result='['
501
+ local first=true
502
+ for item in "${arr[@]}"; do
503
+ if [ "$first" = true ]; then
504
+ first=false
505
+ else
506
+ result+=','
507
+ fi
508
+ # Escape quotes and backslashes for JSON
509
+ item="${item//\\/\\\\}"
510
+ item="${item//\"/\\\"}"
511
+ result+="\"$item\""
512
+ done
513
+ result+=']'
514
+ echo "$result"
515
+ }
516
+
517
+ cat <<EOF
518
+ {
519
+ "status": "$([ ${#CRITICAL_FINDINGS[@]} -gt 0 ] && echo 'INFECTED' || ([ ${#HIGH_FINDINGS[@]} -gt 0 ] && echo 'WARNING' || echo 'CLEAN'))",
520
+ "critical_count": ${#CRITICAL_FINDINGS[@]},
521
+ "high_count": ${#HIGH_FINDINGS[@]},
522
+ "info_count": ${#INFO_FINDINGS[@]},
523
+ "critical_findings": $(json_array "${CRITICAL_FINDINGS[@]}"),
524
+ "high_findings": $(json_array "${HIGH_FINDINGS[@]}"),
525
+ "info_findings": $(json_array "${INFO_FINDINGS[@]}"),
526
+ "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
527
+ }
528
+ EOF
529
+ fi
530
+
531
+ exit $EXIT_CODE