From 6911316fe31cc37f0c21d2f690e8ee8ad6928a23 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 17:24:55 -0500 Subject: [PATCH 01/12] Add secret ref validation --- src/utils.test.ts | 54 +++++++++++++++++++++++++++++++++++++++++++++-- src/utils.ts | 29 ++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/src/utils.test.ts b/src/utils.test.ts index 7b066e5..d981470 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,7 +1,7 @@ import * as core from "@actions/core"; import * as exec from "@actions/exec"; import { read, setClientInfo } from "@1password/op-js"; -import { createClient } from "@1password/sdk"; +import { createClient, Secrets } from "@1password/sdk"; import { extractSecret, loadSecrets, @@ -25,6 +25,9 @@ jest.mock("@actions/exec", () => ({ jest.mock("@1password/op-js"); jest.mock("@1password/sdk", () => ({ createClient: jest.fn(), + Secrets: { + validateSecretReference: jest.fn(), + }, })); beforeEach(() => { @@ -170,11 +173,24 @@ describe("loadSecrets when using Connect", () => { expect(core.exportVariable).not.toHaveBeenCalled(); }); + it("fails with clear message when a secret reference is invalid", async () => { + (Secrets.validateSecretReference as jest.Mock).mockImplementationOnce( + () => { + throw new Error("invalid reference format"); + }, + ); + process.env.MOCK_SECRET = "op://bad/invalid-ref"; + + await expect(loadSecrets(true)).rejects.toThrow( + "Invalid secret reference(s): MOCK_SECRET", + ); + }); + describe("core.exportVariable", () => { it("is called when shouldExportEnv is true", async () => { await loadSecrets(true); - expect(core.exportVariable).toHaveBeenCalledTimes(1); + expect(core.exportVariable).toHaveBeenCalledTimes(2); }); it("is not called when shouldExportEnv is false", async () => { @@ -334,6 +350,40 @@ describe("loadSecrets when using Service Account", () => { expect(core.setSecret).toHaveBeenCalledTimes(3); }); }); + + describe("secret reference validation", () => { + it("fails with clear message when a secret reference is invalid", async () => { + process.env.MY_SECRET = "op://invalid/ref/form"; + (Secrets.validateSecretReference as jest.Mock).mockImplementationOnce( + () => { + throw new Error("invalid reference format"); + }, + ); + + await expect(loadSecrets(true)).rejects.toThrow( + "Invalid secret reference(s): MY_SECRET", + ); + expect(mockResolve).not.toHaveBeenCalled(); + }); + + it("validates all refs before resolving any secrets", async () => { + process.env.MY_SECRET = "op://vault/item/field"; + process.env.OTHER = "op://vault/other/item"; + (Secrets.validateSecretReference as jest.Mock).mockImplementation( + (ref: string) => { + if (ref === "op://vault/other/item") { + throw new Error("invalid"); + } + }, + ); + mockResolve.mockResolvedValue("value1"); + + await expect(loadSecrets(false)).rejects.toThrow( + "Invalid secret reference(s): OTHER", + ); + expect(mockResolve).not.toHaveBeenCalled(); + }); + }); }); describe("unsetPrevious", () => { diff --git a/src/utils.ts b/src/utils.ts index 383e103..9653956 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,7 @@ import * as core from "@actions/core"; import * as exec from "@actions/exec"; import { read, setClientInfo, semverToInt } from "@1password/op-js"; -import { createClient } from "@1password/sdk"; +import { createClient, Secrets } from "@1password/sdk"; import { version } from "../package.json"; import { authErr, @@ -37,6 +37,29 @@ export const getEnvVarNamesWithSecretRefs = (): string[] => process.env[key]?.startsWith("op://"), ); +const validateSecretRefs = (envNames: string[]): void => { + const invalid: string[] = []; + + for (const envName of envNames) { + const ref = process.env[envName]; + if (!ref) { + continue; + } + + try { + Secrets.validateSecretReference(ref); + } catch { + invalid.push(envName); + } + } + + // Throw an error if any secret references are invalid + if (invalid.length > 0) { + const names = invalid.join(", "); + throw new Error(`Invalid secret reference(s): ${names}`); + } +}; + const setResolvedSecret = ( envName: string, secretValue: string, @@ -102,6 +125,8 @@ const loadSecretsViaConnect = async ( } const envs = res.stdout.replace(/\n+$/g, "").split(/\r?\n/); + validateSecretRefs(envs); + for (const envName of envs) { extractSecret(envName, shouldExportEnv); } @@ -119,6 +144,8 @@ const loadSecretsViaServiceAccount = async ( return; } + validateSecretRefs(envs); + const token = process.env[envServiceAccountToken]; if (!token) { throw new Error(authErr); From e7fe4397d919c139550ed539df72c7c9c45b2a6a Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 17:38:00 -0500 Subject: [PATCH 02/12] Add e2e test --- .github/workflows/e2e-tests.yml | 15 +++++++++++++++ tests/assert-invalid-ref-failed.sh | 7 +++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/assert-invalid-ref-failed.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3105fb6..cd77b93 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -105,6 +105,21 @@ jobs: shell: bash run: ./tests/assert-env-unset.sh + - name: Load secrets (invalid ref - expect failure) + id: load_invalid + continue-on-error: true + uses: ./ + env: + BAD_REF: "op://x" + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + with: + export-env: true + + - name: Assert invalid ref failed + run: ./tests/assert-invalid-ref-failed.sh + env: + STEP_OUTCOME: ${{ steps.load_invalid.outcome }} + test-connect: name: Connect (ubuntu-latest, ${{ matrix.version }}, export-env=${{ matrix.export-env }}) runs-on: ubuntu-latest diff --git a/tests/assert-invalid-ref-failed.sh b/tests/assert-invalid-ref-failed.sh new file mode 100644 index 0000000..3d07973 --- /dev/null +++ b/tests/assert-invalid-ref-failed.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e +if [ "$STEP_OUTCOME" != "failure" ]; then + echo "Expected action to fail on invalid ref, got: $STEP_OUTCOME" + exit 1 +fi +echo "Action correctly failed on invalid ref" From 7998453500147ece20e760e0ffebf16dfa8adade Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 17:41:39 -0500 Subject: [PATCH 03/12] Update script --- .github/workflows/e2e-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index cd77b93..958b75a 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -116,6 +116,7 @@ jobs: export-env: true - name: Assert invalid ref failed + shell: bash run: ./tests/assert-invalid-ref-failed.sh env: STEP_OUTCOME: ${{ steps.load_invalid.outcome }} From 604a86ce4e79b0e501658d43dbda0ce70227f53a Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 17:44:26 -0500 Subject: [PATCH 04/12] Make assert-invalid-ref-failed.sh executable --- tests/assert-invalid-ref-failed.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tests/assert-invalid-ref-failed.sh diff --git a/tests/assert-invalid-ref-failed.sh b/tests/assert-invalid-ref-failed.sh old mode 100644 new mode 100755 From 2a828228a826030c69cd594d989e158999eafbc5 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 17:48:21 -0500 Subject: [PATCH 05/12] Try woth test failure --- .github/workflows/e2e-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 958b75a..ef47b56 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -110,7 +110,7 @@ jobs: continue-on-error: true uses: ./ env: - BAD_REF: "op://x" + BAD_REF: "op://${{ secrets.VAULT }}/test-secret/password" OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} with: export-env: true From d456b725135925f84d1826ca34e658e0c1315150 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 17:50:34 -0500 Subject: [PATCH 06/12] Add connect e2e test --- .github/workflows/e2e-tests.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ef47b56..fa18f8b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -110,7 +110,7 @@ jobs: continue-on-error: true uses: ./ env: - BAD_REF: "op://${{ secrets.VAULT }}/test-secret/password" + BAD_REF: "op://x" OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} with: export-env: true @@ -205,3 +205,18 @@ jobs: - name: Assert removed secrets [exported env] if: ${{ matrix.export-env }} run: ./tests/assert-env-unset.sh + + - name: Load secrets (invalid ref - expect failure) + id: load_invalid + continue-on-error: true + uses: ./ + env: + BAD_REF: "op://x" + with: + export-env: true + + - name: Assert invalid ref failed + shell: bash + run: ./tests/assert-invalid-ref-failed.sh + env: + STEP_OUTCOME: ${{ steps.load_invalid.outcome }} From af49dd18deafd85fcc3773c65ad880bb17977ea4 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 18:00:42 -0500 Subject: [PATCH 07/12] Remove connect handling --- .github/workflows/e2e-tests.yml | 15 --------------- src/utils.ts | 2 -- 2 files changed, 17 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index fa18f8b..958b75a 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -205,18 +205,3 @@ jobs: - name: Assert removed secrets [exported env] if: ${{ matrix.export-env }} run: ./tests/assert-env-unset.sh - - - name: Load secrets (invalid ref - expect failure) - id: load_invalid - continue-on-error: true - uses: ./ - env: - BAD_REF: "op://x" - with: - export-env: true - - - name: Assert invalid ref failed - shell: bash - run: ./tests/assert-invalid-ref-failed.sh - env: - STEP_OUTCOME: ${{ steps.load_invalid.outcome }} diff --git a/src/utils.ts b/src/utils.ts index 9653956..97922bb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -125,8 +125,6 @@ const loadSecretsViaConnect = async ( } const envs = res.stdout.replace(/\n+$/g, "").split(/\r?\n/); - validateSecretRefs(envs); - for (const envName of envs) { extractSecret(envName, shouldExportEnv); } From ab44f9f69c6e396597459da2a4c53a1871d9183c Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 18 Feb 2026 18:01:22 -0500 Subject: [PATCH 08/12] Remove unit test --- src/utils.test.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/utils.test.ts b/src/utils.test.ts index d981470..ca954a2 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -173,24 +173,11 @@ describe("loadSecrets when using Connect", () => { expect(core.exportVariable).not.toHaveBeenCalled(); }); - it("fails with clear message when a secret reference is invalid", async () => { - (Secrets.validateSecretReference as jest.Mock).mockImplementationOnce( - () => { - throw new Error("invalid reference format"); - }, - ); - process.env.MOCK_SECRET = "op://bad/invalid-ref"; - - await expect(loadSecrets(true)).rejects.toThrow( - "Invalid secret reference(s): MOCK_SECRET", - ); - }); - describe("core.exportVariable", () => { it("is called when shouldExportEnv is true", async () => { await loadSecrets(true); - expect(core.exportVariable).toHaveBeenCalledTimes(2); + expect(core.exportVariable).toHaveBeenCalledTimes(1); }); it("is not called when shouldExportEnv is false", async () => { From 04984a6c91714492720f156812c0eb8416059cda Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Fri, 20 Feb 2026 08:31:14 -0500 Subject: [PATCH 09/12] Add eslint disable --- src/utils.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils.test.ts b/src/utils.test.ts index d21fa24..f2d9429 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -23,6 +23,7 @@ jest.mock("@actions/exec", () => ({ })), })); jest.mock("@1password/op-js"); +// eslint-disable-next-line @typescript-eslint/naming-convention jest.mock("@1password/sdk", () => ({ createClient: jest.fn(), Secrets: { From 9d7acefac986f92b643437d44d0a263b330b84cb Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Fri, 20 Feb 2026 08:34:52 -0500 Subject: [PATCH 10/12] Move comment --- src/utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.test.ts b/src/utils.test.ts index f2d9429..575c30f 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -23,9 +23,9 @@ jest.mock("@actions/exec", () => ({ })), })); jest.mock("@1password/op-js"); -// eslint-disable-next-line @typescript-eslint/naming-convention jest.mock("@1password/sdk", () => ({ createClient: jest.fn(), + // eslint-disable-next-line @typescript-eslint/naming-convention Secrets: { validateSecretReference: jest.fn(), }, From dc90451a94226792d67567dd0243df96debe84e1 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Sun, 22 Feb 2026 12:31:37 -0500 Subject: [PATCH 11/12] Apply code suggestions --- src/utils.test.ts | 3 +-- src/utils.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/utils.test.ts b/src/utils.test.ts index 575c30f..a74874f 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -356,7 +356,7 @@ describe("loadSecrets when using Service Account", () => { describe("secret reference validation", () => { it("fails with clear message when a secret reference is invalid", async () => { - process.env.MY_SECRET = "op://invalid/ref/form"; + process.env.MY_SECRET = "op://x"; (Secrets.validateSecretReference as jest.Mock).mockImplementationOnce( () => { throw new Error("invalid reference format"); @@ -379,7 +379,6 @@ describe("loadSecrets when using Service Account", () => { } }, ); - mockResolve.mockResolvedValue("value1"); await expect(loadSecrets(false)).rejects.toThrow( "Invalid secret reference(s): OTHER", diff --git a/src/utils.ts b/src/utils.ts index e66cc18..6a3e0bb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -38,7 +38,7 @@ const getEnvVarNamesWithSecretRefs = (): string[] => ); const validateSecretRefs = (envNames: string[]): void => { - const invalid: string[] = []; + const invalid: { name: string; message: string }[] = []; for (const envName of envNames) { const ref = process.env[envName]; @@ -48,15 +48,16 @@ const validateSecretRefs = (envNames: string[]): void => { try { Secrets.validateSecretReference(ref); - } catch { - invalid.push(envName); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + invalid.push({ name: envName, message }); } } // Throw an error if any secret references are invalid if (invalid.length > 0) { - const names = invalid.join(", "); - throw new Error(`Invalid secret reference(s): ${names}`); + const details = invalid.map(({ name, message }) => `${name}: ${message}`).join("; "); + throw new Error(`Invalid secret reference(s): ${details}`); } }; From 398c918d600e677d45e96fb6e8c819c157c7295c Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Mon, 23 Feb 2026 08:13:30 -0500 Subject: [PATCH 12/12] Fix format --- src/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 6a3e0bb..9753e18 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -56,7 +56,9 @@ const validateSecretRefs = (envNames: string[]): void => { // Throw an error if any secret references are invalid if (invalid.length > 0) { - const details = invalid.map(({ name, message }) => `${name}: ${message}`).join("; "); + const details = invalid + .map(({ name, message }) => `${name}: ${message}`) + .join("; "); throw new Error(`Invalid secret reference(s): ${details}`); } };