sentinel-ci 1.0.1 → 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 +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +79 -21
- data/lib/github_client.rb +1 -2
- data/lib/rules/docker_build_arg_secrets.rb +1 -1
- data/lib/rules/excessive_permissions.rb +1 -1
- data/lib/rules/git_config_global.rb +1 -1
- data/lib/rules/missing_timeouts.rb +1 -1
- data/lib/rules/self_hosted_runner_fork.rb +1 -1
- data/lib/rules/static_aws_credentials.rb +1 -1
- data/lib/rules/unpinned_actions.rb +2 -2
- data/lib/rules/unpinned_artifact.rb +1 -1
- data/lib/rules/unscoped_app_token.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93bcfc862a8cc368754114af437432420d70876e8496ecde629658886e888941
|
|
4
|
+
data.tar.gz: 1fa24fc8dbd87c0dc7ed329c4897213bf49f096ab1281b8a421b61cbef0268a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fff33f695f1031ac4f720b2e6ba034b50cb17001dcec2dad92f0448cc5c1709ea61fb0e39152cb104a9f7d7b264aa4110bc0478c1a620620d9d98579f4eca213
|
|
7
|
+
data.tar.gz: e102e7cb5281042fba55955eb55ca0f691adc1e391a3c5762bd18e53758422f53ba47394e7d08847872c2d7a05de35e07e2105dee6e49a6c96c028f943445ab0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 (2026-05-18)
|
|
4
|
+
|
|
5
|
+
### Severity Re-ranking
|
|
6
|
+
- Severities re-evaluated based on actual exploitability
|
|
7
|
+
- Only actively exploitable rules (shell injection, dangerous triggers, hardcoded secrets) are critical
|
|
8
|
+
- Unpinned actions downgraded to medium (requires maintainer compromise first)
|
|
9
|
+
- First-party actions (actions/*) downgraded to low
|
|
10
|
+
|
|
11
|
+
### Bot Improvements
|
|
12
|
+
- Consolidated PRs: one PR per repo instead of one per rule
|
|
13
|
+
- Rule explainer pages at sentinel-bot.copilotkit.dev/rules/*
|
|
14
|
+
- `--limit N` flag to cap repos scanned per run
|
|
15
|
+
- Skip repos that already use Sentinel
|
|
16
|
+
- Fix PR body includes "How this was detected" methodology disclosure
|
|
17
|
+
- Adopt + opt-out links with UUID tokens
|
|
18
|
+
|
|
19
|
+
### Infrastructure
|
|
20
|
+
- Bot web handler live at sentinel-bot.copilotkit.dev (Sinatra on Railway)
|
|
21
|
+
- GitHub App created (sentinel-ci-scanner) for future bot identity
|
|
22
|
+
- GHCR-based deploy pipeline with env var trigger for Railway
|
|
23
|
+
- Rule explainer pages served from markdown with dark theme
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
- file_exists? returning true for 404s
|
|
27
|
+
- Sentinel skip check matching bare word instead of uses: reference
|
|
28
|
+
- Railway deploy: env var change triggers fresh image pull (serviceInstanceRedeploy doesn't)
|
|
29
|
+
- Sinatra host authorization in production mode
|
|
30
|
+
|
|
31
|
+
|
|
3
32
|
## 1.0.1 (2026-05-17)
|
|
4
33
|
|
|
5
34
|
- Smart clone auth: try HTTPS, SSH, then gh token — no manual GITHUB_TOKEN needed for private repos
|
data/README.md
CHANGED
|
@@ -97,6 +97,22 @@ jobs:
|
|
|
97
97
|
Findings appear as inline annotations on the PR diff -- critical/high as errors,
|
|
98
98
|
medium as warnings, low as notices.
|
|
99
99
|
|
|
100
|
+
**Fix mode inputs:**
|
|
101
|
+
|
|
102
|
+
| Name | Default | Description |
|
|
103
|
+
|------|---------|-------------|
|
|
104
|
+
| `fix` | `false` | Auto-fix findings. Pushes to PR branch, or creates fix PR on main. |
|
|
105
|
+
| `anthropic-key` | -- | Anthropic API key -- enables AI-powered fixes for all 28 rules |
|
|
106
|
+
|
|
107
|
+
**Fix mode outputs:**
|
|
108
|
+
|
|
109
|
+
| Name | Description |
|
|
110
|
+
|------|-------------|
|
|
111
|
+
| `fixes-applied` | Number of findings auto-fixed |
|
|
112
|
+
|
|
113
|
+
When `fix: true` on a **pull request**: fixes are pushed directly to the PR branch.
|
|
114
|
+
When `fix: true` on **main/push**: a new `sentinel/fix-*` PR is created.
|
|
115
|
+
|
|
100
116
|
## Pre-commit Hook
|
|
101
117
|
|
|
102
118
|
Scan workflow files automatically before every commit:
|
|
@@ -125,34 +141,66 @@ pre-commit:
|
|
|
125
141
|
|
|
126
142
|
The hook only runs when `.github/workflows/*.yml` files are staged, so it won't slow down unrelated commits.
|
|
127
143
|
|
|
144
|
+
## Policy-as-Code
|
|
145
|
+
|
|
146
|
+
Define security standards in `.sentinel-ci.yml`:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
severity: high
|
|
150
|
+
|
|
151
|
+
rules:
|
|
152
|
+
missing-timeouts: medium
|
|
153
|
+
overly-broad-triggers: "off"
|
|
154
|
+
|
|
155
|
+
ignore:
|
|
156
|
+
- ".github/workflows/dependabot-*.yml"
|
|
157
|
+
|
|
158
|
+
exceptions:
|
|
159
|
+
- rule: credential-window
|
|
160
|
+
file: publish-release.yml
|
|
161
|
+
reason: "Intentional late-injection pattern"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The scanner and GitHub Action both read this file automatically.
|
|
165
|
+
|
|
166
|
+
## Platform Support
|
|
167
|
+
|
|
168
|
+
Sentinel scans GitHub Actions (default), GitLab CI, and Bitbucket Pipelines:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
sentinel scan --local . --platform auto # detect automatically
|
|
172
|
+
sentinel scan --local . --platform gitlab # GitLab CI only
|
|
173
|
+
sentinel scan --local . --platform bitbucket # Bitbucket only
|
|
174
|
+
```
|
|
175
|
+
|
|
128
176
|
## What It Checks
|
|
129
177
|
|
|
130
178
|
| # | Rule | Severity | What |
|
|
131
179
|
|---|------|----------|------|
|
|
132
|
-
| 1 | `unpinned-actions` |
|
|
180
|
+
| 1 | `unpinned-actions` | medium/low | Tag-pinned actions (medium for third-party, low for `actions/*`) |
|
|
133
181
|
| 2 | `shell-injection-expr` | critical | Attacker-controllable `${{ }}` in `run:` blocks |
|
|
134
182
|
| 3 | `shell-injection-jq` | critical | `${VAR}` in double-quoted jq/curl strings |
|
|
135
183
|
| 4 | `hardcoded-secrets` | critical | AWS keys, GitHub PATs, private keys, passwords in plain text |
|
|
136
|
-
| 5 | `self-hosted-runner-fork` |
|
|
184
|
+
| 5 | `self-hosted-runner-fork` | high | Self-hosted runner on fork PR triggers |
|
|
137
185
|
| 6 | `github-script-injection` | critical | Attacker-controllable `${{ }}` in github-script |
|
|
138
186
|
| 7 | `dangerous-triggers` | critical | `pull_request_target` + fork code checkout |
|
|
139
187
|
| 8 | `missing-persist-credentials` | high | `actions/checkout` without `persist-credentials: false` |
|
|
140
188
|
| 9 | `credential-window` | high | Git credentials configured far from push step |
|
|
141
|
-
| 10 | `static-aws-credentials` |
|
|
142
|
-
| 11 | `unscoped-app-token` |
|
|
143
|
-
| 12 | `docker-build-arg-secrets` |
|
|
189
|
+
| 10 | `static-aws-credentials` | medium | Static AWS keys instead of OIDC federation |
|
|
190
|
+
| 11 | `unscoped-app-token` | medium | `create-github-app-token` without `permission-*` scoping |
|
|
191
|
+
| 12 | `docker-build-arg-secrets` | medium | Secrets in Docker build-args (visible in image layers) |
|
|
144
192
|
| 13 | `build-publish-same-job` | high | Build + publish in same job with publish secrets |
|
|
145
193
|
| 14 | `curl-pipe-shell` | high | `curl \| sh` without integrity verification |
|
|
146
194
|
| 15 | `workflow-dispatch-injection` | high | `${{ inputs.* }}` in run blocks |
|
|
147
195
|
| 16 | `missing-permissions` | medium | No top-level permissions block |
|
|
148
|
-
| 17 | `git-config-global` |
|
|
149
|
-
| 18 | `missing-timeouts` |
|
|
196
|
+
| 17 | `git-config-global` | low | `git config --global` with credentials |
|
|
197
|
+
| 18 | `missing-timeouts` | low | Jobs without `timeout-minutes` |
|
|
150
198
|
| 19 | `missing-env-protection` | medium | Publish/deploy jobs without environment protection |
|
|
151
199
|
| 20 | `allow-forks-artifact` | medium | Fork-produced artifact download in privileged context |
|
|
152
200
|
| 21 | `missing-frozen-lockfile` | medium | Package install without `--frozen-lockfile` / `npm ci` |
|
|
153
201
|
| 22 | `cache-poisoning` | medium | Cache keys with fork-controllable refs |
|
|
154
|
-
| 23 | `excessive-permissions` |
|
|
155
|
-
| 24 | `unpinned-artifact` |
|
|
202
|
+
| 23 | `excessive-permissions` | low | Write permissions on jobs that only read |
|
|
203
|
+
| 24 | `unpinned-artifact` | low | download-artifact without specific name |
|
|
156
204
|
| 25 | `unpinned-docker-image` | low | Docker images using `:latest` tag |
|
|
157
205
|
| 26 | `overly-broad-triggers` | low | Push/PR triggers without branch/path filters |
|
|
158
206
|
| 27 | `missing-dependabot` | low | No Dependabot config for github-actions ecosystem |
|
|
@@ -160,29 +208,32 @@ The hook only runs when `.github/workflows/*.yml` files are staged, so it won't
|
|
|
160
208
|
|
|
161
209
|
## Auto-Fix
|
|
162
210
|
|
|
163
|
-
Sentinel can automatically
|
|
211
|
+
Sentinel can automatically fix findings -- 6 rules mechanically, all 28 with AI:
|
|
164
212
|
|
|
165
213
|
```bash
|
|
166
|
-
|
|
167
|
-
|
|
214
|
+
# Mechanical fixes (free, deterministic)
|
|
215
|
+
sentinel fix --local .
|
|
216
|
+
sentinel fix --local . --dry-run # preview changes
|
|
168
217
|
|
|
169
|
-
|
|
218
|
+
# All rules via Claude Opus
|
|
219
|
+
sentinel fix --local . --ai
|
|
170
220
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
require_relative "lib/sha_resolver"
|
|
174
|
-
|
|
175
|
-
resolver = ShaResolver.new
|
|
176
|
-
patched = AutoFix.apply(finding, raw_yaml, sha_resolver: resolver)
|
|
221
|
+
# Fix a remote repo (creates a PR)
|
|
222
|
+
sentinel fix owner/repo
|
|
177
223
|
```
|
|
178
224
|
|
|
179
|
-
**
|
|
225
|
+
**Mechanically fixable rules:**
|
|
180
226
|
|
|
181
227
|
| Rule | Fix Strategy |
|
|
182
228
|
|------|-------------|
|
|
183
229
|
| `unpinned-actions` | Resolves tag to SHA via GitHub API |
|
|
184
230
|
| `shell-injection-expr` | Moves expression to step-level `env:` block |
|
|
185
231
|
| `missing-persist-credentials` | Adds `persist-credentials: false` to checkout |
|
|
232
|
+
| `workflow-dispatch-injection` | Moves `${{ inputs.* }}` to step-level `env:` block |
|
|
233
|
+
| `missing-permissions` | Adds `permissions: contents: read` at workflow level |
|
|
234
|
+
| `missing-timeouts` | Adds `timeout-minutes: 30` to jobs |
|
|
235
|
+
|
|
236
|
+
With `--ai`, Sentinel uses Claude Opus to fix all remaining rules that require understanding workflow intent.
|
|
186
237
|
|
|
187
238
|
## PR Bot
|
|
188
239
|
|
|
@@ -273,13 +324,18 @@ lib/
|
|
|
273
324
|
bot.rb # sentinel bot subcommand
|
|
274
325
|
hook.rb # sentinel hook install/uninstall
|
|
275
326
|
deps.rb # sentinel deps subcommand
|
|
327
|
+
token_resolver.rb # GitHub token lookup chain
|
|
276
328
|
formatter/
|
|
277
329
|
terminal.rb # colored terminal output
|
|
278
330
|
json.rb # JSON output
|
|
279
331
|
sarif.rb # SARIF output for GitHub Security tab
|
|
332
|
+
platforms/
|
|
333
|
+
gitlab.rb # GitLab CI pipeline scanner
|
|
334
|
+
bitbucket.rb # Bitbucket Pipelines scanner
|
|
335
|
+
shared_patterns.rb # cross-platform rule patterns
|
|
280
336
|
rules/
|
|
281
337
|
base.rb # abstract rule interface
|
|
282
|
-
*.rb # one file per rule (
|
|
338
|
+
*.rb # one file per rule (26 rules)
|
|
283
339
|
mcp/
|
|
284
340
|
server.rb # MCP server for AI coding agents
|
|
285
341
|
claude-code-config.json # example configuration for Claude Code
|
|
@@ -287,8 +343,10 @@ bot/
|
|
|
287
343
|
scanner_bot.rb # PR bot orchestrator
|
|
288
344
|
search.rb # GitHub Code Search client
|
|
289
345
|
state.rb # JSON-file state tracking
|
|
346
|
+
state.json # persisted bot state
|
|
290
347
|
pr_writer.rb # cross-fork PR creation
|
|
291
348
|
config.rb # bot configuration
|
|
349
|
+
web.rb # bot web dashboard
|
|
292
350
|
```
|
|
293
351
|
|
|
294
352
|
## Adding Rules
|
data/lib/github_client.rb
CHANGED
|
@@ -2,7 +2,7 @@ module Rules
|
|
|
2
2
|
class ExcessivePermissions < Base
|
|
3
3
|
def name = "excessive-permissions"
|
|
4
4
|
def description = "Job has write permissions but no steps that appear to need them"
|
|
5
|
-
def severity = :
|
|
5
|
+
def severity = :low
|
|
6
6
|
|
|
7
7
|
# Actions that perform write operations
|
|
8
8
|
WRITE_ACTIONS = [
|
|
@@ -2,7 +2,7 @@ module Rules
|
|
|
2
2
|
class UnpinnedActions < Base
|
|
3
3
|
def name = "unpinned-actions"
|
|
4
4
|
def description = "Action referenced by tag instead of SHA pin"
|
|
5
|
-
def severity = :
|
|
5
|
+
def severity = :medium
|
|
6
6
|
|
|
7
7
|
SHA_PATTERN = /@[0-9a-f]{40}\b/
|
|
8
8
|
FIRST_PARTY = %w[actions/ github/].freeze
|
|
@@ -17,7 +17,7 @@ module Rules
|
|
|
17
17
|
next if uses.match?(SHA_PATTERN)
|
|
18
18
|
|
|
19
19
|
first_party = FIRST_PARTY.any? { |prefix| uses.start_with?(prefix) }
|
|
20
|
-
sev = first_party ? :
|
|
20
|
+
sev = first_party ? :low : :medium
|
|
21
21
|
|
|
22
22
|
findings << Finding.new(
|
|
23
23
|
rule: name,
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sentinel-ci
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jordan Ritter
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Scan GitHub Actions workflows for 28 security vulnerabilities. SHA pinning,
|
|
14
14
|
shell injection, credential exposure, dangerous triggers. Optional AI-powered remediation
|