diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2f0e3effa..390a01c99 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,11 +8,11 @@ ## Validation -- [ ] `npm run lint` -- [ ] `npm run typecheck` -- [ ] `npm test` +- [ ] `npm run verify` - [ ] `npm test -- test/documentation.test.ts` -- [ ] `npm run build` +- [ ] if triaging failures, validated component gates: `npm run lint`, `npm run verify:repo`, `npm run verify:quality`, `npm run typecheck`, `npm test`, `npm run build` +- [ ] `npm run doctor:dev` (when troubleshooting setup/environment issues) +- [ ] `npm run setup:dev` (for first-clone reproducibility checks) ## Docs and Governance Checklist diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 000000000..c3a0d1243 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,23 @@ +branches: + - name: main + protection: + required_pull_request_reviews: + required_approving_review_count: 1 + require_code_owner_reviews: false + dismiss_stale_reviews: true + required_status_checks: + strict: true + checks: + - context: "CI / Test on Node.js 20.x" + - context: "CI / Test on Node.js 22.x" + - context: "CI / Coverage Gate" + - context: "CI / Lint" + - context: "CI / Codex Compatibility Smoke" + - context: "CI / Cross-Platform Smoke (windows-latest)" + - context: "CI / Cross-Platform Smoke (macos-latest)" + - context: "CodeQL / Analyze" + - context: "Secret Scan / Gitleaks" + - context: "Supply Chain / Dependency Review" + - context: "Supply Chain / SCA and License Gate" + enforce_admins: true + restrictions: null diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3c4f0b99..5e03c3b2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,18 +2,28 @@ name: CI on: push: - branches: [main] + branches: [main, dev] pull_request: - branches: [main] + branches: [main, dev] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: test: - name: Test on Node.js ${{ matrix.node-version }} - runs-on: ubuntu-latest + name: Test on Node.js ${{ matrix.node-version }} (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 30 strategy: + fail-fast: false matrix: node-version: [20.x, 22.x] + os: [ubuntu-latest] + include: + - node-version: 20.x + os: windows-latest steps: - name: Checkout code @@ -23,37 +33,42 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: 'npm' + cache: npm - name: Install dependencies run: npm ci - - name: Repository hygiene check - run: npm run clean:repo:check - - - name: Security audit (CI policy) - run: npm run audit:ci - - - name: Lockfile floor guard - run: npm run test -- test/lockfile-version-floor.test.ts + - name: Run CI verify pipeline + run: npm run verify:ci - name: Security audit (full dependency tree, non-blocking) continue-on-error: true run: npm run audit:all - - name: Run type check - run: npm run typecheck + - name: Generate and verify SBOM + run: | + npm run sbom:generate + npm run sbom:verify - - name: Run tests with coverage - run: npm run coverage + - name: Assert keychain mode storage contract + run: npm run ops:keychain-assert - - name: Build - run: npm run build + - name: Seed enterprise health fixture + run: | + node scripts/seed-health-fixture.js + + - name: Enterprise health check + env: + CODEX_MULTI_AUTH_DIR: ${{ github.workspace }}/.tmp/health-fixture + run: npm run ops:health-check -- --require-files + + - name: Performance budget check + run: npm run perf:budget-check lint: name: Lint - runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout code @@ -63,17 +78,21 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20.x - cache: 'npm' + cache: npm - name: Install dependencies run: npm ci - - name: Run ESLint + - name: Dev doctor sanity check + run: npm run doctor:dev + + - name: Run lint and format checks run: npm run lint codex-compat: name: Codex Compatibility Smoke runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout code @@ -83,10 +102,41 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20.x - cache: 'npm' + cache: npm - name: Install dependencies run: npm ci - name: Run Codex compatibility tests run: npm run test -- test/codex.test.ts test/host-codex-prompt.test.ts test/request-transformer.test.ts test/fetch-helpers.test.ts + + cross-platform-smoke: + name: Cross-Platform Smoke (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run smoke typecheck + run: npm run typecheck + + - name: Build + run: npm run build + + - name: Run smoke tests + run: npm run test -- test/runtime-paths.test.ts test/codex-bin-wrapper.test.ts test/file-lock.test.ts test/background-jobs.test.ts diff --git a/.github/workflows/recovery-drill.yml b/.github/workflows/recovery-drill.yml new file mode 100644 index 000000000..e74ec2905 --- /dev/null +++ b/.github/workflows/recovery-drill.yml @@ -0,0 +1,69 @@ +name: Recovery Drill + +on: + schedule: + - cron: "30 3 1 * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + recovery-drill: + name: Monthly Storage Recovery Drill + runs-on: ubuntu-latest + timeout-minutes: 30 + concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Run recovery drill tests + run: | + mkdir -p .tmp + npm run ops:recovery-drill -- --reporter=default --reporter=json --outputFile=.tmp/recovery-drill-vitest.json + + - name: Run health check snapshot + run: node scripts/enterprise-health-check.js > .tmp/recovery-drill-health.json 2>&1 + + - name: Upload recovery drill artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: recovery-drill-artifacts + path: | + .tmp/recovery-drill-vitest.json + .tmp/recovery-drill-health.json + + - name: Notify recovery drill failure + if: failure() + env: + RECOVERY_DRILL_WEBHOOK_URL: ${{ secrets.RECOVERY_DRILL_WEBHOOK_URL }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + message="Recovery drill failed. Run: ${RUN_URL}. Artifacts: .tmp/recovery-drill-vitest.json and .tmp/recovery-drill-health.json." + if [[ -n "${RECOVERY_DRILL_WEBHOOK_URL:-}" ]]; then + payload=$(jq -n --arg msg "${message}" '{"text": $msg}') + curl --fail --silent --show-error \ + --max-time 30 \ + -X POST \ + -H "Content-Type: application/json" \ + --data "${payload}" \ + "${RECOVERY_DRILL_WEBHOOK_URL}" + else + echo "::warning::${message} Configure secrets.RECOVERY_DRILL_WEBHOOK_URL for push notifications." + fi diff --git a/.github/workflows/release-provenance.yml b/.github/workflows/release-provenance.yml new file mode 100644 index 000000000..abcbed9da --- /dev/null +++ b/.github/workflows/release-provenance.yml @@ -0,0 +1,59 @@ +name: Release Publish (Provenance) + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + publish: + name: Publish with npm provenance + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Validate quality gates + env: + CODEX_MULTI_AUTH_DIR: ${{ github.workspace }}/.tmp/health-fixture + run: | + mkdir -p "${GITHUB_WORKSPACE}/.tmp/health-fixture/logs" + printf '{"version":3,"accounts":[],"activeIndex":0}\n' > "${GITHUB_WORKSPACE}/.tmp/health-fixture/openai-codex-accounts.json" + printf '{"version":1,"pluginConfig":{},"dashboardDisplaySettings":{}}\n' > "${GITHUB_WORKSPACE}/.tmp/health-fixture/settings.json" + printf '{"timestamp":"%s","action":"request.start","outcome":"success"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "${GITHUB_WORKSPACE}/.tmp/health-fixture/logs/audit.log" + npm run audit:ci + npm run ops:health-check -- --require-files + npm run perf:budget-check + npm run lint + npm run typecheck + npm run build + npm test + npm run ops:keychain-assert + npm run sbom:generate + npm run sbom:verify + node scripts/compliance-evidence-bundle.js --profile=quick --out-dir=.tmp/compliance-evidence-release + + - name: Upload release evidence bundle + uses: actions/upload-artifact@v4 + with: + name: release-evidence-bundle + path: .tmp/compliance-evidence-release + + - name: Publish package with provenance + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/retention-maintenance.yml b/.github/workflows/retention-maintenance.yml new file mode 100644 index 000000000..aefa0ad94 --- /dev/null +++ b/.github/workflows/retention-maintenance.yml @@ -0,0 +1,48 @@ +name: Retention Maintenance + +on: + schedule: + - cron: "15 2 * * 0" + workflow_dispatch: + +permissions: + contents: read + +jobs: + retention: + name: Weekly Retention Cleanup Drill + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set retention root + run: echo "CODEX_MULTI_AUTH_DIR=${{ runner.temp }}/codex-retention-root" >> "$GITHUB_ENV" + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Prepare retention fixture + run: | + node -e "const fs=require('fs'); const path=require('path'); const root=process.env.CODEX_MULTI_AUTH_DIR; const logs=path.join(root,'logs','codex-plugin'); const cache=path.join(root,'cache'); const recovery=path.join(root,'recovery'); fs.mkdirSync(logs,{recursive:true}); fs.mkdirSync(cache,{recursive:true}); fs.mkdirSync(recovery,{recursive:true}); const oldFile=path.join(logs,'old-audit.log'); const newFile=path.join(cache,'fresh-cache.json'); fs.writeFileSync(oldFile,'old'); fs.writeFileSync(newFile,'new'); const oldTime=new Date(Date.now()-120*24*60*60*1000); fs.utimesSync(oldFile,oldTime,oldTime);" + + - name: Run retention cleanup + run: | + mkdir -p .tmp + node scripts/retention-cleanup.js --days=90 > .tmp/retention-report.json + + - name: Verify retention fixture cleanup + run: | + node -e "const fs=require('fs'); const path=require('path'); const root=process.env.CODEX_MULTI_AUTH_DIR; const oldFile=path.join(root,'logs','codex-plugin','old-audit.log'); const newFile=path.join(root,'cache','fresh-cache.json'); if(fs.existsSync(oldFile)){console.error('expected old file to be deleted'); process.exit(1);} if(!fs.existsSync(newFile)){console.error('expected fresh file to remain'); process.exit(1);} console.log('retention verification passed');" + + - name: Upload retention report + uses: actions/upload-artifact@v4 + with: + name: retention-maintenance-report + path: .tmp/retention-report.json diff --git a/.github/workflows/sbom-attestation.yml b/.github/workflows/sbom-attestation.yml new file mode 100644 index 000000000..e0db3e129 --- /dev/null +++ b/.github/workflows/sbom-attestation.yml @@ -0,0 +1,48 @@ +name: SBOM and Dependency Attestation + +on: + pull_request: + branches: [main] + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + id-token: write + attestations: write + +jobs: + sbom: + name: Generate SBOM + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Generate SBOM + run: npm run sbom:generate + + - name: Verify SBOM + run: npm run sbom:verify + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom-cyclonedx + path: .tmp/sbom.cdx.json + + - name: Attest SBOM provenance + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/attest-build-provenance@v2 + with: + subject-path: .tmp/sbom.cdx.json diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 000000000..82b9c8fcc --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,41 @@ +name: Secret Scan + +on: + pull_request: + branches: [main, dev] + push: + branches: [main, dev] + schedule: + - cron: "0 5 * * 1" + +permissions: + contents: read + pull-requests: write + +jobs: + gitleaks: + name: Gitleaks + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + fetch-depth: 0 + + - name: Run gitleaks + uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_VERSION: "8.25.0" + GITLEAKS_CONFIG: .gitleaks.toml + + - name: Verify secret-scan policy regression + run: bash scripts/secret-scan-regression.sh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + EXPECTED_GITLEAKS_VERSION: v8.25.0 + GITLEAKS_CONFIG: .gitleaks.toml diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml new file mode 100644 index 000000000..5a461d62a --- /dev/null +++ b/.github/workflows/supply-chain.yml @@ -0,0 +1,81 @@ +name: Supply Chain + +on: + pull_request: + branches: [main] + push: + branches: [main] + schedule: + - cron: "0 4 * * 1" + +env: + CODEX_LICENSE_DENYLIST: "GPL-2.0,GPL-3.0,AGPL-3.0" + +jobs: + dependency-review: + name: Dependency Review + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Dependency review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high + fail-on-scopes: runtime + deny-licenses: ${{ env.CODEX_LICENSE_DENYLIST }} + + sca-and-license: + name: SCA and License Gate + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run vulnerability policy gate + run: npm run audit:ci + + - name: Run license policy gate + run: npm run license:check + + sbom: + name: Generate SBOM + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Generate CycloneDX SBOM + run: npm run sbom + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom-cyclonedx + path: sbom.cdx.json diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 000000000..be20b3bb8 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,19 @@ +title = "codex-multi-auth gitleaks config" + +[extend] +useDefault = true + +[[allowlists]] +description = "Allowlisted fixture/docs synthetic credentials only" +condition = "AND" +paths = [ + '''^test[\\/]security[\\/]fixtures[\\/]''', + '''^docs[\\/]releases[\\/]''', + '''^docs[\\/]development[\\/]DEEP_AUDIT_2026-03-01\.md$''' +] +regexes = [ + '''fake_refresh_token_[0-9]+''', + '''secret-(access|refresh)-token''', + '''top secret prompt''', + '''sk-test-[A-Za-z0-9]{16,}''' +] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc5ebbcd7..387c4435f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,15 +22,21 @@ If a proposal conflicts with OpenAI policy boundaries, it will be declined. ## Local Setup ```bash -npm ci -npm run typecheck -npm run lint -npm test -npm run build +npm run setup:dev +npm run verify ``` Node requirement: `>=18`. +`npm run verify` runs the full local gate: `npm run lint`, `npm run verify:repo`, and +`npm run verify:quality` (`npm run typecheck` + `npm run coverage`, where coverage already +runs `npm run build`). + +Supporting commands: + +- `npm run doctor:dev` for prerequisite and repo-shape checks +- `npm run format` to apply Biome formatting for repo config files + --- ## Development Standards @@ -56,10 +62,16 @@ Documentation requirements for behavior changes: 1. Create a focused branch from `main`. 2. Keep commits atomic and reviewable. 3. Run full local gate: - - `npm run typecheck` - - `npm run lint` - - `npm test` - - `npm run build` + - `npm run verify` + - `npm run test -- test/documentation.test.ts` + - if triaging failures, run component gates directly: + - `npm run lint` + - `npm run verify:repo` + - `npm run verify:quality` + - `npm run typecheck` + - `npm test` + - `npm run build` + - if troubleshooting setup or first-clone reproducibility, run `npm run doctor:dev` and `npm run setup:dev` 4. Include command output evidence in the PR description. 5. Document behavior changes and migration notes when needed. 6. Ensure no secrets or local runtime data are committed. @@ -116,4 +128,4 @@ Unacceptable behavior: ## License -By contributing, you agree contributions are licensed under the project license in [LICENSE](LICENSE). \ No newline at end of file +By contributing, you agree contributions are licensed under the project license in [LICENSE](LICENSE). diff --git a/README.md b/README.md index e254c6a65..a723df249 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,28 @@ codex auth check --- +## Local Development (Contributors) + +From repo root: + +```bash +npm run setup:dev +``` + +Daily validation: + +```bash +npm run doctor:dev +npm run verify +``` + +- `doctor:dev` validates local prerequisites and required project files. +- `setup:dev` runs install plus the local validation gate. +- `verify` is the canonical local and CI gate. +- `format` applies Biome formatting for repo config files (JSON/JSONC/YAML). + +--- + ## Quick Start ```bash @@ -128,6 +150,7 @@ codex auth doctor --fix | `codex auth fix --dry-run` | Preview safe repairs | | `codex auth fix --live --model gpt-5-codex` | Run repairs with live probe model | | `codex auth doctor --fix` | Diagnose and apply safe fixes | +| `codex auth rotate-secrets --json --idempotency-key ` | Re-encrypt stored secrets with safe retry semantics for automation | --- @@ -186,6 +209,7 @@ Selected runtime/environment overrides: | `CODEX_TUI_V2=0/1` | Disable/enable TUI v2 | | `CODEX_TUI_COLOR_PROFILE=truecolor|ansi256|ansi16` | TUI color profile | | `CODEX_TUI_GLYPHS=ascii|unicode|auto` | TUI glyph style | +| `CODEX_SECRET_STORAGE_MODE` | Token-at-rest backend selection: `keychain`, `plaintext`, or `auto` (`keychain` default; set explicit `keychain` in enterprise deployments) | | `CODEX_AUTH_FETCH_TIMEOUT_MS=` | Request timeout override | | `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS=` | Stream stall timeout override | diff --git a/SECURITY.md b/SECURITY.md index 7d7068856..4fff02e27 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -84,6 +84,10 @@ Before release and after dependency changes: ```bash npm run audit:ci +npm run ops:health-check +npm run perf:budget-check +npm run sbom:generate +npm run sbom:verify npm run lint npm run typecheck npm test diff --git a/biome.jsonc b/biome.jsonc index 71ef6c65e..f670f6576 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -6,7 +6,16 @@ "useIgnoreFile": true }, "files": { - "includes": ["**", "!!**/dist"] + "includes": [ + "package.json", + "biome.jsonc", + ".github/**/*.yml", + ".github/**/*.yaml", + "!!dist/**", + "!!coverage/**", + "!!node_modules/**", + "!!vendor/**" + ] }, "formatter": { "enabled": true, diff --git a/config/performance-budgets.json b/config/performance-budgets.json new file mode 100644 index 000000000..c6a81a6ce --- /dev/null +++ b/config/performance-budgets.json @@ -0,0 +1,9 @@ +{ + "filterInput_small": 2.0, + "filterInput_large": 10.0, + "cleanupToolDefinitions_medium": 10.0, + "cleanupToolDefinitions_large": 20.0, + "accountHybridSelection_200": 30.0, + "resolveRequestAccountId_1000": 3.0, + "normalizeAccountStorage_240": 20.0 +} diff --git a/config/performance-budgets.schema.json b/config/performance-budgets.schema.json new file mode 100644 index 000000000..c2e13b822 --- /dev/null +++ b/config/performance-budgets.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Runtime Performance Budgets", + "description": "Performance budget thresholds for runtime path benchmarks. All values are milliseconds.", + "type": "object", + "additionalProperties": false, + "required": [ + "filterInput_small", + "filterInput_large", + "cleanupToolDefinitions_medium", + "cleanupToolDefinitions_large", + "accountHybridSelection_200", + "resolveRequestAccountId_1000", + "normalizeAccountStorage_240" + ], + "properties": { + "filterInput_small": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for small filterInput benchmark." + }, + "filterInput_large": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for large filterInput benchmark." + }, + "cleanupToolDefinitions_medium": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for medium cleanupToolDefinitions benchmark." + }, + "cleanupToolDefinitions_large": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for large cleanupToolDefinitions benchmark." + }, + "accountHybridSelection_200": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for accountHybridSelection benchmark with 200 accounts." + }, + "resolveRequestAccountId_1000": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for resolveRequestAccountId benchmark with 1000 accounts." + }, + "normalizeAccountStorage_240": { + "type": "number", + "minimum": 0, + "description": "Maximum average runtime in milliseconds for normalizeAccountStorage benchmark with 240 accounts." + } + } +} diff --git a/config/slo-policy.json b/config/slo-policy.json new file mode 100644 index 000000000..adaba5367 --- /dev/null +++ b/config/slo-policy.json @@ -0,0 +1,8 @@ +{ + "windowDays": 30, + "objectives": { + "requestSuccessRatePercent": 99.5, + "healthCheckPassRequired": true, + "staleWalFindingsMax": 0 + } +} diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 8e8b0edcb..8da83a02e 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -39,6 +39,8 @@ Canonical governance for repository documentation quality and consistency. | IA/findability audit (2026-03-01) | `docs/development/IA_FINDABILITY_AUDIT_2026-03-01.md` | | Config fields internals | `docs/development/CONFIG_FIELDS.md` | | Config flow internals | `docs/development/CONFIG_FLOW.md` | +| Local development runbook | `docs/development/LOCAL_DEV.md` | +| Release runbook | `docs/development/RELEASE_RUNBOOK.md` | | Repository ownership map | `docs/development/REPOSITORY_SCOPE.md` | | Testing and release gates | `docs/development/TESTING.md` | | TUI parity checklist | `docs/development/TUI_PARITY_CHECKLIST.md` | diff --git a/docs/README.md b/docs/README.md index 2accdd99f..2f492c4df 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,9 +56,17 @@ Canonical documentation map for `codex-multi-auth`. | [development/IA_FINDABILITY_AUDIT_2026-03-01.md](development/IA_FINDABILITY_AUDIT_2026-03-01.md) | IA/findability baseline, mismatches, and migration plan | | [development/CONFIG_FIELDS.md](development/CONFIG_FIELDS.md) | Complete field and env inventory | | [development/CONFIG_FLOW.md](development/CONFIG_FLOW.md) | Configuration resolution flow | +| [development/LOCAL_DEV.md](development/LOCAL_DEV.md) | Local contributor setup and validation runbook | +| [development/RELEASE_RUNBOOK.md](development/RELEASE_RUNBOOK.md) | Maintainer release gate and checklist | | [development/REPOSITORY_SCOPE.md](development/REPOSITORY_SCOPE.md) | Ownership map by repository path | | [development/TESTING.md](development/TESTING.md) | Validation gates and test matrix | | [development/TUI_PARITY_CHECKLIST.md](development/TUI_PARITY_CHECKLIST.md) | Dashboard UX parity checklist | +| [runbooks/README.md](runbooks/README.md) | Operations and incident response playbooks | +| [operations/incident-response.md](operations/incident-response.md) | Incident triage, containment, and recovery | +| [operations/incident-drill-template.md](operations/incident-drill-template.md) | Monthly tabletop incident drill worksheet | +| [operations/release-runbook.md](operations/release-runbook.md) | Release governance, provenance, and rollback | +| [operations/slo-error-budget.md](operations/slo-error-budget.md) | Reliability objectives and budget policy | +| [operations/audit-forwarding.md](operations/audit-forwarding.md) | SIEM forwarding controls for audit events | | [benchmarks/code-edit-format-benchmark.md](benchmarks/code-edit-format-benchmark.md) | Benchmark methodology and outputs | --- diff --git a/docs/configuration.md b/docs/configuration.md index 172296c74..2870d6840 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -66,8 +66,15 @@ These are safe for most operators and frequently used in day-to-day workflows. | `CODEX_TUI_V2=0/1` | Disable or enable TUI v2 | | `CODEX_TUI_COLOR_PROFILE=truecolor|ansi256|ansi16` | Color profile selection | | `CODEX_TUI_GLYPHS=ascii|unicode|auto` | Glyph mode selection | +| `CODEX_SECRET_STORAGE_MODE` | Secret-at-rest backend mode: `keychain`, `plaintext`, or `auto` (`keychain` default; enterprise profile should pin `keychain`) | | `CODEX_AUTH_FETCH_TIMEOUT_MS=` | HTTP request timeout override | | `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS=` | Stream stall timeout override | +| `CODEX_AUTH_ENCRYPTION_KEY=<32-byte-random-key>` | Enable at-rest encryption for stored account secrets (high-entropy key material only) | +| `CODEX_AUTH_PREVIOUS_ENCRYPTION_KEY=<32-byte-random-key>` | Previous high-entropy key used during staged secret rotation | +| `CODEX_AUTH_ROLE=admin\|operator\|viewer` | CLI authorization role baseline | + +For `CODEX_AUTH_ENCRYPTION_KEY` and `CODEX_AUTH_PREVIOUS_ENCRYPTION_KEY`, use 32-byte +random key material from a secret manager. Do not use user-memorable passwords. --- @@ -81,6 +88,14 @@ Use these only when debugging, controlled benchmarking, or maintainer workflows. - `CODEX_CLI_ACCOUNTS_PATH` - `CODEX_CLI_AUTH_PATH` - refresh lease tuning variables (`CODEX_AUTH_REFRESH_LEASE*`) +- `CODEX_AUTH_BREAK_GLASS` +- `CODEX_AUTH_ABAC_READ_ONLY` +- `CODEX_AUTH_ABAC_DENY_ACTIONS` +- `CODEX_AUTH_ABAC_DENY_COMMANDS` +- `CODEX_AUTH_ABAC_REQUIRE_INTERACTIVE` +- `CODEX_AUTH_ABAC_REQUIRE_IDEMPOTENCY_KEY` +- `CODEX_AUTH_REDACT_JSON_OUTPUT` +- retention tuning variables (`CODEX_AUTH_RETENTION_*`) Full inventory: [development/CONFIG_FIELDS.md](development/CONFIG_FIELDS.md) diff --git a/docs/development/CONFIG_FIELDS.md b/docs/development/CONFIG_FIELDS.md index 9a3ee4cf3..972c8681e 100644 --- a/docs/development/CONFIG_FIELDS.md +++ b/docs/development/CONFIG_FIELDS.md @@ -62,6 +62,7 @@ Used only for host plugin mode through the host runtime config file. | `retryAllAccountsRateLimited` | `true` | | `retryAllAccountsMaxWaitMs` | `0` | | `retryAllAccountsMaxRetries` | `Infinity` | +| `retryAllAccountsAbsoluteCeilingMs` | `0 ms (0–24h; 0 = unlimited)` | | `unsupportedCodexPolicy` | `strict` | | `fallbackOnUnsupportedCodexModel` | `false` | | `fallbackToGpt52OnUnsupportedGpt53` | `true` | @@ -193,8 +194,24 @@ Used only for host plugin mode through the host runtime config file. | `CODEX_TUI_V2` | Toggle TUI v2 | | `CODEX_TUI_COLOR_PROFILE` | TUI color profile | | `CODEX_TUI_GLYPHS` | TUI glyph mode | +| `CODEX_AUTH_RETRY_ALL_ABSOLUTE_CEILING_MS` | Absolute wait ceiling in ms for retry-all-on-rate-limit loop (`0–24h`, `0 = unlimited`) | | `CODEX_AUTH_FETCH_TIMEOUT_MS` | Request timeout override | | `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` | Stream stall timeout override | +| `CODEX_AUTH_ENCRYPTION_KEY` | Primary high-entropy 32-byte key for at-rest secret encryption | +| `CODEX_AUTH_PREVIOUS_ENCRYPTION_KEY` | Previous high-entropy 32-byte key for staged secret rotation | +| `CODEX_AUTH_ROLE` | Authorization role baseline (`admin`, `operator`, `viewer`) | +| `CODEX_AUTH_BREAK_GLASS` | Emergency authorization bypass toggle | +| `CODEX_AUTH_ABAC_READ_ONLY` | Deny mutating actions while allowing read-only command paths | +| `CODEX_AUTH_ABAC_DENY_ACTIONS` | Comma-separated action denies (`accounts:write`, etc.) | +| `CODEX_AUTH_ABAC_DENY_COMMANDS` | Comma-separated command denies (`rotate-secrets`, etc.) | +| `CODEX_AUTH_ABAC_REQUIRE_INTERACTIVE` | Comma-separated actions that require interactive terminal | +| `CODEX_AUTH_ABAC_REQUIRE_IDEMPOTENCY_KEY` | Comma-separated actions that require idempotency key context | +| `CODEX_AUTH_REDACT_JSON_OUTPUT` | Redact sensitive values in JSON command output | +| `CODEX_AUTH_RETENTION_LOG_DAYS` | Log retention window | +| `CODEX_AUTH_RETENTION_CACHE_DAYS` | Cache retention window | +| `CODEX_AUTH_RETENTION_FLAGGED_DAYS` | Flagged-account file retention window | +| `CODEX_AUTH_RETENTION_QUOTA_CACHE_DAYS` | Quota cache retention window | +| `CODEX_AUTH_RETENTION_DLQ_DAYS` | Dead-letter queue retention window | | `CODEX_MULTI_AUTH_SYNC_CODEX_CLI` | Toggle Codex CLI state sync | | `CODEX_MULTI_AUTH_REAL_CODEX_BIN` | Force official Codex binary path | | `CODEX_MULTI_AUTH_BYPASS` | Bypass local auth handling | diff --git a/docs/development/LOCAL_DEV.md b/docs/development/LOCAL_DEV.md new file mode 100644 index 000000000..47603d909 --- /dev/null +++ b/docs/development/LOCAL_DEV.md @@ -0,0 +1,85 @@ +# Local Development Runbook + +Canonical contributor workflow for setting up and validating this repository. + +--- + +## Prerequisites + +- Node.js `>=18` +- npm available in `PATH` +- git available in `PATH` + +Verify environment: + +```bash +npm run doctor:dev +``` + +--- + +## First Clone + +From repo root: + +```bash +npm run setup:dev +``` + +`setup:dev` runs: + +1. environment checks (`doctor:dev`) +2. dependency install (`npm ci`) +3. validation gate (`npm run verify`) +4. docs integrity smoke (`npm test -- test/documentation.test.ts`) + +--- + +## Daily Development + +```bash +npm run verify +``` + +Use component commands when debugging failures: + +```bash +npm run lint +npm run typecheck +npm test +npm run build +``` + +Format repo config files (JSON/JSONC/YAML): + +```bash +npm run format +``` + +--- + +## Common Failure Modes + +- `doctor:dev` fails on missing npm/git: + - ensure shell `PATH` includes Node.js and git executables +- `verify` fails on audit policy: + - run `npm run audit:ci` to inspect blocking advisory output +- `setup:dev`/`doctor:dev` on Windows fails with transient `EBUSY`/`EPERM` lock errors: + - retry `npm ci` first (transient antivirus/file contention is common) + - if it persists, pause antivirus or exclude the repository, then re-run in an elevated PowerShell/CMD session + - if state looks corrupted, run `git clean -fdx` then run `npm ci` again + - if lock contention still stalls installs, try `npm ci --no-audit` or run setup from WSL2 +- `test/documentation.test.ts` fails with missing `dist/lib/*.js`: + - run `npm run build` and re-run the docs test + +--- + +## CI Parity + +CI uses `npm run verify:ci` for the matrix test gate. + +Local equivalent: + +```bash +npm run verify:ci +``` diff --git a/docs/development/RELEASE_RUNBOOK.md b/docs/development/RELEASE_RUNBOOK.md new file mode 100644 index 000000000..30a512b99 --- /dev/null +++ b/docs/development/RELEASE_RUNBOOK.md @@ -0,0 +1,57 @@ +# Release Runbook + +Maintainer checklist for preparing a reliable release from `main`. + +--- + +## Preconditions + +1. Release PR merged to `main` +2. CI checks green on latest `main` +3. Working tree clean + +--- + +## Validation Gate + +Run from repository root: + +```bash +npm run release:check +``` + +This command runs: + +1. `npm run verify` +2. `npm run test -- test/documentation.test.ts` +3. `npm pack --dry-run` + +--- + +## Documentation Gate + +Before publishing/tagging: + +1. update `CHANGELOG.md` +2. add or update matching release note in `docs/releases/` +3. verify docs links in `README.md` and `docs/README.md` point to the latest stable release note + +--- + +## Publish/Tag Flow + +1. bump version in `package.json` and lockfile as needed +2. commit release metadata +3. create signed/annotated git tag +4. push commit and tag +5. verify package metadata and release notes in GitHub + +--- + +## Rollback + +If release validation fails after version bump: + +1. revert release commit on branch +2. re-run `npm run release:check` +3. open a corrective PR with failure evidence diff --git a/docs/development/TESTING.md b/docs/development/TESTING.md index 9292b9065..54ab97845 100644 --- a/docs/development/TESTING.md +++ b/docs/development/TESTING.md @@ -19,11 +19,22 @@ Coverage thresholds in `vitest.config.ts`: statements/branches/functions/lines > ## Core Commands +```bash +npm run setup:dev +npm run doctor:dev +npm run verify +npm run verify:ci +``` + +Component commands: + ```bash npm run typecheck npm run lint npm test npm run build +npm run audit:ci +npm run license:check ``` Optional: @@ -39,11 +50,30 @@ npm run bench:edit-formats:smoke ## Recommended Local Gate Before PR -1. `npm run typecheck` -2. `npm run lint` -3. `npm test` -4. `npm run build` +1. `npm run verify` +2. `npm run audit:ci` +3. `npm run license:check` +4. `npm run test -- test/documentation.test.ts` 5. run docs command checks for newly documented command paths +6. for first-clone setup or environment validation: `npm run setup:dev` and `npm run doctor:dev` + +## Release Gate + +Run before version bump, tag, or publish workflow: + +1. `npm run release:check` +2. `npm run audit:ci` +3. `npm run license:check` +4. verify changelog and release notes alignment +5. confirm PR checks are green on the release branch + +### Upgrade Notes (PR #32) + +- Gate ordering was updated so `npm run coverage` runs before `npm run build`. +- Two required supply-chain checks were added to the standard local sequence: + - `npm run audit:ci` + - `npm run license:check` +- If you maintain local CI wrappers or pre-push scripts, update them to use the order above and rerun once to refresh baselines. * * * diff --git a/docs/index.md b/docs/index.md index 9a59b2d86..ecc3dbf0a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,4 +46,5 @@ Legacy package/path guidance is documented in [upgrade.md](upgrade.md) and [refe - Command flags and hotkeys: [reference/commands.md](reference/commands.md) - Settings and overrides: [reference/settings.md](reference/settings.md) - Storage path matrix: [reference/storage-paths.md](reference/storage-paths.md) -- Full docs portal: [README.md](README.md) \ No newline at end of file +- Operations runbooks: [runbooks/README.md](runbooks/README.md) +- Full docs portal: [README.md](README.md) diff --git a/docs/operations/audit-forwarding.md b/docs/operations/audit-forwarding.md new file mode 100644 index 000000000..1a78e235b --- /dev/null +++ b/docs/operations/audit-forwarding.md @@ -0,0 +1,79 @@ +# Audit Forwarding + +Forward local audit logs to a central SIEM endpoint. + +--- + +## Purpose + +- Export append-only audit events from local log files. +- Maintain checkpointed delivery (`audit-forwarder-checkpoint.json`) to avoid duplicate sends. +- Support dry-run validation before production rollout. + +--- + +## Required Configuration + +- `CODEX_SIEM_ENDPOINT` (HTTPS ingestion endpoint) +- `CODEX_SIEM_API_KEY` (bearer token; required when the SIEM endpoint enforces authentication) +- `CODEX_MULTI_AUTH_DIR` (optional runtime root override) + +--- + +## Commands + +Dry run: + +```bash +npm run ops:audit-forwarder -- --dry-run +``` + +Send batch: + +```bash +npm run ops:audit-forwarder -- --batch-size=500 +``` + +Explicit endpoint: + +```bash +node scripts/audit-log-forwarder.js --endpoint=https://siem.example.com/ingest --batch-size=500 +``` + +--- + +## Delivery Contract + +Payload fields: + +- `source` +- `generatedAt` +- `count` +- `checksum` (SHA-256 over event payload) +- `entries` (JSON audit entries) + +Checkpoint fields: + +- `file` +- `line` +- `updatedAt` + +### Failure & Retry Behavior + +- Export delivery retries on HTTP `429` or `5xx`, plus timeout/network failures. +- Retry count and timeout are configurable: + - `CODEX_AUDIT_FORWARDER_MAX_ATTEMPTS` (default `3`) + - `CODEX_AUDIT_FORWARDER_TIMEOUT_MS` (default `15000`) +- Backoff is exponential with jitter (`250ms * 2^attempt + random(0..99ms)`). +- Non-retryable responses and terminal retry failures stop the run and return non-zero. +- Checkpoints are written only after a successful send batch. Failed sends keep the prior checkpoint (`file`, `line`, `updatedAt`) so operators can re-run safely. + +--- + +## Alerting Recommendations + +Configure SIEM alerts for: + +1. `request.failure` spikes above baseline. +2. auth failures crossing incident threshold. +3. stale WAL detection events from scheduled health checks. diff --git a/docs/operations/incident-drill-template.md b/docs/operations/incident-drill-template.md new file mode 100644 index 000000000..827f9c891 --- /dev/null +++ b/docs/operations/incident-drill-template.md @@ -0,0 +1,81 @@ +# Incident Drill Template + +Use this template for monthly incident-response tabletop drills. + +--- + +## Drill Metadata + +- Drill date (UTC): +- Facilitator: +- Participants: +- Scenario ID: +- Related runbook version: + +--- + +## Scenario Setup + +1. Trigger condition: +2. Initial symptoms: +3. Assumed blast radius: +4. Detection source: + +--- + +## Timeline (UTC) + +| Timestamp | Event | Owner | +| --- | --- | --- | +| | | | +| | | | +| | | | + +--- + +## Required Command Evidence + +```bash +npm run ops:health-check +codex auth report --live --json +codex auth doctor --json +``` + +```powershell +npm run ops:health-check +codex auth report --live --json +codex auth doctor --json +``` + +Attach: + +- command outputs +- branch and commit SHA +- incident severity classification + +--- + +## Decision Log + +| Decision | Reason | Approver | +| --- | --- | --- | +| | | | +| | | | + +--- + +## Exit Criteria Review + +- [ ] health check returned `status: "pass"` (verify JSON `status` field) +- [ ] no unresolved `SEV-1` conditions +- [ ] rollback decision documented (if applicable) +- [ ] prevention tasks created with owners and due dates + +--- + +## Follow-ups + +| Action | Owner | Due date | +| --- | --- | --- | +| | | | +| | | | diff --git a/docs/operations/incident-response.md b/docs/operations/incident-response.md new file mode 100644 index 000000000..0d731103f --- /dev/null +++ b/docs/operations/incident-response.md @@ -0,0 +1,86 @@ +# Incident Response Runbook + +Operational incident workflow for `codex-multi-auth` deployments in enterprise environments. + +--- + +## Severity Model + +| Severity | Definition | Initial response | +| --- | --- | --- | +| `SEV-1` | Auth/token failures causing broad outage or data exposure risk | Acknowledge within 15 minutes | +| `SEV-2` | Partial degradation (intermittent auth, persistent retries, stale WAL) | Acknowledge within 30 minutes | +| `SEV-3` | Non-critical defects with workaround available | Acknowledge within 1 business day | + +--- + +## Detection Commands + +```bash +npm run ops:health-check +codex auth report --live --json +codex auth doctor --json +``` + +Required evidence: + +- health-check JSON output +- `codex auth report --live --json` +- `codex auth doctor --json` +- current commit SHA and branch + +--- + +## First 30 Minutes + +1. Run `npm run ops:health-check` and capture output. +2. If status is `fail`, block release or rollback active release candidate. +3. If stale WAL is reported, run `codex auth doctor --fix --dry-run` first, then `codex auth doctor --fix`. +4. If auth failures persist, rotate account via `codex auth switch ` and re-run `codex auth check`. +5. If all accounts are exhausted/disabled, escalate immediately to `SEV-1`, stop automated retries, and switch to fallback credentials via incident commander approval. +6. Record timeline with absolute UTC timestamps. + +Windows operator note: + +- Default path is `%USERPROFILE%\\.codex\\multi-auth`; if `CODEX_HOME` is set, use `%CODEX_HOME%\\multi-auth`. +- When deleting WAL artifacts manually, close shells/editors first to avoid `EPERM`/`EBUSY` locks. + +--- + +## Containment and Recovery + +1. Disable debug body logging unless actively diagnosing: + - ensure `CODEX_PLUGIN_LOG_BODIES` is unset +2. Run containment commands serially (do not run concurrently): + - `npm run ops:retention-cleanup` +3. Re-run verification pack: + - `npm run ops:health-check` + - `npm run audit:ci` + - `npm run test -- test/storage.test.ts test/fetch-helpers.test.ts` + +Recovery exit criteria: + +- `ops:health-check` status is `pass` +- no unresolved `SEV-1` findings +- CI checks green on remediation branch + +--- + +## Post-Incident + +1. Publish root-cause analysis with: + - trigger + - blast radius + - remediation commit SHA + - prevention tasks with owners and due dates +2. Add/adjust regression tests in `test/` for the failure mode. +3. Update this runbook if manual steps were required. + +--- + +## Drill Cadence + +- Run a tabletop drill monthly. +- Run `npm run ops:recovery-drill` as the drill execution command and archive outputs. +- Use [incident-drill-template.md](incident-drill-template.md) for drill evidence. +- Track unresolved drill actions as release blockers when severity is `SEV-1` equivalent. diff --git a/docs/operations/release-runbook.md b/docs/operations/release-runbook.md new file mode 100644 index 000000000..7562620c1 --- /dev/null +++ b/docs/operations/release-runbook.md @@ -0,0 +1,82 @@ +# Release and Rollback Runbook + +Release governance for `codex-multi-auth` with provenance and rollback controls. + +--- + +## Preconditions + +1. Branch is up to date with `main`. +2. Required checks pass: + - `npm run lint` + - `npm run typecheck` + - `npm test` + - `npm run build` + - `npm run audit:ci` + - `npm run perf:budget-check` +3. `secret-scan` workflow is green. + +--- + +## Release Procedure + +1. Create release tag from validated commit. +2. Publish GitHub release. +3. Trigger workflow: + - `.github/workflows/release-provenance.yml` + - `.github/workflows/sbom-attestation.yml` +4. Validate published package integrity: + - `npm view codex-multi-auth version` + - verify provenance is attached to the publish event. +5. Capture compliance evidence bundle: + - `node scripts/compliance-evidence-bundle.js --profile=release --out-dir=.tmp/compliance-evidence-release` + +Required release record: + +- release tag +- commit SHA +- workflow run URL +- test evidence timestamp +- SBOM artifact reference +- compliance evidence bundle path + +--- + +## Rollback Procedure + +Use rollback when `SEV-1` or unmitigated `SEV-2` occurs after release. + +1. Stop further publishing. +2. Re-point consumers to previous known-good tag. +3. Open hotfix branch from previous stable SHA. +4. Re-run mandatory checks and republish fixed patch. + +Rollback verification: + +```bash +npm run ops:health-check +npm run audit:ci +npm run test -- test/storage.test.ts test/codex-manager-cli.test.ts +``` + +Rollback is complete only when: + +- verification commands pass +- issue reproduction no longer occurs +- release notes include rollback details + +--- + +## Retention and Cleanup + +Run scheduled cleanup at least weekly: + +```bash +npm run ops:retention-cleanup +``` + +Default retention is 90 days. Override for emergency cleanup: + +```bash +npm run ops:retention-cleanup -- --days=30 +``` diff --git a/docs/operations/slo-error-budget.md b/docs/operations/slo-error-budget.md new file mode 100644 index 000000000..411a3e252 --- /dev/null +++ b/docs/operations/slo-error-budget.md @@ -0,0 +1,58 @@ +# SLO and Error Budget Policy + +Reliability policy for enterprise operation of `codex-multi-auth`. + +--- + +## Measurement Window + +- Rolling window: 30 days +- Data source: + - audit logs (`request.success`, `request.failure`) + - `ops:health-check` findings +- Policy file: `config/slo-policy.json` + +--- + +## SLO Objectives + +| Objective | Target | +| --- | --- | +| Request success rate | `>= 99.5%` | +| Health-check status | `pass` | +| Stale WAL findings | `0` | + +--- + +## Error Budget + +- Request error budget: `0.5%` per 30-day window. +- Budget burn: + - `100 - requestSuccessRatePercent` +- Trigger thresholds: + - `>= 50%` burn: freeze non-critical feature work for reliability review. + - `>= 100%` burn: incident review required before next release. + +--- + +## Reporting + +Generate report: + +```bash +npm run ops:slo-report +``` + +Enforce gate (non-zero exit on violations): + +```bash +node scripts/slo-budget-report.js --enforce --output=.tmp/slo-report.json +``` + +--- + +## Governance + +1. Review SLO report weekly. +2. Review error budget during release readiness. +3. If budget is exhausted, require remediation plan and owner sign-off. diff --git a/docs/privacy.md b/docs/privacy.md index 4fa153420..ed867f4fe 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -9,6 +9,8 @@ - No custom analytics pipeline in this repository. - No project-owned remote database. - Network calls are limited to required OAuth/backend/update endpoints. +- Local structured telemetry is written to `~/.codex/multi-auth/logs/product-telemetry.jsonl`. +- Set `CODEX_AUTH_TELEMETRY_ENABLED=0` (or `telemetryEnabled: false`) to disable local telemetry writes. --- @@ -20,7 +22,9 @@ | Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` | Primary saved account pool | | Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` | Accounts with hard auth failures | | Quota cache | `~/.codex/multi-auth/quota-cache.json` | Cached quota snapshots | +| Background DLQ | `~/.codex/multi-auth/background-job-dlq.jsonl` | Failed background jobs after retry exhaustion | | Logs | `~/.codex/multi-auth/logs/codex-plugin/` | Optional diagnostics | +| Local telemetry ledger | `~/.codex/multi-auth/logs/product-telemetry.jsonl` | Local event timeline for CLI/plugin outcomes | | Prompt/cache files | `~/.codex/multi-auth/cache/` | Cached prompt/template metadata | | Codex CLI state | `~/.codex/accounts.json`, `~/.codex/auth.json` | Official Codex CLI files | @@ -48,6 +52,50 @@ Current external destinations: Raw body logs may contain sensitive payload text. Treat logs as sensitive data and rotate/delete as needed. +`CODEX_AUTH_REDACT_JSON_OUTPUT=1` redacts sensitive values from JSON command output for automation logs. + +--- + +## Secret Encryption and Rotation + +- Account refresh/access tokens can be encrypted at rest when `CODEX_AUTH_ENCRYPTION_KEY` is set. +- Key rotation supports staged migration with `CODEX_AUTH_PREVIOUS_ENCRYPTION_KEY`. +- Both key variables should be 32-byte high-entropy key material (not passwords). +- Rotation command: + +```bash +codex auth rotate-secrets --json +``` + +Store encryption keys in a secret manager or CI secret store, not in repository files. + +--- + +## Retention + +Startup retention cleanup removes expired local artifacts based on: + +- `CODEX_AUTH_RETENTION_LOG_DAYS` +- `CODEX_AUTH_RETENTION_CACHE_DAYS` +- `CODEX_AUTH_RETENTION_FLAGGED_DAYS` +- `CODEX_AUTH_RETENTION_QUOTA_CACHE_DAYS` +- `CODEX_AUTH_RETENTION_DLQ_DAYS` + +Retention control: + +```bash +npm run ops:retention-cleanup +npm run ops:retention-cleanup -- --days=30 +``` + +Default retention window is 90 days. + +Audit forwarding (for central SIEM ingestion): + +```bash +npm run ops:audit-forwarder -- --dry-run +``` + --- ## Data Cleanup @@ -60,6 +108,7 @@ rm -f ~/.codex/multi-auth/openai-codex-accounts.json rm -f ~/.codex/multi-auth/openai-codex-flagged-accounts.json rm -f ~/.codex/multi-auth/quota-cache.json rm -rf ~/.codex/multi-auth/logs/codex-plugin +rm -f ~/.codex/multi-auth/logs/product-telemetry.jsonl* rm -rf ~/.codex/multi-auth/cache # Override-root cleanup examples (if overrides are set): [ -n "${CODEX_MULTI_AUTH_DIR:-}" ] && [ -d "$CODEX_MULTI_AUTH_DIR/logs/codex-plugin" ] && rm -rf "$CODEX_MULTI_AUTH_DIR/logs/codex-plugin" @@ -74,6 +123,7 @@ Remove-Item "$HOME\.codex\multi-auth\openai-codex-accounts.json" -Force -ErrorAc Remove-Item "$HOME\.codex\multi-auth\openai-codex-flagged-accounts.json" -Force -ErrorAction SilentlyContinue Remove-Item "$HOME\.codex\multi-auth\quota-cache.json" -Force -ErrorAction SilentlyContinue Remove-Item "$HOME\.codex\multi-auth\logs\codex-plugin" -Recurse -Force -ErrorAction SilentlyContinue +Remove-Item "$HOME\.codex\multi-auth\logs\product-telemetry.jsonl*" -Force -ErrorAction SilentlyContinue Remove-Item "$HOME\.codex\multi-auth\cache" -Recurse -Force -ErrorAction SilentlyContinue # Override-root cleanup examples (if overrides are set): if ($env:CODEX_MULTI_AUTH_DIR) { Remove-Item "$env:CODEX_MULTI_AUTH_DIR\\*" -Recurse -Force -ErrorAction SilentlyContinue } diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 43c877fae..ef74196a2 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -38,6 +38,8 @@ Compatibility aliases are supported: | `codex auth report` | Generate full health report | | `codex auth fix` | Apply safe account storage fixes | | `codex auth doctor` | Run diagnostics and optional repairs | +| `codex auth rotate-secrets` | Re-encrypt account secrets using current encryption key | +| `codex auth telemetry` | Show local telemetry summary and recent events | --- @@ -45,13 +47,18 @@ Compatibility aliases are supported: | Flag | Applies to | Meaning | | --- | --- | --- | -| `--json` | verify-flagged, forecast, report, fix, doctor | Print machine-readable output | +| `--json` | verify-flagged, forecast, report, fix, doctor, rotate-secrets, telemetry | Print machine-readable output | | `--live` | forecast, report, fix | Use live probe before decisions/output | | `--dry-run` | verify-flagged, fix, doctor | Preview without writing storage | | `--model ` | forecast, report, fix | Specify model for live probe paths | | `--out ` | report | Write report output to file | | `--fix` | doctor | Apply safe repairs | | `--no-restore` | verify-flagged | Verify only; do not restore healthy flagged accounts | +| `--page-size ` | list, status (`--json`) | Page size for JSON list output (1-200) | +| `--cursor ` | list, status (`--json`) | Cursor token for JSON list pagination | +| `--idempotency-key ` | rotate-secrets | Safe retry key for automation | +| `--since-hours ` | telemetry | Include events newer than this many hours | +| `--limit ` | telemetry | Max events to include in report | --- @@ -108,6 +115,7 @@ Repair and recovery: codex auth fix --dry-run codex auth fix --live --model gpt-5-codex codex auth doctor --fix +codex auth rotate-secrets --json --idempotency-key "$CI_JOB_ID" ``` --- diff --git a/docs/reference/error-contracts.md b/docs/reference/error-contracts.md index 62694d4f6..2196f1a7c 100644 --- a/docs/reference/error-contracts.md +++ b/docs/reference/error-contracts.md @@ -31,17 +31,24 @@ Examples: The following commands support `--json` and produce pretty-printed JSON objects: +- `codex auth list --json` - `codex auth forecast --json` - `codex auth report --json` - `codex auth fix --json` - `codex auth doctor --json` - `codex auth verify-flagged --json` +- `codex auth rotate-secrets --json` Compatibility guarantees: - Output is valid JSON. - `command` field identifies the command family. +- `schemaVersion` is required for machine-consumable contracts. - Documented top-level sections remain stable unless a migration note is provided. +- Optional redaction mode (`CODEX_AUTH_REDACT_JSON_OUTPUT=1`) masks sensitive fields without changing schema shape. +- Paginated list output uses `pagination.{cursor,nextCursor,hasMore,pageSize}`. + +For `rotate-secrets`, automation may provide `--idempotency-key ` to avoid duplicate side effects on retried runs. --- diff --git a/docs/reference/public-api.md b/docs/reference/public-api.md index 865189ff9..d916a379c 100644 --- a/docs/reference/public-api.md +++ b/docs/reference/public-api.md @@ -63,6 +63,24 @@ Positional signatures are preserved for backward compatibility. --- +## API Standards Baseline + +Where this repository exposes machine-readable command output or future HTTP endpoints, use these defaults: + +- Versioning: + - include `schemaVersion` in JSON command output. + - increment schema version only when contract shape changes. +- Idempotency: + - mutating automation flows should support caller-provided idempotency keys. + - repeated requests with the same idempotency key should not duplicate side effects. +- Pagination: + - list-style payloads should prefer cursor-based pagination (`nextCursor`, `hasMore`) over offset-only paging. + - response envelopes should include stable paging metadata even for empty result sets. + +This project currently applies the versioning baseline to JSON command outputs and documents idempotency/pagination standards for future API expansion. + +--- + ## Semver Guidance - Breaking Tier A change: `MAJOR` diff --git a/docs/reference/settings.md b/docs/reference/settings.md index 1466374b9..d91b8b257 100644 --- a/docs/reference/settings.md +++ b/docs/reference/settings.md @@ -74,6 +74,7 @@ Examples: - `sessionAffinityTtlMs` - `sessionAffinityMaxEntries` - `perProjectAccounts` +- `telemetryEnabled` ### Rotation and Quota @@ -86,6 +87,8 @@ Examples: - `retryAllAccountsRateLimited` - `retryAllAccountsMaxWaitMs` - `retryAllAccountsMaxRetries` +- `retryAllAccountsAbsoluteCeilingMs` + Unit: milliseconds. Bounds: `0` to `24h`. `0` means unlimited. ### Refresh and Recovery @@ -126,8 +129,22 @@ Common operator overrides: - `CODEX_TUI_V2` - `CODEX_TUI_COLOR_PROFILE` - `CODEX_TUI_GLYPHS` +- `CODEX_SECRET_STORAGE_MODE` +- `CODEX_AUTH_RETRY_ALL_ABSOLUTE_CEILING_MS` + Rotation & Quota override for `retryAllAccountsAbsoluteCeilingMs` (ms, `0` to `24h`, `0` = unlimited). - `CODEX_AUTH_FETCH_TIMEOUT_MS` - `CODEX_AUTH_STREAM_STALL_TIMEOUT_MS` +- `CODEX_AUTH_TELEMETRY_ENABLED` +- `CODEX_AUTH_ENCRYPTION_KEY` +- `CODEX_AUTH_PREVIOUS_ENCRYPTION_KEY` +- `CODEX_AUTH_ROLE` + +Encryption key variables must be high-entropy 32-byte key material (for example, +from a secret manager). Do not use passwords. + +Enterprise recommendation: + +- pin `CODEX_SECRET_STORAGE_MODE=keychain` for production. --- @@ -141,6 +158,14 @@ Maintainer/debug-focused overrides include: - `CODEX_CLI_ACCOUNTS_PATH` - `CODEX_CLI_AUTH_PATH` - refresh lease controls (`CODEX_AUTH_REFRESH_LEASE*`) +- `CODEX_AUTH_BREAK_GLASS` +- `CODEX_AUTH_ABAC_READ_ONLY` +- `CODEX_AUTH_ABAC_DENY_ACTIONS` +- `CODEX_AUTH_ABAC_DENY_COMMANDS` +- `CODEX_AUTH_ABAC_REQUIRE_INTERACTIVE` +- `CODEX_AUTH_ABAC_REQUIRE_IDEMPOTENCY_KEY` +- `CODEX_AUTH_REDACT_JSON_OUTPUT` +- retention controls (`CODEX_AUTH_RETENTION_*`) Full inventory: [../development/CONFIG_FIELDS.md](../development/CONFIG_FIELDS.md) @@ -175,4 +200,4 @@ codex auth forecast --live - [commands.md](commands.md) - [storage-paths.md](storage-paths.md) -- [../configuration.md](../configuration.md) \ No newline at end of file +- [../configuration.md](../configuration.md) diff --git a/docs/reference/storage-paths.md b/docs/reference/storage-paths.md index bae76b844..6e5690750 100644 --- a/docs/reference/storage-paths.md +++ b/docs/reference/storage-paths.md @@ -26,11 +26,17 @@ Override root: | Accounts WAL | `~/.codex/multi-auth/openai-codex-accounts.json.wal` | | Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` | | Quota cache | `~/.codex/multi-auth/quota-cache.json` | +| Background job DLQ | `~/.codex/multi-auth/background-job-dlq.jsonl` | | Logs | `~/.codex/multi-auth/logs/codex-plugin/` | | Cache | `~/.codex/multi-auth/cache/` | | Codex CLI accounts | `~/.codex/accounts.json` | | Codex CLI auth | `~/.codex/auth.json` | +Security note: + +- Current secure format (`version: 4`) stores keychain references (`refreshTokenRef`, `accessTokenRef`) instead of raw token values in account storage files. +- Set `CODEX_SECRET_STORAGE_MODE=plaintext` only for controlled migration/testing environments. + Ownership note: - `~/.codex/multi-auth/*` is managed by this project. diff --git a/docs/runbooks/README.md b/docs/runbooks/README.md new file mode 100644 index 000000000..7b7d5302c --- /dev/null +++ b/docs/runbooks/README.md @@ -0,0 +1,12 @@ +# Runbooks + +Operational runbooks for `codex-multi-auth`. + +## Runbook Index + +- [operations.md](operations.md): routine operational checks, release gates, and maintenance tasks. +- [incident-response.md](incident-response.md): severity model, containment flow, and post-incident process. + +## Scope + +These runbooks cover plugin-owned local state under `~/.codex/multi-auth` (or `CODEX_MULTI_AUTH_DIR`) and repository-level CI/security controls. diff --git a/docs/runbooks/incident-response.md b/docs/runbooks/incident-response.md new file mode 100644 index 000000000..230f8e24a --- /dev/null +++ b/docs/runbooks/incident-response.md @@ -0,0 +1,99 @@ +# Incident Response Playbook + +Incident response workflow for `codex-multi-auth`. + +--- + +## Severity Levels + +- `SEV-1`: active secret exposure, auth bypass, or broad production outage. +- `SEV-2`: major functionality degraded, high failure rate, or persistent data corruption risk. +- `SEV-3`: contained bug with workaround, no ongoing security impact. + +--- + +## Response Timeline + +### 1. Detect and Declare (0-15 min) + +1. Open an internal incident channel. +2. Assign incident commander and communications lead. +3. Record: + - first detection timestamp + - affected command flows + - impacted storage paths/environment variables + +### 2. Contain (15-60 min) + +1. For credential exposure: + - rotate affected OAuth/session credentials + - set new `CODEX_AUTH_ENCRYPTION_KEY` + - run `codex auth rotate-secrets --idempotency-key ` +2. For unauthorized command execution: + - downgrade role to `CODEX_AUTH_ROLE=viewer` where possible + - enable `CODEX_AUTH_ABAC_READ_ONLY=1` until containment is complete + - deny high-risk commands with `CODEX_AUTH_ABAC_DENY_COMMANDS=rotate-secrets,fix` + - reserve `CODEX_AUTH_BREAK_GLASS=1` for explicit emergency changes +3. For filesystem instability: + - pause mutation commands (`login`, `switch`, `fix`) + - inspect lock files and dead-letter entries + +### 3. Eradicate and Recover (within 24h) + +1. Patch root cause and merge behind required CI checks. +2. Validate: + - `npm run typecheck` + - `npm run lint` + - `npm test` + - `npm run audit:ci` +3. Re-enable normal command paths and monitor audit logs. + +--- + +## Communication Template + +Use this internal status template: + +```text +Incident: +Severity: +Start Time: +Current Status: +Impact: +Mitigation: +Next Update: