sentinel-ci 0.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +235 -0
  4. data/bin/gh-workflow-scanner +1 -0
  5. data/bin/sentinel +57 -0
  6. data/lib/auto_fix.rb +485 -0
  7. data/lib/cli/bot.rb +53 -0
  8. data/lib/cli/fix.rb +50 -0
  9. data/lib/cli/scan.rb +145 -0
  10. data/lib/clone_client.rb +64 -0
  11. data/lib/finding.rb +27 -0
  12. data/lib/formatter/json.rb +18 -0
  13. data/lib/formatter/terminal.rb +47 -0
  14. data/lib/github_client.rb +98 -0
  15. data/lib/local_client.rb +33 -0
  16. data/lib/rule_engine.rb +39 -0
  17. data/lib/rules/allow_forks_artifact.rb +22 -0
  18. data/lib/rules/base.rb +33 -0
  19. data/lib/rules/build_publish_same_job.rb +39 -0
  20. data/lib/rules/credential_window.rb +43 -0
  21. data/lib/rules/curl_pipe_shell.rb +29 -0
  22. data/lib/rules/dangerous_triggers.rb +43 -0
  23. data/lib/rules/docker_build_arg_secrets.rb +30 -0
  24. data/lib/rules/git_config_global.rb +25 -0
  25. data/lib/rules/missing_env_protection.rb +37 -0
  26. data/lib/rules/missing_frozen_lockfile.rb +28 -0
  27. data/lib/rules/missing_permissions.rb +18 -0
  28. data/lib/rules/missing_persist_creds.rb +51 -0
  29. data/lib/rules/missing_timeouts.rb +25 -0
  30. data/lib/rules/overly_broad_triggers.rb +31 -0
  31. data/lib/rules/shell_injection_expr.rb +57 -0
  32. data/lib/rules/shell_injection_jq.rb +59 -0
  33. data/lib/rules/static_aws_credentials.rb +33 -0
  34. data/lib/rules/unpinned_actions.rb +35 -0
  35. data/lib/rules/unpinned_docker_image.rb +25 -0
  36. data/lib/rules/unscoped_app_token.rb +31 -0
  37. data/lib/scanner.rb +95 -0
  38. data/lib/sha_resolver.rb +60 -0
  39. data/lib/version.rb +3 -0
  40. data/lib/workflow.rb +100 -0
  41. metadata +84 -0
data/lib/workflow.rb ADDED
@@ -0,0 +1,100 @@
1
+ require "yaml"
2
+
3
+ class Workflow
4
+ attr_reader :filename, :raw, :raw_lines, :data
5
+
6
+ def initialize(filename:, content:)
7
+ @filename = filename
8
+ @raw = content
9
+ @raw_lines = content.lines
10
+ @data = YAML.safe_load(content, permitted_classes: [Symbol]) || {}
11
+ rescue YAML::SyntaxError => e
12
+ @data = {}
13
+ @parse_error = e.message
14
+ end
15
+
16
+ def parse_error? = !@parse_error.nil?
17
+
18
+ def triggers
19
+ @data["on"] || @data[true] || {}
20
+ end
21
+
22
+ def jobs
23
+ @data["jobs"] || {}
24
+ end
25
+
26
+ def steps(job)
27
+ job_hash = job.is_a?(String) ? jobs[job] : job
28
+ return [] unless job_hash.is_a?(Hash)
29
+ job_hash["steps"] || []
30
+ end
31
+
32
+ def permissions(scope: :workflow, job: nil)
33
+ case scope
34
+ when :workflow
35
+ @data["permissions"]
36
+ when :job
37
+ j = job.is_a?(String) ? jobs[job] : job
38
+ j&.dig("permissions")
39
+ end
40
+ end
41
+
42
+ def env(scope: :workflow, step: nil)
43
+ case scope
44
+ when :workflow
45
+ @data["env"] || {}
46
+ when :step
47
+ step&.dig("env") || {}
48
+ end
49
+ end
50
+
51
+ def line_of(pattern)
52
+ raw_lines.each_with_index do |line, i|
53
+ return i + 1 if line.match?(pattern)
54
+ end
55
+ nil
56
+ end
57
+
58
+ def lines_of(pattern)
59
+ results = []
60
+ raw_lines.each_with_index do |line, i|
61
+ results << (i + 1) if line.match?(pattern)
62
+ end
63
+ results
64
+ end
65
+
66
+ def line_content(num)
67
+ raw_lines[num - 1]&.rstrip
68
+ end
69
+
70
+ def uses_actions
71
+ results = []
72
+ seen_lines = Hash.new(0)
73
+ jobs.each do |_job_id, job_hash|
74
+ steps(job_hash).each do |step|
75
+ next unless step["uses"]
76
+ all_lines = lines_of(/uses:\s*#{Regexp.escape(step["uses"])}/)
77
+ idx = seen_lines[step["uses"]]
78
+ line = all_lines[idx] || all_lines.last
79
+ seen_lines[step["uses"]] += 1
80
+ results << { uses: step["uses"], step: step, line: line }
81
+ end
82
+ end
83
+ results
84
+ end
85
+
86
+ def run_blocks
87
+ results = []
88
+ all_run_lines = lines_of(/^\s+run:\s*[\|>]?\s*/)
89
+ run_idx = 0
90
+ jobs.each do |_job_id, job_hash|
91
+ steps(job_hash).each do |step|
92
+ next unless step["run"]
93
+ line = all_run_lines[run_idx] || all_run_lines.last
94
+ run_idx += 1
95
+ results << { run: step["run"], step: step, env: step["env"] || {}, line: line }
96
+ end
97
+ end
98
+ results
99
+ end
100
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sentinel-ci
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jordan Ritter
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Scan GitHub Actions workflows for 21 security vulnerabilities. SHA pinning,
13
+ shell injection, credential exposure, dangerous triggers. No AI, no dependencies
14
+ — pure Ruby stdlib.
15
+ email: jpr5@darkridge.com
16
+ executables:
17
+ - sentinel
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - bin/gh-workflow-scanner
24
+ - bin/sentinel
25
+ - lib/auto_fix.rb
26
+ - lib/cli/bot.rb
27
+ - lib/cli/fix.rb
28
+ - lib/cli/scan.rb
29
+ - lib/clone_client.rb
30
+ - lib/finding.rb
31
+ - lib/formatter/json.rb
32
+ - lib/formatter/terminal.rb
33
+ - lib/github_client.rb
34
+ - lib/local_client.rb
35
+ - lib/rule_engine.rb
36
+ - lib/rules/allow_forks_artifact.rb
37
+ - lib/rules/base.rb
38
+ - lib/rules/build_publish_same_job.rb
39
+ - lib/rules/credential_window.rb
40
+ - lib/rules/curl_pipe_shell.rb
41
+ - lib/rules/dangerous_triggers.rb
42
+ - lib/rules/docker_build_arg_secrets.rb
43
+ - lib/rules/git_config_global.rb
44
+ - lib/rules/missing_env_protection.rb
45
+ - lib/rules/missing_frozen_lockfile.rb
46
+ - lib/rules/missing_permissions.rb
47
+ - lib/rules/missing_persist_creds.rb
48
+ - lib/rules/missing_timeouts.rb
49
+ - lib/rules/overly_broad_triggers.rb
50
+ - lib/rules/shell_injection_expr.rb
51
+ - lib/rules/shell_injection_jq.rb
52
+ - lib/rules/static_aws_credentials.rb
53
+ - lib/rules/unpinned_actions.rb
54
+ - lib/rules/unpinned_docker_image.rb
55
+ - lib/rules/unscoped_app_token.rb
56
+ - lib/scanner.rb
57
+ - lib/sha_resolver.rb
58
+ - lib/version.rb
59
+ - lib/workflow.rb
60
+ homepage: https://sentinel.copilotkit.dev
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ source_code_uri: https://github.com/CopilotKit/sentinel
65
+ bug_tracker_uri: https://github.com/CopilotKit/sentinel/issues
66
+ homepage_uri: https://sentinel.copilotkit.dev
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '3.2'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.6.9
82
+ specification_version: 4
83
+ summary: Deterministic security scanner for GitHub Actions workflows
84
+ test_files: []