Validate repository org-ownership before storage record creation (#328)

* check if the repository is owned by org before attempting storage record creation

Signed-off-by: Meredith Lancaster <malancas@github.com>

* linter

Signed-off-by: Meredith Lancaster <malancas@github.com>

* generate dist

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add fixtures for repoOwnerIsOrg function

Signed-off-by: Meredith Lancaster <malancas@github.com>

* formatter

Signed-off-by: Meredith Lancaster <malancas@github.com>

* clean up fixtures

Signed-off-by: Meredith Lancaster <malancas@github.com>

* more clean up

Signed-off-by: Meredith Lancaster <malancas@github.com>

* fix function declaration

Signed-off-by: Meredith Lancaster <malancas@github.com>

* clean up fixtures

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add test when repo is not owned by org

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add more expect statements, clean up mock calls

Signed-off-by: Meredith Lancaster <malancas@github.com>

* formatter

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add more spy expect statements

Signed-off-by: Meredith Lancaster <malancas@github.com>

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
This commit is contained in:
Meredith Lancaster
2026-01-26 08:31:21 -08:00
committed by GitHub
parent 7433fa7e7a
commit 20eb46ce7a
3 changed files with 98 additions and 12 deletions

View File

@@ -10,6 +10,7 @@ import * as github from '@actions/github'
import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock'
import * as oci from '@sigstore/oci'
import * as attest from '@actions/attest'
import * as localAttest from '../src/attest'
import fs from 'fs/promises'
import nock from 'nock'
import os from 'os'
@@ -29,7 +30,7 @@ const setFailedMock = jest.spyOn(core, 'setFailed')
setFailedMock.mockImplementation(() => {})
const summaryWriteMock = jest.spyOn(core.summary, 'write')
summaryWriteMock.mockImplementation(async () => Promise.resolve(core.summary))
summaryWriteMock.mockResolvedValue(core.summary)
// Mock the action's main function
const runMock = jest.spyOn(main, 'run')
@@ -230,6 +231,9 @@ describe('action', () => {
describe('when the repository is public', () => {
const getRegCredsSpy = jest.spyOn(oci, 'getRegistryCredentials')
const attachArtifactSpy = jest.spyOn(oci, 'attachArtifactToImage')
const repoOwnerIsOrgSpy = jest.spyOn(localAttest, 'repoOwnerIsOrg')
const createStorageRecordSpy = jest.spyOn(attest, 'createStorageRecord')
const createAttestationSpy = jest.spyOn(localAttest, 'createAttestation')
const inputs: main.RunInputs = {
...defaultInputs,
@@ -258,13 +262,12 @@ describe('action', () => {
username: 'username',
password: 'password'
}))
attachArtifactSpy.mockImplementation(async () =>
Promise.resolve({
digest: 'sha256:123456',
mediaType: 'application/vnd.cncf.notary.v2',
size: 123456
})
)
attachArtifactSpy.mockResolvedValue({
digest: 'sha256:123456',
mediaType: 'application/vnd.cncf.notary.v2',
size: 123456
})
repoOwnerIsOrgSpy.mockResolvedValue(true)
})
it('invokes the action w/o error', async () => {
@@ -274,6 +277,9 @@ describe('action', () => {
expect(setFailedMock).not.toHaveBeenCalled()
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
expect(attachArtifactSpy).toHaveBeenCalled()
expect(createAttestationSpy).toHaveBeenCalled()
expect(repoOwnerIsOrgSpy).toHaveBeenCalled()
expect(createStorageRecordSpy).toHaveBeenCalled()
expect(warningMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenNthCalledWith(
1,
@@ -338,7 +344,6 @@ describe('action', () => {
it('catches error when storage record creation fails and continues', async () => {
// Mock the createStorageRecord function and throw an error
const createStorageRecordSpy = jest.spyOn(attest, 'createStorageRecord')
createStorageRecordSpy.mockRejectedValueOnce(
new Error('Failed to persist storage record: Not Found')
)
@@ -346,12 +351,50 @@ describe('action', () => {
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(createAttestationSpy).toHaveBeenCalled()
expect(repoOwnerIsOrgSpy).toHaveBeenCalled()
expect(createStorageRecordSpy).toHaveBeenCalled()
expect(setFailedMock).not.toHaveBeenCalled()
expect(warningMock).toHaveBeenNthCalledWith(
1,
expect.stringMatching('Failed to create storage record')
)
})
it('does not create a storage record when the repo is owned by a user', async () => {
repoOwnerIsOrgSpy.mockResolvedValueOnce(false)
await main.run(inputs)
expect(runMock).toHaveReturned()
expect(setFailedMock).not.toHaveBeenCalled()
expect(getRegCredsSpy).toHaveBeenCalledWith(subjectName)
expect(attachArtifactSpy).toHaveBeenCalled()
expect(createAttestationSpy).toHaveBeenCalled()
expect(repoOwnerIsOrgSpy).toHaveBeenCalled()
expect(createStorageRecordSpy).not.toHaveBeenCalled()
expect(warningMock).not.toHaveBeenCalled()
expect(infoMock).toHaveBeenCalledWith(
expect.stringMatching(
`Attestation created for ${subjectName}@${subjectDigest}`
)
)
expect(infoMock).not.toHaveBeenCalledWith(
expect.stringMatching('Storage record created')
)
expect(infoMock).not.toHaveBeenCalledWith(
expect.stringMatching('Storage record IDs: 987654321')
)
expect(setOutputMock).toHaveBeenCalledWith(
'attestation-id',
expect.stringMatching(attestationID)
)
expect(setOutputMock).not.toHaveBeenCalledWith(
'storage-record-ids',
expect.stringMatching(storageRecordID.toString())
)
expect(setFailedMock).not.toHaveBeenCalled()
})
})
describe('when the subject count is greater than 1', () => {