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 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. 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>([