score
Compute a single posture score (0–100, letter grade A–F) for a repository’s GitHub Actions configuration. Implements the public scoring rubric — every point deducted maps to a named rule, so the grade can be re-derived by hand from the finding list.
Workflows are discovered under .github/workflows/, .forgejo/workflows/, and .gitea/workflows/ (whichever exist are all scanned). Forgejo and Gitea use GitHub-compatible workflow syntax.
pinprick scorepinprick score /path/to/repopinprick score --jsonpinprick score --html > report.htmlpinprick score --badge > badge.jsonBehavior
Section titled “Behavior”- Scans
.ymland.yamlworkflows under.github/workflows/,.forgejo/workflows/, and.gitea/workflows/, plus reachable local and remote action source, and emits findings across four categories:pin.*,workflow.*,source.*,runtime.* - Each finding has a fixed point deduction; the score is
max(0, 100 - sum(points)) - Exits 1 when any finding deducts points. Coverage is reported independently in every output format
- Runs its workflow-owned rules offline. With a GitHub token, it also evaluates
source.archived,source.advisory, and runtime findings in fetched remote action source - Marks report coverage incomplete when token-gated checks do not run, reachable source cannot be inspected completely, or configuration suppresses coverage
Output formats
Section titled “Output formats”- Default: a compact human-readable summary with grade, finding count, prioritized rules, and targets
--json: the full finding list andcoverage_completestate as JSON for CI integration or downstream processing--html: a self-contained HTML report that displays incomplete-coverage reasons--badge: a shields.io endpoint-badge JSON document. Incomplete coverage produces a grey error badge instead of an unqualified grade--no-repo-config: ignore the scanned repository’s.pinprick.tomland use the global config (or defaults)
--json, --html, and --badge are mutually exclusive.
Rule catalog
Section titled “Rule catalog”The full catalog lives in docs/scoring.md. Summary:
| Category | Rule | Severity | Points |
|---|---|---|---|
pin |
pin.branch (branch ref) |
high | 15 |
pin |
pin.sliding (sliding tag @v4) |
medium | 5 |
pin |
pin.full_tag (e.g. @v4.2.1) |
low | 2 |
pin |
pin.docker_latest (docker:// image, :latest/no tag) |
high | 15 |
pin |
pin.docker_tag (docker:// image tag, no digest) |
medium | 5 |
source |
source.archived |
high | 10 |
source |
source.advisory (GHSA match) |
high | 15 |
runtime |
runtime.pipe_to_shell |
high | 20 |
runtime |
runtime.fetch.high |
high | 15 |
runtime |
runtime.fetch.medium |
medium | 8 |
runtime |
runtime.fetch.low |
low | 3 |
workflow |
workflow.permissions_write_all |
high | 10 |
workflow |
workflow.pull_request_target |
high | 5 |
workflow |
workflow.workflow_run |
medium | 3 |
Grade bands: A 90–100, B 80–89, C 70–79, D 60–69, F 0–59.
Because the scanned repository’s own .pinprick.toml applies, a third-party repo can shape its own grade (trusted-hosts, extra-data-formats, ignore rules). Whenever a repo-local config changes the score, pinprick prints a note to stderr saying what it changed; pass --no-repo-config to ignore the file entirely.
Container-action findings identify docker:// references that use a tag instead of an immutable digest. pinprick pin --write does not resolve registry tags, so replace these references with a reviewed @sha256: digest manually.
Example
Section titled “Example”$ pinprick scorepinprick score v0.11.0 rubric
Grade: A (95 / 100)
Findings (1 unique, 1 occurrences): medium -5 pin.sliding actions/checkout@v4
3 workflows scanned, 8 unique action references (including local paths).
Run with --json for the full report.Versioning
Section titled “Versioning”The rubric is independently versioned from the pinprick binary (currently v0.11.0). Every scan records the rubric version so historical scores remain interpretable as the rubric evolves. Re-scoring against a newer rubric is always explicit — pinprick never silently re-grades a past scan.