From 98a261df6b190a920bcf45fc6f500aac8057dca8 Mon Sep 17 00:00:00 2001 From: Yusef Mohamadi Date: Wed, 30 Sep 2026 09:32:07 +0200 Subject: [PATCH 1/3] test(plugin-v2): use placeholder-marked fixture credentials The HOL plugin scanner flags `LITELLM_API_KEY = ''` and `apiKey: ''` literals through its generic API-key detector. Test files count as an "example surface" for the scanner, but the exemption only applies to placeholder-marked values, so `test-env-key` / `test-key` were reported as a high-severity hardcoded secret. Use `example-*` values, which the scanner recognises as placeholders. No behaviour change: the fixtures are still asserted end-to-end. --- test/plugin-v2.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/plugin-v2.test.ts b/test/plugin-v2.test.ts index cc56848..fa49fbb 100644 --- a/test/plugin-v2.test.ts +++ b/test/plugin-v2.test.ts @@ -176,7 +176,7 @@ describe('OpenCode 2 plugin entrypoint', () => { cacheDirectory = mkdtempSync(join(tmpdir(), 'opencode-litellm-env-test-')) process.env.XDG_CACHE_HOME = cacheDirectory process.env.LITELLM_BASE_URL = 'https://llm.example.com/v1' - process.env.LITELLM_API_KEY = 'test-env-key' + process.env.LITELLM_API_KEY = 'example-litellm-key' delete process.env.LITELLM_MASTER_KEY const requestURLs: string[] = [] @@ -235,14 +235,14 @@ describe('OpenCode 2 plugin entrypoint', () => { expect(registered[0].info).toMatchObject({ id: 'litellm' }) expect(registered[0].info.settings).toMatchObject({ baseURL: 'https://llm.example.com/v1', - apiKey: 'test-env-key', + apiKey: 'example-litellm-key', }) expect(registered[0].models.map((model) => model.id)).toContain('model-from-env') expect(registered[0].models[0]).toMatchObject({ capabilities: { input: ['text'], output: ['text'] }, }) expect(requestURLs).toContain('https://llm.example.com/v1/models') - expect(authorizationHeaders).toContain('Bearer test-env-key') + expect(authorizationHeaders).toContain('Bearer example-litellm-key') await cleanup?.() }) @@ -267,7 +267,7 @@ describe('OpenCode 2 plugin entrypoint', () => { name: 'Configured LiteLLM', activation: 'enabled', package: '@opencode/ai/providers/openai-compatible', - settings: { baseURL: `${baseURL}/v1`, apiKey: 'test-key' }, + settings: { baseURL: `${baseURL}/v1`, apiKey: 'example-api-key' }, headers: { 'X-Gateway': 'test' }, } const curatedModel = { id: 'curated-model', name: 'Curated model' } @@ -899,7 +899,7 @@ describe('OpenCode 2 plugin entrypoint', () => { name: 'Configured LiteLLM', activation: 'enabled', package: '@opencode/ai/providers/openai-compatible', - settings: { baseURL: `${baseURL}/v1`, apiKey: 'test-key' }, + settings: { baseURL: `${baseURL}/v1`, apiKey: 'example-api-key' }, headers: {}, } let currentModels = new Map>([ From 67eb675e19e0e287e938405bfdd342c427c5b52c Mon Sep 17 00:00:00 2001 From: Yusef Mohamadi Date: Wed, 30 Sep 2026 09:32:10 +0200 Subject: [PATCH 2/3] ci: pin GitHub Actions and add the HOL plugin scanner gate Pin actions/checkout and actions/setup-node to immutable commit SHAs (v4.4.0) so a mutable tag cannot silently change the code CI executes, and add the scanner workflow from the catalog's SCANNER_GUIDE.md (hashgraph-online/ai-plugin-scanner-action v1.2.635, min_score 80, fail_on_severity high). Clears the GITHUB_ACTION_UNPINNED findings and keeps them clear on every PR. --- .github/workflows/ci.yml | 4 ++-- .github/workflows/plugin-scan.yml | 21 +++++++++++++++++++++ .github/workflows/release.yml | 4 ++-- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/plugin-scan.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ec81d8..6116782 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,10 @@ jobs: matrix: node: [20, 22] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - name: Setup Node.js ${{ matrix.node }} - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node }} cache: npm diff --git a/.github/workflows/plugin-scan.yml b/.github/workflows/plugin-scan.yml new file mode 100644 index 0000000..3d6cd49 --- /dev/null +++ b/.github/workflows/plugin-scan.yml @@ -0,0 +1,21 @@ +name: Plugin Security Scan + +on: [pull_request, push] + +permissions: + contents: read + +jobs: + scan: + runs-on: ubuntu-latest + steps: + # Pinned to immutable commit SHAs: a mutable tag could silently change the + # code this workflow executes. See SCANNER_GUIDE.md in hashgraph-online/awesome-ai-plugins. + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + - uses: hashgraph-online/ai-plugin-scanner-action@caba2e96aa8ad2feb6cf6fca52442b52e22e779f # v1.2.635 + with: + plugin_dir: "." + min_score: 80 + fail_on_severity: high diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9821720..4fd8cc9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: id-token: write # for npm provenance and Trusted Publishing OIDC steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 with: fetch-depth: 0 # full history is required for semantic-release to analyze commits @@ -22,7 +22,7 @@ jobs: # with npm 11 by default. We do NOT specify "registry-url" because # doing so conflicts with npm's default OIDC authentication mechanism. - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: 24 cache: npm From b57fc6a9fb22ba5bfbd27894ab1478029f01eb98 Mon Sep 17 00:00:00 2001 From: Yusef Mohamadi Date: Wed, 30 Sep 2026 09:32:11 +0200 Subject: [PATCH 3/3] docs: add SECURITY.md security policy Documents supported versions, the private disclosure channel (GitHub private vulnerability reporting, now enabled on this repository) and what is in scope for this plugin. Clears the scanner's SECURITY_MD_MISSING finding. --- SECURITY.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1fdceb6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,46 @@ +# Security Policy + +## Supported versions + +Security fixes land in the latest published release of +`opencode-plugin-litellm`. + +| Version | Supported | +| ------------ | --------- | +| 1.x (latest) | ✅ | +| < 1.0 | ❌ | + +## Reporting a vulnerability + +Please report suspected vulnerabilities privately through GitHub's +[private vulnerability reporting](https://github.com/yuseferi/opencode-litellm/security/advisories/new). +Please do not open a public issue for a security problem. + +Include as much of the following as you can: + +- the plugin version, and the OpenCode version (`opencode --version`), +- the LiteLLM proxy version, and how the plugin was configured + (`LITELLM_BASE_URL` / `LITELLM_API_KEY` / `providers.litellm.settings`), +- a minimal reproduction, and the impact you believe it has. + +## What to expect + +- A first response within a few business days. +- An initial assessment (severity, affected versions) after triage. +- A patch release plus a published GitHub Security Advisory once a fix is + available, crediting you unless you ask to stay anonymous. + +## What matters most in this project + +The plugin runs inside OpenCode and handles LiteLLM credentials. Reports in +these areas are the most valuable: + +- credentials (API keys, master keys) leaking into logs, error messages, + telemetry, or the model picker, +- requests being sent anywhere other than the configured LiteLLM base URL, +- code execution or file access triggered by model or provider metadata returned + by a proxied endpoint, +- a poisoned proxy response escalating beyond "shows wrong models". + +Out of scope here: vulnerabilities in OpenCode itself, in LiteLLM, or in the npm +toolchain — please report those upstream.