Skip to content

jpr5/sentinel

Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

Sentinel

Deterministic security scanner for GitHub Actions workflows

Build Ruby License

Scan GitHub Actions workflows for 32 security vulnerabilities. Optional AI-powered remediation via Claude. Pure Ruby stdlib.

Documentation: https://sentinel.copilotkit.dev

Install

# Zero-config for public repos — no GITHUB_TOKEN needed
gem install sentinel-ci
sentinel scan owner/repo

# One-shot (like npx)
gem exec sentinel-ci scan owner/repo

# For private repos or org scanning, set a token
export GITHUB_TOKEN=$(gh auth token)
sentinel scan --org my-org

Requires Ruby 3.2+ and git. Public repos are scanned via shallow clone -- no API token required. For private repos or --org scanning, set GITHUB_TOKEN.

Usage

# Scan a single repo
sentinel scan owner/repo

# Scan a local checkout
sentinel scan --local /path/to/repo

# Scan an entire GitHub org
sentinel scan --org my-org

# JSON output, filter to high+ severity
sentinel scan --format json --severity high owner/repo

# SARIF output for GitHub Security tab
sentinel scan --format sarif owner/repo > results.sarif

GitHub Action

Use as a GitHub Action to automatically scan workflows on every PR:

- uses: jpr5/sentinel@v1
  with:
    severity: high

Full workflow example:

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

Inputs:

Name Default Description
severity high Minimum severity: critical, high, medium, low
fail-on-findings true Fail the check if findings above threshold exist

Outputs:

Name Description
findings-count Total findings at or above severity
critical-count Critical findings count
high-count High findings count

Findings appear as inline annotations on the PR diff -- critical/high as errors, medium as warnings, low as notices.

Fix mode inputs:

Name Default Description
fix false Auto-fix findings. Pushes to PR branch, or creates fix PR on main.
anthropic-key -- Anthropic API key -- enables AI-powered fixes for all 32 rules

Fix mode outputs:

Name Description
fixes-applied Number of findings auto-fixed

When fix: true on a pull request: fixes are pushed directly to the PR branch. When fix: true on main/push: a new sentinel/fix-* PR is created.

Pre-commit Hook

Scan workflow files automatically before every commit:

# Auto-install
sentinel hook install

# Manual removal
sentinel hook uninstall

Works with hook managers too:

# husky
echo 'sentinel hook run' >> .husky/pre-commit

# lefthook (lefthook.yml)
pre-commit:
  commands:
    sentinel:
      glob: ".github/workflows/*.{yml,yaml}"
      run: sentinel hook run

The hook only runs when .github/workflows/*.yml files are staged, so it won't slow down unrelated commits.

Policy-as-Code

Define security standards in .sentinel-ci.yml:

severity: high

rules:
  missing-timeouts: medium
  overly-broad-triggers: "off"

ignore:
  - ".github/workflows/dependabot-*.yml"

exceptions:
  - rule: credential-window
    file: publish-release.yml
    reason: "Intentional late-injection pattern"

The scanner and GitHub Action both read this file automatically.

Platform Support

Sentinel scans GitHub Actions (default), GitLab CI, and Bitbucket Pipelines:

sentinel scan --local . --platform auto     # detect automatically
sentinel scan --local . --platform gitlab   # GitLab CI only
sentinel scan --local . --platform bitbucket # Bitbucket only

What It Checks

# Rule Severity What
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

Auto-Fix

Sentinel can automatically fix findings -- 6 rules mechanically, all 32 with AI:

# Mechanical fixes (free, deterministic)
sentinel fix --local .
sentinel fix --local . --dry-run    # preview changes

# All rules via Claude Opus
sentinel fix --local . --ai

# Fix a remote repo (creates a PR)
sentinel fix owner/repo

Mechanically fixable rules:

Rule Fix Strategy
unpinned-actions Resolves tag to SHA via GitHub API
shell-injection-expr Moves expression to step-level env: block
missing-persist-credentials Adds persist-credentials: false to checkout
workflow-dispatch-injection Moves ${{ inputs.* }} to step-level env: block
missing-permissions Adds permissions: contents: read at workflow level
missing-timeouts Adds timeout-minutes: 30 to jobs

With --ai, Sentinel uses Claude Opus to fix all remaining rules that require understanding workflow intent.

PR Bot

Proactively scan popular public repos and open fix PRs for critical findings.

ruby bot/scanner_bot.rb --pattern shell-injection --dry-run

Features:

  • GitHub Code Search to find vulnerable repos
  • Auto-generates fix PRs for mechanically-fixable rules
  • Rate limited (50 PRs/day), stars threshold (>100)
  • Opt-out support, clear bot identity
  • Runs as daily cron via GitHub Actions

MCP Server

Use Sentinel as a tool in AI coding agents (Claude Code, Copilot, Cursor):

# Start the MCP server
sentinel mcp

# Configure in Claude Code (~/.claude.json)
{
  "mcpServers": {
    "sentinel": {
      "command": "sentinel",
      "args": ["mcp"]
    }
  }
}

Three tools available: sentinel_scan, sentinel_deps, sentinel_fix.

Supply Chain Analysis

Map third-party action dependencies with risk scoring:

sentinel deps --local .
sentinel deps owner/repo
sentinel deps --org my-org --format json

Options

--format FORMAT    terminal (default), json, or sarif
--severity LEVEL   minimum severity: critical, high, medium, low (default: low)
--local PATH       scan local directory
--org ORG          scan all repos in a GitHub org (requires GITHUB_TOKEN)
--token TOKEN      GitHub API token — only needed for private repos and --org scanning
--platform PLAT    github (default), gitlab, bitbucket, or auto
--dry-run          preview changes without writing files (fix mode)
--ai               enable AI-powered fixes via Claude
--model MODEL      AI model to use (default: claude-opus-4-20250514)
--ai-key KEY       Anthropic API key for AI fixes

Exit Codes

  • 0 -- no critical or high findings
  • 1 -- critical or high findings present
  • 2 -- usage error

Architecture

bin/sentinel                    # CLI entry point (subcommand dispatcher)
action/
  annotate.rb                   # GitHub Action annotation emitter
lib/
  scanner.rb                    # orchestrator
  rule_engine.rb                # loads + runs all rules
  workflow.rb                   # YAML parser + helpers
  finding.rb                    # finding data struct
  github_client.rb              # GitHub API client
  local_client.rb               # filesystem client
  clone_client.rb               # git-clone client for public repos
  auto_fix.rb                   # mechanical auto-fix engine
  ai_fix.rb                     # AI-powered fix via Claude
  sha_resolver.rb               # GitHub tag -> SHA resolver
  policy.rb                     # policy-as-code engine (.sentinel-ci.yml)
  supply_chain.rb               # action dependency graph + risk scoring
  version.rb                    # gem version constant
  cli/
    scan.rb                     # sentinel scan subcommand
    fix.rb                      # sentinel fix subcommand
    bot.rb                      # sentinel bot subcommand
    hook.rb                     # sentinel hook install/uninstall
    deps.rb                     # sentinel deps subcommand
    token_resolver.rb           # GitHub token lookup chain
  formatter/
    terminal.rb                 # colored terminal output
    json.rb                     # JSON output
    sarif.rb                    # SARIF output for GitHub Security tab
  platforms/
    gitlab.rb                   # GitLab CI pipeline scanner
    bitbucket.rb                # Bitbucket Pipelines scanner
    shared_patterns.rb          # cross-platform rule patterns
  rules/
    base.rb                     # abstract rule interface
    *.rb                        # one file per rule (29 rules)
mcp/
  server.rb                     # MCP server for AI coding agents
  claude-code-config.json       # example configuration for Claude Code
bot/
  scanner_bot.rb                # PR bot orchestrator
  search.rb                     # GitHub Code Search client
  state.rb                      # JSON-file state tracking
  state.json                    # persisted bot state
  pr_writer.rb                  # cross-fork PR creation
  config.rb                     # bot configuration
  github_app_auth.rb            # GitHub App JWT + installation token auth
  web.rb                        # bot web dashboard

Adding Rules

Create lib/rules/my_rule.rb:

module Rules
    class MyRule < Base
        def name = "my-rule"
        def description = "What this detects"
        def severity = :high  # :critical, :high, :medium, :low

        def check(workflow)
            findings = []
            # workflow.uses_actions, workflow.run_blocks, workflow.raw_lines, etc.
            # Use finding() helper or construct Finding.new() directly
            findings
        end
    end
end

Rules are auto-discovered from lib/rules/.

License

MIT

About

Security scanner for GitHub Actions workflows — 28 rules, auto-fix engine, AI-powered remediation via Claude. Ruby CLI, GitHub Action, pre-commit hook.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors