From 8296a207c6926259ea8ae88f0c29f88c082cd36b Mon Sep 17 00:00:00 2001 From: Hitesh Borase Date: Fri, 27 Feb 2026 19:29:23 +0530 Subject: [PATCH] harness CI drone plugin PLUGIN_KSM_CONFIG convention added, multi-platform Docker (amd64/arm64) support and added MIT license --- .github/workflows/publish.harness.yml | 8 +- LICENSE | 21 +++++ README.md | 19 ++-- package.json | 6 +- src/index.js | 4 +- src/test/index.test.js | 130 +++++++++++++------------- 6 files changed, 105 insertions(+), 83 deletions(-) create mode 100644 LICENSE diff --git a/.github/workflows/publish.harness.yml b/.github/workflows/publish.harness.yml index cc5cb19..3eb5410 100644 --- a/.github/workflows/publish.harness.yml +++ b/.github/workflows/publish.harness.yml @@ -101,10 +101,11 @@ jobs: echo "Image tag: ${IMAGE_TAG}" echo "Latest tag: ${LATEST_TAG}" - - name: Build Docker image + - name: Build Docker image (multi-platform) uses: docker/build-push-action@v5 with: context: . + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/') }} tags: | ${{ steps.docker_tags.outputs.IMAGE_TAG }} @@ -281,11 +282,10 @@ jobs: spec: image: ${{ steps.docker_image.outputs.IMAGE_NAME }} settings: + ksm_config: <+secrets.getValue("Keeper_Config_Secret")> secrets: | VeYTRo-PHElAwfQT6f0TIA/field/password > DB_PASSWORD VeYTRo-PHElAwfQT6f0TIA/field/login > DB_USERNAME - envVariables: - KSM_CONFIG: <+secrets.getValue("Keeper_Config_Secret")> ``` ### What's Changed @@ -293,4 +293,4 @@ jobs: draft: false prerelease: false env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9d6dd3b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index eb8915e..e4247c1 100644 --- a/README.md +++ b/README.md @@ -59,13 +59,12 @@ pipeline: name: Fetch_Keeper_Secrets identifier: Fetch_Keeper_Secrets spec: - image: dhborse/keeper-harness-plugin + image: keeper/harness-plugin:latest settings: + ksm_config: <+secrets.getValue("keeper_base64_secret")> secrets: | RECORD_UID/field/password > PASSWORD RECORD_UID/field/login > USERNAME - envVariables: - KSM_CONFIG: <+secrets.getValue("keeper_base64_secret")> - step: type: Run name: Use_Secrets @@ -87,13 +86,15 @@ pipeline: ## Inputs -### KSM_CONFIG +### ksm_config (PLUGIN_KSM_CONFIG) -Keeper Secrets Manager configuration for authentication. Store in Harness secrets and reference: +Keeper Secrets Manager configuration for authentication. Store in Harness secrets and reference via the standard `settings` block (Harness/Drone maps `ksm_config` to `PLUGIN_KSM_CONFIG`): ```yaml -envVariables: - KSM_CONFIG: <+secrets.getValue("Keeper_Config_Secret")> +settings: + ksm_config: <+secrets.getValue("Keeper_Config_Secret")> + secrets: | + RECORD_UID/field/password > PASSWORD ``` **Supported Formats:** @@ -172,7 +173,7 @@ Secrets are stored in `/harness/secrets/` directory. Read them in subsequent ste - **Scope:** Pipeline execution only - automatically cleaned up after completion - **Access:** Read files directly using `cat` or file operations -The `envVariables` section is **only** used to pass `KSM_CONFIG` to the plugin. Actual secrets are written to files in `/harness/secrets/` directory. +The `ksm_config` setting is passed via the standard `settings` block (mapped to `PLUGIN_KSM_CONFIG`). Actual secrets are written to files in `/harness/secrets/` directory. ## Security @@ -194,7 +195,7 @@ The `envVariables` section is **only** used to pass `KSM_CONFIG` to the plugin. | Error | Solution | |-------|----------| -| `KSM config is required` | Verify secret exists and expression `<+secrets.getValue("SECRET_NAME")>` is correct | +| `KSM config is required` | Verify secret exists, `ksm_config` is set in settings, and expression `<+secrets.getValue("SECRET_NAME")>` is correct | | `Invalid token format` | Check token starts with `US:` or `{` for JSON config | | `Value not found for notation` | Verify Record UID, field name (case-sensitive), and Application permissions | | `Failed to download file` | Check file exists in record, name matches exactly, and Application has file access permissions | diff --git a/package.json b/package.json index 5150870..067fbe3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "harness_keeper_plugin", "version": "1.0.0", "private": true, - "description": "", + "description": "Keeper Secrets Manager integration for Harness CI", "main": "index.js", "scripts": { "test": "jest --coverage", @@ -11,8 +11,8 @@ "lint:fix": "eslint . --fix" }, "keywords": [], - "author": "", - "license": "ISC", + "author": "Keeper Security", + "license": "MIT", "type": "commonjs", "dependencies": { "@keeper-security/secrets-manager-core": "^17.4.0" diff --git a/src/index.js b/src/index.js index 09ec491..db29958 100644 --- a/src/index.js +++ b/src/index.js @@ -29,7 +29,7 @@ const splitInput = (text) => { const processToken = (rawToken) => { if (!rawToken) { core.error('KSM config is required'); - core.error('Set KSM_CONFIG environment variable'); + core.error('Set PLUGIN_KSM_CONFIG (or ksm_config in settings)'); process.exit(1); } @@ -103,7 +103,7 @@ const runPlugin = async () => { core.info('Starting Keeper Secrets Manager plugin'); fs.mkdirSync('/app', { recursive: true }); - const { token, isConfigJson, config } = processToken(process.env.KSM_CONFIG); + const { token, isConfigJson, config } = processToken(process.env.PLUGIN_KSM_CONFIG); const inputs = parseSecretMappings(); const storage = await setupStorage(token, isConfigJson, config); const secrets = await getSecrets({ storage }); diff --git a/src/test/index.test.js b/src/test/index.test.js index 617c7d4..bf9e598 100644 --- a/src/test/index.test.js +++ b/src/test/index.test.js @@ -72,7 +72,7 @@ describe('index.js - Complete Test Suite', () => { pathModule.join = path.join; // Clear environment variables - delete process.env.KSM_CONFIG; + delete process.env.PLUGIN_KSM_CONFIG; delete process.env.PLUGIN_SECRETS; }); @@ -89,45 +89,45 @@ describe('index.js - Complete Test Suite', () => { describe('processToken - Error Cases', () => { test('should exit when rawToken is null', () => { - delete process.env.KSM_CONFIG; + delete process.env.PLUGIN_KSM_CONFIG; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); expect(mockStderrWrite).toHaveBeenCalledWith(expect.stringContaining('KSM config is required')); }); test('should exit when rawToken is undefined', () => { - delete process.env.KSM_CONFIG; + delete process.env.PLUGIN_KSM_CONFIG; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when rawToken is empty string', () => { - process.env.KSM_CONFIG = ''; + process.env.PLUGIN_KSM_CONFIG = ''; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when token contains ${ Harness expression', () => { - process.env.KSM_CONFIG = '${harness.expression}'; + process.env.PLUGIN_KSM_CONFIG = '${harness.expression}'; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); expect(mockStderrWrite).toHaveBeenCalledWith(expect.stringContaining('Harness expression not resolved')); }); test('should exit when token contains <+ Harness expression', () => { - process.env.KSM_CONFIG = '<+expression>'; + process.env.PLUGIN_KSM_CONFIG = '<+expression>'; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when token contains ngSecretManager', () => { - process.env.KSM_CONFIG = 'ngSecretManager.token'; + process.env.PLUGIN_KSM_CONFIG = 'ngSecretManager.token'; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when token is empty after trim', () => { - process.env.KSM_CONFIG = ' '; + process.env.PLUGIN_KSM_CONFIG = ' '; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); expect(mockStderrWrite).toHaveBeenCalledWith(expect.stringContaining('Token is null or empty')); @@ -135,7 +135,7 @@ describe('index.js - Complete Test Suite', () => { test('should exit when JSON config is missing hostname field', () => { const jsonConfig = JSON.stringify({ clientId: 'client', privateKey: 'key' }); - process.env.KSM_CONFIG = jsonConfig; + process.env.PLUGIN_KSM_CONFIG = jsonConfig; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); expect(mockStderrWrite).toHaveBeenCalledWith(expect.stringContaining('Missing required fields')); @@ -143,34 +143,34 @@ describe('index.js - Complete Test Suite', () => { test('should exit when JSON config is missing clientId field', () => { const jsonConfig = JSON.stringify({ hostname: 'test.com', privateKey: 'key' }); - process.env.KSM_CONFIG = jsonConfig; + process.env.PLUGIN_KSM_CONFIG = jsonConfig; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when JSON config is missing privateKey field', () => { const jsonConfig = JSON.stringify({ hostname: 'test.com', clientId: 'client' }); - process.env.KSM_CONFIG = jsonConfig; + process.env.PLUGIN_KSM_CONFIG = jsonConfig; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when JSON config is missing multiple fields', () => { const jsonConfig = JSON.stringify({ hostname: 'test.com' }); - process.env.KSM_CONFIG = jsonConfig; + process.env.PLUGIN_KSM_CONFIG = jsonConfig; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); }); test('should exit when JSON config is invalid', () => { - process.env.KSM_CONFIG = '{invalid json}'; + process.env.PLUGIN_KSM_CONFIG = '{invalid json}'; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); expect(mockStderrWrite).toHaveBeenCalledWith(expect.stringContaining('Invalid JSON config')); }); test('should exit when token format is invalid (not US: or JSON)', () => { - process.env.KSM_CONFIG = 'INVALID:token'; + process.env.PLUGIN_KSM_CONFIG = 'INVALID:token'; require('../index'); expect(mockExit).toHaveBeenCalledWith(1); expect(mockStderrWrite).toHaveBeenCalledWith(expect.stringContaining('Invalid token format')); @@ -180,7 +180,7 @@ describe('index.js - Complete Test Suite', () => { describe('processToken - Success Cases', () => { test('should decode base64 token successfully', async () => { const base64Token = Buffer.from('US:decoded-token').toString('base64'); - process.env.KSM_CONFIG = base64Token; + process.env.PLUGIN_KSM_CONFIG = base64Token; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -193,7 +193,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle base64 decode error gracefully', async () => { - process.env.KSM_CONFIG = 'invalid-base64!@#'; + process.env.PLUGIN_KSM_CONFIG = 'invalid-base64!@#'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -207,7 +207,7 @@ describe('index.js - Complete Test Suite', () => { test('should parse valid JSON config', async () => { const jsonConfig = JSON.stringify({ hostname: 'test.com', clientId: 'client', privateKey: 'key' }); - process.env.KSM_CONFIG = jsonConfig; + process.env.PLUGIN_KSM_CONFIG = jsonConfig; process.env.PLUGIN_SECRETS = 'notation>output'; const mockStorage = {}; localConfigStorage.mockReturnValue(mockStorage); @@ -221,7 +221,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should accept US: token format', async () => { - process.env.KSM_CONFIG = 'US:test-token'; + process.env.PLUGIN_KSM_CONFIG = 'US:test-token'; process.env.PLUGIN_SECRETS = 'notation>output'; const mockStorage = {}; localConfigStorage.mockReturnValue(mockStorage); @@ -235,7 +235,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle token with whitespace', async () => { - process.env.KSM_CONFIG = ' US:test-token '; + process.env.PLUGIN_KSM_CONFIG = ' US:test-token '; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -248,7 +248,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should call info function on plugin start', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -268,7 +268,7 @@ describe('index.js - Complete Test Suite', () => { throw new Error('Base64 decode error'); }); - process.env.KSM_CONFIG = 'not-us-format-token'; + process.env.PLUGIN_KSM_CONFIG = 'not-us-format-token'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -284,7 +284,7 @@ describe('index.js - Complete Test Suite', () => { describe('parseSecretMappings', () => { test('should treat env: prefix as literal destination name (no special parsing)', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>env:VAR_NAME'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -297,7 +297,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should parse file: destination type', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -310,7 +310,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should parse output destination type (default)', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -323,7 +323,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle input without > separator', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -336,7 +336,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle multiple > characters', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>part1>destination'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -348,7 +348,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle multiple secret mappings', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation1>env:VAR1\nnotation2>file:/path/file\nnotation3>output1'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -361,7 +361,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should filter empty lines from multiline input', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation1>output1\n\nnotation2>output2\n \nnotation3>output3'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -374,7 +374,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle empty secrets input', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = ''; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -388,7 +388,7 @@ describe('index.js - Complete Test Suite', () => { describe('runPlugin - File Handling', () => { test('should create /app directory', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; const mockStorage = {}; localConfigStorage.mockReturnValue(mockStorage); @@ -406,7 +406,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should treat file: prefix as literal destination (writes to /harness/secrets/)', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -419,7 +419,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should create /harness/secrets for any destination', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -433,7 +433,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file secret with fileId', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -448,7 +448,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file secret with fileUid', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -463,7 +463,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file secret with url', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -478,7 +478,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file secret with /file/ notation', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'record/field/file/file.txt>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -495,7 +495,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle downloadFile error gracefully', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -510,7 +510,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle Uint8Array file data', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -526,7 +526,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle Buffer file data', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -541,7 +541,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle other file data types', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -557,7 +557,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file data that is neither Uint8Array nor Buffer (line 135 branch)', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -576,7 +576,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file data that is Buffer (line 134 branch)', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -598,7 +598,7 @@ describe('index.js - Complete Test Suite', () => { test('should handle file data that is plain Uint8Array (not Buffer)', async () => { // Test the second branch: Buffer.isBuffer is false but instanceof Uint8Array is true // Use a plain Uint8Array (not a Buffer) to test the middle branch of the ternary - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>file:/path/to/file.txt'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -624,7 +624,7 @@ describe('index.js - Complete Test Suite', () => { describe('runPlugin - Secret Value Handling', () => { test('should handle string secret value', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -637,7 +637,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle Buffer secret value', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -650,7 +650,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle non-string, non-buffer secret value', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -663,7 +663,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should skip secret when value is not found', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -677,7 +677,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should skip secret when value is undefined', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -692,7 +692,7 @@ describe('index.js - Complete Test Suite', () => { describe('runPlugin - Destination Types', () => { test('should handle env-prefixed destination as literal (writes to /harness/secrets/)', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>env:VAR_NAME'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -707,7 +707,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle env-prefixed destination with Buffer data', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>env:VAR_NAME'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -722,7 +722,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle env-prefixed destination with non-Buffer data', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>env:VAR_NAME'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -738,7 +738,7 @@ describe('index.js - Complete Test Suite', () => { test('should create /harness/secrets directory for non-file destinations', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -751,7 +751,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should set file permissions to 0o600 for secret files', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -764,7 +764,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should write only to /harness/secrets for output destinations', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output_var'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -779,7 +779,7 @@ describe('index.js - Complete Test Suite', () => { describe('runPlugin - Error Handling', () => { test('should handle storage initialization error', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockRejectedValue(new Error('Storage initialization failed')); @@ -791,7 +791,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle token-related error with specific message', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockRejectedValue(new Error('Invalid token')); @@ -802,7 +802,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle expired token error', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockRejectedValue(new Error('Token expired')); @@ -813,7 +813,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle invalid token error', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockRejectedValue(new Error('invalid')); @@ -824,7 +824,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle getSecrets error', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -837,7 +837,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle generic error without token message', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -852,7 +852,7 @@ describe('index.js - Complete Test Suite', () => { describe('Edge Cases and Integration', () => { test('should handle null secret object', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -865,7 +865,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle object secret without file properties', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -880,7 +880,7 @@ describe('index.js - Complete Test Suite', () => { test('should handle object secret without file properties and notation without /file/', async () => { // Test the branch where typeof secret === 'object' && secret !== null is true // but (secret.fileId || secret.fileUid || secret.url || notationIsFile) is false - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'regular/notation>output'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -896,7 +896,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle complex multiline input with mixed types', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'notation1>env:VAR1\nnotation2>file:/path/file\nnotation3>output1\nnotation4>env:VAR2'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue(); @@ -909,7 +909,7 @@ describe('index.js - Complete Test Suite', () => { }); test('should handle file notation with multiple file references', async () => { - process.env.KSM_CONFIG = 'US:test'; + process.env.PLUGIN_KSM_CONFIG = 'US:test'; process.env.PLUGIN_SECRETS = 'record1/field/file/file1.txt>file:/path/file1\nrecord2/field/file/file2.txt>file:/path/file2'; localConfigStorage.mockReturnValue({}); initializeStorage.mockResolvedValue();