vtk 1.1.0 → 1.3.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: c41b577facc94e32eb15aeea7f5817a49f54c31a568654c5dfe5da082d0775f9
4
- data.tar.gz: 87820698519528ff97594bf9e148a0a20ca700026079a0fc2b65ede3bee84b19
3
+ metadata.gz: 5cdbc7b68bcc69890c11e5ac9e9db8b14c224616d223fe02309b329c12c52783
4
+ data.tar.gz: 777d7543e02729b297de739b379e55dcd954e1403f8d81d3f142fa9f33afd9f6
5
5
  SHA512:
6
- metadata.gz: 729f7b3a93dd61661ad557d471796a059ada830970afe16bbeef7bdf84e0e92887a321249c5ffc025db051fe71dfffb00fdec836382e3041392ae3ddcc9e5b59
7
- data.tar.gz: eb5411ac4f245210470d310767fabd2e877759a5dcb023b00ae4925e8dcb4dec16f7637441433ca978d809d807734b18e4e51d901f69107e8291d186309d2ba3
6
+ metadata.gz: 0c99ba6157bf038dbcca4510a7b1443aa1de2896404d066852b18822af9f2ef07778f55efa73f5c0df738dc69f9d6fb38c4cfabef986d66d0a7a99a9d183dd4a
7
+ data.tar.gz: c8af1ce8914b585ef40002a15576a08011fa49a7d258b65ffe37c565b6a1b9d804d7d1b6c686451e09a3994db1e3aca332609e8b20926cdc37e974129686d4c7
data/CHANGELOG.md CHANGED
@@ -1,21 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.3.0](https://github.com/department-of-veterans-affairs/vtk/tree/v1.3.0) (2026-04-14)
4
+
5
+ [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v1.2.0...v1.3.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - feat\(scan\): add vtk scan actions for tracing GitHub Actions usage [\#72](https://github.com/department-of-veterans-affairs/vtk/pull/72) ([ericboehs](https://github.com/ericboehs))
10
+
11
+ ## [v1.2.0](https://github.com/department-of-veterans-affairs/vtk/tree/v1.2.0) (2026-01-09)
12
+
13
+ [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v1.1.0...v1.2.0)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - chore: release v1.2.0 [\#70](https://github.com/department-of-veterans-affairs/vtk/pull/70) ([ericboehs](https://github.com/ericboehs))
18
+ - feat\(scan\): add PowerShell scripts for Windows users [\#69](https://github.com/department-of-veterans-affairs/vtk/pull/69) ([ericboehs](https://github.com/ericboehs))
19
+ - feat\(scan\): add vtk scan credentials for security incident response [\#68](https://github.com/department-of-veterans-affairs/vtk/pull/68) ([ericboehs](https://github.com/ericboehs))
20
+ - feat\(scan\): add vtk scan repo for compromised package detection [\#65](https://github.com/department-of-veterans-affairs/vtk/pull/65) ([ericboehs](https://github.com/ericboehs))
21
+
3
22
  ## [v1.1.0](https://github.com/department-of-veterans-affairs/vtk/tree/v1.1.0) (2025-12-15)
4
23
 
5
24
  [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v1.0.0...v1.1.0)
6
25
 
7
26
  **Merged pull requests:**
8
27
 
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))
28
+ - chore: bump version to 1.1.0 [\#67](https://github.com/department-of-veterans-affairs/vtk/pull/67) ([ericboehs](https://github.com/ericboehs))
10
29
  - 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))
30
+ - 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))
31
+ - Update GH to run on Ubuntu Latest [\#63](https://github.com/department-of-veterans-affairs/vtk/pull/63) ([ericboehs](https://github.com/ericboehs))
12
32
 
13
33
  ## [v1.0.0](https://github.com/department-of-veterans-affairs/vtk/tree/v1.0.0) (2024-09-18)
14
34
 
15
- 🎉
16
-
17
35
  [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v0.9.5...v1.0.0)
18
36
 
37
+ **Closed issues:**
38
+
39
+ - Error: GitHub Repository Not Mapped To eMASS System [\#59](https://github.com/department-of-veterans-affairs/vtk/issues/59)
40
+ - Error: GitHub Repository Not Mapped To eMASS System [\#58](https://github.com/department-of-veterans-affairs/vtk/issues/58)
41
+ - Error: GitHub Repository Not Mapped To eMASS System [\#57](https://github.com/department-of-veterans-affairs/vtk/issues/57)
42
+ - Error: GitHub Repository Not Mapped To eMASS System [\#56](https://github.com/department-of-veterans-affairs/vtk/issues/56)
43
+ - Error: GitHub Repository Not Mapped To eMASS System [\#55](https://github.com/department-of-veterans-affairs/vtk/issues/55)
44
+ - Error: GitHub Repository Not Mapped To eMASS System [\#54](https://github.com/department-of-veterans-affairs/vtk/issues/54)
45
+ - Error: GitHub Repository Not Mapped To eMASS System [\#51](https://github.com/department-of-veterans-affairs/vtk/issues/51)
46
+
19
47
  **Merged pull requests:**
20
48
 
21
49
  - 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))
@@ -25,6 +53,26 @@
25
53
 
26
54
  [Full Changelog](https://github.com/department-of-veterans-affairs/vtk/compare/v0.9.4...v0.9.5)
27
55
 
56
+ **Closed issues:**
57
+
58
+ - Error: GitHub Repository Not Mapped To eMASS System [\#50](https://github.com/department-of-veterans-affairs/vtk/issues/50)
59
+ - Error: GitHub Repository Not Mapped To eMASS System [\#49](https://github.com/department-of-veterans-affairs/vtk/issues/49)
60
+ - Error: GitHub Repository Not Mapped To eMASS System [\#48](https://github.com/department-of-veterans-affairs/vtk/issues/48)
61
+ - Error: GitHub Repository Not Mapped To eMASS System [\#47](https://github.com/department-of-veterans-affairs/vtk/issues/47)
62
+ - Error: GitHub Repository Not Mapped To eMASS System [\#46](https://github.com/department-of-veterans-affairs/vtk/issues/46)
63
+ - Error: GitHub Repository Not Mapped To eMASS System [\#45](https://github.com/department-of-veterans-affairs/vtk/issues/45)
64
+ - Error: GitHub Repository Not Mapped To eMASS System [\#44](https://github.com/department-of-veterans-affairs/vtk/issues/44)
65
+ - Error: GitHub Repository Not Mapped To eMASS System [\#43](https://github.com/department-of-veterans-affairs/vtk/issues/43)
66
+ - Error: GitHub Repository Not Mapped To eMASS System [\#42](https://github.com/department-of-veterans-affairs/vtk/issues/42)
67
+ - Error: GitHub Repository Not Mapped To eMASS System [\#41](https://github.com/department-of-veterans-affairs/vtk/issues/41)
68
+ - Error: GitHub Repository Not Mapped To eMASS System [\#40](https://github.com/department-of-veterans-affairs/vtk/issues/40)
69
+ - Error: GitHub Repository Not Mapped To eMASS System [\#39](https://github.com/department-of-veterans-affairs/vtk/issues/39)
70
+ - Error: GitHub Repository Not Mapped To eMASS System [\#38](https://github.com/department-of-veterans-affairs/vtk/issues/38)
71
+ - Error: GitHub Repository Not Mapped To eMASS System [\#37](https://github.com/department-of-veterans-affairs/vtk/issues/37)
72
+ - Error: GitHub Repository Not Mapped To eMASS System [\#36](https://github.com/department-of-veterans-affairs/vtk/issues/36)
73
+ - Error: GitHub Repository Not Mapped To eMASS System [\#35](https://github.com/department-of-veterans-affairs/vtk/issues/35)
74
+ - Notice: Automatic archival of repository due to inactivity [\#34](https://github.com/department-of-veterans-affairs/vtk/issues/34)
75
+
28
76
  **Merged pull requests:**
29
77
 
30
78
  - Add sudo to proxy setup command for MacOS. [\#52](https://github.com/department-of-veterans-affairs/vtk/pull/52) ([omahane](https://github.com/omahane))
data/README.md CHANGED
@@ -98,6 +98,81 @@ Example:
98
98
  $ vtk scan machine --quiet && echo "Clean" || echo "Check machine!"
99
99
  ```
100
100
 
101
+ #### Windows (PowerShell)
102
+
103
+ For Windows users, standalone PowerShell scripts are available that don't require Ruby or the vtk gem.
104
+
105
+ **Requirements:** PowerShell 5.1+ (ships with Windows 10/11). PowerShell 7+ recommended for best experience.
106
+
107
+ **Download and run:**
108
+
109
+ ```powershell
110
+ # Machine scanner - checks for active infection
111
+ Invoke-WebRequest -Uri "https://raw.githubusercontent.com/department-of-veterans-affairs/vtk/master/scripts/shai-hulud-machine-check.ps1" -OutFile "shai-hulud-machine-check.ps1"
112
+
113
+ # Allow script execution (if needed)
114
+ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
115
+
116
+ # Run the scanner
117
+ .\shai-hulud-machine-check.ps1
118
+ .\shai-hulud-machine-check.ps1 -Verbose # Detailed output
119
+ .\shai-hulud-machine-check.ps1 -Json # JSON output
120
+ ```
121
+
122
+ **Repository scanner** - checks lockfiles for compromised packages:
123
+
124
+ ```powershell
125
+ Invoke-WebRequest -Uri "https://raw.githubusercontent.com/department-of-veterans-affairs/vtk/master/scripts/shai-hulud-repo-check.ps1" -OutFile "shai-hulud-repo-check.ps1"
126
+
127
+ .\shai-hulud-repo-check.ps1 # Scan current directory
128
+ .\shai-hulud-repo-check.ps1 -Path C:\Code\app # Scan specific directory
129
+ .\shai-hulud-repo-check.ps1 -Recursive # Recursive scan
130
+ ```
131
+
132
+ **Credential audit** - lists credentials that may need rotation:
133
+
134
+ ```powershell
135
+ Invoke-WebRequest -Uri "https://raw.githubusercontent.com/department-of-veterans-affairs/vtk/master/scripts/credential-audit.ps1" -OutFile "credential-audit.ps1"
136
+
137
+ .\credential-audit.ps1
138
+ .\credential-audit.ps1 -Verbose # Show all checks
139
+ ```
140
+
141
+ ---
142
+
143
+ ```
144
+ $ vtk scan credentials
145
+ ```
146
+
147
+ The **credentials subcommand** audits which credentials are present on your machine and provides rotation instructions for each. Run this after a suspected or confirmed security incident.
148
+
149
+ **What it checks:**
150
+ - **NPM**: `~/.npmrc`, `$NPM_TOKEN`, `$NPM_CONFIG_TOKEN`
151
+ - **AWS**: `~/.aws/credentials`, `~/.aws/config`, `$AWS_ACCESS_KEY_ID`, `$AWS_SECRET_ACCESS_KEY`
152
+ - **GCP**: `~/.config/gcloud/application_default_credentials.json`, `$GOOGLE_APPLICATION_CREDENTIALS`
153
+ - **Azure**: `~/.azure/` directory, `$AZURE_CLIENT_SECRET`
154
+ - **GitHub**: `~/.config/gh/hosts.yml`, `$GITHUB_TOKEN`, `$GH_TOKEN`, `~/.git-credentials`
155
+ - **SSH**: Private keys in `~/.ssh/`
156
+ - **Docker**: `~/.docker/config.json`
157
+ - **Kubernetes**: `~/.kube/config`
158
+ - **Environment**: Sensitive env vars (token, secret, password, etc.)
159
+
160
+ **Exit codes:**
161
+ - `0` - No credentials found
162
+ - `1` - Credentials found (rotation recommended)
163
+
164
+ **Options:**
165
+ - `--verbose` / `-v` - Show all checks including clean ones
166
+ - `--json` / `-j` - JSON output format
167
+
168
+ Example:
169
+ ```
170
+ $ vtk scan credentials --json | jq -r '.credentials[].service' | sort -u
171
+ AWS
172
+ GitHub
173
+ SSH
174
+ ```
175
+
101
176
  ---
102
177
 
103
178
  ### Help
data/lib/vtk/cli.rb CHANGED
@@ -9,7 +9,7 @@ module Vtk
9
9
  # @api public
10
10
  class CLI < Thor
11
11
  # Error raised by this runner
12
- Error = Class.new(StandardError)
12
+ class Error < StandardError; end
13
13
 
14
14
  desc 'version', 'vtk version'
15
15
  def version
@@ -0,0 +1,102 @@
1
+ # VTK Scan Commands
2
+
3
+ Security scanning commands for detecting Shai-Hulud malware and supply chain compromises.
4
+
5
+ ## Commands
6
+
7
+ ### `vtk scan machine`
8
+
9
+ Quick check for active malware infection on your developer machine.
10
+
11
+ ```bash
12
+ vtk scan machine # Compact output
13
+ vtk scan machine --verbose # Detailed checks
14
+ vtk scan machine --json # JSON output
15
+ vtk scan machine --quiet # Exit code only
16
+ ```
17
+
18
+ **What it checks:**
19
+ - `~/.dev-env/` persistence folder (critical indicator)
20
+ - Malicious processes (Runner.Listener, SHA1HULUD)
21
+ - Malware files (setup_bun.js, bun_environment.js)
22
+ - Exfiltration artifacts
23
+ - Unexpected Bun/Trufflehog installations
24
+
25
+ **Exit codes:**
26
+ - `0` - Clean
27
+ - `1` - Infected (critical indicators found)
28
+ - `2` - Warning (needs investigation)
29
+
30
+ ### `vtk scan repo [PATH]`
31
+
32
+ Scan a repository for compromised npm packages and backdoor workflows.
33
+
34
+ ```bash
35
+ vtk scan repo # Scan current directory
36
+ vtk scan repo /path/to/repo # Scan specific path
37
+ vtk scan repo -r # Recursive scan (default depth: 5)
38
+ vtk scan repo -r --depth=10 # Recursive with custom depth
39
+ vtk scan repo -r --depth=0 # Recursive with unlimited depth
40
+ vtk scan repo -v # Verbose output (show each lockfile)
41
+ vtk scan repo --refresh # Force refresh package list
42
+ vtk scan repo --json # JSON output
43
+ vtk scan repo -r --json # JSONL output (streaming)
44
+ vtk scan repo --quiet # Exit code only
45
+ ```
46
+
47
+ **What it checks:**
48
+ - Lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) against 1,600+ known compromised packages
49
+ - Backdoor workflows (.github/workflows/discussion.yaml)
50
+ - Secrets extraction workflows (.github/workflows/formatter_*.yml)
51
+
52
+ **Exit codes:**
53
+ - `0` - Clean
54
+ - `1` - Compromised packages found
55
+ - `2` - Backdoor workflow found
56
+
57
+ **Output formats:**
58
+
59
+ JSON (`--json`) - Single JSON object for non-recursive scans:
60
+ ```json
61
+ {"path":"/repo","status":"CLEAN","packages_scanned":2595,"compromised_packages":[],...}
62
+ ```
63
+
64
+ JSONL (`-r --json`) - One JSON line per lockfile as scanned (streaming):
65
+ ```
66
+ {"lockfile":"/repo/yarn.lock","packages_scanned":2595,"status":"CLEAN","compromised_packages":[]}
67
+ {"lockfile":"/repo/app/package-lock.json","packages_scanned":100,"status":"CLEAN","compromised_packages":[]}
68
+ {"type":"summary","status":"CLEAN","total_packages_scanned":2695,...}
69
+ ```
70
+
71
+ ## Data Sources
72
+
73
+ The compromised packages list is fetched from [Cobenian/shai-hulud-detect](https://github.com/Cobenian/shai-hulud-detect) and cached locally for 24 hours.
74
+
75
+ **Cache location:** `$XDG_CACHE_HOME/vtk/compromised-packages.txt` (defaults to `~/.cache/vtk/`)
76
+
77
+ ## Standalone Scripts
78
+
79
+ Both scan commands are available as standalone shell scripts that work without vtk installed. These are useful for CI/CD pipelines or machines without Ruby.
80
+
81
+ ```bash
82
+ # Machine scan
83
+ ./scripts/shai-hulud-machine-check.sh
84
+ ./scripts/shai-hulud-machine-check.sh --json
85
+
86
+ # Repo scan
87
+ ./scripts/shai-hulud-repo-check.sh /path/to/repo
88
+ ./scripts/shai-hulud-repo-check.sh -r ~/Code # Scan all projects
89
+ ./scripts/shai-hulud-repo-check.sh -r --json # JSONL output
90
+ ```
91
+
92
+ ## Known Limitations
93
+
94
+ - **pnpm-lock.yaml** - Parsing tested with pnpm v6/v7/v9; other versions may vary
95
+ - **node_modules** - Not scanned directly; lockfile is the source of truth (by design)
96
+ - **Short flags** - Cannot be combined in shell script (use `-r -j` not `-rj`)
97
+
98
+ ## References
99
+
100
+ - [Datadog: Shai-Hulud 2.0 npm Worm](https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/)
101
+ - [Cobenian/shai-hulud-detect](https://github.com/Cobenian/shai-hulud-detect)
102
+ - [VA Cleanup Playbook](https://department-of-veterans-affairs.github.io/eert/shai-hulud-dev-machine-cleanup-playbook)
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require_relative '../../command'
5
+
6
+ module Vtk
7
+ module Commands
8
+ class Scan
9
+ # Trace direct and transitive uses of GitHub Actions across an org.
10
+ # Shells out to gh-action-trace.sh for the actual work.
11
+ class Actions < Vtk::Command
12
+ BOOLEAN_FLAGS = {
13
+ external: '--external',
14
+ quiet: '--quiet',
15
+ verbose: '--verbose'
16
+ }.freeze
17
+
18
+ VALUE_FLAGS = {
19
+ org: '--org',
20
+ depth: '--depth',
21
+ format: '--format',
22
+ output: '--output',
23
+ check_runs: '--check-runs'
24
+ }.freeze
25
+
26
+ attr_reader :options
27
+
28
+ def initialize(options)
29
+ @options = options
30
+ super()
31
+ end
32
+
33
+ def execute(output: $stdout)
34
+ error = validation_error
35
+ return error_out(output, error) if error
36
+
37
+ script_path, gem_root = find_script
38
+ return script_not_found(output, gem_root) unless script_path
39
+
40
+ run_script(script_path)
41
+ end
42
+
43
+ private
44
+
45
+ def validation_error
46
+ return 'ERROR: --org is required' if blank?(options[:org])
47
+ return 'ERROR: --action is required (at least one)' if blank?(options[:action])
48
+
49
+ nil
50
+ end
51
+
52
+ def blank?(value)
53
+ value.nil? || value.to_s.empty? || (value.respond_to?(:empty?) && value.empty?)
54
+ end
55
+
56
+ def error_out(output, message)
57
+ output.puts message
58
+ 1
59
+ end
60
+
61
+ def script_not_found(output, gem_root)
62
+ output.puts 'ERROR: Could not find gh-action-trace.sh script'
63
+ output.puts "Expected at: #{gem_root}/scripts/gh-action-trace.sh"
64
+ 1
65
+ end
66
+
67
+ def run_script(script_path)
68
+ cmd = ['bash', script_path] + script_options
69
+ system(*cmd)
70
+ $CHILD_STATUS.exitstatus
71
+ end
72
+
73
+ def script_options
74
+ boolean_script_flags + value_script_flags + action_script_flags
75
+ end
76
+
77
+ def boolean_script_flags
78
+ BOOLEAN_FLAGS.select { |key, _| options[key] }.values
79
+ end
80
+
81
+ def value_script_flags
82
+ VALUE_FLAGS.flat_map do |key, flag|
83
+ value = options[key]
84
+ blank?(value) ? [] : [flag, value.to_s]
85
+ end
86
+ end
87
+
88
+ def action_script_flags
89
+ Array(options[:action]).flat_map { |action| ['--action', action] }
90
+ end
91
+
92
+ def find_script
93
+ # __dir__ = lib/vtk/commands/scan, so go up 4 levels to the gem root
94
+ gem_root = File.expand_path('../../../..', __dir__)
95
+ script_path = File.join(gem_root, 'scripts', 'gh-action-trace.sh')
96
+ return [script_path, gem_root] if File.exist?(script_path)
97
+
98
+ [nil, gem_root]
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require_relative '../../command'
5
+
6
+ module Vtk
7
+ module Commands
8
+ class Scan
9
+ # Audit credentials that may have been accessed by Shai-Hulud malware
10
+ class Credentials < Vtk::Command
11
+ attr_reader :options
12
+
13
+ def initialize(options)
14
+ @options = options
15
+ super()
16
+ end
17
+
18
+ OPTION_FLAGS = {
19
+ verbose: '--verbose',
20
+ json: '--json'
21
+ }.freeze
22
+
23
+ def execute(output: $stdout)
24
+ @output = output
25
+
26
+ script_path, gem_root = find_script
27
+ return script_not_found(output, gem_root) unless script_path
28
+
29
+ run_script(script_path)
30
+ end
31
+
32
+ private
33
+
34
+ def script_not_found(output, gem_root)
35
+ output.puts 'ERROR: Could not find credential-audit.sh script'
36
+ output.puts "Expected at: #{gem_root}/scripts/credential-audit.sh"
37
+ 1
38
+ end
39
+
40
+ def run_script(script_path)
41
+ cmd = ['bash', script_path]
42
+ cmd += OPTION_FLAGS.filter_map { |key, flag| flag if options[key] }
43
+
44
+ system(*cmd)
45
+ $CHILD_STATUS.exitstatus
46
+ end
47
+
48
+ def find_script
49
+ gem_root = File.expand_path('../../../..', __dir__)
50
+ script_path = File.join(gem_root, 'scripts', 'credential-audit.sh')
51
+
52
+ return [script_path, gem_root] if File.exist?(script_path)
53
+
54
+ [nil, gem_root]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require_relative '../../command'
5
+
6
+ module Vtk
7
+ module Commands
8
+ class Scan
9
+ # Scan a repository for compromised packages and backdoor workflows
10
+ # Shells out to shai-hulud-repo-check.sh for the actual scanning
11
+ class Repo < Vtk::Command
12
+ OPTION_FLAGS = {
13
+ refresh: '--refresh',
14
+ json: '--json',
15
+ quiet: '--quiet',
16
+ verbose: '--verbose',
17
+ recursive: '--recursive'
18
+ }.freeze
19
+
20
+ attr_reader :options, :path
21
+
22
+ def initialize(path, options)
23
+ @path = path || Dir.pwd
24
+ @options = options
25
+ super()
26
+ end
27
+
28
+ def execute(output: $stdout)
29
+ @output = output
30
+
31
+ unless File.directory?(@path)
32
+ output.puts "ERROR: Directory not found: #{@path}"
33
+ return 1
34
+ end
35
+
36
+ script_path, gem_root = find_script
37
+ return script_not_found(output, gem_root) unless script_path
38
+
39
+ run_script(script_path)
40
+ end
41
+
42
+ private
43
+
44
+ def script_not_found(output, gem_root)
45
+ output.puts 'ERROR: Could not find shai-hulud-repo-check.sh script'
46
+ output.puts "Expected at: #{gem_root}/scripts/shai-hulud-repo-check.sh"
47
+ 1
48
+ end
49
+
50
+ def run_script(script_path)
51
+ cmd = ['bash', script_path] + script_options + [@path]
52
+ system(*cmd)
53
+ $CHILD_STATUS.exitstatus
54
+ end
55
+
56
+ def script_options
57
+ # Use select + map instead of filter_map for Ruby 2.6 compatibility
58
+ flags = OPTION_FLAGS.select { |key, _flag| options[key] }.values
59
+ flags << "--depth=#{options[:depth]}" if options[:depth]
60
+ flags
61
+ end
62
+
63
+ def find_script
64
+ # Look for script relative to this gem's location
65
+ # __dir__ = lib/vtk/commands/scan, so go up 4 levels to get to vtk root
66
+ gem_root = File.expand_path('../../../..', __dir__)
67
+ script_path = File.join(gem_root, 'scripts', 'shai-hulud-repo-check.sh')
68
+
69
+ # Use explicit bash interpreter, so executable bit not required
70
+ return [script_path, gem_root] if File.exist?(script_path)
71
+
72
+ [nil, gem_root]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -29,9 +29,78 @@ module Vtk
29
29
  end
30
30
  end
31
31
 
32
- # Future subcommands:
33
- # desc 'repos', 'Scan lockfiles for compromised packages'
34
- # desc 'credentials', 'Inventory credentials that may need rotation'
32
+ desc 'repo [PATH]', 'Scan a repository for compromised packages'
33
+ method_option :help, aliases: '-h', type: :boolean,
34
+ desc: 'Display usage information'
35
+ method_option :refresh, type: :boolean,
36
+ desc: 'Force refresh of compromised packages list'
37
+ method_option :json, aliases: '-j', type: :boolean,
38
+ desc: 'Output results as JSON'
39
+ method_option :quiet, aliases: '-q', type: :boolean,
40
+ desc: 'Exit code only, no output'
41
+ method_option :verbose, aliases: '-v', type: :boolean,
42
+ desc: 'Show each lockfile as it is scanned'
43
+ method_option :recursive, aliases: '-r', type: :boolean,
44
+ desc: 'Recursively scan subdirectories (default depth: 5)'
45
+ method_option :depth, type: :numeric, default: 5,
46
+ desc: 'Max directory depth for recursive scan (0=unlimited)'
47
+ def repo(path = nil)
48
+ if options[:help]
49
+ invoke :help, ['repo']
50
+ else
51
+ require_relative 'scan/repo'
52
+ exit_status = Vtk::Commands::Scan::Repo.new(path, options).execute
53
+ exit exit_status
54
+ end
55
+ end
56
+
57
+ desc 'credentials', 'Audit credentials that may need rotation after a security incident'
58
+ method_option :help, aliases: '-h', type: :boolean,
59
+ desc: 'Display usage information'
60
+ method_option :verbose, aliases: '-v', type: :boolean,
61
+ desc: 'Show all checks including clean ones'
62
+ method_option :json, aliases: '-j', type: :boolean,
63
+ desc: 'Output results as JSON'
64
+ def credentials
65
+ if options[:help]
66
+ invoke :help, ['credentials']
67
+ else
68
+ require_relative 'scan/credentials'
69
+ exit_status = Vtk::Commands::Scan::Credentials.new(options).execute
70
+ exit exit_status
71
+ end
72
+ end
73
+
74
+ desc 'actions', 'Trace direct and transitive uses of GitHub Actions across an org'
75
+ method_option :help, aliases: '-h', type: :boolean,
76
+ desc: 'Display usage information'
77
+ method_option :org, type: :string, required: false,
78
+ desc: 'GitHub org to search (required)'
79
+ method_option :action, type: :array, default: [],
80
+ desc: 'Action to trace; repeat to trace multiple (required)'
81
+ method_option :depth, type: :numeric,
82
+ desc: 'Max recursion depth for shared workflows (default: 2)'
83
+ method_option :format, type: :string,
84
+ desc: 'Output format: text, json, csv, both (default: both)'
85
+ method_option :external, type: :boolean,
86
+ desc: 'Also search all of GitHub for external shared workflows (slower)'
87
+ method_option :output, type: :string,
88
+ desc: 'Write report output to file (JSON or CSV depending on --format)'
89
+ method_option :check_runs, type: :string,
90
+ desc: 'Check workflow run history during ISO 8601 window (FROM..TO, TO optional)'
91
+ method_option :quiet, aliases: '-q', type: :boolean,
92
+ desc: 'Suppress progress output'
93
+ method_option :verbose, aliases: '-v', type: :boolean,
94
+ desc: 'Show detailed debug info'
95
+ def actions
96
+ if options[:help]
97
+ invoke :help, ['actions']
98
+ else
99
+ require_relative 'scan/actions'
100
+ exit_status = Vtk::Commands::Scan::Actions.new(options).execute
101
+ exit exit_status
102
+ end
103
+ end
35
104
  end
36
105
  end
37
106
  end
@@ -490,7 +490,7 @@ module Vtk
490
490
  end
491
491
 
492
492
  def wsl?
493
- @wsl ||= File.exist?('/proc/version') && File.open('/proc/version').grep(/Microsoft/i).any?
493
+ @wsl ||= File.exist?('/proc/version') && File.foreach('/proc/version').grep(/Microsoft/i).any?
494
494
  end
495
495
 
496
496
  def ubuntu_like?
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.1.0'
4
+ VERSION = '1.3.0'
5
5
  end