prepare v4 release (#835)

Signed-off-by: Brian DeHamer <bdehamer@github.com>
This commit is contained in:
Brian DeHamer 2026-02-25 14:38:07 -08:00 committed by GitHub
parent 02a49bdc41
commit e4d4f7c39a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 52 additions and 10366 deletions

View File

@ -1,66 +0,0 @@
# In TypeScript actions, `dist/` is a special directory. When you reference
# an action with the `uses:` property, `dist/index.js` is the code that will be
# run. For this project, the `dist/index.js` file is transpiled from other
# source files. This workflow ensures the `dist/` directory contains the
# expected transpiled code.
#
# If this workflow is run from a feature branch, it will act as an additional CI
# check and fail if the checked-in `dist/` directory does not match what is
# expected from the build.
name: Check Transpiled JavaScript
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
contents: read
jobs:
check-dist:
name: Check dist/
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .node-version
cache: npm
- name: Install Dependencies
id: install
run: npm ci
- name: Build dist/ Directory
id: build
run: npm run bundle
# This will fail the workflow if the PR wasn't created by Dependabot.
- name: Compare Directories
id: diff
run: |
if [ "$(git diff --ignore-space-at-eol --text dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff --ignore-space-at-eol --text dist/
exit 1
fi
# If `dist/` was different than expected, and this was not a Dependabot
# PR, upload the expected version as a workflow artifact.
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
name: Upload Artifact
id: upload
uses: actions/upload-artifact@v6.0.0
with:
name: dist
path: dist/

View File

@ -7,45 +7,11 @@ on:
push: push:
branches: branches:
- main - main
- 'releases/*' - "releases/*"
permissions: {} permissions: {}
jobs: jobs:
test-typescript:
name: TypeScript Tests
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
id: checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
id: setup-node
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version-file: .node-version
cache: npm
- name: Install Dependencies
id: npm-ci
run: npm ci
- name: Check Format
id: npm-format-check
run: npm run format:check
- name: Lint
id: npm-lint
run: npm run lint
- name: Test
id: npm-ci-test
run: npm run ci-test
test-attest-provenance: test-attest-provenance:
name: Test attest-provenance action name: Test attest-provenance action
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -62,10 +28,10 @@ jobs:
id: attest-provenance id: attest-provenance
uses: ./ uses: ./
env: env:
INPUT_PRIVATE-SIGNING: 'true' INPUT_PRIVATE-SIGNING: "true"
with: with:
subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' subject-digest: "sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32"
subject-name: 'subject' subject-name: "subject"
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Dump output - name: Dump output
run: jq < ${{ steps.attest-provenance.outputs.bundle-path }} run: jq < ${{ steps.attest-provenance.outputs.bundle-path }}

View File

@ -1,50 +0,0 @@
name: CodeQL
on:
pull_request:
branches:
- main
push:
branches:
- main
schedule:
- cron: '31 7 * * 3'
permissions: {}
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
checks: write
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language:
- TypeScript
steps:
- name: Checkout
id: checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
id: initialize
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
languages: ${{ matrix.language }}
source-root: src
- name: Autobuild
id: autobuild
uses: github/codeql-action/autobuild@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
- name: Perform CodeQL Analysis
id: analyze
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2

View File

@ -1,7 +0,0 @@
# Unordered list style
MD004:
style: dash
# Ordered list item prefix
MD029:
style: one

View File

@ -1 +0,0 @@
24.5.0

View File

@ -1,3 +0,0 @@
dist/
node_modules/
coverage/

View File

@ -1,16 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": true,
"arrowParens": "avoid",
"proseWrap": "always",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
}

314
README.md
View File

@ -22,7 +22,7 @@ initiated.
Attestations can be verified using the [`attestation` command in the GitHub Attestations can be verified using the [`attestation` command in the GitHub
CLI][5]. CLI][5].
See [Using artifact attestations to establish provenance for builds][9] for more See [Using artifact attestations to establish provenance for builds][6] for more
information on artifact attestations. information on artifact attestations.
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
@ -37,307 +37,15 @@ information on artifact attestations.
## Usage ## Usage
Within the GitHub Actions workflow which builds some artifact you would like to **As of version 4, `actions/attest-build-provenance` is simply a wrapper on top
attest: of [`actions/attest`][7].**
1. Ensure that the following permissions are set: Existing applications may continue to use the `attest-build-provenance` action,
but new implementations should use `actions/attest` instead. Please see the
[`actions/attest`][7] repository for usage information.
```yaml Documentation for previous versions of this action can be found
permissions: [here](https://github.com/actions/attest-build-provenance/blob/v3.2.0/README.md).
id-token: write
attestations: write
artifact-metadata: write
```
The `id-token` permission gives the action the ability to mint the OIDC token
necessary to request a Sigstore signing certificate. The `attestations`
permission is necessary to persist the attestation.
The `artifact-metadata` permission is required to generate artifact
metadata storage records. If this permission is not included, the action
will continue without creating the record.
1. Add the following to your workflow after your artifact has been built:
```yaml
- uses: actions/attest-build-provenance@v3
with:
subject-path: '<PATH TO ARTIFACT>'
```
The `subject-path` parameter should identify the artifact for which you want
to generate an attestation.
### Inputs
See [action.yml](action.yml)
```yaml
- uses: actions/attest-build-provenance@v3
with:
# Path to the artifact serving as the subject of the attestation. Must
# specify exactly one of "subject-path", "subject-digest", or
# "subject-checksums". May contain a glob pattern or list of paths
# (total subject count cannot exceed 1024).
subject-path:
# SHA256 digest of the subject for the attestation. Must be in the form
# "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one
# of "subject-path", "subject-digest", or "subject-checksums".
subject-digest:
# Subject name as it should appear in the attestation. Required when
# identifying the subject with the "subject-digest" input.
subject-name:
# Path to checksums file containing digest and name of subjects for
# attestation. Must specify exactly one of "subject-path", "subject-digest",
# or "subject-checksums".
subject-checksums:
# Whether to push the attestation to the image registry. Requires that the
# "subject-name" parameter specify the fully-qualified image name and that
# the "subject-digest" parameter be specified. Defaults to false.
push-to-registry:
# Whether to create a storage record for the artifact.
# Requires that push-to-registry is set to true.
# Requires that the "subject-name" parameter specify the fully-qualified
# image name. Defaults to true.
create-storage-record:
# Whether to attach a list of generated attestations to the workflow run
# summary page. Defaults to true.
show-summary:
# The GitHub token used to make authenticated API requests. Default is
# ${{ github.token }}
github-token:
```
### Outputs
<!-- markdownlint-disable MD013 -->
| Name | Description | Example |
| ----------------- | -------------------------------------------------------------- | ------------------------------------------------ |
| `attestation-id` | GitHub ID for the attestation | `123456` |
| `attestation-url` | URL for the attestation summary | `https://github.com/foo/bar/attestations/123456` |
| `bundle-path` | Absolute path to the file containing the generated attestation | `/tmp/attestation.json` |
<!-- markdownlint-enable MD013 -->
Attestations are saved in the JSON-serialized [Sigstore bundle][6] format.
If multiple subjects are being attested at the same time, a single attestation
will be created with references to each of the supplied subjects.
The absolute path to the generated attestation is appended to the file
`${RUNNER_TEMP}/created_attestation_paths.txt`. This file will accumulate the
paths to all attestations created over the course of a single workflow.
## Attestation Limits
### Subject Limits
No more than 1024 subjects can be attested at the same time.
## Examples
### Identify Subject by Path
For the basic use case, simply add the `attest-build-provenance` action to your
workflow and supply the path to the artifact for which you want to generate
attestation.
```yaml
name: build-attest
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
attestations: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build artifact
run: make my-app
- name: Attest
uses: actions/attest-build-provenance@v3
with:
subject-path: '${{ github.workspace }}/my-app'
```
### Identify Multiple Subjects
If you are generating multiple artifacts, you can attest all of them at the same
time by using a wildcard in the `subject-path` input.
```yaml
- uses: actions/attest-build-provenance@v3
with:
subject-path: 'dist/**/my-bin-*'
```
For supported wildcards along with behavior and documentation, see
[@actions/glob][8] which is used internally to search for files.
Alternatively, you can explicitly list multiple subjects with either a comma or
newline delimited list:
```yaml
- uses: actions/attest-build-provenance@v3
with:
subject-path: 'dist/foo, dist/bar'
```
```yaml
- uses: actions/attest-build-provenance@v3
with:
subject-path: |
dist/foo
dist/bar
```
### Identify Subjects with Checksums File
If you are using tools like
[goreleaser](https://goreleaser.com/customization/checksum/) or
[jreleaser](https://jreleaser.org/guide/latest/reference/checksum.html) which
generate a checksums file you can identify the attestation subjects by passing
the path of the checksums file to the `subject-checksums` input. Each of the
artifacts identified in the checksums file will be listed as a subject for the
attestation.
```yaml
- name: Calculate artifact digests
run: |
shasum -a 256 foo_0.0.1_* > subject.checksums.txt
- uses: actions/attest-build-provenance@v3
with:
subject-checksums: subject.checksums.txt
```
<!-- markdownlint-disable MD038 -->
The file referenced by the `subject-checksums` input must conform to the same
format used by the shasum tools. Each subject should be listed on a separate
line including the hex-encoded digest (either SHA256 or SHA512), a space, a
single character flag indicating either binary (`*`) or text (` `) input mode,
and the filename.
<!-- markdownlint-enable MD038 -->
```text
b569bf992b287f55d78bf8ee476497e9b7e9d2bf1c338860bfb905016218c740 foo_0.0.1_darwin_amd64
a54fc515e616cac7fcf11a49d5c5ec9ec315948a5935c1e11dd610b834b14dde foo_0.0.1_darwin_arm64
```
### Container Image
When working with container images you can invoke the action with the
`subject-name` and `subject-digest` inputs.
If you want to publish the attestation to the container registry with the
`push-to-registry` option, it is important that the `subject-name` specify the
fully-qualified image name (e.g. "ghcr.io/user/app" or
"acme.azurecr.io/user/app"). Do NOT include a tag as part of the image name --
the specific image being attested is identified by the supplied digest.
Attestation bundles are stored in the OCI registry according to the [Cosign
Bundle Specification][10].
> **NOTE**: When pushing to Docker Hub, please use "index.docker.io" as the
> registry portion of the image name.
```yaml
name: build-attested-image
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
packages: write
contents: read
attestations: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
id: push
uses: docker/build-push-action@v5.0.0
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Attest
uses: actions/attest-build-provenance@v3
id: attest
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
```
#### Artifact Metadata Storage Records
If the `push-to-registry` option is set to true, the Action will also
emit an [Artifact Metadata Storage Record](https://docs.github.com/en/rest/orgs/artifact-metadata?apiVersion=2022-11-28#create-artifact-metadata-storage-record).
Storage records enrich artifact metadata by capturing storage
related details, such as which registry an image is hosted on
and whether it's marked as active.
If you do not want to emit a storage record, set `create-storage-record` to `false`.
> **NOTE**: Storage records can only be created for artifacts
> built from [organization-owned](https://docs.github.com/en/organizations/collaborating-with-groups-in-organizations/about-organizations)
> repositories.
Artifacts associated with a storage record can be viewed by navigating to
the `Linked Artifacts` page in your organization:
`https://github.com/orgs/YOUR_ORG/artifacts`
(replace `YOUR_ORG` with your organization name).
### Integration with `actions/upload-artifact`
If you'd like to create an attestation for an archive created with the
[actions/upload-artifact][11] action you can feed the digest of the generated
artifact directly into the `subject-digest` input of the attestation action.
```yaml
- name: Upload build artifact
id: upload
uses: actions/upload-artifact@v4
with:
path: dist/*
name: artifact.zip
- uses: actions/attest-build-provenance@v3
with:
subject-name: artifact.zip
subject-digest: sha256:${{ steps.upload.outputs.artifact-digest }}
```
[1]: https://github.com/actions/toolkit/tree/main/packages/attest [1]: https://github.com/actions/toolkit/tree/main/packages/attest
[2]: https://github.com/in-toto/attestation/tree/main/spec/v1 [2]: https://github.com/in-toto/attestation/tree/main/spec/v1
@ -345,9 +53,5 @@ artifact directly into the `subject-digest` input of the attestation action.
[4]: https://www.sigstore.dev/ [4]: https://www.sigstore.dev/
[5]: https://cli.github.com/manual/gh_attestation_verify [5]: https://cli.github.com/manual/gh_attestation_verify
[6]: [6]:
https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto
[8]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns
[9]:
https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds
[10]: https://github.com/sigstore/cosign/blob/main/specs/BUNDLE_SPEC.md [7]: https://github.com/actions/attest
[11]: https://github.com/actions/upload-artifact

View File

@ -1,79 +0,0 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`main when a non-default OIDC issuer is used successfully run main 1`] = `
{
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "refs/heads/main",
"repository": "https://example-01.ghe.com/owner/repo",
},
},
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "repo-id",
"repository_owner_id": "owner-id",
"runner_environment": "github-hosted",
},
},
"resolvedDependencies": [
{
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://example-01.ghe.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://example-01.ghe.com/owner/shared/.github/workflows/build.yml@main",
},
"metadata": {
"invocationId": "https://example-01.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
}
`;
exports[`main when the default OIDC issuer is used successfully run main 1`] = `
{
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "refs/heads/main",
"repository": "https://github.com/owner/repo",
},
},
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "repo-id",
"repository_owner_id": "owner-id",
"runner_environment": "github-hosted",
},
},
"resolvedDependencies": [
{
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://github.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://github.com/owner/shared/.github/workflows/build.yml@main",
},
"metadata": {
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
}
`;

View File

@ -1,17 +0,0 @@
/**
* Unit tests for the action's entrypoint, src/index.ts
*/
import * as main from '../src/main'
// Mock the action's entrypoint
const runMock = jest.spyOn(main, 'run').mockImplementation()
describe('index', () => {
it('calls run when imported', () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../src/index')
expect(runMock).toHaveBeenCalled()
})
})

View File

@ -1,157 +0,0 @@
import * as core from '@actions/core'
import * as jose from 'jose'
import nock from 'nock'
import * as main from '../src/main'
// Mock the GitHub Actions core library functions
const setOutputMock = jest.spyOn(core, 'setOutput')
const setFailedMock = jest.spyOn(core, 'setFailed')
// Ensure that setFailed doesn't set an exit code during tests
setFailedMock.mockImplementation(() => {})
describe('main', () => {
let outputs = {} as Record<string, string>
const originalEnv = process.env
beforeEach(() => {
jest.resetAllMocks()
setOutputMock.mockImplementation((key, value) => {
outputs[key] = value
})
})
afterEach(() => {
outputs = {}
process.env = originalEnv
})
describe('when the default OIDC issuer is used', () => {
const issuer = 'https://token.actions.githubusercontent.com'
const audience = 'nobody'
const jwksPath = '/.well-known/jwks.json'
const tokenPath = '/token'
const claims = {
iss: issuer,
aud: 'nobody',
repository: 'owner/repo',
ref: 'refs/heads/main',
sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
workflow_ref: 'owner/repo/.github/workflows/main.yml@main',
job_workflow_ref: 'owner/shared/.github/workflows/build.yml@main',
event_name: 'push',
repository_id: 'repo-id',
repository_owner_id: 'owner-id',
run_id: 'run-id',
run_attempt: 'run-attempt',
runner_environment: 'github-hosted'
}
beforeEach(async () => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://github.com',
GITHUB_REPOSITORY: claims.repository
}
// Generate JWT signing key
const key = await jose.generateKeyPair('PS256')
// Create JWK, JWKS, and JWT
const kid = '12345'
const jwk = await jose.exportJWK(key.publicKey)
const jwks = { keys: [{ ...jwk, kid }] }
const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({ alg: 'PS256', kid })
.sign(key.privateKey)
// Mock OpenID configuration and JWKS endpoints
nock(issuer)
.get('/.well-known/openid-configuration')
.reply(200, { jwks_uri: `${issuer}${jwksPath}` })
nock(issuer).get(jwksPath).reply(200, jwks)
// Mock OIDC token endpoint for populating the provenance
nock(issuer).get(tokenPath).query({ audience }).reply(200, { value: jwt })
})
it('successfully run main', async () => {
// Run the main function
await main.run()
// Verify that outputs were set correctly
expect(setOutputMock).toHaveBeenCalledTimes(2)
expect(outputs['predicate']).toMatchSnapshot()
expect(outputs['predicate-type']).toBe('https://slsa.dev/provenance/v1')
})
})
describe('when a non-default OIDC issuer is used', () => {
const issuer = 'https://token.actions.example-01.ghe.com'
const audience = 'nobody'
const jwksPath = '/.well-known/jwks.json'
const tokenPath = '/token'
const claims = {
iss: issuer,
aud: 'nobody',
repository: 'owner/repo',
ref: 'refs/heads/main',
sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
workflow_ref: 'owner/repo/.github/workflows/main.yml@main',
job_workflow_ref: 'owner/shared/.github/workflows/build.yml@main',
event_name: 'push',
repository_id: 'repo-id',
repository_owner_id: 'owner-id',
run_id: 'run-id',
run_attempt: 'run-attempt',
runner_environment: 'github-hosted'
}
beforeEach(async () => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://example-01.ghe.com',
GITHUB_REPOSITORY: claims.repository
}
// Generate JWT signing key
const key = await jose.generateKeyPair('PS256')
// Create JWK, JWKS, and JWT
const kid = '12345'
const jwk = await jose.exportJWK(key.publicKey)
const jwks = { keys: [{ ...jwk, kid }] }
const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({ alg: 'PS256', kid })
.sign(key.privateKey)
// Mock OpenID configuration and JWKS endpoints
nock(issuer)
.get('/.well-known/openid-configuration')
.reply(200, { jwks_uri: `${issuer}${jwksPath}` })
nock(issuer).get(jwksPath).reply(200, jwks)
// Mock OIDC token endpoint for populating the provenance
nock(issuer).get(tokenPath).query({ audience }).reply(200, { value: jwt })
})
it('successfully run main', async () => {
// Run the main function
await main.run()
// Verify that outputs were set correctly
expect(setOutputMock).toHaveBeenCalledTimes(2)
expect(outputs['predicate']).toMatchSnapshot()
expect(outputs['predicate-type']).toBe('https://slsa.dev/provenance/v1')
})
})
})

View File

@ -1,17 +1,17 @@
name: 'Attest Build Provenance' name: "Attest Build Provenance"
description: 'Generate provenance attestations for build artifacts' description: "Generate provenance attestations for build artifacts"
author: 'GitHub' author: "GitHub"
branding: branding:
color: 'blue' color: "blue"
icon: 'lock' icon: "lock"
inputs: inputs:
subject-path: subject-path:
description: > description: >
Path to the artifact serving as the subject of the attestation. Must Path to the artifact serving as the subject of the attestation. Must
specify exactly one of "subject-path", "subject-digest", or specify exactly one of "subject-path", "subject-digest", or
"subject-checksums". May contain a glob pattern or list of paths "subject-checksums". May contain a glob pattern or list of paths (total
(total subject count cannot exceed 1024). subject count cannot exceed 1024).
required: false required: false
subject-digest: subject-digest:
description: > description: >
@ -29,6 +29,23 @@ inputs:
attestation. Must specify exactly one of "subject-path", "subject-digest", attestation. Must specify exactly one of "subject-path", "subject-digest",
or "subject-checksums". or "subject-checksums".
required: false required: false
predicate-type:
description: >
URI identifying the type of the predicate. Required when using "predicate"
or "predicate-path" for custom attestations.
required: false
predicate:
description: >
String containing the value for the attestation predicate. String length
cannot exceed 16MB. Must supply exactly one of "predicate-path" or
"predicate" when creating custom attestations.
required: false
predicate-path:
description: >
Path to the file which contains the content for the attestation predicate.
File size cannot exceed 16MB. Must supply exactly one of "predicate-path"
or "predicate" when creating custom attestations.
required: false
push-to-registry: push-to-registry:
description: > description: >
Whether to push the provenance statement to the image registry. Requires Whether to push the provenance statement to the image registry. Requires
@ -38,8 +55,8 @@ inputs:
required: false required: false
create-storage-record: create-storage-record:
description: > description: >
Whether to create a storage record for the artifact. Whether to create a storage record for the artifact. Requires that
Requires that push-to-registry is set to true. Defaults to true. push-to-registry is set to true. Defaults to true.
default: true default: true
required: false required: false
show-summary: show-summary:
@ -56,31 +73,34 @@ inputs:
outputs: outputs:
bundle-path: bundle-path:
description: 'The path to the file containing the attestation bundle.' description: "The path to the file containing the attestation bundle."
value: ${{ steps.attest.outputs.bundle-path }} value: ${{ steps.attest.outputs.bundle-path }}
attestation-id: attestation-id:
description: 'The ID of the attestation.' description: "The ID of the attestation."
value: ${{ steps.attest.outputs.attestation-id }} value: ${{ steps.attest.outputs.attestation-id }}
attestation-url: attestation-url:
description: 'The URL for the attestation summary.' description: "The URL for the attestation summary."
value: ${{ steps.attest.outputs.attestation-url }} value: ${{ steps.attest.outputs.attestation-url }}
storage-record-ids:
description: "GitHub IDs for the storage records"
value: ${{ steps.attest.outputs.storage-record-ids }}
runs: runs:
using: 'composite' using: "composite"
steps: steps:
- uses: actions/attest-build-provenance/predicate@864457a58d4733d7f1574bd8821fa24e02cf7538 # predicate@2.0.0 - name: Attest
id: generate-build-provenance-predicate
- uses: actions/attest@e59cbc1ad1ac2d59339667419eb8cdde6eb61e3d # v3.2.0
id: attest id: attest
env: env:
NODE_OPTIONS: "--max-http-header-size=32768" NODE_OPTIONS: "--max-http-header-size=32768"
uses: actions/attest@c32b4b8b198b65d0bd9d63490e847ff7b53989d4 # v4.0.0
with: with:
subject-path: ${{ inputs.subject-path }} subject-path: ${{ inputs.subject-path }}
subject-digest: ${{ inputs.subject-digest }}
subject-name: ${{ inputs.subject-name }} subject-name: ${{ inputs.subject-name }}
subject-digest: ${{ inputs.subject-digest }}
subject-checksums: ${{ inputs.subject-checksums }} subject-checksums: ${{ inputs.subject-checksums }}
predicate-type: ${{ steps.generate-build-provenance-predicate.outputs.predicate-type }} predicate-type: ${{ inputs.predicate-type }}
predicate: ${{ steps.generate-build-provenance-predicate.outputs.predicate }} predicate: ${{ inputs.predicate }}
predicate-path: ${{ inputs.predicate-path }}
push-to-registry: ${{ inputs.push-to-registry }} push-to-registry: ${{ inputs.push-to-registry }}
create-storage-record: ${{ inputs.create-storage-record }} create-storage-record: ${{ inputs.create-storage-record }}
show-summary: ${{ inputs.show-summary }} show-summary: ${{ inputs.show-summary }}

BIN
dist/606.index.js generated vendored

Binary file not shown.

BIN
dist/index.js generated vendored

Binary file not shown.

BIN
dist/licenses.txt generated vendored

Binary file not shown.

View File

@ -1,92 +0,0 @@
import eslint from '@eslint/js'
import importplugin from 'eslint-plugin-import'
import jestplugin from 'eslint-plugin-jest'
import tseslint from 'typescript-eslint'
export default tseslint.config(
// Ignore non-project files
{
name: 'ignore',
ignores: ['.github', 'dist', 'coverage', '**/*.json', 'jest.setup.js', 'eslint.config.mjs']
},
// Use recommended rules from ESLint, TypeScript, and other plugins
eslint.configs.recommended,
tseslint.configs.recommendedTypeChecked,
jestplugin.configs['flat/recommended'],
importplugin.flatConfigs.recommended,
importplugin.flatConfigs.typescript,
// Override some rules
{
name: 'project-settings',
languageOptions: {
ecmaVersion: 2023,
parserOptions: {
project: ['./tsconfig.lint.json']
}
},
rules: {
// eslint rules
eqeqeq: ['error', 'smart'],
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': 'off',
'no-implicit-globals': 'error',
'no-inner-declarations': 'error',
'no-invalid-this': 'error',
'no-return-assign': 'error',
'no-sequences': 'error',
'no-shadow': 'error',
'no-useless-concat': 'error',
'object-shorthand': ['error', 'always', { avoidQuotes: true }],
'one-var': ['error', 'never'],
'prefer-template': 'error',
// typescript-eslint rules
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/consistent-type-assertions': 'error',
'@typescript-eslint/explicit-function-return-type': [
'error',
{ allowExpressions: true }
],
'@typescript-eslint/explicit-member-accessibility': [
'error',
{ accessibility: 'no-public' }
],
'@typescript-eslint/no-extraneous-class': 'error',
'@typescript-eslint/no-inferrable-types': 'error',
'@typescript-eslint/no-non-null-assertion': 'warn',
'@typescript-eslint/no-unnecessary-qualifier': 'error',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-useless-constructor': 'error',
'@typescript-eslint/prefer-for-of': 'warn',
'@typescript-eslint/prefer-function-type': 'warn',
'@typescript-eslint/prefer-includes': 'error',
'@typescript-eslint/prefer-string-starts-ends-with': 'error',
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/require-array-sort-compare': 'error',
'@typescript-eslint/restrict-template-expressions': 'off',
// eslint-plugin-import rules
'import/extensions': 'error',
'import/first': 'error',
'import/no-absolute-path': 'error',
'import/no-commonjs': 'error',
'import/no-deprecated': 'warn',
'import/no-dynamic-require': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-namespace': 'off',
'import/no-unresolved': ['error', { ignore: ['csv-parse/sync'] }],
'import/no-anonymous-default-export': [
'error',
{
allowAnonymousClass: false,
allowAnonymousFunction: false,
allowArray: true,
allowArrowFunction: false,
allowLiteral: true,
allowObject: true
}
]
}
}
)

View File

@ -1 +0,0 @@
process.stdout.write = jest.fn()

9352
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +0,0 @@
{
"name": "actions/attest-build-provenance",
"description": "Generate signed build provenance attestations",
"version": "2.0.0",
"author": "",
"private": true,
"homepage": "https://github.com/actions/attest-build-provenance",
"repository": {
"type": "git",
"url": "git+https://github.com/actions/attest-build-provenance.git"
},
"bugs": {
"url": "https://github.com/actions/attest-build-provenance/issues"
},
"keywords": [
"actions",
"attestation",
"provenance"
],
"exports": {
".": "./dist/index.js"
},
"engines": {
"node": ">=24"
},
"scripts": {
"bundle": "npm run format:write && npm run package",
"ci-test": "jest",
"format:write": "prettier --write **/*.ts",
"format:check": "prettier --check **/*.ts",
"lint:eslint": "npx eslint",
"lint:markdown": "npx markdownlint --config .markdown-lint.yml \"*.md\"",
"lint": "npm run lint:eslint && npm run lint:markdown",
"package": "ncc build src/index.ts --license licenses.txt",
"package:watch": "npm run package -- --watch",
"test": "jest",
"all": "npm run format:write && npm run lint && npm run test && npm run package"
},
"license": "MIT",
"jest": {
"preset": "ts-jest",
"verbose": true,
"clearMocks": true,
"testEnvironment": "node",
"moduleFileExtensions": [
"js",
"ts"
],
"setupFilesAfterEnv": [
"./jest.setup.js"
],
"testMatch": [
"**/*.test.ts"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/dist/"
],
"transform": {
"^.+\\.ts$": "ts-jest"
},
"coverageReporters": [
"json-summary",
"text",
"lcov"
],
"collectCoverage": true,
"collectCoverageFrom": [
"./src/**"
]
},
"dependencies": {
"@actions/attest": "^2.2.1",
"@actions/core": "^2.0.3"
},
"devDependencies": {
"@eslint/js": "^9.39.2",
"@types/jest": "^30.0.0",
"@types/node": "^25.2.2",
"@vercel/ncc": "^0.38.4",
"eslint": "^9.39.2",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.13.0",
"jest": "^30.2.0",
"jose": "^5.9.6",
"markdownlint-cli": "^0.47.0",
"nock": "^14.0.10",
"prettier": "^3.8.1",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0"
}
}

View File

@ -1,14 +0,0 @@
name: 'Build Provenance Predicate'
description: 'Generate predicate for build provenance attestations'
author: 'GitHub'
outputs:
predicate:
description: >
The JSON-serialized of the attestation predicate.
predicate-type:
description: >
URI identifying the type of the predicate.
runs:
using: node24
main: ../dist/index.js

View File

@ -1,7 +0,0 @@
/**
* The entrypoint for the action.
*/
import { run } from './main'
// eslint-disable-next-line @typescript-eslint/no-floating-promises
run()

View File

@ -1,20 +0,0 @@
import { buildSLSAProvenancePredicate } from '@actions/attest'
import * as core from '@actions/core'
/**
* The main function for the action.
* @returns {Promise<void>} Resolves when the action is complete.
*/
export async function run(): Promise<void> {
try {
// Calculate subject from inputs and generate provenance
const predicate = await buildSLSAProvenancePredicate()
core.setOutput('predicate', predicate.params)
core.setOutput('predicate-type', predicate.type)
} catch (err) {
const error = err instanceof Error ? err : new Error(`${err}`)
// Fail the workflow run if an error occurs
core.setFailed(error.message)
}
}

View File

@ -1,20 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"rootDir": "./src",
"moduleResolution": "NodeNext",
"isolatedModules": true,
"baseUrl": "./",
"sourceMap": true,
"outDir": "./dist",
"noImplicitAny": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"newLine": "lf"
},
"exclude": ["./dist", "./node_modules", "./__tests__", "./coverage"]
}

View File

@ -1,9 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true
},
"include": ["./__tests__/**/*", "./src/**/*"],
"exclude": ["./dist", "./node_modules", "./coverage", "*.json"]
}