Developer-Dashboard

 view release on metacpan or  search on metacpan

script/audit-action-pins  view on Meta::CPAN

__END__

=pod

=head1 NAME

audit-action-pins - resolve every SHA-pinned GitHub Action against its upstream
tag and its declared runtime

=head1 PURPOSE

Make a workflow's action pins mean what they say. Every C<uses:> line in this
repository is pinned to an immutable 40-hex commit and annotated with a
C<# vX.Y.Z> comment naming the release that commit belongs to. The SHA is the
security control; the comment is the only part a human can read, and it is what
the repository's own guardrail test reads its version floors from. This gate is
what keeps the two in agreement.

=head1 WHY IT EXISTS

Because a comment nobody verifies is trusted and can still be false. Three pins
in this repository carried comments written from intent rather than resolved
from the tag: C<actions/checkout> was annotated C<# v5.2.2>, a tag that has
never existed upstream, over a commit that is really v4.2.2;
C<shogo82148/actions-setup-perl> was annotated C<# v1.32.0> over v1.31.3.

All three were node20 actions. GitHub force-runs node20 actions on the node24
runtime, which C<actions/checkout> survives and C<actions-setup-perl> v1.31.3
does not - it fails with C<Error: unable to get latest version>. The C<Setup
Perl> step therefore failed on every CI run for ten days, skipping the test
suite, the coverage gate and both dependency audits, while the guardrail test
certified the node24 migration as complete because the comments said so. A
version floor read from a comment cannot catch a comment that lies, so the check
that can - resolving the SHA - lives here.

=head1 WHEN TO USE

Run it whenever an action pin is added or changed, and on every CI push. It is
wired into the test workflow after the dependency install, because it needs the
project's own C<LWP::UserAgent> and C<JSON::XS>.

=head1 HOW TO USE

    script/audit-action-pins
    script/audit-action-pins .github/workflows
    GITHUB_TOKEN=... script/audit-action-pins

The audit reports two distinct kinds of problem, and exits non-zero for both:

=over 4

=item *

C<FAIL> - the pin was resolved and is wrong: it declares a runtime below node24,
or its version comment names a tag that resolves to a different commit.

=item *

C<UNUSABLE> - the pin could not be resolved at all, so this run found nothing
out. This is never reported as success. An unauthenticated run can hit the
GitHub rate limit, and the C</repos/.../commits/{sha}> endpoint is unreliable
for some large repositories, so the manifest is read through the contents API
instead.

=back

Exit codes are C<0> clean, C<1> findings, C<2> usage error, C<3> could not
audit.

=head1 WHAT USES IT

C<.github/workflows/test.yml> runs it on every push and pull request.
C<t/34-scorecard-guardrails.t> asserts that wiring still exists, and
C<t/142-action-pin-provenance.t> exercises this script's own logic.

=head1 OFFLINE TESTING

Setting C<DD_ACTION_PIN_FIXTURE> to a JSON file replaces the network with a
canned resolver, so the audit logic can be tested hermetically:

    {
      "contents": { "actions/checkout|action.yml|<sha>": "runs:\n  using: node24\n" },
      "tags":     { "actions/checkout|v7.0.1": "<sha>" }
    }

A fixture-backed run prints a banner saying so and does not certify anything - a
fixture can assert whatever it likes, which is the point in a test and would be
a hole anywhere else.

=head1 EXAMPLES

Audit the repository's workflows before pushing a pin change:

    script/audit-action-pins; echo "exit=$?"

Audit a single directory of workflows copied elsewhere:

    script/audit-action-pins /tmp/candidate-workflows

Confirm the gate fails closed when it cannot reach the API:

    GITHUB_TOKEN= script/audit-action-pins /tmp/empty-dir; echo "exit=$?"   # 3

=cut



( run in 0.702 second using v1.01-cache-2.11-cpan-9789f410c06 )