Deterministic pattern matching against 32 security dimensions. SHA pinning, shell injection, credential exposure, dangerous triggers — everything your workflows need auditing for. AI-powered fixes via Claude when you want them.
gem install sentinel-ci
sentinel scan owner/repo
gem exec sentinel-ci scan owner/repo
Requires Ruby 3.2+ and git. No GITHUB_TOKEN needed for public repos.
name: Workflow Security Scan on: pull_request: paths: ['.github/workflows/**'] permissions: contents: read jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jpr5/sentinel@v1 id: scan with: severity: high fail-on-findings: true
Critical to low, covering supply chain attacks, shell injection, credential exposure, permission scoping, and dangerous triggers. Every rule maps to a real-world exploit vector.
Inline annotations on PRs, fail-on-findings gate, zero config. Findings appear as errors and warnings directly on the diff.
Mechanical fixes for unpinned actions, shell injection patterns, and persist-credentials leaks. Safe, deterministic rewrites you can review and merge.
Proactive scanning of popular repos with targeted fix PRs for critical findings. Helping the ecosystem one pull request at a time.
Scan every repo in a GitHub org with a single command. Aggregated reporting across hundreds of repositories, sorted by severity.
Pure Ruby stdlib. No gems, no cloud service. Optional AI-powered fixes via Claude.
Just yaml, net/http, optparse,
and json. Runs anywhere Ruby runs.
Scan workflow files before every commit. Works standalone or with husky/lefthook.
Same security checks for GitLab CI and Bitbucket Pipelines. One tool, every CI platform.
name: Workflow Security on: pull_request: paths: ['.github/workflows/**'] permissions: contents: read jobs: scan: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: jpr5/sentinel@v1 with: severity: high
Findings appear as inline annotations on pull request diffs — critical and high as errors, medium as warnings. Merge gates on severity threshold.
Every rule maps to a documented exploit vector. No heuristics, no guessing — deterministic pattern matching against known-bad configurations.
| # | Rule | Severity | What it detects |
|---|---|---|---|
| 1 | unpinned-actions | medium/low | Tag-pinned actions (medium for third-party, low for actions/*) |
| 2 | shell-injection-expr | critical | Attacker-controllable ${{ }} in run: blocks |
| 3 | shell-injection-jq | critical | ${VAR} in double-quoted jq/curl strings |
| 4 | hardcoded-secrets | critical | AWS keys, GitHub PATs, private keys, passwords in plain text |
| 5 | self-hosted-runner-fork | high | Self-hosted runner on fork PR triggers |
| 6 | github-script-injection | critical | Attacker-controllable ${{ }} in github-script |
| 7 | dangerous-triggers | critical | pull_request_target + fork code checkout |
| 8 | missing-persist-credentials | high | actions/checkout without persist-credentials: false |
| 9 | credential-window | high | Git credentials configured far from push step |
| 10 | static-aws-credentials | medium | Static AWS keys instead of OIDC federation |
| 11 | unscoped-app-token | medium | create-github-app-token without permission scoping |
| 12 | docker-build-arg-secrets | medium | Secrets in Docker build-args (visible in image layers) |
| 13 | build-publish-same-job | high | Build + publish in same job with publish secrets |
| 14 | curl-pipe-shell | high | curl | sh without integrity verification |
| 15 | workflow-dispatch-injection | high | ${{ inputs.* }} in run blocks |
| 16 | missing-permissions | medium | No top-level permissions block |
| 17 | git-config-global | low | git config --global with credentials |
| 18 | missing-timeouts | low | Jobs without timeout-minutes |
| 19 | missing-env-protection | medium | Publish/deploy jobs without environment protection |
| 20 | allow-forks-artifact | medium | Fork-produced artifact download in privileged context |
| 21 | missing-frozen-lockfile | medium | Package install without --frozen-lockfile / npm ci |
| 22 | cache-poisoning | medium | Cache keys with fork-controllable refs |
| 23 | excessive-permissions | low | Write permissions on jobs that only read |
| 24 | unpinned-artifact | low | download-artifact without specific name |
| 25 | unpinned-docker-image | low | Docker images using :latest tag |
| 26 | overly-broad-triggers | low | Push/PR triggers without branch/path filters |
| 27 | missing-dependabot | low | No Dependabot config for github-actions ecosystem |
| 28 | missing-zizmor | low | No zizmor static analysis workflow |
| 29 | ide-config-injection | critical | Workflow writes to IDE/AI config files (.claude/, .vscode/tasks.json) |
| 30 | dangerous-lifecycle-scripts | medium | Package install without --ignore-scripts in workflows with secrets |
| 31 | github-dependency-refs | medium | Direct GitHub commit/branch ref in package install |
| 32 | jq-arg-escape-sequences | medium | jq --arg value contains backslash escape sequences that won't be interpreted |
$ sentinel scan owner/repo
$ sentinel scan --local .
$ sentinel scan --org my-org