Compare commits
62 Commits
predicate@
...
v1.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5026d36637 | ||
|
|
dc92b4c0be | ||
|
|
dd4b089aa5 | ||
|
|
fa5285f58e | ||
|
|
bd470e0ef8 | ||
|
|
0d076ca0ac | ||
|
|
79af85adb2 | ||
|
|
9e75edd833 | ||
|
|
f19ab44411 | ||
|
|
8507f05fe1 | ||
|
|
25b3c0884a | ||
|
|
b5fe8a6c40 | ||
|
|
47c91cee86 | ||
|
|
6d7733f629 | ||
|
|
adc5c62972 | ||
|
|
8541e845e0 | ||
|
|
bfc4aecb6d | ||
|
|
3d6693daad | ||
|
|
8a2267cfa7 | ||
|
|
9acafbf4c3 | ||
|
|
91d05efbc3 | ||
|
|
ab8de8941e | ||
|
|
c43b2b4d84 | ||
|
|
fe4a732a3e | ||
|
|
ba663bc478 | ||
|
|
49e7311f18 | ||
|
|
cb316d67b7 | ||
|
|
4696efab19 | ||
|
|
dcbe3081a3 | ||
|
|
aaa2d0a82e | ||
|
|
dc3e3b331e | ||
|
|
c29e4e9225 | ||
|
|
798ee587a2 | ||
|
|
7d87da1e33 | ||
|
|
e318c7dfea | ||
|
|
614575ea4f | ||
|
|
d00b213255 | ||
|
|
f975621746 | ||
|
|
5297f161fa | ||
|
|
10c27177cd | ||
|
|
c80e3ed30c | ||
|
|
c168f2354d | ||
|
|
5448b22ebd | ||
|
|
b125530ffd | ||
|
|
534423496e | ||
|
|
2f5f68fcc3 | ||
|
|
36d21cdc72 | ||
|
|
38c481ec87 | ||
|
|
d9763b28c9 | ||
|
|
1afe01eb23 | ||
|
|
a624c741b8 | ||
|
|
d4f0c27f8d | ||
|
|
ab147f15c3 | ||
|
|
41d694c98d | ||
|
|
0b5415aa25 | ||
|
|
817d650747 | ||
|
|
5d89d51206 | ||
|
|
f8e0f3ab00 | ||
|
|
e37c92d3ba | ||
|
|
d023f128e6 | ||
|
|
48e5743928 | ||
|
|
05284cc010 |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -9,6 +9,8 @@ updates:
|
|||||||
update-types:
|
update-types:
|
||||||
- minor
|
- minor
|
||||||
- patch
|
- patch
|
||||||
|
ignore:
|
||||||
|
- dependency-name: 'actions/attest-sbom'
|
||||||
|
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: /
|
directory: /
|
||||||
|
|||||||
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
@@ -46,11 +46,12 @@ jobs:
|
|||||||
id: npm-ci-test
|
id: npm-ci-test
|
||||||
run: npm run ci-test
|
run: npm run ci-test
|
||||||
|
|
||||||
test-attest-sbom-with-local-sbom-file:
|
test-attest-sbom:
|
||||||
name: Test attest-sbom action with local sbom file
|
name: Test attest-sbom action with local sbom file
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
attestations: write
|
||||||
|
contents: read
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -69,31 +70,3 @@ jobs:
|
|||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Dump output
|
- name: Dump output
|
||||||
run: jq < ${{ steps.attest-sbom.outputs.bundle-path }}
|
run: jq < ${{ steps.attest-sbom.outputs.bundle-path }}
|
||||||
test-attest-sbom:
|
|
||||||
name: Test attest-sbom action
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
id-token: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
id: checkout
|
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
|
||||||
- name: Run attest-sbom with spdx format
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
|
||||||
subject-name: 'subject'
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
sbom-format: 'spdx'
|
|
||||||
- name: Run attest-sbom with cyclonedx format
|
|
||||||
id: attest-sbom
|
|
||||||
uses: ./
|
|
||||||
env:
|
|
||||||
INPUT_PRIVATE-SIGNING: 'true'
|
|
||||||
with:
|
|
||||||
subject-digest: 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32'
|
|
||||||
subject-name: 'subject'
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
sbom-format: 'cyclonedx'
|
|
||||||
|
|||||||
5
.github/workflows/linter.yml
vendored
5
.github/workflows/linter.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Lint Codebase
|
- name: Lint Codebase
|
||||||
id: super-linter
|
id: super-linter
|
||||||
uses: super-linter/super-linter/slim@v6
|
uses: super-linter/super-linter/slim@v7
|
||||||
env:
|
env:
|
||||||
DEFAULT_BRANCH: main
|
DEFAULT_BRANCH: main
|
||||||
FILTER_REGEX_EXCLUDE: dist/**/*
|
FILTER_REGEX_EXCLUDE: dist/**/*
|
||||||
@@ -46,4 +46,5 @@ jobs:
|
|||||||
TYPESCRIPT_DEFAULT_STYLE: prettier
|
TYPESCRIPT_DEFAULT_STYLE: prettier
|
||||||
VALIDATE_ALL_CODEBASE: true
|
VALIDATE_ALL_CODEBASE: true
|
||||||
VALIDATE_JAVASCRIPT_STANDARD: false
|
VALIDATE_JAVASCRIPT_STANDARD: false
|
||||||
VALIDATE_JSCPD: false
|
VALIDATE_TYPESCRIPT_STANDARD: false
|
||||||
|
VALIDATE_JSCPD: false
|
||||||
|
|||||||
105
README.md
105
README.md
@@ -5,9 +5,8 @@ the [@actions/attest][1] package.
|
|||||||
|
|
||||||
Attestations bind some subject (a named artifact along with its digest) to a a
|
Attestations bind some subject (a named artifact along with its digest) to a a
|
||||||
Software Bill of Materials (SBOM) using the [in-toto][2] format. The action
|
Software Bill of Materials (SBOM) using the [in-toto][2] format. The action
|
||||||
accepts SBOMs which have been generated by external tools or can generate one
|
accepts SBOMs which have been generated by external tools. Provided SBOMs must
|
||||||
automatically by invoking the [anchore/sbom-action][3]. Externally generated
|
be in either the [SPDX][4] or [CycloneDX][5] JSON-serialized format.
|
||||||
SBOMs must be in either the [SPDX][4] or [CycloneDX][5] JSON-serialized format.
|
|
||||||
|
|
||||||
A verifiable signature is generated for the attestation using a short-lived
|
A verifiable signature is generated for the attestation using a short-lived
|
||||||
[Sigstore][6]-issued signing certificate. If the repository initiating the
|
[Sigstore][6]-issued signing certificate. If the repository initiating the
|
||||||
@@ -19,9 +18,12 @@ Once the attestation has been created and signed, it will be uploaded to the GH
|
|||||||
attestations API and associated with the repository from which the workflow was
|
attestations API and associated with the repository from which the workflow was
|
||||||
initiated.
|
initiated.
|
||||||
|
|
||||||
Attestations can be verified using the `attestation` command in the [GitHub
|
Attestations can be verified using the [`attestation` command in the GitHub
|
||||||
CLI][7].
|
CLI][7].
|
||||||
|
|
||||||
|
See [Using artifact attestations to establish provenance for builds][11] for
|
||||||
|
more information on artifact attestations.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Within the GitHub Actions workflow which builds some artifact you would like to
|
Within the GitHub Actions workflow which builds some artifact you would like to
|
||||||
@@ -32,38 +34,40 @@ attest:
|
|||||||
```yaml
|
```yaml
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write # TODO: Update this
|
attestations: write
|
||||||
```
|
```
|
||||||
|
|
||||||
The `id-token` permission gives the action the ability to mint the OIDC token
|
The `id-token` permission gives the action the ability to mint the OIDC token
|
||||||
necessary to request a Sigstore signing certificate. The `contents`
|
necessary to request a Sigstore signing certificate. The `attestations`
|
||||||
permission is necessary to persist the attestation.
|
permission is necessary to persist the attestation.
|
||||||
|
|
||||||
1. Add the following to your workflow after your artifact has been built:
|
1. Add the following to your workflow after your artifact has been built and
|
||||||
|
your SBOM has been generated:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: actions/attest-sbom@v1
|
- uses: actions/attest-sbom@v1
|
||||||
with:
|
with:
|
||||||
subject-path: '<PATH TO ARTIFACT>'
|
subject-path: '<PATH TO ARTIFACT>'
|
||||||
|
sbom-path: '<PATH TO SBOM>'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `subject-path` parameter should identity the artifact for which you want
|
The `subject-path` parameter should identify the artifact for which you want
|
||||||
to generate an SBOM attestation. When no other inputs are specified, the
|
to generate an SBOM attestation. The `sbom-path` parameter should identify
|
||||||
action will automatically generate an SPDX SBOM by scanning the
|
the SBOM document to be associated with the subject.
|
||||||
`github.workspace` directory.
|
|
||||||
|
|
||||||
### Inputs
|
### Inputs
|
||||||
|
|
||||||
See [action.yml](action.yml)
|
See [action.yml](action.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: actions/attest@v1
|
- uses: actions/attest-sbom@v1
|
||||||
with:
|
with:
|
||||||
# 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" or "subject-digest".
|
# specify exactly one of "subject-path" or "subject-digest". May contain a
|
||||||
|
# glob pattern or list of paths (total subject count cannot exceed 2500).
|
||||||
subject-path:
|
subject-path:
|
||||||
|
|
||||||
# SHA256 digest of the subject for for the attestation. Must be in the form
|
# SHA256 digest of the subject for the attestation. Must be in the form
|
||||||
# "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one
|
# "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one
|
||||||
# of "subject-path" or "subject-digest".
|
# of "subject-path" or "subject-digest".
|
||||||
subject-digest:
|
subject-digest:
|
||||||
@@ -73,24 +77,19 @@ See [action.yml](action.yml)
|
|||||||
# path.
|
# path.
|
||||||
subject-name:
|
subject-name:
|
||||||
|
|
||||||
# Path to the JSON-formatted SBOM file to attest. When specified, the
|
# Path to the JSON-formatted SBOM file to attest. File size cannot exceed
|
||||||
# "scan-path" and "sbom-format" inputs are ignored.
|
# 16MB.
|
||||||
sbom-path:
|
sbom-path:
|
||||||
|
|
||||||
# Path on the filesystem to scan for SBOM generation. Ignored if "sbom-path"
|
|
||||||
# is specified. Defaults to ${{ github.workspace }}
|
|
||||||
scan-path:
|
|
||||||
|
|
||||||
# Format to use for the generated SBOM output. Supported formats are
|
|
||||||
# "spdx" and "cyclonedx". Ignored if "sbom-path" is specified. Defaults to
|
|
||||||
# "spdx".
|
|
||||||
sbom-format:
|
|
||||||
|
|
||||||
# Whether to push the attestation to the image registry. Requires that the
|
# Whether to push the attestation to the image registry. Requires that the
|
||||||
# "subject-name" parameter specify the fully-qualified image name and that
|
# "subject-name" parameter specify the fully-qualified image name and that
|
||||||
# the "subject-digest" parameter be specified. Defaults to false.
|
# the "subject-digest" parameter be specified. Defaults to false.
|
||||||
push-to-registry:
|
push-to-registry:
|
||||||
|
|
||||||
|
# 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
|
# The GitHub token used to make authenticated API requests. Default is
|
||||||
# ${{ github.token }}
|
# ${{ github.token }}
|
||||||
github-token:
|
github-token:
|
||||||
@@ -112,6 +111,19 @@ If multiple subjects are being attested at the same time, each attestation will
|
|||||||
be written to the output file on a separate line (using the [JSON Lines][9]
|
be written to the output file on a separate line (using the [JSON Lines][9]
|
||||||
format).
|
format).
|
||||||
|
|
||||||
|
## Attestation Limits
|
||||||
|
|
||||||
|
### Subject Limits
|
||||||
|
|
||||||
|
No more than 2500 subjects can be attested at the same time. Subjects will be
|
||||||
|
processed in batches 50. After the initial group of 50, each subsequent batch
|
||||||
|
will incur an exponentially increasing amount of delay (capped at 1 minute of
|
||||||
|
delay per batch) to avoid overwhelming the attestation API.
|
||||||
|
|
||||||
|
### SBOM Limits
|
||||||
|
|
||||||
|
The SBOM supplied via the `sbom-path` input cannot exceed 16MB.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Identify Subject and SBOM by Path
|
### Identify Subject and SBOM by Path
|
||||||
@@ -130,7 +142,8 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
contents: write
|
contents: read
|
||||||
|
attestations: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -138,15 +151,18 @@ jobs:
|
|||||||
- name: Build artifact
|
- name: Build artifact
|
||||||
run: make my-app
|
run: make my-app
|
||||||
- name: Generate SBOM
|
- name: Generate SBOM
|
||||||
run: make sbom
|
uses: anchore/sbom-action@v0
|
||||||
|
with:
|
||||||
|
format: 'spdx-json'
|
||||||
|
output-file: 'sbom.spdx.json'
|
||||||
- name: Attest
|
- name: Attest
|
||||||
uses: actions/attest-sbom@v1
|
uses: actions/attest-sbom@v1
|
||||||
with:
|
with:
|
||||||
subject-path: '${{ github.workspace }}/my-app'
|
subject-path: '${{ github.workspace }}/my-app'
|
||||||
sbom-path: '${{ github.workspace }}/my-app.sbom.spdx.json'
|
sbom-path: 'sbom.spdx.json'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Identify Subjects by Wildcard
|
### Identify Multiple Subjects
|
||||||
|
|
||||||
If you are generating multiple artifacts, you can generate an attestation for
|
If you are generating multiple artifacts, you can generate an attestation for
|
||||||
each by using a wildcard in the `subject-path` input.
|
each by using a wildcard in the `subject-path` input.
|
||||||
@@ -161,6 +177,23 @@ each by using a wildcard in the `subject-path` input.
|
|||||||
For supported wildcards along with behavior and documentation, see
|
For supported wildcards along with behavior and documentation, see
|
||||||
[@actions/glob][10] which is used internally to search for files.
|
[@actions/glob][10] 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-sbom@v1
|
||||||
|
with:
|
||||||
|
subject-path: 'dist/foo, dist/bar'
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: actions/attest-sbom@v1
|
||||||
|
with:
|
||||||
|
subject-path: |
|
||||||
|
dist/foo
|
||||||
|
dist/bar
|
||||||
|
```
|
||||||
|
|
||||||
### Container Image
|
### Container Image
|
||||||
|
|
||||||
When working with container images you can invoke the action with the
|
When working with container images you can invoke the action with the
|
||||||
@@ -188,7 +221,8 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
packages: write
|
packages: write
|
||||||
contents: write
|
contents: read
|
||||||
|
attestations: write
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
@@ -210,7 +244,11 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
- name: Generate SBOM
|
- name: Generate SBOM
|
||||||
run: make sbom
|
uses: anchore/sbom-action@v0
|
||||||
|
with:
|
||||||
|
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
||||||
|
format: 'cyclonedx-json'
|
||||||
|
output-file: 'sbom.cyclonedx.json'
|
||||||
- name: Attest
|
- name: Attest
|
||||||
uses: actions/attest-sbom@v1
|
uses: actions/attest-sbom@v1
|
||||||
id: attest
|
id: attest
|
||||||
@@ -223,12 +261,13 @@ jobs:
|
|||||||
|
|
||||||
[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
|
||||||
[3]: https://github.com/anchore/sbom-action
|
|
||||||
[4]: https://spdx.dev/
|
[4]: https://spdx.dev/
|
||||||
[5]: https://cyclonedx.org/
|
[5]: https://cyclonedx.org/
|
||||||
[6]: https://www.sigstore.dev/
|
[6]: https://www.sigstore.dev/
|
||||||
[7]: https://cli.github.com/
|
[7]: https://cli.github.com/manual/gh_attestation_verify
|
||||||
[8]:
|
[8]:
|
||||||
https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto
|
https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto
|
||||||
[9]: https://jsonlines.org/
|
[9]: https://jsonlines.org/
|
||||||
[10]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns
|
[10]: https://github.com/actions/toolkit/tree/main/packages/glob#patterns
|
||||||
|
[11]:
|
||||||
|
https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds
|
||||||
|
|||||||
37
RELEASE.md
Normal file
37
RELEASE.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Release Instructions
|
||||||
|
|
||||||
|
Follow the steps below to tag a new release for the `actions/attest-sbom`
|
||||||
|
action.
|
||||||
|
|
||||||
|
If changes were made to the internal `actions/attest-sbom/predicate` action (any
|
||||||
|
updates to [`./predicate/action.yaml`](./predicate/action.yml) or any of the
|
||||||
|
code in the [`./src`](./src) directory), start with step #1; otherwise, skip
|
||||||
|
directly to step #5.
|
||||||
|
|
||||||
|
1. Merge the latest changes to the `main` branch.
|
||||||
|
1. Create and push a new predicate tag of the form `predicate@X.X.X` following
|
||||||
|
SemVer conventions:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git tag -a "predicate@X.X.X" -m "predicate@X.X.X Release"
|
||||||
|
git push --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Update the reference to the `actions/attest-sbom/predicate` action in
|
||||||
|
[`action.yml`](./action.yml) to point to the SHA of the newly created tag.
|
||||||
|
1. Push the `action.yml` change and open a PR. Once it has been reviewed, merge
|
||||||
|
the PR and proceed with the release instructions.
|
||||||
|
1. Create a new release for the top-level action using a tag of the form
|
||||||
|
`vX.X.X` following SemVer conventions:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
gh release create vX.X.X
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Move (or create) the major version tag to point to the same commit tagged
|
||||||
|
above:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git tag -fa vX -m "vX"
|
||||||
|
git push origin vX --force
|
||||||
|
```
|
||||||
@@ -1,41 +1,38 @@
|
|||||||
{
|
{
|
||||||
"spdxVersion": "SPDX-2.3",
|
"spdxVersion": "SPDX-2.3",
|
||||||
"dataLicense": "CC0-1.0",
|
"dataLicense": "CC0-1.0",
|
||||||
"SPDXID": "SPDXRef-DOCUMENT",
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
"name": "./",
|
"name": "./",
|
||||||
"documentNamespace": "https://anchore.com/syft/dir/80b363b6-87f4-4162-853f-60d402537d20",
|
"documentNamespace": "https://anchore.com/syft/dir/80b363b6-87f4-4162-853f-60d402537d20",
|
||||||
"creationInfo": {
|
"creationInfo": {
|
||||||
"licenseListVersion": "3.22",
|
"licenseListVersion": "3.22",
|
||||||
"creators": [
|
"creators": ["Organization: Anchore, Inc", "Tool: syft-0.103.1"],
|
||||||
"Organization: Anchore, Inc",
|
"created": "2024-01-31T18:22:50Z"
|
||||||
"Tool: syft-0.103.1"
|
},
|
||||||
],
|
"packages": [
|
||||||
"created": "2024-01-31T18:22:50Z"
|
{
|
||||||
},
|
"name": "@ampproject/remapping",
|
||||||
"packages": [
|
"SPDXID": "SPDXRef-Package-npm--ampproject-remapping-5266573ba4f24a42",
|
||||||
{
|
"versionInfo": "2.2.1",
|
||||||
"name": "@ampproject/remapping",
|
"supplier": "NOASSERTION",
|
||||||
"SPDXID": "SPDXRef-Package-npm--ampproject-remapping-5266573ba4f24a42",
|
"downloadLocation": "NOASSERTION",
|
||||||
"versionInfo": "2.2.1",
|
"filesAnalyzed": false,
|
||||||
"supplier": "NOASSERTION",
|
"sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock",
|
||||||
"downloadLocation": "NOASSERTION",
|
"licenseConcluded": "NOASSERTION",
|
||||||
"filesAnalyzed": false,
|
"licenseDeclared": "Apache-2.0",
|
||||||
"sourceInfo": "acquired package info from installed node module manifest file: /yarn.lock",
|
"copyrightText": "NOASSERTION",
|
||||||
"licenseConcluded": "NOASSERTION",
|
"externalRefs": [
|
||||||
"licenseDeclared": "Apache-2.0",
|
{
|
||||||
"copyrightText": "NOASSERTION",
|
"referenceCategory": "SECURITY",
|
||||||
"externalRefs": [
|
"referenceType": "cpe23Type",
|
||||||
{
|
"referenceLocator": "cpe:2.3:a:\\@ampproject\\/remapping:\\@ampproject\\/remapping:2.2.1:*:*:*:*:*:*:*"
|
||||||
"referenceCategory": "SECURITY",
|
},
|
||||||
"referenceType": "cpe23Type",
|
{
|
||||||
"referenceLocator": "cpe:2.3:a:\\@ampproject\\/remapping:\\@ampproject\\/remapping:2.2.1:*:*:*:*:*:*:*"
|
"referenceCategory": "PACKAGE-MANAGER",
|
||||||
},
|
"referenceType": "purl",
|
||||||
{
|
"referenceLocator": "pkg:npm/%40ampproject/remapping@2.2.1"
|
||||||
"referenceCategory": "PACKAGE-MANAGER",
|
}
|
||||||
"referenceType": "purl",
|
]
|
||||||
"referenceLocator": "pkg:npm/%40ampproject/remapping@2.2.1"
|
}
|
||||||
}
|
]
|
||||||
]
|
}
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|||||||
68
action.yml
68
action.yml
@@ -1,16 +1,20 @@
|
|||||||
name: 'Attest SBOM'
|
name: 'Attest SBOM'
|
||||||
description: 'Generate SBOM attestations for build artifacts'
|
description: 'Generate SBOM attestations for build artifacts'
|
||||||
author: 'GitHub'
|
author: 'GitHub'
|
||||||
|
branding:
|
||||||
|
color: 'blue'
|
||||||
|
icon: 'paperclip'
|
||||||
|
|
||||||
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" or "subject-digest".
|
specify exactly one of "subject-path" or "subject-digest". May contain a
|
||||||
|
glob pattern or list of paths (total subject count cannot exceed 2500).
|
||||||
required: false
|
required: false
|
||||||
subject-digest:
|
subject-digest:
|
||||||
description: >
|
description: >
|
||||||
SHA256 digest of the subject for for the attestation. Must be in the form
|
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
|
"sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one of
|
||||||
"subject-path" or "subject-digest".
|
"subject-path" or "subject-digest".
|
||||||
required: false
|
required: false
|
||||||
@@ -21,21 +25,9 @@ inputs:
|
|||||||
path.
|
path.
|
||||||
sbom-path:
|
sbom-path:
|
||||||
description: >
|
description: >
|
||||||
Path to the JSON-formatted SBOM file to attest. When specified, the
|
Path to the JSON-formatted SBOM file to attest. File size cannot exceed
|
||||||
"scan-path" and "sbom-format" inputs are ignored.
|
16MB.
|
||||||
required: false
|
required: true
|
||||||
scan-path:
|
|
||||||
description: >
|
|
||||||
Path on the filesystem to scan for SBOM generation. Ignored if "sbom-path"
|
|
||||||
is specified.
|
|
||||||
default: ${{ github.workspace }}
|
|
||||||
required: false
|
|
||||||
sbom-format:
|
|
||||||
description: >
|
|
||||||
Format to use for the generated SBOM output. Supported formats are "spdx"
|
|
||||||
and "cyclonedx". Ignored if "sbom-path" is specified.
|
|
||||||
default: 'spdx'
|
|
||||||
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
|
||||||
@@ -43,11 +35,18 @@ inputs:
|
|||||||
and that the "subject-digest" parameter be specified. Defaults to false.
|
and that the "subject-digest" parameter be specified. Defaults to false.
|
||||||
default: false
|
default: false
|
||||||
required: false
|
required: false
|
||||||
|
show-summary:
|
||||||
|
description: >
|
||||||
|
Whether to attach a list of generated attestations to the workflow run
|
||||||
|
summary page. Defaults to true.
|
||||||
|
default: true
|
||||||
|
required: false
|
||||||
github-token:
|
github-token:
|
||||||
description: >
|
description: >
|
||||||
The GitHub token used to make authenticated API requests.
|
The GitHub token used to make authenticated API requests.
|
||||||
default: ${{ github.token }}
|
default: ${{ github.token }}
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
bundle-path:
|
bundle-path:
|
||||||
description: 'The path to the file containing the attestation bundle(s).'
|
description: 'The path to the file containing the attestation bundle(s).'
|
||||||
@@ -56,45 +55,20 @@ outputs:
|
|||||||
runs:
|
runs:
|
||||||
using: 'composite'
|
using: 'composite'
|
||||||
steps:
|
steps:
|
||||||
- name: Generate random SBOM output file name
|
- uses: actions/attest-sbom/predicate@534423496eab34674190bc45fdacbb8b1198e07f # predicate@1.0.0
|
||||||
if: inputs.sbom-path == ''
|
|
||||||
id: sbom-output
|
|
||||||
run:
|
|
||||||
echo "path=${{ runner.temp }}/sbom_$(openssl rand -hex 6).json" >> $GITHUB_OUTPUT
|
|
||||||
shell: bash
|
|
||||||
- name: SBOM format check
|
|
||||||
id: check-sbom-format
|
|
||||||
if: inputs.sbom-path == ''
|
|
||||||
run: |
|
|
||||||
if [ "${{inputs.sbom-format}}" != "spdx" ] && [ "${{inputs.sbom-format}}" != "cyclonedx" ] ]; then
|
|
||||||
echo "Invalid SBOM format. Supported formats are spdx and cyclonedx."
|
|
||||||
exit 1
|
|
||||||
elif [ "${{inputs.sbom-format}}" == "spdx" ]; then
|
|
||||||
echo "format=spdx-json" >> $GITHUB_OUTPUT
|
|
||||||
elif [ "${{inputs.sbom-format}}" == "cyclonedx" ]; then
|
|
||||||
echo "format=cyclonedx-json" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
- name: Generate SBOM
|
|
||||||
if: inputs.sbom-path == ''
|
|
||||||
uses: anchore/sbom-action@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8
|
|
||||||
with:
|
|
||||||
path: ${{ inputs.scan-path }}
|
|
||||||
output-file: ${{ steps.sbom-output.outputs.path }}
|
|
||||||
format: ${{ steps.check-sbom-format.outputs.format }}
|
|
||||||
- uses: actions/attest-sbom/predicate@main
|
|
||||||
id: generate-sbom-predicate
|
id: generate-sbom-predicate
|
||||||
with:
|
with:
|
||||||
sbom-path: ${{ inputs.sbom-path || steps.sbom-output.outputs.path }}
|
sbom-path: ${{ inputs.sbom-path }}
|
||||||
- uses: actions/attest@main
|
- uses: actions/attest@67422f5511b7ff725f4dbd6fb9bd2cd925c65a8d # v1.4.1
|
||||||
id: attest
|
id: attest
|
||||||
with:
|
with:
|
||||||
subject-path: ${{ inputs.subject-path }}
|
subject-path: ${{ inputs.subject-path }}
|
||||||
subject-digest: ${{ inputs.subject-digest }}
|
subject-digest: ${{ inputs.subject-digest }}
|
||||||
subject-name: ${{ inputs.subject-name }}
|
subject-name: ${{ inputs.subject-name }}
|
||||||
push-to-registry: ${{ inputs.push-to-registry }}
|
|
||||||
predicate-type:
|
predicate-type:
|
||||||
${{ steps.generate-sbom-predicate.outputs.predicate-type }}
|
${{ steps.generate-sbom-predicate.outputs.predicate-type }}
|
||||||
predicate-path:
|
predicate-path:
|
||||||
${{ steps.generate-sbom-predicate.outputs.predicate-path }}
|
${{ steps.generate-sbom-predicate.outputs.predicate-path }}
|
||||||
|
push-to-registry: ${{ inputs.push-to-registry }}
|
||||||
|
show-summary: ${{ inputs.show-summary }}
|
||||||
github-token: ${{ inputs.github-token }}
|
github-token: ${{ inputs.github-token }}
|
||||||
|
|||||||
314
dist/index.js
generated
vendored
314
dist/index.js
generated
vendored
@@ -1590,7 +1590,7 @@ class HttpClient {
|
|||||||
if (this._keepAlive && useProxy) {
|
if (this._keepAlive && useProxy) {
|
||||||
agent = this._proxyAgent;
|
agent = this._proxyAgent;
|
||||||
}
|
}
|
||||||
if (this._keepAlive && !useProxy) {
|
if (!useProxy) {
|
||||||
agent = this._agent;
|
agent = this._agent;
|
||||||
}
|
}
|
||||||
// if agent is already assigned use that agent.
|
// if agent is already assigned use that agent.
|
||||||
@@ -1622,16 +1622,12 @@ class HttpClient {
|
|||||||
agent = tunnelAgent(agentOptions);
|
agent = tunnelAgent(agentOptions);
|
||||||
this._proxyAgent = agent;
|
this._proxyAgent = agent;
|
||||||
}
|
}
|
||||||
// if reusing agent across request and tunneling agent isn't assigned create a new agent
|
// if tunneling agent isn't assigned create a new agent
|
||||||
if (this._keepAlive && !agent) {
|
if (!agent) {
|
||||||
const options = { keepAlive: this._keepAlive, maxSockets };
|
const options = { keepAlive: this._keepAlive, maxSockets };
|
||||||
agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
|
agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
|
||||||
this._agent = agent;
|
this._agent = agent;
|
||||||
}
|
}
|
||||||
// if not using private agent and tunnel agent isn't setup then use global agent
|
|
||||||
if (!agent) {
|
|
||||||
agent = usingSsl ? https.globalAgent : http.globalAgent;
|
|
||||||
}
|
|
||||||
if (usingSsl && this._ignoreSslError) {
|
if (usingSsl && this._ignoreSslError) {
|
||||||
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
||||||
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
||||||
@@ -8446,6 +8442,132 @@ function onConnectTimeout (socket) {
|
|||||||
module.exports = buildConnector
|
module.exports = buildConnector
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 4462:
|
||||||
|
/***/ ((module) => {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
/** @type {Record<string, string | undefined>} */
|
||||||
|
const headerNameLowerCasedRecord = {}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/docs/Web/HTTP/Headers
|
||||||
|
const wellknownHeaderNames = [
|
||||||
|
'Accept',
|
||||||
|
'Accept-Encoding',
|
||||||
|
'Accept-Language',
|
||||||
|
'Accept-Ranges',
|
||||||
|
'Access-Control-Allow-Credentials',
|
||||||
|
'Access-Control-Allow-Headers',
|
||||||
|
'Access-Control-Allow-Methods',
|
||||||
|
'Access-Control-Allow-Origin',
|
||||||
|
'Access-Control-Expose-Headers',
|
||||||
|
'Access-Control-Max-Age',
|
||||||
|
'Access-Control-Request-Headers',
|
||||||
|
'Access-Control-Request-Method',
|
||||||
|
'Age',
|
||||||
|
'Allow',
|
||||||
|
'Alt-Svc',
|
||||||
|
'Alt-Used',
|
||||||
|
'Authorization',
|
||||||
|
'Cache-Control',
|
||||||
|
'Clear-Site-Data',
|
||||||
|
'Connection',
|
||||||
|
'Content-Disposition',
|
||||||
|
'Content-Encoding',
|
||||||
|
'Content-Language',
|
||||||
|
'Content-Length',
|
||||||
|
'Content-Location',
|
||||||
|
'Content-Range',
|
||||||
|
'Content-Security-Policy',
|
||||||
|
'Content-Security-Policy-Report-Only',
|
||||||
|
'Content-Type',
|
||||||
|
'Cookie',
|
||||||
|
'Cross-Origin-Embedder-Policy',
|
||||||
|
'Cross-Origin-Opener-Policy',
|
||||||
|
'Cross-Origin-Resource-Policy',
|
||||||
|
'Date',
|
||||||
|
'Device-Memory',
|
||||||
|
'Downlink',
|
||||||
|
'ECT',
|
||||||
|
'ETag',
|
||||||
|
'Expect',
|
||||||
|
'Expect-CT',
|
||||||
|
'Expires',
|
||||||
|
'Forwarded',
|
||||||
|
'From',
|
||||||
|
'Host',
|
||||||
|
'If-Match',
|
||||||
|
'If-Modified-Since',
|
||||||
|
'If-None-Match',
|
||||||
|
'If-Range',
|
||||||
|
'If-Unmodified-Since',
|
||||||
|
'Keep-Alive',
|
||||||
|
'Last-Modified',
|
||||||
|
'Link',
|
||||||
|
'Location',
|
||||||
|
'Max-Forwards',
|
||||||
|
'Origin',
|
||||||
|
'Permissions-Policy',
|
||||||
|
'Pragma',
|
||||||
|
'Proxy-Authenticate',
|
||||||
|
'Proxy-Authorization',
|
||||||
|
'RTT',
|
||||||
|
'Range',
|
||||||
|
'Referer',
|
||||||
|
'Referrer-Policy',
|
||||||
|
'Refresh',
|
||||||
|
'Retry-After',
|
||||||
|
'Sec-WebSocket-Accept',
|
||||||
|
'Sec-WebSocket-Extensions',
|
||||||
|
'Sec-WebSocket-Key',
|
||||||
|
'Sec-WebSocket-Protocol',
|
||||||
|
'Sec-WebSocket-Version',
|
||||||
|
'Server',
|
||||||
|
'Server-Timing',
|
||||||
|
'Service-Worker-Allowed',
|
||||||
|
'Service-Worker-Navigation-Preload',
|
||||||
|
'Set-Cookie',
|
||||||
|
'SourceMap',
|
||||||
|
'Strict-Transport-Security',
|
||||||
|
'Supports-Loading-Mode',
|
||||||
|
'TE',
|
||||||
|
'Timing-Allow-Origin',
|
||||||
|
'Trailer',
|
||||||
|
'Transfer-Encoding',
|
||||||
|
'Upgrade',
|
||||||
|
'Upgrade-Insecure-Requests',
|
||||||
|
'User-Agent',
|
||||||
|
'Vary',
|
||||||
|
'Via',
|
||||||
|
'WWW-Authenticate',
|
||||||
|
'X-Content-Type-Options',
|
||||||
|
'X-DNS-Prefetch-Control',
|
||||||
|
'X-Frame-Options',
|
||||||
|
'X-Permitted-Cross-Domain-Policies',
|
||||||
|
'X-Powered-By',
|
||||||
|
'X-Requested-With',
|
||||||
|
'X-XSS-Protection'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (let i = 0; i < wellknownHeaderNames.length; ++i) {
|
||||||
|
const key = wellknownHeaderNames[i]
|
||||||
|
const lowerCasedKey = key.toLowerCase()
|
||||||
|
headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] =
|
||||||
|
lowerCasedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`.
|
||||||
|
Object.setPrototypeOf(headerNameLowerCasedRecord, null)
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
wellknownHeaderNames,
|
||||||
|
headerNameLowerCasedRecord
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 8045:
|
/***/ 8045:
|
||||||
@@ -9278,6 +9400,7 @@ const { InvalidArgumentError } = __nccwpck_require__(8045)
|
|||||||
const { Blob } = __nccwpck_require__(4300)
|
const { Blob } = __nccwpck_require__(4300)
|
||||||
const nodeUtil = __nccwpck_require__(3837)
|
const nodeUtil = __nccwpck_require__(3837)
|
||||||
const { stringify } = __nccwpck_require__(3477)
|
const { stringify } = __nccwpck_require__(3477)
|
||||||
|
const { headerNameLowerCasedRecord } = __nccwpck_require__(4462)
|
||||||
|
|
||||||
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v))
|
||||||
|
|
||||||
@@ -9487,6 +9610,15 @@ function parseKeepAliveTimeout (val) {
|
|||||||
return m ? parseInt(m[1], 10) * 1000 : null
|
return m ? parseInt(m[1], 10) * 1000 : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a header name and returns its lowercase value.
|
||||||
|
* @param {string | Buffer} value Header name
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function headerNameToString (value) {
|
||||||
|
return headerNameLowerCasedRecord[value] || value.toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
function parseHeaders (headers, obj = {}) {
|
function parseHeaders (headers, obj = {}) {
|
||||||
// For H2 support
|
// For H2 support
|
||||||
if (!Array.isArray(headers)) return headers
|
if (!Array.isArray(headers)) return headers
|
||||||
@@ -9758,6 +9890,7 @@ module.exports = {
|
|||||||
isIterable,
|
isIterable,
|
||||||
isAsyncIterable,
|
isAsyncIterable,
|
||||||
isDestroyed,
|
isDestroyed,
|
||||||
|
headerNameToString,
|
||||||
parseRawHeaders,
|
parseRawHeaders,
|
||||||
parseHeaders,
|
parseHeaders,
|
||||||
parseKeepAliveTimeout,
|
parseKeepAliveTimeout,
|
||||||
@@ -16405,14 +16538,18 @@ const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(3983
|
|||||||
const assert = __nccwpck_require__(9491)
|
const assert = __nccwpck_require__(9491)
|
||||||
const { isUint8Array } = __nccwpck_require__(9830)
|
const { isUint8Array } = __nccwpck_require__(9830)
|
||||||
|
|
||||||
|
let supportedHashes = []
|
||||||
|
|
||||||
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
|
// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable
|
||||||
/** @type {import('crypto')|undefined} */
|
/** @type {import('crypto')|undefined} */
|
||||||
let crypto
|
let crypto
|
||||||
|
|
||||||
try {
|
try {
|
||||||
crypto = __nccwpck_require__(6113)
|
crypto = __nccwpck_require__(6113)
|
||||||
|
const possibleRelevantHashes = ['sha256', 'sha384', 'sha512']
|
||||||
|
supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash))
|
||||||
|
/* c8 ignore next 3 */
|
||||||
} catch {
|
} catch {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function responseURL (response) {
|
function responseURL (response) {
|
||||||
@@ -16940,66 +17077,56 @@ function bytesMatch (bytes, metadataList) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. If parsedMetadata is the empty set, return true.
|
// 3. If response is not eligible for integrity validation, return false.
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
// 4. If parsedMetadata is the empty set, return true.
|
||||||
if (parsedMetadata.length === 0) {
|
if (parsedMetadata.length === 0) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Let metadata be the result of getting the strongest
|
// 5. Let metadata be the result of getting the strongest
|
||||||
// metadata from parsedMetadata.
|
// metadata from parsedMetadata.
|
||||||
const list = parsedMetadata.sort((c, d) => d.algo.localeCompare(c.algo))
|
const strongest = getStrongestMetadata(parsedMetadata)
|
||||||
// get the strongest algorithm
|
const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest)
|
||||||
const strongest = list[0].algo
|
|
||||||
// get all entries that use the strongest algorithm; ignore weaker
|
|
||||||
const metadata = list.filter((item) => item.algo === strongest)
|
|
||||||
|
|
||||||
// 5. For each item in metadata:
|
// 6. For each item in metadata:
|
||||||
for (const item of metadata) {
|
for (const item of metadata) {
|
||||||
// 1. Let algorithm be the alg component of item.
|
// 1. Let algorithm be the alg component of item.
|
||||||
const algorithm = item.algo
|
const algorithm = item.algo
|
||||||
|
|
||||||
// 2. Let expectedValue be the val component of item.
|
// 2. Let expectedValue be the val component of item.
|
||||||
let expectedValue = item.hash
|
const expectedValue = item.hash
|
||||||
|
|
||||||
// See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
|
// See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e
|
||||||
// "be liberal with padding". This is annoying, and it's not even in the spec.
|
// "be liberal with padding". This is annoying, and it's not even in the spec.
|
||||||
|
|
||||||
if (expectedValue.endsWith('==')) {
|
|
||||||
expectedValue = expectedValue.slice(0, -2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Let actualValue be the result of applying algorithm to bytes.
|
// 3. Let actualValue be the result of applying algorithm to bytes.
|
||||||
let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
|
let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64')
|
||||||
|
|
||||||
if (actualValue.endsWith('==')) {
|
if (actualValue[actualValue.length - 1] === '=') {
|
||||||
actualValue = actualValue.slice(0, -2)
|
if (actualValue[actualValue.length - 2] === '=') {
|
||||||
|
actualValue = actualValue.slice(0, -2)
|
||||||
|
} else {
|
||||||
|
actualValue = actualValue.slice(0, -1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. If actualValue is a case-sensitive match for expectedValue,
|
// 4. If actualValue is a case-sensitive match for expectedValue,
|
||||||
// return true.
|
// return true.
|
||||||
if (actualValue === expectedValue) {
|
if (compareBase64Mixed(actualValue, expectedValue)) {
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
let actualBase64URL = crypto.createHash(algorithm).update(bytes).digest('base64url')
|
|
||||||
|
|
||||||
if (actualBase64URL.endsWith('==')) {
|
|
||||||
actualBase64URL = actualBase64URL.slice(0, -2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actualBase64URL === expectedValue) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Return false.
|
// 7. Return false.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
|
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options
|
||||||
// https://www.w3.org/TR/CSP2/#source-list-syntax
|
// https://www.w3.org/TR/CSP2/#source-list-syntax
|
||||||
// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
|
// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1
|
||||||
const parseHashWithOptions = /((?<algo>sha256|sha384|sha512)-(?<hash>[A-z0-9+/]{1}.*={0,2}))( +[\x21-\x7e]?)?/i
|
const parseHashWithOptions = /(?<algo>sha256|sha384|sha512)-((?<hash>[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
|
* @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
|
||||||
@@ -17013,8 +17140,6 @@ function parseMetadata (metadata) {
|
|||||||
// 2. Let empty be equal to true.
|
// 2. Let empty be equal to true.
|
||||||
let empty = true
|
let empty = true
|
||||||
|
|
||||||
const supportedHashes = crypto.getHashes()
|
|
||||||
|
|
||||||
// 3. For each token returned by splitting metadata on spaces:
|
// 3. For each token returned by splitting metadata on spaces:
|
||||||
for (const token of metadata.split(' ')) {
|
for (const token of metadata.split(' ')) {
|
||||||
// 1. Set empty to false.
|
// 1. Set empty to false.
|
||||||
@@ -17024,7 +17149,11 @@ function parseMetadata (metadata) {
|
|||||||
const parsedToken = parseHashWithOptions.exec(token)
|
const parsedToken = parseHashWithOptions.exec(token)
|
||||||
|
|
||||||
// 3. If token does not parse, continue to the next token.
|
// 3. If token does not parse, continue to the next token.
|
||||||
if (parsedToken === null || parsedToken.groups === undefined) {
|
if (
|
||||||
|
parsedToken === null ||
|
||||||
|
parsedToken.groups === undefined ||
|
||||||
|
parsedToken.groups.algo === undefined
|
||||||
|
) {
|
||||||
// Note: Chromium blocks the request at this point, but Firefox
|
// Note: Chromium blocks the request at this point, but Firefox
|
||||||
// gives a warning that an invalid integrity was given. The
|
// gives a warning that an invalid integrity was given. The
|
||||||
// correct behavior is to ignore these, and subsequently not
|
// correct behavior is to ignore these, and subsequently not
|
||||||
@@ -17033,11 +17162,11 @@ function parseMetadata (metadata) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Let algorithm be the hash-algo component of token.
|
// 4. Let algorithm be the hash-algo component of token.
|
||||||
const algorithm = parsedToken.groups.algo
|
const algorithm = parsedToken.groups.algo.toLowerCase()
|
||||||
|
|
||||||
// 5. If algorithm is a hash function recognized by the user
|
// 5. If algorithm is a hash function recognized by the user
|
||||||
// agent, add the parsed token to result.
|
// agent, add the parsed token to result.
|
||||||
if (supportedHashes.includes(algorithm.toLowerCase())) {
|
if (supportedHashes.includes(algorithm)) {
|
||||||
result.push(parsedToken.groups)
|
result.push(parsedToken.groups)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17050,6 +17179,82 @@ function parseMetadata (metadata) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList
|
||||||
|
*/
|
||||||
|
function getStrongestMetadata (metadataList) {
|
||||||
|
// Let algorithm be the algo component of the first item in metadataList.
|
||||||
|
// Can be sha256
|
||||||
|
let algorithm = metadataList[0].algo
|
||||||
|
// If the algorithm is sha512, then it is the strongest
|
||||||
|
// and we can return immediately
|
||||||
|
if (algorithm[3] === '5') {
|
||||||
|
return algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i < metadataList.length; ++i) {
|
||||||
|
const metadata = metadataList[i]
|
||||||
|
// If the algorithm is sha512, then it is the strongest
|
||||||
|
// and we can break the loop immediately
|
||||||
|
if (metadata.algo[3] === '5') {
|
||||||
|
algorithm = 'sha512'
|
||||||
|
break
|
||||||
|
// If the algorithm is sha384, then a potential sha256 or sha384 is ignored
|
||||||
|
} else if (algorithm[3] === '3') {
|
||||||
|
continue
|
||||||
|
// algorithm is sha256, check if algorithm is sha384 and if so, set it as
|
||||||
|
// the strongest
|
||||||
|
} else if (metadata.algo[3] === '3') {
|
||||||
|
algorithm = 'sha384'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMetadataListByAlgorithm (metadataList, algorithm) {
|
||||||
|
if (metadataList.length === 1) {
|
||||||
|
return metadataList
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = 0
|
||||||
|
for (let i = 0; i < metadataList.length; ++i) {
|
||||||
|
if (metadataList[i].algo === algorithm) {
|
||||||
|
metadataList[pos++] = metadataList[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataList.length = pos
|
||||||
|
|
||||||
|
return metadataList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two base64 strings, allowing for base64url
|
||||||
|
* in the second string.
|
||||||
|
*
|
||||||
|
* @param {string} actualValue always base64
|
||||||
|
* @param {string} expectedValue base64 or base64url
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function compareBase64Mixed (actualValue, expectedValue) {
|
||||||
|
if (actualValue.length !== expectedValue.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let i = 0; i < actualValue.length; ++i) {
|
||||||
|
if (actualValue[i] !== expectedValue[i]) {
|
||||||
|
if (
|
||||||
|
(actualValue[i] === '+' && expectedValue[i] === '-') ||
|
||||||
|
(actualValue[i] === '/' && expectedValue[i] === '_')
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
|
// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request
|
||||||
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
|
function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) {
|
||||||
// TODO
|
// TODO
|
||||||
@@ -17465,7 +17670,8 @@ module.exports = {
|
|||||||
urlHasHttpsScheme,
|
urlHasHttpsScheme,
|
||||||
urlIsHttpHttpsScheme,
|
urlIsHttpHttpsScheme,
|
||||||
readAllBytes,
|
readAllBytes,
|
||||||
normalizeMethodRecord
|
normalizeMethodRecord,
|
||||||
|
parseMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -19552,12 +19758,17 @@ function parseLocation (statusCode, headers) {
|
|||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||||
function shouldRemoveHeader (header, removeContent, unknownOrigin) {
|
function shouldRemoveHeader (header, removeContent, unknownOrigin) {
|
||||||
return (
|
if (header.length === 4) {
|
||||||
(header.length === 4 && header.toString().toLowerCase() === 'host') ||
|
return util.headerNameToString(header) === 'host'
|
||||||
(removeContent && header.toString().toLowerCase().indexOf('content-') === 0) ||
|
}
|
||||||
(unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') ||
|
if (removeContent && util.headerNameToString(header).startsWith('content-')) {
|
||||||
(unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie')
|
return true
|
||||||
)
|
}
|
||||||
|
if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) {
|
||||||
|
const name = util.headerNameToString(header)
|
||||||
|
return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization'
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc7231#section-6.4
|
// https://tools.ietf.org/html/rfc7231#section-6.4
|
||||||
@@ -24734,7 +24945,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.run = void 0;
|
exports.run = run;
|
||||||
const core = __importStar(__nccwpck_require__(2186));
|
const core = __importStar(__nccwpck_require__(2186));
|
||||||
const sbom_1 = __nccwpck_require__(6210);
|
const sbom_1 = __nccwpck_require__(6210);
|
||||||
/**
|
/**
|
||||||
@@ -24758,7 +24969,6 @@ async function run() {
|
|||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.run = run;
|
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
@@ -24795,7 +25005,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.generateSBOMPredicate = exports.storePredicate = exports.parseSBOMFromPath = void 0;
|
exports.generateSBOMPredicate = exports.storePredicate = void 0;
|
||||||
|
exports.parseSBOMFromPath = parseSBOMFromPath;
|
||||||
const fs_1 = __importDefault(__nccwpck_require__(7147));
|
const fs_1 = __importDefault(__nccwpck_require__(7147));
|
||||||
const path = __importStar(__nccwpck_require__(1017));
|
const path = __importStar(__nccwpck_require__(1017));
|
||||||
async function parseSBOMFromPath(filePath) {
|
async function parseSBOMFromPath(filePath) {
|
||||||
@@ -24810,7 +25021,6 @@ async function parseSBOMFromPath(filePath) {
|
|||||||
}
|
}
|
||||||
throw new Error('Unsupported SBOM format');
|
throw new Error('Unsupported SBOM format');
|
||||||
}
|
}
|
||||||
exports.parseSBOMFromPath = parseSBOMFromPath;
|
|
||||||
function checkIsSPDX(sbomObject) {
|
function checkIsSPDX(sbomObject) {
|
||||||
if (sbomObject?.spdxVersion && sbomObject?.SPDXID) {
|
if (sbomObject?.spdxVersion && sbomObject?.SPDXID) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
2160
package-lock.json
generated
2160
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -1,21 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "typescript-action",
|
"name": "actions/attest-sbom",
|
||||||
"description": "GitHub Actions TypeScript template",
|
"description": "Generate signed SBOM attestations",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"homepage": "https://github.com/actions/typescript-action",
|
"homepage": "https://github.com/actions/attest-sbom",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/actions/typescript-action.git"
|
"url": "git+https://github.com/actions/attest-sbom.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/actions/typescript-action/issues"
|
"url": "https://github.com/actions/attest-sbom/issues"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"actions",
|
"actions",
|
||||||
"node",
|
"attestation",
|
||||||
"setup"
|
"sbom"
|
||||||
],
|
],
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/index.js"
|
".": "./dist/index.js"
|
||||||
@@ -73,22 +73,22 @@
|
|||||||
"@actions/core": "^1.10.1"
|
"@actions/core": "^1.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@actions/attest": "^1.0.0",
|
"@actions/attest": "^1.4.0",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.11.24",
|
"@types/node": "^22.4.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
"@vercel/ncc": "^0.38.1",
|
"@vercel/ncc": "^0.38.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-github": "^4.10.2",
|
"eslint-plugin-github": "^5.0.1",
|
||||||
"eslint-plugin-jest": "^27.9.0",
|
"eslint-plugin-jest": "^28.8.0",
|
||||||
"eslint-plugin-jsonc": "^2.13.0",
|
"eslint-plugin-jsonc": "^2.16.0",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"markdownlint-cli": "^0.39.0",
|
"markdownlint-cli": "^0.41.0",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.3.3",
|
||||||
"prettier-eslint": "^16.3.0",
|
"prettier-eslint": "^16.3.0",
|
||||||
"ts-jest": "^29.1.2",
|
"ts-jest": "^29.2.4",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# About:
|
|
||||||
#
|
|
||||||
# This is a helper script to tag and push a new release. GitHub Actions use
|
|
||||||
# release tags to allow users to select a specific version of the action to use.
|
|
||||||
#
|
|
||||||
# See: https://github.com/actions/typescript-action#publishing-a-new-release
|
|
||||||
#
|
|
||||||
# This script will do the following:
|
|
||||||
#
|
|
||||||
# 1. Get the latest release tag
|
|
||||||
# 2. Prompt the user for a new release tag
|
|
||||||
# 3. Tag the new release
|
|
||||||
# 4. Push the new tag to the remote
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# script/release
|
|
||||||
|
|
||||||
# Terminal colors
|
|
||||||
OFF='\033[0m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
|
|
||||||
# Get the latest release tag
|
|
||||||
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
|
||||||
|
|
||||||
if [[ -z "$latest_tag" ]]; then
|
|
||||||
# There are no existing release tags
|
|
||||||
echo -e "No tags found (yet) - Continue to create and push your first tag"
|
|
||||||
latest_tag="[unknown]"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Display the latest release tag
|
|
||||||
echo -e "The latest release tag is: ${BLUE}${latest_tag}${OFF}"
|
|
||||||
|
|
||||||
# Prompt the user for the new release tag
|
|
||||||
read -r -p 'Enter a new release tag (vX.X.X format): ' new_tag
|
|
||||||
|
|
||||||
# Validate the new release tag
|
|
||||||
tag_regex='v[0-9]+\.[0-9]+\.[0-9]+$'
|
|
||||||
if echo "$new_tag" | grep -q -E "$tag_regex"; then
|
|
||||||
echo -e "Tag: ${BLUE}$new_tag${OFF} is valid"
|
|
||||||
else
|
|
||||||
# Release tag is not `vX.X.X` format
|
|
||||||
echo -e "Tag: ${BLUE}$new_tag${OFF} is ${RED}not valid${OFF} (must be in vX.X.X format)"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Tag the new release
|
|
||||||
git tag -a "$new_tag" -m "$new_tag Release"
|
|
||||||
echo -e "${GREEN}Tagged: $new_tag${OFF}"
|
|
||||||
|
|
||||||
# Push the new tag to the remote
|
|
||||||
git push --tags
|
|
||||||
echo -e "${GREEN}Release tag pushed to remote${OFF}"
|
|
||||||
echo -e "${GREEN}Done!${OFF}"
|
|
||||||
Reference in New Issue
Block a user