Deterministic pattern matching against 28 security dimensions. SHA pinning, shell injection, credential exposure, dangerous triggers — everything your workflows need auditing for. No AI, no gems, no false confidence.
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 AI, no cloud service required.
Just yaml, net/http, optparse,
and json. Runs anywhere Ruby runs.
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 | critical/medium | Tag-pinned actions (critical for third-party, medium 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 | critical | 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 | high | Static AWS keys instead of OIDC federation |
| 11 | unscoped-app-token | high | create-github-app-token without permission scoping |
| 12 | docker-build-arg-secrets | high | 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 | medium | git config --global with credentials |
| 18 | missing-timeouts | medium | 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 | medium | Write permissions on jobs that only read |
| 24 | unpinned-artifact | medium | 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 |
$ sentinel scan owner/repo
$ sentinel scan --local .
$ sentinel scan --org my-org