vtk 1.0.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6acd180a7e7d3839aebff0617c306c092205492ab3c5ce124f3b5e957970e42b
4
- data.tar.gz: c2c5fe18ee04efc2624eb092125c4c738d041e854173d1f934543cb5dbe8dcd2
3
+ metadata.gz: c41b577facc94e32eb15aeea7f5817a49f54c31a568654c5dfe5da082d0775f9
4
+ data.tar.gz: 87820698519528ff97594bf9e148a0a20ca700026079a0fc2b65ede3bee84b19
5
5
  SHA512:
6
- metadata.gz: 06b298ec4d35bb31e1472913c305377649ef70399cc87eb2818fae5c5f4f6395be90516df1cfb16aa2cdd4ba9ae8bccb9caec475654fa2c98b26e17be5a070fd
7
- data.tar.gz: 793f7811c57a9167f17704a14105a4b8d1624974f910295088a3d72c29528e84f2d57921ff073a7d59fee357953b3972d31f5fc5cc19876b66dcd20b2effe5fa
6
+ metadata.gz: 729f7b3a93dd61661ad557d471796a059ada830970afe16bbeef7bdf84e0e92887a321249c5ffc025db051fe71dfffb00fdec836382e3041392ae3ddcc9e5b59
7
+ data.tar.gz: eb5411ac4f245210470d310767fabd2e877759a5dcb023b00ae4925e8dcb4dec16f7637441433ca978d809d807734b18e4e51d901f69107e8291d186309d2ba3
@@ -4,7 +4,7 @@ on: [push,pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
7
- runs-on: ubuntu-20.04
7
+ runs-on: ubuntu-22.04
8
8
  steps:
9
9
  - uses: actions/checkout@v2
10
10
  - uses: ruby/setup-ruby@v1
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.2.2
1
+ ruby 3.4.7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.1.0](https://github.com/department-of-veterans-affairs/vtk/tree/v1.1.0) (2025-12-15)
4
+
5
+ [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v1.0.0...v1.1.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - feat(scan): add vtk scan machine for Shai-Hulud detection [\#64](https://github.com/department-of-veterans-affairs/vtk/pull/64) ([ericboehs](https://github.com/ericboehs))
10
+ - fix: resolve rubocop offenses in socks/setup.rb [\#66](https://github.com/department-of-veterans-affairs/vtk/pull/66) ([ericboehs](https://github.com/ericboehs))
11
+ - Fix rubocop exceptions and update GH to run on Ubuntu Latest [\#63](https://github.com/department-of-veterans-affairs/vtk/pull/63) ([ericboehs](https://github.com/ericboehs))
12
+
13
+ ## [v1.0.0](https://github.com/department-of-veterans-affairs/vtk/tree/v1.0.0) (2024-09-18)
14
+
15
+ 🎉
16
+
17
+ [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v0.9.5...v1.0.0)
18
+
19
+ **Merged pull requests:**
20
+
21
+ - fix: OpenStruct is no longer auto required in Ruby 3.2 [\#62](https://github.com/department-of-veterans-affairs/vtk/pull/62) ([ericboehs](https://github.com/ericboehs))
22
+ - Update RuboCop w/ corrections [\#53](https://github.com/department-of-veterans-affairs/vtk/pull/53) ([ericboehs](https://github.com/ericboehs))
23
+
24
+ ## [v0.9.5](https://github.com/department-of-veterans-affairs/vtk/tree/v0.9.5) (2023-12-15)
25
+
26
+ [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v0.9.4...v0.9.5)
27
+
28
+ **Merged pull requests:**
29
+
30
+ - Add sudo to proxy setup command for MacOS. [\#52](https://github.com/department-of-veterans-affairs/vtk/pull/52) ([omahane](https://github.com/omahane))
31
+
3
32
  ## [v0.9.4](https://github.com/department-of-veterans-affairs/vtk/tree/v0.9.4) (2022-04-11)
4
33
 
5
34
  [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v0.9.3...v0.9.4)
data/README.md CHANGED
@@ -67,6 +67,39 @@ $ vtk socks off
67
67
  ----> Disconnected from SOCKS.
68
68
  ```
69
69
 
70
+ ### Scan
71
+
72
+ Security scanning commands for detecting malware and vulnerabilities.
73
+
74
+ ---
75
+
76
+ ```
77
+ $ vtk scan machine
78
+ ```
79
+
80
+ The **machine subcommand** checks your local machine for signs of active Shai-Hulud malware infection. This is a fast (~5 second) check that looks for:
81
+
82
+ - **Critical indicators**: `~/.dev-env/` persistence folder, malicious processes (Runner.Listener, SHA1HULUD), malware payload files
83
+ - **High-risk indicators**: Exfiltration artifacts, unexpected Bun/Trufflehog installations
84
+ - **Credential inventory**: Lists credential files that should be rotated if infected
85
+
86
+ **Exit codes:**
87
+ - `0` - Clean (no infection indicators)
88
+ - `1` - Infected (critical indicators found)
89
+ - `2` - Warning (needs investigation)
90
+
91
+ **Options:**
92
+ - `--verbose` / `-v` - Detailed output with all checks
93
+ - `--quiet` / `-q` - Exit code only, no output
94
+ - `--json` / `-j` - JSON output format
95
+
96
+ Example:
97
+ ```
98
+ $ vtk scan machine --quiet && echo "Clean" || echo "Check machine!"
99
+ ```
100
+
101
+ ---
102
+
70
103
  ### Help
71
104
 
72
105
  For helpful information about commands and subcommands run the following:
@@ -74,6 +107,7 @@ For helpful information about commands and subcommands run the following:
74
107
  $ vtk -h
75
108
  $ vtk module -h
76
109
  $ vtk socks -h
110
+ $ vtk scan -h
77
111
 
78
112
  ### Docker
79
113
 
data/lib/vtk/cli.rb CHANGED
@@ -23,5 +23,8 @@ module Vtk
23
23
 
24
24
  require_relative 'commands/module'
25
25
  register Vtk::Commands::Module, 'module', 'module [SUBCOMMAND]', 'Command description...'
26
+
27
+ require_relative 'commands/scan'
28
+ register Vtk::Commands::Scan, 'scan', 'scan [SUBCOMMAND]', 'Security scanning for malware and vulnerabilities'
26
29
  end
27
30
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require_relative '../../command'
5
+
6
+ module Vtk
7
+ module Commands
8
+ class Scan
9
+ # Check for active malware infection indicators (Shai-Hulud)
10
+ class Machine < Vtk::Command
11
+ attr_reader :options
12
+
13
+ def initialize(options)
14
+ @options = options
15
+ super()
16
+ end
17
+
18
+ def execute(output: $stdout)
19
+ @output = output
20
+
21
+ script_path, gem_root = find_script
22
+ return script_not_found(output, gem_root) unless script_path
23
+
24
+ run_script(script_path)
25
+ end
26
+
27
+ private
28
+
29
+ def script_not_found(output, gem_root)
30
+ output.puts 'ERROR: Could not find shai-hulud-machine-check.sh script'
31
+ output.puts "Expected at: #{gem_root}/scripts/shai-hulud-machine-check.sh"
32
+ 1
33
+ end
34
+
35
+ def run_script(script_path)
36
+ cmd = ['bash', script_path]
37
+ cmd << '--verbose' if options[:verbose]
38
+ cmd << '--json' if options[:json]
39
+ cmd << '--quiet' if options[:quiet]
40
+ cmd << "--scan-dirs=#{options[:scan_dirs]}" if options[:scan_dirs]
41
+
42
+ system(*cmd)
43
+ $CHILD_STATUS.exitstatus
44
+ end
45
+
46
+ def find_script
47
+ # Look for script relative to this gem's location
48
+ # __dir__ = lib/vtk/commands/scan, so go up 4 levels to get to vtk root
49
+ gem_root = File.expand_path('../../../..', __dir__)
50
+ script_path = File.join(gem_root, 'scripts', 'shai-hulud-machine-check.sh')
51
+
52
+ # Use explicit bash interpreter, so executable bit not required
53
+ return [script_path, gem_root] if File.exist?(script_path)
54
+
55
+ [nil, gem_root]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Vtk
6
+ module Commands
7
+ # Security scanning commands for developer machines and repositories
8
+ class Scan < Thor
9
+ namespace :scan
10
+
11
+ desc 'machine', 'Check for active malware infection indicators (Shai-Hulud)'
12
+ method_option :help, aliases: '-h', type: :boolean,
13
+ desc: 'Display usage information'
14
+ method_option :verbose, aliases: '-v', type: :boolean,
15
+ desc: 'Detailed output with all checks'
16
+ method_option :json, aliases: '-j', type: :boolean,
17
+ desc: 'Output results as JSON'
18
+ method_option :quiet, aliases: '-q', type: :boolean,
19
+ desc: 'Exit code only, no output'
20
+ method_option :scan_dirs, type: :string,
21
+ desc: 'Additional directories to scan for backdoor workflows (comma-separated)'
22
+ def machine
23
+ if options[:help]
24
+ invoke :help, ['machine']
25
+ else
26
+ require_relative 'scan/machine'
27
+ exit_status = Vtk::Commands::Scan::Machine.new(options).execute
28
+ exit exit_status
29
+ end
30
+ end
31
+
32
+ # Future subcommands:
33
+ # desc 'repos', 'Scan lockfiles for compromised packages'
34
+ # desc 'credentials', 'Inventory credentials that may need rotation'
35
+ end
36
+ end
37
+ end
@@ -208,7 +208,7 @@ module Vtk
208
208
  @repo_url ||= begin
209
209
  keyscan_github_com
210
210
 
211
- if github_ssh_configured
211
+ if github_ssh_configured?
212
212
  'git@github.com:department-of-veterans-affairs/devops.git'
213
213
  else
214
214
  'https://github.com/department-of-veterans-affairs/devops.git'
@@ -222,7 +222,7 @@ module Vtk
222
222
  `ssh-keyscan -H github.com >> ~/.ssh/known_hosts 2> /dev/null`
223
223
  end
224
224
 
225
- def github_ssh_configured
225
+ def github_ssh_configured?
226
226
  !`ssh -T git@github.com 2>&1`.include?('Permission denied')
227
227
  end
228
228
 
@@ -460,8 +460,8 @@ module Vtk
460
460
  return true if `gsettings get org.gnome.system.proxy mode` == "'auto'\n"
461
461
 
462
462
  log 'Configuring system proxy to use SOCKS tunnel...' do
463
- `gsettings set org.gnome.system.proxy mode 'auto'` &&
464
- `gsettings set org.gnome.system.proxy autoconfig-url "#{PROXY_URL}"`
463
+ system("gsettings set org.gnome.system.proxy mode 'auto'") &&
464
+ system("gsettings set org.gnome.system.proxy autoconfig-url '#{PROXY_URL}'")
465
465
  end
466
466
  end
467
467
 
data/lib/vtk/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vtk
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -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
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vtk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Boehs
8
8
  - Lindsey Hattamer
9
9
  - Travis Hilton
10
- autorequire:
11
10
  bindir: exe
12
11
  cert_chain: []
13
- date: 2024-09-18 00:00:00.000000000 Z
12
+ date: 1980-01-02 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: thor
@@ -91,6 +90,8 @@ files:
91
90
  - lib/vtk/commands/module/model.rb
92
91
  - lib/vtk/commands/module/serializer.rb
93
92
  - lib/vtk/commands/module/service.rb
93
+ - lib/vtk/commands/scan.rb
94
+ - lib/vtk/commands/scan/machine.rb
94
95
  - lib/vtk/commands/socks.rb
95
96
  - lib/vtk/commands/socks/off.rb
96
97
  - lib/vtk/commands/socks/on.rb
@@ -101,6 +102,7 @@ files:
101
102
  - lib/vtk/templates/socks/setup/gov.va.socks.plist.erb
102
103
  - lib/vtk/templates/socks/setup/va_gov_socks.service.erb
103
104
  - lib/vtk/version.rb
105
+ - scripts/shai-hulud-machine-check.sh
104
106
  - vtk.gemspec
105
107
  homepage: https://github.com/department-of-veterans-affairs/vtk
106
108
  licenses:
@@ -111,7 +113,6 @@ metadata:
111
113
  source_code_uri: https://github.com/department-of-veterans-affairs/vtk
112
114
  changelog_uri: https://github.com/department-of-veterans-affairs/vtk/blob/master/CHANGELOG.md
113
115
  rubygems_mfa_required: 'true'
114
- post_install_message:
115
116
  rdoc_options: []
116
117
  require_paths:
117
118
  - lib
@@ -126,8 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
127
  - !ruby/object:Gem::Version
127
128
  version: '0'
128
129
  requirements: []
129
- rubygems_version: 3.4.14
130
- signing_key:
130
+ rubygems_version: 3.6.9
131
131
  specification_version: 4
132
132
  summary: A CLI for the platform
133
133
  test_files: []