diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/actual-complexity.json b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/actual-complexity.json new file mode 100644 index 00000000..9f955ec2 --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/actual-complexity.json @@ -0,0 +1,28 @@ +{ + "schema": 1, + "task": "Replace hardcoded secret-looking strings in test files to resolve SAST hardcoded-secrets findings.", + "generated": "2026-07-03T00:00:00Z", + "dimensions": { + "component_scope": { "score": 1, "label": "XS" }, + "requirements_clarity": { "score": 1, "label": "XS" }, + "technical_risk": { "score": 1, "label": "XS" }, + "file_change_estimate": { "score": 1, "label": "XS" }, + "dependencies": { "score": 1, "label": "XS" }, + "affected_layers": { "score": 1, "label": "XS" } + }, + "total": 6, + "size": "XS", + "routing": "writing-plans", + "key_reasoning": [ + { + "dimension": "component_scope", + "reason": "Only two test files modified — AgentCLI-effort.test.ts and moonshot-subscription.template.test.ts. No production code or cross-layer concerns involved." + }, + { + "dimension": "file_change_estimate", + "reason": "Exactly 2 files changed with 6 insertions and 6 deletions; no new files created. Maps directly to XS (1–2 files)." + } + ], + "red_flags_applied": [], + "split_recommendation": null +} diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/code-review-final.json b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/code-review-final.json new file mode 100644 index 00000000..83ef9a4e --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/code-review-final.json @@ -0,0 +1,25 @@ +{ + "decision": "approve", + "rationale": "All 6 changed lines are test-only value substitutions. Blind lens raised 4 concerns; triage classified 1 as defer (pre-existing gap: apiKey empty-string behavior untested — not introduced by this change) and 3 as dismiss (scanner speculation, mutation-sentinel robustness — cosmetic). Edge-case lens returned no findings. No spec/story artifact present (genuine no-spec round; confidence lowered accordingly so a human can confirm the SAST scanner accepts the new values). 0 blocking findings.", + "confidence": "low", + "risk_flags": [], + "business_review": [], + "standards_review": [ + { + "kind": "commit-format", + "status": "pass", + "notes": "fix(tests): follows Conventional Commits; type 'fix', scope 'tests', subject within 100 chars" + }, + { + "kind": "code-quality", + "status": "pass", + "notes": "test-only changes; no production code conventions affected" + }, + { + "kind": "security", + "status": "pass", + "notes": "no production code touched; changes remove hardcoded-like string values from test fixtures" + } + ], + "findings": [] +} diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/decisions.jsonl b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/decisions.jsonl new file mode 100644 index 00000000..71b07b5c --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/decisions.jsonl @@ -0,0 +1 @@ +{"ts":"2026-07-03T12:00:00Z","gate_id":"code-review.final","mode":"hitl","verdict":{"decision":"approve","rationale":"All 6 changed lines are test-only value substitutions. Blind lens raised 4 concerns; triage classified 1 as defer and 3 as dismiss. Edge-case lens returned no findings. Genuine no-spec round.","confidence":"low","risk_flags":[],"source":"hitl"},"escalated":true,"prior_context":{"question":"Code review complete. Approve or request changes?","phase":5,"risk_flags":["security"],"artifact_refs":["docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/plan.md","docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/code-review.diff"],"prior_orchestrator_verdict":{"decision":"approve","confidence":"low","risk_flags":[],"findings":[]}}} diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/events.jsonl b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/events.jsonl new file mode 100644 index 00000000..cc0649fa --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/events.jsonl @@ -0,0 +1 @@ +{"schema":1,"ts":"2026-07-03T12:00:00Z","event":"decision.recorded","run_id":"sast-hardcoded-secrets","phase":5,"actor":"decision-router","summary":"Decision recorded for code-review.final: approve","artifacts":["decisions.jsonl"],"data":{"gate_id":"code-review.final","mode":"hitl","decision":"approve","source":"hitl","escalated":true,"prior_context":{"question":"Code review complete — approve or request changes?","options":["approve","request-changes"],"phase":5,"risk_flags":["security"],"artifact_refs":["plan.md","code-review.diff"]}}} diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/gate-plan.json b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/gate-plan.json new file mode 100644 index 00000000..8a40e930 --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/gate-plan.json @@ -0,0 +1,17 @@ +{ + "schema": 1, + "runner": "npm", + "gates": [ + {"id": "license-check", "command": "npm run license-check", "available": true}, + {"id": "lint", "command": "npm run lint", "available": true}, + {"id": "typecheck", "command": "npm run typecheck", "available": true}, + {"id": "build", "command": "npm run build", "available": true}, + {"id": "unit", "command": "npm run test:unit", "available": true}, + {"id": "integration", "command": "npm run test:integration", "available": true}, + {"id": "secrets", "command": "npm run validate:secrets", "available": true}, + {"id": "commitlint", "command": "npm run commitlint:last", "available": true}, + {"id": "ui", "command": "n/a", "available": false} + ], + "ui_globs": ["\\.(tsx|jsx|css|html|vue|svelte)$", "src/(ui|frontend|components)/"], + "detected_at": "2026-07-03T12:00:00Z" +} diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/plan.md b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/plan.md new file mode 100644 index 00000000..a0c88024 --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/plan.md @@ -0,0 +1,27 @@ +# Plan — Fix SAST HardcodedNonCryptoSecret in Test Files + +## Requirements + +Fix four SAST `HardcodedNonCryptoSecret` findings in two test files. Tickets: EPMCDME-13309, EPMCDME-13310, EPMCDME-13311, EPMCDME-13312. The SAST scanner flags API-key-like field names paired with any non-empty string value. The fix replaces flagged values with alternatives that satisfy the type contract and preserve test intent without triggering the pattern. + +## Tasks + +### Task 1 — Fix AgentCLI-effort.test.ts (EPMCDME-13309, EPMCDME-13310) + +Replace both `apiKey: 'test-key'` occurrences with `apiKey: ''`. + +Neither test asserts on `apiKey`; an empty string satisfies the TypeScript type and eliminates the SAST signal. + +Test-first: no — both tests already pass; this is a value substitution that preserves existing GREEN state. + +**Files**: `src/agents/core/__tests__/AgentCLI-effort.test.ts` + +### Task 2 — Fix moonshot-subscription.template.test.ts (EPMCDME-13311, EPMCDME-13312) + +Replace `KIMI_MODEL_API_KEY: 'some-key'` with `KIMI_MODEL_API_KEY: 'placeholder'` in the two flagged test cases. Update the corresponding `.toBe('some-key')` assertions to `.toBe('placeholder')`. + +The value must remain consistent between input and assertion (immutability test checks the original reference). + +Test-first: no — existing tests stay GREEN; this is a value rename that preserves test semantics. + +**Files**: `src/providers/plugins/moonshot-subscription/__tests__/moonshot-subscription.template.test.ts` diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/qa-report.md b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/qa-report.md new file mode 100644 index 00000000..3c1fe2d8 --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/qa-report.md @@ -0,0 +1,28 @@ +# QA Gate Report — sast-hardcoded-secrets + +**Branch**: fix/sast-hardcoded-secrets +**Runner**: npm +**Started**: 2026-07-03T12:00:00Z +**Status**: PASSED + +## Gates + +| Gate | Status | Command | Notes | +|--------------|---------|--------------------------------|------------------------------------| +| license-check | PASS | `npm run license-check` | exit 0; no missing headers | +| lint | PASS | `npm run lint` | 0 errors, 0 warnings | +| typecheck | PASS | `npm run typecheck` | no diagnostics | +| build | PASS | `npm run build` | dist/ rebuilt cleanly | +| unit | PASS | `npm run test:unit` | 2205 passed, 1 skipped (145 files) | +| integration | PASS | `npm run test:integration` | 220 passed, 1 skipped (27 files) | +| secrets | PASS | `npm run validate:secrets` | no leaks detected | +| commitlint | PASS | `npm run commitlint:last` | 0 problems, 0 warnings | +| ui | SKIPPED | n/a | no UI surface changed | + +## Failure detail + +None. + +## Drift signal + +no diff --git a/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/technical-analysis.md b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/technical-analysis.md new file mode 100644 index 00000000..5ea38204 --- /dev/null +++ b/docs/superpowers/tasks/2026-07-03-sast-hardcoded-secrets/technical-analysis.md @@ -0,0 +1,34 @@ +# Technical Analysis — SAST HardcodedNonCryptoSecret Fix + +## Task + +Fix four SAST `HardcodedNonCryptoSecret` findings in test files: +- EPMCDME-13309, EPMCDME-13310: `src/agents/core/__tests__/AgentCLI-effort.test.ts` +- EPMCDME-13311, EPMCDME-13312: `src/providers/plugins/moonshot-subscription/__tests__/moonshot-subscription.template.test.ts` + +## Codebase Findings + +### AgentCLI-effort.test.ts + +**Purpose**: Tests `ConfigLoader.exportProviderEnvVars` focusing on `CODEMIE_REASONING_EFFORT` env var emission. + +**Flagged lines**: `apiKey: 'test-key'` appears in two `it` blocks (lines 9 and 20). + +**Key observation**: Neither test asserts on the `apiKey` value. It is a required structural field on the config object but is irrelevant to both test assertions. An empty string `''` satisfies the TypeScript type contract without triggering the SAST pattern-match. + +### moonshot-subscription.template.test.ts + +**Purpose**: Tests `MoonshotSubscriptionTemplate` — env var export, Kimi hook injection, stripping/non-mutation of `KIMI_MODEL_API_KEY`. + +**Flagged lines**: `KIMI_MODEL_API_KEY: 'some-key'` in two test cases. + +**Key observation**: Two tests (`strips Kimi model env vars…` and `returns env unchanged…`) use `'some-key'` both as input and in the immutability/identity assertion (`.toBe('some-key')`). The value itself is arbitrary — what matters is that it is present before the hook runs and either stripped or passed through unchanged. Renaming to `'placeholder'` preserves test intent without matching key-like SAST patterns. + +Three other `KIMI_MODEL_API_KEY: 'key'` occurrences in later test cases were not flagged and are left untouched. + +## Risk Indicators + +1. Changes are confined to test files only — no production code is touched. +2. No test assertions rely on the semantic meaning of `'test-key'` or `'some-key'`; they only check presence/absence or identity. +3. The `ConfigLoader.exportProviderEnvVars` signature accepts `apiKey: string` — empty string is a valid value. +4. The `MoonshotSubscriptionTemplate` strips `KIMI_MODEL_API_KEY` regardless of value when agent is `kimi`. diff --git a/src/agents/core/__tests__/AgentCLI-effort.test.ts b/src/agents/core/__tests__/AgentCLI-effort.test.ts index 6ef17f4e..574ac29a 100644 --- a/src/agents/core/__tests__/AgentCLI-effort.test.ts +++ b/src/agents/core/__tests__/AgentCLI-effort.test.ts @@ -6,7 +6,7 @@ describe('exportProviderEnvVars — reasoningEffort', () => { const env = ConfigLoader.exportProviderEnvVars({ provider: 'openai', baseUrl: 'https://example.com', - apiKey: 'test-key', + apiKey: '', model: 'gpt-4o', reasoningEffort: 'high', }); @@ -17,7 +17,7 @@ describe('exportProviderEnvVars — reasoningEffort', () => { const env = ConfigLoader.exportProviderEnvVars({ provider: 'openai', baseUrl: 'https://example.com', - apiKey: 'test-key', + apiKey: '', model: 'gpt-4o', }); expect(env.CODEMIE_REASONING_EFFORT).toBeUndefined(); diff --git a/src/providers/plugins/moonshot-subscription/__tests__/moonshot-subscription.template.test.ts b/src/providers/plugins/moonshot-subscription/__tests__/moonshot-subscription.template.test.ts index d241d8c1..30d6899e 100644 --- a/src/providers/plugins/moonshot-subscription/__tests__/moonshot-subscription.template.test.ts +++ b/src/providers/plugins/moonshot-subscription/__tests__/moonshot-subscription.template.test.ts @@ -64,7 +64,7 @@ describe('MoonshotSubscriptionTemplate', () => { it('strips Kimi model env vars and does not mutate the original env when agent is kimi', async () => { const env: Record = { - KIMI_MODEL_API_KEY: 'some-key', + KIMI_MODEL_API_KEY: 'placeholder', KIMI_MODEL_BASE_URL: 'http://localhost:1234', KIMI_MODEL_NAME: 'kimi-for-coding', OTHER_VAR: 'keep-me', @@ -79,14 +79,14 @@ describe('MoonshotSubscriptionTemplate', () => { expect(result.OTHER_VAR).toBe('keep-me'); // Must not mutate the caller's object - expect(env.KIMI_MODEL_API_KEY).toBe('some-key'); + expect(env.KIMI_MODEL_API_KEY).toBe('placeholder'); expect(env.KIMI_MODEL_BASE_URL).toBe('http://localhost:1234'); expect(env.KIMI_MODEL_NAME).toBe('kimi-for-coding'); }); it('returns env unchanged for non-kimi agents', async () => { const env: Record = { - KIMI_MODEL_API_KEY: 'some-key', + KIMI_MODEL_API_KEY: 'placeholder', OTHER_VAR: 'keep-me', }; @@ -94,7 +94,7 @@ describe('MoonshotSubscriptionTemplate', () => { const result = await hook!.beforeRun!(env, { agent: 'claude' }); expect(result).toBe(env); // exact same reference — no copy created - expect(result.KIMI_MODEL_API_KEY).toBe('some-key'); + expect(result.KIMI_MODEL_API_KEY).toBe('placeholder'); }); it('injects Kimi hooks when agent is kimi', async () => {