From c58d52c41de99d28c3b0cfaf3fd205fbe004e5b0 Mon Sep 17 00:00:00 2001 From: Brian DeHamer Date: Mon, 6 May 2024 11:51:42 -0700 Subject: [PATCH] limit attestation subject count (#53) Signed-off-by: Brian DeHamer --- README.md | 2 +- __tests__/main.test.ts | 50 +++++++++++++++++++++++++++++++++++++++++- action.yml | 2 +- dist/index.js | 6 ++++- src/main.ts | 10 ++++++++- 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c5062c..b51cf9f 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ See [action.yml](action.yml) with: # Path to the artifact serving as the subject of the attestation. Must # specify exactly one of "subject-path" or "subject-digest". May contain - # a glob pattern or list of paths. + # a glob pattern or list of paths (total subject count cannot exceed 64). subject-path: # SHA256 digest of the subject for the attestation. Must be in the form diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 0ae2ad5..3d6668c 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -5,12 +5,14 @@ * Specifically, the inputs listed in `action.yml` should be set as environment * variables following the pattern `INPUT_`. */ - import * as core from '@actions/core' import * as github from '@actions/github' import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock' import * as oci from '@sigstore/oci' +import fs from 'fs/promises' import nock from 'nock' +import os from 'os' +import path from 'path' import { MockAgent, setGlobalDispatcher } from 'undici' import { SEARCH_PUBLIC_GOOD_URL } from '../src/endpoints' import * as main from '../src/main' @@ -286,6 +288,52 @@ describe('action', () => { expect(setFailedMock).not.toHaveBeenCalled() }) }) + + describe('when too many subjects are specified', () => { + let dir = '' + + beforeEach(async () => { + const filename = 'subject' + const content = 'file content' + + // Set-up temp directory + const tmpDir = await fs.realpath(os.tmpdir()) + dir = await fs.mkdtemp(tmpDir + path.sep) + + // Add files for glob testing + for (let i = 0; i < 65; i++) { + await fs.writeFile(path.join(dir, `${filename}-${i}`), content) + } + + // Set the GH context with private repository visibility and a repo owner. + setGHContext({ + payload: { repository: { visibility: 'private' } }, + repo: { owner: 'foo', repo: 'bar' } + }) + + // Mock the action's inputs + getInputMock.mockImplementation( + mockInput({ + predicate: '{}', + 'subject-path': path.join(dir, `${filename}-*`) + }) + ) + }) + + afterEach(async () => { + // Clean-up temp directory + await fs.rm(dir, { recursive: true }) + }) + + it('sets a failed status', async () => { + await main.run() + + expect(runMock).toHaveReturned() + expect(setFailedMock).toHaveBeenCalledWith( + 'Too many subjects specified. The maximum number of subjects is 64.' + ) + }) + }) }) function mockInput(inputs: Record): typeof core.getInput { diff --git a/action.yml b/action.yml index 0150f1b..7b4a6b1 100644 --- a/action.yml +++ b/action.yml @@ -10,7 +10,7 @@ inputs: description: > Path to the artifact serving as the subject of the attestation. Must specify exactly one of "subject-path" or "subject-digest". May contain a - glob pattern or list of paths. + glob pattern or list of paths (total subject count cannot exceed 64). required: false subject-digest: description: > diff --git a/dist/index.js b/dist/index.js index 314a06f..712b5ee 100644 --- a/dist/index.js +++ b/dist/index.js @@ -79644,6 +79644,7 @@ const subject_1 = __nccwpck_require__(95206); const COLOR_CYAN = '\x1B[36m'; const COLOR_DEFAULT = '\x1B[39m'; const ATTESTATION_FILE_NAME = 'attestation.jsonl'; +const MAX_SUBJECT_COUNT = 64; /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. @@ -79661,8 +79662,11 @@ async function run() { if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) { throw new Error('missing "id-token" permission. Please add "permissions: id-token: write" to your workflow.'); } - // Calculate subject from inputs and generate provenance + // Gather list of subjets const subjects = await (0, subject_1.subjectFromInputs)(); + if (subjects.length > MAX_SUBJECT_COUNT) { + throw new Error(`Too many subjects specified. The maximum number of subjects is ${MAX_SUBJECT_COUNT}.`); + } const predicate = (0, predicate_1.predicateFromInputs)(); const outputPath = path_1.default.join(tempDir(), ATTESTATION_FILE_NAME); // Generate attestations for each subject serially diff --git a/src/main.ts b/src/main.ts index 3e0c8eb..aea3f7c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,6 +16,8 @@ const COLOR_CYAN = '\x1B[36m' const COLOR_DEFAULT = '\x1B[39m' const ATTESTATION_FILE_NAME = 'attestation.jsonl' +const MAX_SUBJECT_COUNT = 64 + /** * The main function for the action. * @returns {Promise} Resolves when the action is complete. @@ -38,8 +40,14 @@ export async function run(): Promise { ) } - // Calculate subject from inputs and generate provenance + // Gather list of subjets const subjects = await subjectFromInputs() + if (subjects.length > MAX_SUBJECT_COUNT) { + throw new Error( + `Too many subjects specified. The maximum number of subjects is ${MAX_SUBJECT_COUNT}.` + ) + } + const predicate = predicateFromInputs() const outputPath = path.join(tempDir(), ATTESTATION_FILE_NAME)