From 7063830f7c981797df88b5adecbcc0c1691c741b Mon Sep 17 00:00:00 2001 From: JoshRob297 <99092299+JoshRob297@users.noreply.github.com> Date: Sat, 31 Oct 2026 11:47:48 -0600 Subject: [PATCH] feat: pass through zero data retention opt-in (CMD_ZDR) Command Code exposes ZDR as x-cmd-zdr: 1, the same opt-in its CLI reads from CMD_ZDR=1. Mirror it for plugin users and strip any caller-supplied header when the opt-in is absent, so a session that never asked for ZDR cannot emit the flag by accident. Applied on the strict Command Code host only, never on a lookalike that merely shares the request path. --- CHANGELOG.md | 10 ++++ README.es.md | 12 +++++ README.md | 12 +++++ index.ts | 1 + package.json | 2 +- src/hooks/zdr.ts | 40 ++++++++++++++++ src/index.ts | 16 ++++++- tests/zdr.test.ts | 116 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 src/hooks/zdr.ts create mode 100644 tests/zdr.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a00e3f..204a3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) - SemVer. ## [Unreleased] +## [1.0.7] - 2026-10-31 + +### Added +- Zero data retention passthrough. With `CMD_ZDR=1` every request to + `api.commandcode.ai` carries `x-cmd-zdr: 1`, mirroring the Command Code CLI. + When the opt-in is absent, any caller-supplied `x-cmd-zdr` header is stripped + so a session that never asked for ZDR cannot emit the flag by accident. The + header is applied on the strict Command Code host only, never on a lookalike + that merely shares the request path. + ## [1.0.6] - 2026-10-25 ### Added diff --git a/README.es.md b/README.es.md index 0ff3884..ae0a2d4 100644 --- a/README.es.md +++ b/README.es.md @@ -94,6 +94,18 @@ un request): - **Registro de reintentos** - persiste los reintentos de transporte solo de Command Code, para poder diagnosticar una caida upstream a posteriori. +### Zero data retention (opt-in) + +Command Code expone ZDR como cabecera de peticion (`x-cmd-zdr: 1`), el mismo +opt-in que su CLI lee de `CMD_ZDR=1`. El plugin lo replica: + +- `CMD_ZDR=1` - toda peticion a `api.commandcode.ai` lleva `x-cmd-zdr: 1`. +- En cualquier otro caso - se elimina cualquier `x-cmd-zdr` enviada por el + cliente, para que una sesion sin opt-in no pueda emitir la cabecera por error. + +La cabecera solo se aplica en el host estricto de Command Code, nunca en un host +parecido que solo comparta `/provider/v1/...` en la ruta. + ### Telemetria de cuota - Herramienta `commandcode_quota` y comando `/commandcode-quota`: plan, estado, diff --git a/README.md b/README.md index 11d5cac..f2743c9 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,18 @@ request): - **Retry logger** - persists transport retries for Command Code only, so an upstream outage can be diagnosed after the fact. +### Zero data retention (opt-in) + +Command Code exposes ZDR as a request header (`x-cmd-zdr: 1`), the same opt-in +its CLI reads from `CMD_ZDR=1`. The plugin mirrors it: + +- `CMD_ZDR=1` - every request to `api.commandcode.ai` carries `x-cmd-zdr: 1`. +- Otherwise - any caller-supplied `x-cmd-zdr` header is stripped, so a session + that never opted in cannot emit the flag by accident. + +The header is applied only on the strict Command Code host, never on a lookalike +that merely shares `/provider/v1/...` in its path. + ### Quota telemetry - Tool `commandcode_quota` and command `/commandcode-quota`: plan, status, diff --git a/index.ts b/index.ts index 3e818ae..05cfa77 100644 --- a/index.ts +++ b/index.ts @@ -4,4 +4,5 @@ export default plugin; export * from "./src/auth/resolver.ts"; export * from "./src/client/api.ts"; export * from "./src/hooks/resilience.ts"; +export * from "./src/hooks/zdr.ts"; export * from "./src/tools/quota.ts"; diff --git a/package.json b/package.json index baf907f..70bde93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opencode-commandcode", - "version": "1.0.6", + "version": "1.0.7", "description": "Production-grade OpenCode plugin for Command Code: dynamic model discovery, stability hooks and quota telemetry", "main": "index.ts", "type": "module", diff --git a/src/hooks/zdr.ts b/src/hooks/zdr.ts new file mode 100644 index 0000000..ca35c27 --- /dev/null +++ b/src/hooks/zdr.ts @@ -0,0 +1,40 @@ +/** + * Zero data retention (ZDR) passthrough. + * + * Command Code exposes ZDR as a request header (`x-cmd-zdr: 1`), the same + * opt-in its CLI reads from `CMD_ZDR=1`. The header is only emitted when the + * user explicitly opts in: + * + * - `CMD_ZDR=1` -> every Command Code request carries `x-cmd-zdr: 1` + * - anything else -> any caller-supplied `x-cmd-zdr` header is stripped, so + * a session that did not opt in can never emit it by accident + * + * Stripping (instead of ignoring) matters because the header is a policy + * control: a lookalike client or a stale config must not be able to turn it on + * for a user who never asked for it. + */ + +export const ZDR_HEADER = "x-cmd-zdr"; + +/** True only for the explicit opt-in value documented by the CLI. */ +export function isZdrEnabled(env: Record = process.env): boolean { + return env.CMD_ZDR === "1"; +} + +/** + * Applies the ZDR policy to a request's headers. + * + * Headers is case-insensitive, so `get`/`delete`/`set` cover every casing the + * caller may have used. + */ +export function applyZdrHeader( + headers: Headers, + env: Record = process.env +): void { + if (headers.get(ZDR_HEADER) !== null) { + headers.delete(ZDR_HEADER); + } + if (isZdrEnabled(env)) { + headers.set(ZDR_HEADER, "1"); + } +} diff --git a/src/index.ts b/src/index.ts index 88ff822..adafe23 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import { supportsChatCompletions, supportsVision, } from "./hooks/resilience.ts"; +import { applyZdrHeader } from "./hooks/zdr.ts"; import { getQuotaReport } from "./tools/quota.ts"; import { describeRetryEvent } from "./telemetry.ts"; import { buildReleaseDate, freeCostEntry } from "./catalog.ts"; @@ -166,7 +167,18 @@ export default { } } - // B. Credential injection, STRICT origin only (no lookalike hosts, + // B. Zero data retention policy (opt-in only, spoofed headers stripped). + // Strict origin: the header is a Command Code policy flag and must + // never be sent to a host that only happens to share the path. + if (isCommandCodeHost(url)) { + try { + applyZdrHeader(req.headers); + } catch { + // immutable headers in this runtime + } + } + + // C. Credential injection, STRICT origin only (no lookalike hosts, // no path-based matching: this is where the API key travels). if (isCommandCodeHost(url) && !req.headers.has("authorization")) { const key = resolveApiKey(); @@ -179,7 +191,7 @@ export default { } } - // C. Payload sanitizer (no credentials involved). + // D. Payload sanitizer (no credentials involved). try { const method = String(req.method || "GET").toUpperCase(); if (method === "GET" || method === "HEAD") return; diff --git a/tests/zdr.test.ts b/tests/zdr.test.ts new file mode 100644 index 0000000..03e6a47 --- /dev/null +++ b/tests/zdr.test.ts @@ -0,0 +1,116 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import plugin from "../src/index.ts"; +import { applyZdrHeader, isZdrEnabled, ZDR_HEADER } from "../src/hooks/zdr.ts"; + +const URL_CC = "https://api.commandcode.ai/provider/v1/chat/completions"; + +function makeCtx() { + const hooks: Record Promise)[]> = { + "http.request": [], + "http.response": [], + retry: [], + }; + const ctx: any = { + session: { + hook: async (name: any, cb: any) => { + hooks[name]?.push(cb); + return { dispose: async () => {} }; + }, + synthetic: async () => ({}), + }, + model: { transform: async () => {} }, + tool: { transform: async () => {} }, + command: { transform: async () => {} }, + }; + return { ctx, hooks }; +} + +async function runHook(hooks: Record Promise)[]>, request: Request) { + const event: any = { request, sessionID: "ses_test" }; + for (const cb of hooks["http.request"] ?? []) { + await cb(event); + } + return event.request as Request; +} + +describe("isZdrEnabled", () => { + test("only the documented opt-in value enables ZDR", () => { + expect(isZdrEnabled({ CMD_ZDR: "1" })).toBe(true); + expect(isZdrEnabled({ CMD_ZDR: "0" })).toBe(false); + expect(isZdrEnabled({ CMD_ZDR: "true" })).toBe(false); + expect(isZdrEnabled({})).toBe(false); + }); +}); + +describe("applyZdrHeader", () => { + test("sets the header when opted in", () => { + const headers = new Headers(); + applyZdrHeader(headers, { CMD_ZDR: "1" }); + expect(headers.get(ZDR_HEADER)).toBe("1"); + }); + + test("strips a caller-supplied header when not opted in", () => { + const headers = new Headers({ "x-cmd-zdr": "1" }); + applyZdrHeader(headers, {}); + expect(headers.get(ZDR_HEADER)).toBeNull(); + }); + + test("strips any casing of the header when not opted in", () => { + const headers = new Headers({ "X-CMD-ZDR": "1" }); + applyZdrHeader(headers, { CMD_ZDR: "0" }); + expect(headers.get(ZDR_HEADER)).toBeNull(); + }); + + test("keeps exactly one header value when opted in", () => { + const headers = new Headers(); + headers.set("x-cmd-zdr", "0"); + applyZdrHeader(headers, { CMD_ZDR: "1" }); + expect(headers.get(ZDR_HEADER)).toBe("1"); + }); +}); + +describe("plugin ZDR hook", () => { + const previous = process.env.CMD_ZDR; + + afterEach(() => { + if (previous === undefined) delete process.env.CMD_ZDR; + else process.env.CMD_ZDR = previous; + }); + + test("emits x-cmd-zdr on Command Code requests when CMD_ZDR=1", async () => { + process.env.CMD_ZDR = "1"; + const { ctx, hooks } = makeCtx(); + await plugin.setup(ctx); + + const request = new Request(URL_CC, { method: "POST", body: "{}" }); + const outgoing = await runHook(hooks, request); + expect(outgoing.headers.get(ZDR_HEADER)).toBe("1"); + }); + + test("strips a spoofed header when CMD_ZDR is not set", async () => { + delete process.env.CMD_ZDR; + const { ctx, hooks } = makeCtx(); + await plugin.setup(ctx); + + const request = new Request(URL_CC, { + method: "POST", + body: "{}", + headers: { "x-cmd-zdr": "1" }, + }); + const outgoing = await runHook(hooks, request); + expect(outgoing.headers.get(ZDR_HEADER)).toBeNull(); + }); + + test("never touches a lookalike host that only shares the path", async () => { + process.env.CMD_ZDR = "1"; + const { ctx, hooks } = makeCtx(); + await plugin.setup(ctx); + + const request = new Request("https://api.commandcode.ai.evil.com/provider/v1/chat/completions", { + method: "POST", + body: "{}", + }); + const outgoing = await runHook(hooks, request); + expect(outgoing.headers.get(ZDR_HEADER)).toBeNull(); + }); +});