diff --git a/.bestpractices.json b/.bestpractices.json new file mode 100644 index 00000000..0488cc60 --- /dev/null +++ b/.bestpractices.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://bestpractices.coreinfrastructure.org/projects.schema.json", + "_comment": "OpenSSF Best Practices self-assessment skeleton for RandomCodeSpace/codeiq. The numeric project_id and badge URL are populated by a board admin after registering the project at https://www.bestpractices.dev/ — RAN-46 AC #8 calls this out as auth-blocked. Once the registration is complete, fill `project_id` and re-render the README badge with the resolved URL.", + "project_id": null, + "name": "codeiq", + "description": "Deterministic code knowledge graph — scans codebases to map services, endpoints, entities, infrastructure, auth patterns, and framework usage. No AI, pure static analysis.", + "homepage_url": "https://github.com/RandomCodeSpace/codeiq", + "repo_url": "https://github.com/RandomCodeSpace/codeiq", + "license": "MIT", + "level": "passing", + "status": { + "basics": "self-assessed-passing", + "change_control": "self-assessed-passing", + "reporting": "self-assessed-passing", + "quality": "self-assessed-passing", + "security": "self-assessed-passing", + "analysis": "self-assessed-passing" + }, + "evidence": { + "vulnerability_report_process": "SECURITY.md", + "release_process": "shared/runbooks/release.md", + "rollback_process": "shared/runbooks/rollback.md", + "first_time_setup": "shared/runbooks/first-time-setup.md", + "engineering_standards": "shared/runbooks/engineering-standards.md", + "license_file": "LICENSE", + "build_reproducible": "mvn -B -ntp clean verify", + "ci_workflow": ".github/workflows/ci-java.yml", + "code_scanning": "GitHub repo setting (CodeQL default setup, java-kotlin + javascript-typescript + actions). Workflow-driven CodeQL was tried in PR #74 but conflicts with default setup at SARIF upload — keeping default setup as the SSoT.", + "supply_chain_scorecard": ".github/workflows/scorecard.yml", + "dependency_updates": ".github/dependabot.yml", + "signed_commits": "scripts/setup-git-signed.sh", + "secret_scanning": "GitHub repo setting (secret_scanning + push_protection enabled)", + "static_analysis": "SpotBugs (mvn spotbugs:check) + SonarCloud Quality Gate", + "vulnerability_scanning": "OWASP Dependency-Check (mvn dependency-check:check) + Dependabot security updates" + }, + "audit": { + "self_assessment_date": "2026-04-25", + "self_assessment_author": "TechLead (RAN-46)", + "registration_blocker": "https://www.bestpractices.dev/ requires human OAuth/form. Tracked under RAN-46 AC #8." + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..589f9073 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,125 @@ +# Dependabot configuration for codeiq. +# Docs: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# +# Strategy: +# * weekly cadence — keeps the noise floor low while still catching CVEs early +# * grouped updates per ecosystem so PR fan-out stays manageable +# * security updates fire whenever needed regardless of the weekly slot +# +# RAN-46 AC #4: Dependabot (security + version updates, weekly, grouped). Also +# enable repo-level "Dependabot security updates" via gh api (the version-updates +# below cover routine bumps; security updates are the reactive channel). + +version: 2 +updates: + # ----- Maven (the codeiq application) ----- + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 10 + labels: + - "type:dependencies" + - "area:backend" + commit-message: + prefix: "chore(deps)" + include: "scope" + groups: + spring: + patterns: + - "org.springframework*" + - "org.springframework.boot:*" + - "org.springframework.security:*" + - "org.springframework.ai:*" + jackson: + patterns: + - "com.fasterxml.jackson*" + neo4j: + patterns: + - "org.neo4j:*" + - "org.neo4j.driver:*" + antlr: + patterns: + - "org.antlr:*" + maven-plugins: + patterns: + - "org.apache.maven.plugins:*" + - "org.codehaus.*" + - "org.jacoco:*" + - "com.github.spotbugs:*" + - "org.owasp:*" + - "org.sonarsource.scanner.maven:*" + - "org.sonatype.central:*" + test-libs: + patterns: + - "org.junit.jupiter:*" + - "org.mockito:*" + - "org.assertj:*" + - "org.hamcrest:*" + - "com.h2database:*" + + # ----- GitHub Actions (CI / release / security) ----- + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: + - "type:dependencies" + - "area:ci" + commit-message: + prefix: "chore(actions)" + include: "scope" + groups: + actions: + patterns: + - "*" + + # ----- Frontend (npm under src/main/frontend) ----- + - package-ecosystem: "npm" + directory: "/src/main/frontend" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Etc/UTC" + open-pull-requests-limit: 5 + labels: + - "type:dependencies" + - "area:frontend" + commit-message: + prefix: "chore(frontend)" + include: "scope" + groups: + react: + patterns: + - "react" + - "react-dom" + - "@types/react*" + ant-design: + patterns: + - "antd" + - "@ant-design/*" + vite: + patterns: + - "vite" + - "@vitejs/*" + echarts: + patterns: + - "echarts" + - "echarts-for-react" + eslint: + patterns: + - "eslint*" + - "@eslint/*" + - "@typescript-eslint/*" + typescript: + patterns: + - "typescript" + - "@types/*" diff --git a/.github/workflows/beta-java.yml b/.github/workflows/beta-java.yml index 46aa89f8..212c890f 100644 --- a/.github/workflows/beta-java.yml +++ b/.github/workflows/beta-java.yml @@ -9,11 +9,11 @@ jobs: contents: write packages: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 with: fetch-depth: 0 - - uses: actions/setup-java@v4 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1 with: distribution: 'temurin' java-version: '25' @@ -60,7 +60,7 @@ jobs: git push origin ${{ steps.version.outputs.tag }} - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: ${{ steps.version.outputs.tag }} name: "Beta ${{ steps.version.outputs.version }}" diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml index 3c030510..9fce4340 100644 --- a/.github/workflows/ci-java.yml +++ b/.github/workflows/ci-java.yml @@ -10,21 +10,54 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 with: fetch-depth: 0 - - uses: actions/setup-java@v4 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1 with: distribution: 'temurin' java-version: '25' cache: 'maven' - - run: mvn clean verify -B - - uses: actions/upload-artifact@v4 + # Cache the OWASP Dependency-Check NVD data directory across runs so the + # CVE gate does not need to re-download the full feed on every PR. + # `key` is unique per run (forces a save on every run), `restore-keys` + # falls back to the most recent prior cache so the H2 DB is incrementally + # updated rather than rebuilt. + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.m2/repository/org/owasp/dependency-check-data + key: dependency-check-${{ runner.os }}-${{ github.run_id }} + restore-keys: | + dependency-check-${{ runner.os }}- + # Pre-warm the OWASP Dependency-Check NVD cache as a SEPARATE Maven + # invocation. On a cold cache (first run on a branch / cache eviction) + # running `update-only` first avoids the dependency-check-maven 12.2.0 + # H2 init race that surfaces as `NullPointerException: Cannot invoke + # BasicDataSource.getConnection() because connectionPool is null` + # during the verify phase (observed on PR #74 build run 24930518462). + # When the cache is warm this step short-circuits via the H2 incremental + # update path. `failOnError=false` so a transient NVD-feed problem here + # does not mask the real CVSS>=7 gate enforced in the verify step + # below — that step still hard-fails on operational scanner failures + # (Reviewer round-3 finding #1). + - name: Pre-warm dependency-check NVD cache + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + run: mvn -B -ntp dependency-check:update-only -DfailOnError=false + - name: Build + verify (jacoco 85% + SpotBugs + dependency-check) + env: + # When the NVD_API_KEY secret is unset, dependency-check falls back + # to the unauthenticated NVD endpoint (rate-limited but functional + # once the cache is warm). Provisioning the secret is tracked under + # RAN-42. + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + run: mvn -B -ntp clean verify + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4.6.2 if: always() with: name: test-results path: target/surefire-reports/ - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4.6.2 with: name: coverage-report path: target/site/jacoco/ diff --git a/.github/workflows/release-java.yml b/.github/workflows/release-java.yml index 96eb7361..bbabb535 100644 --- a/.github/workflows/release-java.yml +++ b/.github/workflows/release-java.yml @@ -12,8 +12,8 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1 with: distribution: 'temurin' java-version: '25' @@ -23,23 +23,80 @@ jobs: server-password: MAVEN_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} gpg-passphrase: MAVEN_GPG_PASSPHRASE - - name: Set release version + - name: Configure git identity and non-interactive GPG for signed commit/tag + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + # Use the GPG key imported by setup-java (MAVEN_GPG_PRIVATE_KEY) for + # both commit and tag signing — same trust path as the artifact. + KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}') + if [ -z "$KEYID" ]; then + echo "no GPG secret key in agent — release-java.yml needs MAVEN_GPG_PRIVATE_KEY" >&2 + exit 1 + fi + git config user.signingkey "$KEYID" + git config gpg.format openpgp + git config commit.gpgsign true + git config tag.gpgsign true + # Reviewer finding cf64b44d (RAN-47, R5-1): + # `git commit -S` / `git tag -s` invoke gpg interactively by default + # and fail in non-interactive Actions shells when the imported key + # has a passphrase. setup-java only wires the passphrase for Maven + # signing (via MAVEN_GPG_PASSPHRASE in settings.xml); git itself + # has no equivalent autoconfig. Configure the gpg-agent for loopback + # pinentry, point gpg.program at a thin wrapper that injects + # --batch / --pinentry-mode loopback / --passphrase from + # MAVEN_GPG_PASSPHRASE, so signing succeeds non-interactively. + mkdir -p "$HOME/.gnupg" + chmod 700 "$HOME/.gnupg" + printf '%s\n' 'use-agent' 'pinentry-mode loopback' > "$HOME/.gnupg/gpg.conf" + printf '%s\n' 'allow-loopback-pinentry' > "$HOME/.gnupg/gpg-agent.conf" + gpgconf --kill gpg-agent || true + # Wrapper script: git invokes this with the same flags as gpg. + # We exec into gpg with --batch + loopback + the passphrase from + # the env (MAVEN_GPG_PASSPHRASE is set on each step that signs). + cat > "$HOME/.gnupg/gpg-loopback.sh" <<'WRAPPER' + #!/usr/bin/env bash + # Non-interactive gpg wrapper for `git commit -S` / `git tag -s`. + # MAVEN_GPG_PASSPHRASE is set on the workflow step that signs. + exec gpg --batch --yes --pinentry-mode loopback \ + --passphrase "${MAVEN_GPG_PASSPHRASE:-}" "$@" + WRAPPER + chmod +x "$HOME/.gnupg/gpg-loopback.sh" + git config gpg.program "$HOME/.gnupg/gpg-loopback.sh" + - name: Set release version and create signed release commit env: RELEASE_VERSION: ${{ inputs.version }} - run: mvn versions:set -DnewVersion="$RELEASE_VERSION" + # Picked up by the gpg-loopback wrapper script for `git commit -S`. + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + # Commit the version bump on a detached HEAD off the workflow's + # checkout. The commit is reachable only via the tag created below — + # no push to `main`, so this works under branch protection. + # The commit captures the exact source tree that the deploy step + # will build from, fixing the prior "tag diverges from released + # artifact" gap (Reviewer finding 47b718b9). + run: | + mvn -B -ntp versions:set -DnewVersion="$RELEASE_VERSION" -DgenerateBackupPoms=false + git add pom.xml + git commit -S -m "chore(release): ${RELEASE_VERSION}" - name: Deploy to Maven Central env: MAVEN_USERNAME: ${{ secrets.OSS_NEXUS_USER }} MAVEN_PASSWORD: ${{ secrets.OSS_NEXUS_PASS }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - run: mvn clean deploy -P release -B - - name: Tag release + run: mvn -B -ntp -P release clean deploy + - name: Create signed annotated tag and push env: RELEASE_VERSION: ${{ inputs.version }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} + # Annotated + GPG-signed tag pointing at the release commit (the + # current HEAD after the commit step above). Push only the tag — + # the release commit lives only as a tag-reachable object so we + # never need to update `main`, and branch protection stays clean. run: | - git tag "v${RELEASE_VERSION}" - git push origin "v${RELEASE_VERSION}" - - uses: softprops/action-gh-release@v2 + git tag -s "v${RELEASE_VERSION}" -m "codeiq ${RELEASE_VERSION}" + git push origin "refs/tags/v${RELEASE_VERSION}" + - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: v${{ inputs.version }} generate_release_notes: true diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000..3fbc8d39 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,66 @@ +# OpenSSF Scorecard supply-chain analysis. +# RAN-46 AC #9. Best-effort target — no hard numeric floor; Scorecard does not gate merge. +# Docs: https://github.com/ossf/scorecard-action + +name: Scorecard supply-chain security + +on: + push: + branches: [main] + schedule: + # Mondays 06:00 UTC + - cron: "0 6 * * 1" + workflow_dispatch: + +# Restrict the default GITHUB_TOKEN to read-only; the steps below request the +# narrow scopes they actually need. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Required for upload to the code-scanning Security tab. + security-events: write + # Required to read OIDC token for publish_results. + id-token: write + # Default scopes for actions/checkout. + contents: read + actions: read + + steps: + - name: Harden runner egress + # step-security/harden-runner v2.19.0 + uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 + with: + egress-policy: audit + + - name: Checkout code + # actions/checkout v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false + + - name: Run Scorecard analysis + # ossf/scorecard-action v2.4.3 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a + with: + results_file: results.sarif + results_format: sarif + # Publish the results so they appear on the public Scorecard dashboard. + publish_results: true + + - name: Upload Scorecard SARIF (artifact) + # actions/upload-artifact v7.0.1 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: scorecard-sarif + path: results.sarif + retention-days: 5 + + - name: Upload SARIF to GitHub code-scanning + # github/codeql-action/upload-sarif v3.35.2 + uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a + with: + sarif_file: results.sarif diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..c9c986d9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,43 @@ +# AGENTS.md — codeiq + +> **Repo-root entry point for any agent collaborator.** This file is intentionally short and lists pointers; the canonical contents live elsewhere and are linked from here. + +## What this repo is + +codeiq is a CLI + read-only server that builds a deterministic code-knowledge graph over a codebase. No AI, no external APIs — pure static analysis. See [`/CLAUDE.md`](CLAUDE.md) for the architecture, package map, pipeline, conventions, and gotchas. + +## Pointers, in priority order + +1. **Read [`/CLAUDE.md`](CLAUDE.md) first.** It is the SSoT for architecture, build/test commands, package layout, and the long-tail of "things that bite you on this codebase." +2. **Then [`/shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md).** Coverage, CVE, signed-commits, and quality-gate policy. +3. **Then the runbooks you'll actually need:** + - [`shared/runbooks/first-time-setup.md`](shared/runbooks/first-time-setup.md) — get from clean clone to green local build. + - [`shared/runbooks/release.md`](shared/runbooks/release.md) — how to ship; gates downstream RAN-* product work. + - [`shared/runbooks/rollback.md`](shared/runbooks/rollback.md) — when a ship goes bad. +4. **Security**: [`/SECURITY.md`](SECURITY.md) for disclosure; private reports only. + +## Hard rules for any agent doing work in this repo + +- **Branch off `main`.** Never commit to `main` directly. +- **Sign every commit.** The repo-local config (`scripts/setup-git-signed.sh`) makes this automatic; do not rewrite it. +- **One logical change per commit.** Conventional-commit subjects (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`). +- **Squash-merge only.** Branch protection rejects merge commits and force-pushes to `main`. +- **Tests + jacoco gate must pass.** `mvn -B -ntp clean verify` is the contract. +- **Determinism is non-negotiable.** Same input → same output, byte-for-byte. Any new detector ships with a determinism test. +- **Read-only serving layer.** MCP and REST API on the `serve` path do not mutate. If you find yourself adding `POST /api/` that writes, stop and reconsider. +- **No secrets in code.** Repo-level GitHub Actions secrets only. + +## Paperclip / RAN-* coordination + +This codebase tracks work in Paperclip under the `RAN-*` prefix. When you pick up a task: + +1. Checkout the issue (`POST /api/issues/{id}/checkout`) before you start. +2. Comment progress on every heartbeat — terse markdown, link the PR. +3. Branch protection requires TechLead approval; route review there. +4. Reference the issue in your commit/PR body (`Closes RAN-N`). + +If the task asks for product/feature work and `shared/runbooks/release.md` is missing on `main`, **stop**: the RAN-46 bootstrap precondition has not landed yet and product work is gated on it. + +## Auth escalation + +If you hit something requiring GitHub App / PAT / OAuth that the runtime cannot satisfy (org admin escalation, Sonatype Central re-namespace, OpenSSF Best Practices form, etc.), do **not** improvise auth: PATCH the issue to `blocked` with the exact ask and `@`-mention the board. diff --git a/CLAUDE.md b/CLAUDE.md index 3239294f..8e8c45f8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -429,6 +429,10 @@ bean for code paths that haven't been ported yet. - **SonarCloud project key**: `RandomCodeSpace_codeiq`, org: `randomcodespace` - **CI workflow**: Single `ci-java.yml` runs build + SonarCloud analysis. No cross-platform builds needed (JVM). +## Deploy + +codeiq's deploy surface is **Maven Central + GitHub Releases** (per RAN-46 AC #10 ruling, option a). The single Java JAR (with the React UI bundled inside) is published via two `workflow_dispatch`-only workflows: `.github/workflows/beta-java.yml` (manual beta cut → Sonatype Central beta + GitHub pre-release) and `.github/workflows/release-java.yml` (manual GA cut with a `version` input → the workflow builds a GPG-signed release commit on a detached HEAD, deploys from that exact tree, then creates and pushes a GPG-signed annotated `vX.Y.Z` tag pointing at the release commit + a GitHub Release). There is no static-CDN frontend, no hosted backend, no VPS — codeiq runs on the developer's machine. See [`shared/runbooks/release.md`](shared/runbooks/release.md) and [`shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md) §7.1. + ## Updating This File After significant changes (new detectors, new endpoints, architectural decisions, conventions learned), update this CLAUDE.md to reflect the current state. Keep it concise and actionable. diff --git a/LICENSE b/LICENSE index 060d5911..64be958b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 RandomCodeSpace +Copyright (c) 2026 Amit Kumar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d04f7fd3..35da5f30 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ MIT License Security Reliability + OpenSSF Scorecard + OpenSSF Best Practices (pending registration — RAN-46 AC #8) 97 Detectors 35+ Languages

diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..ce02c32c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,65 @@ +# Security Policy + +## Supported versions + +Security fixes are issued against the latest minor release line on Maven Central. While codeiq is pre-1.0 (`0.x.y`) only the **latest** released `0.MINOR.x` line receives backports; older minor lines are EOL the moment a new minor ships. + +| Version line | Status | +|---|---| +| `0.1.x` | Supported (current) | +| `< 0.1.0` | Unsupported | + +`-SNAPSHOT` builds are development snapshots; they do not receive security fixes by themselves — you should be tracking the latest tagged release. + +## Reporting a vulnerability + +Please **do not open a public GitHub issue** for security problems. + +Use one of: + +- **GitHub private vulnerability report** — preferred. Open `https://github.com/RandomCodeSpace/codeiq/security/advisories/new` (you must be signed in to GitHub). The advisory channel is monitored by the maintainer. +- **Email** — `ak.nitrr13@gmail.com`. Put `[codeiq security]` in the subject so the report is triaged ahead of normal mail. + +Please include: + +- The codeiq version (`java -jar code-iq-*-cli.jar version` or `pom.xml` coordinate). +- The shortest reproducer you can produce — a CLI command or test case is ideal. +- Your assessment of impact (e.g., RCE, path traversal, info-disclosure, DoS). +- Whether the issue is in a transitive dependency (please name the dependency + advisory ID if known). + +## What you can expect + +- **Acknowledgement** within 72 hours. +- **Initial triage** within 7 days, with a severity rating (CVSS v3.1) and an indicative remediation timeline. +- **Coordinated disclosure** — we will agree on a public-disclosure date with the reporter; default is 90 days from triage, sooner for low-impact / already-public issues. +- **Credit** in the GHSA advisory and `CHANGELOG.md` (unless the reporter requests anonymity). + +We do not currently run a paid bug bounty. + +## Scope + +In-scope: + +- The codeiq CLI (`code-iq-*-cli.jar`). +- The library JAR (`io.github.randomcodespace.iq:code-iq`). +- The bundled REST API + MCP server (`serve` subcommand) — including path traversal, authn/authz, deserialisation, request smuggling, and SSRF. +- The bundled React UI assets shipped inside the JAR. +- The pipeline cache (H2) and graph store (Neo4j Embedded) — including local privilege escalation and data tampering. + +Out of scope: + +- Vulnerabilities that require pre-existing local code execution on the developer's machine (we ship as a developer tool — by definition you trust the code you point it at). +- Public-internet attack surface — codeiq does not expose any service to the public internet by default; deploying the `serve` endpoint behind hostile reverse-proxies is out of scope. +- Findings in third-party services we do not control (Maven Central, GitHub itself, SonarCloud, etc.) — please report those upstream. + +## Hardening references + +- [`shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md) — CVE policy and quality gates. +- [`shared/runbooks/rollback.md`](shared/runbooks/rollback.md) §6 — secret rotation flow. +- `.github/workflows/scorecard.yml` — OpenSSF Scorecard supply-chain checks. +- GitHub repo-level **CodeQL default setup** (java-kotlin + javascript-typescript + actions) — code scanning, SARIF in the Security tab. Configured under repo Settings → Code security → Code scanning, not via a workflow file (a workflow-driven `codeql.yml` was tried and removed because GitHub rejects duplicate SARIF uploads when default setup is on for the same language). +- `.github/dependabot.yml` — automated dependency / GHA / npm bumps. + +## Changelog + +This file is versioned as part of the repo. Material changes (e.g., raising the supported-versions table, changing the disclosure timeline) are announced via a Release note and a Paperclip board comment. diff --git a/dependency-check-suppressions.xml b/dependency-check-suppressions.xml new file mode 100644 index 00000000..67c74350 --- /dev/null +++ b/dependency-check-suppressions.xml @@ -0,0 +1,204 @@ + + + + + + + + + + ^pkg:maven/org\.springframework\.ai/spring-ai-.*@.*$ + cpe:/a:vmware:server + + + + ^pkg:maven/org\.springframework\.ai/spring-ai-.*@.*$ + cpe:/a:vmware:spring_ai + + + + + + ^pkg:maven/org\.springframework\.boot/spring-boot-neo4j@.*$ + cpe:/a:neo4j:neo4j + + + + + + + + ^pkg:maven/org\.apache\.arrow/.*@.*$ + CVE-2026-25087 + + + + + + ^pkg:maven/io\.grpc/.*@.*$ + CVE-2026-33186 + + + + + + ^pkg:maven/org\.eclipse\.jetty(\.[a-z0-9]+)*/.*@.*$ + CVE-2026-5795 + + + diff --git a/pom.xml b/pom.xml index 691566be..8ebf9070 100644 --- a/pom.xml +++ b/pom.xml @@ -62,8 +62,14 @@ log4j-core 2.25.3 -> 2.25.4 (CVE-2026-34477 MOD, CVE-2026-34478 MOD, CVE-2026-34480 MOD) + log4j-api 2.25.3 -> 2.25.4 (umbrella-CPE attribution + of log4j-core CVEs; + bumped to keep the dep + tree consistent and to + stop the dep-check + umbrella match) log4j-layout-template-json 2.25.3 -> 2.25.4 (CVE-2026-34481 MOD) - •both pulled in transitively by Neo4j 2026.02.3. + •all three pulled in transitively by Neo4j 2026.02.3. shiro-core 2.0.6 -> 2.1.0 (CVE-2026-23901 LOW) •pulled in by neo4j-security. mcp-core 1.1.0 -> 1.1.1 (CVE-2026-34237 MOD) @@ -77,6 +83,11 @@ log4j-core 2.25.4 + + org.apache.logging.log4j + log4j-api + 2.25.4 + org.apache.logging.log4j log4j-layout-template-json @@ -369,10 +380,9 @@ report - - @@ -401,13 +410,91 @@ ${spotbugs.version} spotbugs-exclude.xml + + High + true + + + + spotbugs-check-on-verify + verify + + check + + + org.owasp dependency-check-maven ${owasp.dependency-check.version} + + + 7 + + ${user.home}/.m2/repository/org/owasp/dependency-check-data + + ${env.NVD_API_KEY} + + 10 + 4000 + + + ${project.basedir}/dependency-check-suppressions.xml + + + + + + dependency-check-on-verify + verify + + check + + + diff --git a/scripts/setup-git-signed.sh b/scripts/setup-git-signed.sh new file mode 100755 index 00000000..01bc5a3c --- /dev/null +++ b/scripts/setup-git-signed.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env bash +# scripts/setup-git-signed.sh +# +# Apply the repo-local git config required by RAN-46 AC #2. Supports BOTH +# ssh-format and openpgp-format signing — picks up whichever the contributor +# already has wired into their global git config. +# +# Defaults (when nothing is set globally): +# user.signingkey = ~/.ssh/id_ed25519.pub +# gpg.format = ssh +# +# Honored env / global-config inputs: +# GIT_USER_NAME (else: git config --global user.name) +# GIT_USER_EMAIL (else: git config --global user.email) +# GIT_SIGNING_KEY (else: git config --global user.signingkey, +# else default SSH key) +# GIT_GPG_FORMAT (else: git config --global gpg.format, +# else "ssh") +# +# Idempotent: re-running is a no-op except for the verification block at the end. +# Run from the repo root (or any subdirectory of the worktree). + +set -euo pipefail + +# Resolve the worktree root and refuse to run anywhere else. +if ! repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then + echo "error: not inside a git working tree." >&2 + exit 1 +fi + +cd "$repo_root" + +# Identity is taken from env vars first, then from the user's GLOBAL git +# config — never hard-coded to the maintainer. This avoids silently +# misattributing every contributor's signed commits to the maintainer +# (earlier Reviewer finding). +GIT_USER_NAME=${GIT_USER_NAME:-$(git config --global --get user.name 2>/dev/null || true)} +GIT_USER_EMAIL=${GIT_USER_EMAIL:-$(git config --global --get user.email 2>/dev/null || true)} +GIT_SIGNING_KEY=${GIT_SIGNING_KEY:-$(git config --global --get user.signingkey 2>/dev/null || echo "$HOME/.ssh/id_ed25519.pub")} +GIT_GPG_FORMAT=${GIT_GPG_FORMAT:-$(git config --global --get gpg.format 2>/dev/null || echo "ssh")} + +if [ -z "$GIT_USER_NAME" ] || [ -z "$GIT_USER_EMAIL" ]; then + cat >&2 <<'EOF' +error: contributor identity not set. + +This script does not assume a default identity. Set yours either: + 1. Globally (recommended): + git config --global user.name "Your Name" + git config --global user.email "you@example.com" + 2. Per-invocation: + GIT_USER_NAME="Your Name" GIT_USER_EMAIL="you@example.com" \ + scripts/setup-git-signed.sh + +Then re-run this script. Signed commits will use the identity you set. +EOF + exit 4 +fi + +# Signing-key validation depends on gpg.format: +# - ssh: user.signingkey is a path on disk (the file must exist) +# - openpgp: user.signingkey is a key id / fingerprint (gpg must know it) +# - x509: user.signingkey is a key id / fingerprint (gpgsm must know it) +# Reviewer finding cf64b44d (RAN-47, R5-2): the previous version forced the +# ssh-path check unconditionally, which rejected contributors using OpenPGP +# globally even when their key chain was healthy. +case "$GIT_GPG_FORMAT" in + ssh) + if [ ! -f "$GIT_SIGNING_KEY" ]; then + cat >&2 </dev/null | grep -q '^sec:'; then + cat >&2 < +And re-run this script. +EOF + exit 2 + fi + ;; + x509) + if ! gpgsm --list-secret-keys "$GIT_SIGNING_KEY" >/dev/null 2>&1; then + cat >&2 <&2 + exit 5 + ;; +esac + +apply() { + local key="$1" value="$2" + git config --local "$key" "$value" +} + +apply user.name "$GIT_USER_NAME" +apply user.email "$GIT_USER_EMAIL" +apply user.signingkey "$GIT_SIGNING_KEY" +apply gpg.format "$GIT_GPG_FORMAT" +apply commit.gpgsign true +apply tag.gpgsign true + +echo "Applied repo-local git config:" +printf " %-22s = %s\n" \ + user.name "$(git config --local --get user.name)" \ + user.email "$(git config --local --get user.email)" \ + user.signingkey "$(git config --local --get user.signingkey)" \ + gpg.format "$(git config --local --get gpg.format)" \ + commit.gpgsign "$(git config --local --get commit.gpgsign)" \ + tag.gpgsign "$(git config --local --get tag.gpgsign)" + +# Verification: produce a throwaway signed object and verify it. +# `git commit-tree` does not touch refs, so this is non-destructive. +echo +echo "Verifying signing produces a valid signature ..." +tree=$(git write-tree) +sig_commit=$(echo "setup-git-signed.sh verification" | git commit-tree "$tree" -S) +if git verify-commit --raw "$sig_commit" 2>&1 | grep -q '^GOODSIG\|^SSH_OK\|GOOD signature'; then + echo " ok — signing chain is healthy." +elif git verify-commit "$sig_commit" >/dev/null 2>&1; then + echo " ok — signing chain is healthy." +else + cat >&2 <> ~/.config/git/allowed_signers + Then re-run this script. +EOF + exit 3 +fi + +echo +echo "done. Every commit and tag from this worktree will now be ssh-signed." diff --git a/shared/runbooks/engineering-standards.md b/shared/runbooks/engineering-standards.md new file mode 100644 index 00000000..d5c64d0c --- /dev/null +++ b/shared/runbooks/engineering-standards.md @@ -0,0 +1,137 @@ +# Engineering Standards — codeiq + +> **SSoT** for the cross-cutting engineering rules that every contributor — human or agent — must follow on this repo. Per-issue specifics live in the issue thread; per-component conventions live in [`/CLAUDE.md`](../../CLAUDE.md). This document is what the runbooks ([`release.md`](release.md), [`rollback.md`](rollback.md), [`first-time-setup.md`](first-time-setup.md)) reference for "what counts as done." + +The rule of last resort: **`/home/dev/.claude/rules/*.md` wins.** This file does not contradict it; it specialises it for codeiq. + +--- + +## 1. Quality gates (hard / non-negotiable) + +| Gate | Threshold | Where it runs | Failure action | +|---|---|---|---| +| Unit + integration tests | All pass | `mvn verify` (CI + local) | Block merge | +| JaCoCo coverage | ≥ 85% line (project-wide, post-exclusions) | `jacoco-maven-plugin` rule in `pom.xml` | Block merge | +| SonarCloud Quality Gate | `Passed` (`Sonar way` profile + 80% new-code coverage) | `ci-java.yml` | Block merge | +| SpotBugs | Zero High/Critical findings; `spotbugs-exclude.xml` justified per-entry | `mvn spotbugs:check` | Block merge | +| OWASP Dependency-Check | No High/Critical CVEs (`failBuildOnCVSS=7`); Medium tracked | `mvn -B -ntp clean verify` (the `dependency-check:check` execution is bound to the `verify` phase in `pom.xml`); `ci-java.yml` runs on every PR + push to `main` | Block merge | +| OpenSSF Scorecard | Best-effort; no hard score floor; `Pinned-Dependencies` is a soft target | `scorecard.yml` (push to `main` + weekly) | Surface in security tab; do **not** gate merge | +| Signed commits | Every commit on `main` must verify | Branch protection + `gh api ... /commits/{sha}/check-runs` | Block merge | + +Coverage exclusions are enumerated in `pom.xml` `` config — only generated ANTLR sources, the `application/` Spring Boot main, and pure data records are excluded. Adding to that list requires TechLead sign-off. + +**Planned, not yet enforced:** OSV-Scanner as a second-source CVE feed (cross-checks OWASP Dependency-Check against the OSV / GitHub Advisory Database). Tracked under [RAN-42](/RAN/issues/RAN-42); will land as `.github/workflows/osv-scanner.yml` and a row added to the table above. Until then OSV is **not** part of the gate — only OWASP Dependency-Check is. + +--- + +## 2. Code style + +- **Java 25** — virtual threads, records, sealed types, pattern matching are first-class. Do not write defensive `instanceof` chains where pattern matching applies. +- **No mutable state in detectors.** `@Component` beans are singletons; per-call state lives on method locals. +- **UTF-8 everywhere** (`StandardCharsets.UTF_8` explicit, never the default). +- **Determinism is enforced.** No `Set` iteration without `TreeSet`/`stream().sorted()`. No reliance on thread completion order. Every detector must have a determinism test (run twice → identical bytes). +- **Property-key constants** — extract any string literal that appears in the same file 3+ times. +- **Exception hygiene** — never catch `Exception` to hide it; always either rethrow with context or log at the right level. + +Formatting is handled by editor defaults (no auto-formatter committed). Reviewers will not nit on whitespace; they will block on convention drift. + +--- + +## 3. Branch, commit, PR rules + +- Branch off `main`. Conventional-commit subjects (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`). +- One logical change per commit. Squash-merge is the only path into `main`. +- Every commit ssh-signed (RAN-46 AC #2). Branch protection rejects unsigned commits. +- PR title is a conventional-commit subject. Body contains: + - Linked Paperclip issue (e.g., `Closes RAN-42`). + - "Why" in 1–2 sentences (the diff covers "what"). + - Tests / coverage notes if non-obvious. + - Rollback note if the change is risky. +- No force-push to `main` ever. Force-push to feature branches is fine until the PR is open; once it is open, prefer additive commits so review threads stay anchored. + +--- + +## 4. Testing tiers + +| Tier | What it tests | Where it lives | Speed budget | +|---|---|---|---| +| Unit | Pure logic, no I/O | `src/test/java/.../` next to the SUT | < 10 ms each | +| Integration | Real H2 / real Neo4j (Embedded) / real filesystem | `src/test/java/.../analyzer/`, `.../graph/`, `.../e2e/` | < 5 s each | +| E2E quality | Full pipeline against a real repo (Spring PetClinic, etc.) | `E2EQualityTest`, ground-truth files under `src/test/resources/e2e/` | Run on demand + nightly | + +Ground rules: +- Test behaviour at the boundary; do not test private internals. +- Every detector ships with: a positive case, a discriminator-guard negative case, a determinism case. +- Flaky test = broken test. Fix in the same PR, quarantine with a tracked Paperclip issue, or delete. + +--- + +## 5. Security + +- **Inputs** — every public-facing endpoint validates input at the boundary; parameterised queries only; output encoded by default. +- **Path traversal** — anything that takes a user path goes through the canonical-path check pattern used by `/api/file` (see RAN-8 fix). +- **Secrets** — never in code, config, or commit history. CI secrets are repo-level; rotation cadence is annual or on suspected exposure. +- **CVE policy** — High/Critical → block; Medium → fix if a patched version exists, else document non-exploitability with TechLead sign-off; Low → tracked in the next dependency bump cycle. +- **Vulnerability reporting** — see [`/SECURITY.md`](../../SECURITY.md). Private disclosure only. + +--- + +## 6. Performance + +- Default to streaming and bounded concurrency. No unbounded queues, buffers, or virtual-thread fan-outs. +- Every external call has a timeout and a cancellation path. +- Performance-sensitive paths (`Analyzer`, `GraphStore.bulkSave`, `LayerClassifier`) have a microbenchmark or a regression-detection test before they ship. +- "Make it correct, then make it fast" — but data-structure and query-shape decisions must be performance-aware up front (see `/home/dev/.claude/rules/performance.md`). + +--- + +## 7. Build & distribution + +- Vendor what you can; deterministic builds (`mvn -B -ntp clean verify`) are the contract. +- No public-CDN runtime fetches, no auto-update phone-home, no telemetry default-on. +- GitHub Actions are pinned by commit SHA in every workflow. Rationale: OpenSSF Scorecard `Pinned-Dependencies` and supply-chain integrity. +- Container artifacts (when added) build from a minimal/distroless base, are pushed to GHCR with provenance attestations, and are pinned by digest at consumer sites. + +### 7.1 Deploy targets (RAN-46 AC #10 ruling — option a) + +codeiq's deploy surface is the **Maven Central + GitHub Releases** pipeline. There is intentionally no static-CDN frontend or always-on hosted backend. + +Rationale (per @CEO's RAN-46 ruling): + +- codeiq ships as a single Java JAR (`code-iq-*-cli.jar`). The React UI is bundled **inside** that JAR and served via Spring Boot's static resource handler — there is no separately deployable frontend. +- The intended runtime is the developer's own machine (`codeiq index → enrich → serve`). There is no shared SaaS surface that needs a VPS — codeiq's value is scanning *your* code on *your* machine. +- The literal AC #10 wording ("frontend → static host / CDN; backend → container registry + VPS rollout") was written for a generic product template that does not match codeiq's shape; the deploy surface is the existing release pipeline instead. + +The pipeline: + +| Cadence | Workflow | Trigger | Artifact destination | +|---|---|---|---| +| Beta | `.github/workflows/beta-java.yml` | `workflow_dispatch` (manual) | Sonatype Central beta + GitHub pre-release | +| GA | `.github/workflows/release-java.yml` | `workflow_dispatch` (manual, with `version` input) | Sonatype Central GA + GitHub Release | + +Both workflows are `workflow_dispatch`-only — there is no tag-push trigger and no automatic release on merge. A GA cut: the workflow builds a GPG-signed release commit on a detached HEAD, deploys from that exact tree, then creates and pushes a GPG-signed annotated `vX.Y.Z` tag pointing at the release commit plus a GitHub Release. Tags are an *output* of the GA workflow, not a trigger. See [`release.md`](release.md) §3 for the full sequence. + +Hello-world / pipeline proof: `git tag -l 'v0.0.1-beta.*' | wc -l` is non-zero (47+ beta tags as of the AC #10 ruling) and `gh release list` shows the corresponding GitHub pre-releases. AC #10 is satisfied by the existing pipeline; no new deploy scaffold is required. + +If the product later needs a hosted demo or container surface, that is a **new RAN-* issue**, not a re-open of RAN-46. + +--- + +## 8. Documentation + +- `/CLAUDE.md` is the architecture + conventions SSoT for code-touching changes. +- `/AGENTS.md` (repo root) is the entry-point for agent collaborators. +- Every runbook lives under `shared/runbooks/`. Adding a new runbook requires updating this file's References section. +- ADRs (`docs/adr/NNN-title.md`) for any decision that changes a contract: persistence, public API surface, deployment shape. + +--- + +## 9. References + +- `/CLAUDE.md` — architecture and conventions. +- `/SECURITY.md` — disclosure policy. +- `shared/runbooks/release.md`, `rollback.md`, `first-time-setup.md`. +- `/home/dev/.claude/rules/*.md` — global engineering rules (parent SSoT). +- `pom.xml` — quality-gate plugin wiring (`jacoco`, `spotbugs`, `dependency-check`, `central-publishing`). +- `.github/workflows/` — CI / release / security automations. +- **CodeQL** — handled by GitHub repo-level **CodeQL default setup** (java-kotlin + javascript-typescript + actions), not a workflow file. A workflow-driven CodeQL was attempted in PR #74 and removed because GitHub rejects duplicate SARIF uploads when default setup is also enabled for the same language. Configuration lives under repo Settings → Code security → Code scanning. diff --git a/shared/runbooks/first-time-setup.md b/shared/runbooks/first-time-setup.md new file mode 100644 index 00000000..194f49f0 --- /dev/null +++ b/shared/runbooks/first-time-setup.md @@ -0,0 +1,170 @@ +# First-time Setup — codeiq + +> Get a fresh contributor (human or agent) from a clean machine to a green local build, signed-commit-ready, in one pass. Pairs with [`release.md`](release.md), [`rollback.md`](rollback.md), and [`engineering-standards.md`](engineering-standards.md). + +--- + +## 0. What you'll have at the end + +- Repo cloned at `~/projects/codeiq` with the offline build path verified. +- Java 25 + Maven 3.9 on PATH. +- A signed-commit configuration (ssh) bound to your `id_ed25519` key. +- `mvn verify` exits 0 on `main`. +- `codeiq` CLI runs end-to-end against this repo as a smoke target. + +If any step fails, stop and follow the troubleshooting note inline — do not "fix forward" against a partially-set-up machine. + +--- + +## 1. System prerequisites + +| Tool | Min version | Notes | +|---|---|---| +| Java | 25 | Required by `pom.xml` `maven-enforcer-plugin` (`[25,)`). Use Adoptium / Temurin. | +| Maven | 3.9.x | Newer minor versions are fine; do not use 4.x snapshots. | +| Git | 2.34+ | Required for commit signing in any of the supported formats (`gpg.format` = `ssh`, `openpgp` / `gpg`, or `x509`). | +| OpenSSH | 8.0+ | Required only for `gpg.format=ssh` (the default; bundles `ssh-keygen -Y verify`). Skip if you sign with OpenPGP or x509. | +| GnuPG | 2.2+ | Required only for `gpg.format=openpgp` / `gpg`. Skip if you sign with SSH or x509. | +| Node.js | 20.x LTS | Only needed for the bundled React UI — `mvn package` shells out to it via the frontend Maven plugin. | +| `gh` CLI | 2.40+ | For PR/release plumbing. | + +Verify in one shot: + +```bash +java --version | head -1 # openjdk 25 ... +mvn -v | head -1 # Apache Maven 3.9.x +git --version # git version 2.x +ssh -V # OpenSSH_8.x or newer +node --version # v20.x +gh --version | head -1 # gh version 2.x +``` + +--- + +## 2. Clone and configure + +```bash +git clone git@github.com:RandomCodeSpace/codeiq.git ~/projects/codeiq +cd ~/projects/codeiq +``` + +Apply the repo-local signed-commit config (this is what RAN-46 AC #2 codifies): + +```bash +./scripts/setup-git-signed.sh +``` + +That script is idempotent and is the single SSoT for the per-repo `git config --local` block. It writes `user.name`, `user.email`, `user.signingkey`, `gpg.format`, `commit.gpgsign=true`, `tag.gpgsign=true`, then verifies the configured key resolves in your keychain. + +The script picks up your preferred signing format from (in order) the `GIT_GPG_FORMAT` env var, your global `git config gpg.format`, or the `ssh` default. Per-format expectations: + +- **`ssh` (default)** — `user.signingkey` must be a path on disk to your **public** key (typically `~/.ssh/id_ed25519.pub`). If you do not have an ed25519 keypair, generate one (`ssh-keygen -t ed25519 -C "you@example.com"`) and upload the public key to your GitHub account under `Settings → SSH and GPG keys → New SSH key → Key type: Signing Key` before re-running. +- **`openpgp` / `gpg`** — `user.signingkey` must be a key id or fingerprint that `gpg --list-secret-keys` knows about. Generate / import the key first (`gpg --full-generate-key`), then `git config --global user.signingkey ` and `git config --global gpg.format openpgp` before running this script. +- **`x509`** — `user.signingkey` must be a key id or fingerprint that `gpgsm --list-secret-keys` knows about. Configure x509 signing keys in `gpgsm` first. + +Sanity-check the config: + +```bash +git config --local --get user.signingkey # ssh: a .pub path; openpgp/x509: a key id or fingerprint +git config --local --get gpg.format # ssh | openpgp | gpg | x509 +git config --local --get commit.gpgsign # should print "true" + +# Produce a throwaway signed commit object (no refs touched) and verify it. +sig_commit=$(echo "verify-signing" | git commit-tree HEAD^{tree} -S) +git verify-commit "$sig_commit" # expect "Good ... signature" +git log -1 --pretty=%G? "$sig_commit" # expect: G +``` + +`git verify-commit` operates on a commit object id, not stdin — capturing the +output of `git commit-tree -S` first and then verifying that id is the right +shape. If the verification line errors with "no principal matched", point git +at an `allowed_signers` file: see `scripts/setup-git-signed.sh` output for the +canonical template. + +--- + +## 3. Build, test, run + +The standard offline path is: + +```bash +mvn -B -ntp -DskipTests=false clean verify +``` + +This runs the full pipeline: unit tests, integration tests, jacoco coverage gate (≥85%), SpotBugs, OWASP Dependency-Check, and the executable CLI JAR build. Expect ~2-3 min on a warm cache. + +For a faster inner loop while iterating: + +```bash +mvn -B -ntp test \ + -Dspotbugs.skip=true -Ddependency-check.skip=true # unit tests only (Surefire), no static analysis / CVE plugins +mvn -B -ntp -Dtest=SomeDetectorTest test # single unit test class +mvn -B -ntp -DskipTests=true package # JAR only, no tests +mvn -B -ntp verify \ + -Dspotbugs.skip=true -Ddependency-check.skip=true # unit + integration tests (Surefire + Failsafe), no static analysis / CVE plugins +``` + +The first command **does run tests** — earlier drafts incorrectly passed `-DskipTests` here, which would have skipped them. Reviewer finding cf64b44d (RAN-47, R5-3): Maven's `test` phase only runs Surefire (unit tests). This repo's integration tests are wired through Failsafe at the `integration-test` / `verify` phases — use the fourth form above when you need both unit + integration in the inner loop. Use `-Dspotbugs.skip` / `-Ddependency-check.skip` to keep things fast without dropping test coverage. + +Smoke-test the CLI end-to-end against this repo: + +```bash +java -jar target/code-iq-*-cli.jar version +java -jar target/code-iq-*-cli.jar index . +java -jar target/code-iq-*-cli.jar enrich . +java -jar target/code-iq-*-cli.jar serve . & # opens http://localhost:8080 +curl -fsS http://localhost:8080/api/stats > /dev/null && echo OK +kill %1 +``` + +--- + +## 4. Optional: full quality gate locally + +If you want to mirror the CI gate before pushing: + +```bash +mvn -B -ntp clean verify # tests + jacoco +mvn -B -ntp spotbugs:check # static analysis gate +mvn -B -ntp dependency-check:check -DfailBuildOnCVSS=7 # CVE gate +``` + +Sonar runs only in CI (the token is not on local machines by design). + +--- + +## 5. Branch / PR workflow + +Create branches off `main`: + +```bash +git fetch origin main +git switch -c / origin/main +``` + +Conventional-commit subjects (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`). One logical change per commit. Sign every commit (the local config makes this automatic). Push and open a PR: + +```bash +git push -u origin HEAD +gh pr create --fill --base main +``` + +Branch protection on `main` requires: +- A Codex review approval from TechLead (or delegate). +- CI green on the PR: `ci-java.yml` (build + jacoco 85% + dependency-check + Sonar), the repo-level CodeQL default-setup checks (`Analyze (java-kotlin)`, `Analyze (javascript-typescript)`, `Analyze (actions)`), Socket Security, SonarCloud Code Analysis. +- All commits in the PR signed (branch protection rejects unsigned commits — there is no separate "signed-commits" status check). +- OpenSSF Scorecard runs on push-to-`main` and a weekly cron, **not** on PRs, and is intentionally non-gating per [`engineering-standards.md`](engineering-standards.md) §1. + +Force-push to `main` is disabled. Direct pushes are disabled. Squash-merge is the default and only path. + +--- + +## 6. Where to look next + +- Architecture, layout, and convention SSoT: [`/CLAUDE.md`](../../CLAUDE.md). +- Coverage / Sonar / CVE policy: [`engineering-standards.md`](engineering-standards.md). +- Releasing: [`release.md`](release.md). +- Rolling back a bad release: [`rollback.md`](rollback.md). +- Security reporting: [`/SECURITY.md`](../../SECURITY.md). + +If you hit anything that looks like a runtime gap (missing tool, broken hook, weird auth), open a Paperclip issue against `type:devx` rather than working around it locally — the bootstrap is meant to be reproducible. diff --git a/shared/runbooks/release.md b/shared/runbooks/release.md new file mode 100644 index 00000000..759d9da3 --- /dev/null +++ b/shared/runbooks/release.md @@ -0,0 +1,127 @@ +# Release Runbook — codeiq + +> **SSoT for shipping codeiq.** Owner: TechLead (until bootstrap completes); thereafter the engineer who owns the change. This runbook is the gate referenced by the bootstrap precondition (`RAN-46`): it MUST exist on `main` before any product `RAN-*` issue can leave `backlog`. + +--- + +## 1. Release surfaces + +> **AC #10 ruling (RAN-46, @CEO).** Maven Central + GitHub Releases **is** the codeiq deploy surface. There is no separate static-CDN frontend (the React UI is bundled inside the JAR) and no always-on hosted backend (codeiq runs on the developer's machine). See [`engineering-standards.md`](engineering-standards.md) §7.1 for the full rationale. + +codeiq ships in three forms. A "release" updates **all three** in lockstep: + +| Surface | Artifact | Distribution | +|---|---|---| +| Library JAR | `io.github.randomcodespace.iq:code-iq` (POM packaging) | Maven Central via Sonatype Central Portal | +| Executable CLI JAR | `target/code-iq--cli.jar` | GitHub Release asset | +| Source tag | `v` (annotated, GPG/OpenPGP-signed by `release-java.yml`) | Git tag pushed to `RandomCodeSpace/codeiq` | + +Versioning is [SemVer](https://semver.org/). Pre-`1.0.0` releases use `0.MINOR.PATCH`; breaking changes bump MINOR. + +Snapshot artifacts (`-SNAPSHOT`) are published from `main` by `beta-java.yml` to OSSRH snapshots; consumers must opt into the snapshot repo. Snapshots are **not** releases. + +--- + +## 2. Pre-release checklist + +Run BEFORE creating the tag: + +1. `main` is green: `gh run list --branch main --workflow ci-java.yml --limit 1` → `success`. +2. SonarCloud Quality Gate: `gh api /repos/RandomCodeSpace/codeiq/actions/runs?branch=main --jq '.workflow_runs[0].conclusion'` and SonarCloud project page both green. +3. Coverage ≥ 85% (jacoco rule + Sonar new-code ≥ 80% — see [`engineering-standards.md`](engineering-standards.md)). +4. Dependency audit clean: `mvn -B -ntp clean verify` exits 0 (the OWASP `dependency-check:check` goal is bound to `verify` and fails the build on CVSS ≥ 7 — see `pom.xml`). Cross-check with the Dependabot security tab for any open advisories. +5. SpotBugs clean: `mvn spotbugs:check` exits 0. +6. CHANGELOG entry drafted under `[Unreleased]` and ready to promote. +7. Working copy of `main` is clean (`git status --porcelain` empty). +8. GPG release-signing secrets present in repo settings: `MAVEN_GPG_PRIVATE_KEY` and `MAVEN_GPG_PASSPHRASE` (verify via `gh secret list`). The workflow signs both the release commit and the annotated tag with the imported OpenPGP key — no local SSH or GPG key is required on the maintainer's machine for the GA path (Reviewer finding fd559a54, R5-7). + +--- + +## 3. Cut a release (canonical path) + +Driven by `release-java.yml` via **manual `workflow_dispatch`** with a `version` input. The workflow does **everything**: it creates a release commit (signed) on a detached HEAD with the bumped version, deploys to Maven Central from that exact source tree, and then creates a GPG-signed annotated tag pointing at that release commit. The tag is the only persistent reference to the release commit — `main` is never directly pushed by the workflow, so branch protection stays clean. + +```bash +# 1. Promote CHANGELOG on main (PR + merge per branch protection) +$EDITOR CHANGELOG.md # move [Unreleased] → [X.Y.Z] - YYYY-MM-DD +gh pr create --fill --base main +gh pr merge --squash --auto + +# 2. Trigger the release workflow with the target version +gh workflow run release-java.yml --ref main -f version=X.Y.Z + +# 3. Watch it run +gh run watch $(gh run list --workflow release-java.yml --limit 1 --json databaseId --jq '.[0].databaseId') +``` + +`release-java.yml` then, in order: +1. Configures git identity (`github-actions[bot]`) and binds it to the imported `MAVEN_GPG_PRIVATE_KEY` for both commit and tag signing — same trust path as the published artifact. +2. Runs `mvn versions:set -DnewVersion=X.Y.Z` and creates a **GPG-signed release commit** on a detached HEAD capturing that tree. +3. Runs `mvn -P release clean deploy` from that release commit's tree (full quality gate runs along the way: jacoco 85%, SpotBugs, OWASP Dependency-Check). +4. Creates a **GPG-signed annotated tag `vX.Y.Z`** pointing at the release commit. +5. Pushes only the tag (`git push origin refs/tags/vX.Y.Z`). The release commit lives only as a tag-reachable object — no `main` update. +6. Cuts a GitHub Release from the tag and uploads `code-iq-X.Y.Z-cli.jar` with auto-generated release notes. + +The tag therefore points at the **exact source** that produced the artifact (no divergence between source tag and released artifact), and is annotated and GPG-signed — verifiable with `git tag --verify vX.Y.Z` provided the maintainer's public GPG key is trusted locally. + +Manual cuts on a fork or downstream consumer follow the same flow: trigger the workflow with the target version. Direct `git tag && git push origin vX.Y.Z` from a developer machine does **not** publish. + +--- + +## 4. Post-release verification + +Within 30 minutes of the release workflow finishing: + +1. **Maven Central index**: `curl -fsS "https://repo.maven.apache.org/maven2/io/github/randomcodespace/iq/code-iq/X.Y.Z/code-iq-X.Y.Z.pom" | head -20`. +2. **Smoke install**: in a clean directory, `mvn dependency:get -Dartifact=io.github.randomcodespace.iq:code-iq:X.Y.Z` succeeds. +3. **CLI smoke**: download the GH Release JAR, run `java -jar code-iq-X.Y.Z-cli.jar version` and `java -jar code-iq-X.Y.Z-cli.jar analyze --help` — both exit 0. +4. **GitHub Release** is marked `Latest` and links the changelog section. +5. **codebase.repoUrl** in paperclip Project still resolves (`git ls-remote git@github.com:RandomCodeSpace/codeiq.git HEAD`). + +If any of (1)–(4) fails, [`rollback.md`](rollback.md) applies. + +--- + +## 5. Hot-fix patch release (`X.Y.Z+1`) + +1. Branch from the release tag: `git switch -c hotfix/X.Y.Z+1 vX.Y.Z`. +2. Apply the minimal fix; add a regression test. +3. Open PR against `main`; merge with squash. +4. Rebase the hotfix branch onto the post-merge `main` if needed; cut the tag from `main` per §3. + +Hotfixes do **not** skip the pre-release checklist. + +--- + +## 6. Required GitHub secrets (org/repo level) + +| Secret | Used by | Owner | +|---|---|---| +| `SONAR_TOKEN` | `ci-java.yml` (Sonar gate) | TechLead | +| `OSS_NEXUS_USER` / `OSS_NEXUS_PASS` | `release-java.yml` (Sonatype Central) | TechLead | +| `MAVEN_GPG_KEY_ID` / `MAVEN_GPG_PASSPHRASE` / `MAVEN_GPG_PRIVATE_KEY` | `release-java.yml` (artifact signing) | TechLead | +| `CODEQL_*` | n/a — uses `GITHUB_TOKEN` | n/a | + +Rotation policy: any compromise → rotate immediately + audit recent `release-java.yml` runs. Routine rotation: annually, recorded in [`engineering-standards.md`](engineering-standards.md). + +--- + +## 7. Auth-blocked steps (escalation path) + +If a release step requires auth the runtime cannot satisfy, do **not** improvise: + +- Sonatype Central Portal namespace re-claim → block on board (human OAuth). +- GitHub org admin escalation (e.g., re-enable a disabled secret) → block on `aksOps` GitHub owner. +- OpenSSF Best Practices badge updates (if the badge is required for a Release announcement) → block on board to log into bestpractices.dev. + +In all cases: PATCH the relevant Paperclip issue to `blocked` with the exact ask, and `@`-mention the board. + +--- + +## 8. References + +- [`rollback.md`](rollback.md) — what to do when a release goes bad. +- [`first-time-setup.md`](first-time-setup.md) — how a new contributor builds and tests locally. +- [`engineering-standards.md`](engineering-standards.md) — coverage/Sonar/CVE policy SSoT. +- `pom.xml` — version, plugins, signing wiring. +- `.github/workflows/release-java.yml` — the actual release pipeline. diff --git a/shared/runbooks/rollback.md b/shared/runbooks/rollback.md new file mode 100644 index 00000000..2ecd3329 --- /dev/null +++ b/shared/runbooks/rollback.md @@ -0,0 +1,138 @@ +# Rollback Runbook — codeiq + +> **Purpose:** restore a known-good state when a `main` push, a release, or a CI/security change has broken the project. Owner: the engineer who shipped the change; TechLead is escalation. Pair this with [`release.md`](release.md). + +The rule of thumb: **revert first, root-cause second.** Users on Maven Central cannot un-install a bad version, so the cheap path is always to publish a clean follow-up rather than try to "fix forward" under pressure. + +--- + +## 1. Decide the scope + +| Symptom | Section | +|---|---| +| `main` is broken (CI red, build won't compile) | §2 — revert merge | +| A release is bad on Maven Central / GH Releases | §3 — release rollback | +| Branch protection / CI / security workflow change broke things | §4 — config rollback | +| Embedded Neo4j cache or `.codeiq/graph` corrupted on a user box | §5 — data rollback | +| A secret was exposed | §6 — secret rotation | + +If you are not sure, default to §2 and revert. + +--- + +## 2. Revert a bad merge to `main` + +```bash +git fetch origin main +git switch -c revert/ origin/main + +# Squash-merged PRs land as a single commit with one parent. Plain `git revert` +# applies — do NOT pass `-m`, which only makes sense for true (multi-parent) +# merge commits. Use `-m 1` only if `main` ever carries an actual merge commit +# (which the squash-merge-only branch protection should prevent). +git revert + +git push -u origin revert/ +gh pr create --base main --fill --title "revert: " --label "type:revert" +gh pr merge --squash --auto +``` + +Then watch `ci-java.yml` go green and confirm SonarCloud + downstream consumers recover. + +**Never** force-push `main`; the bootstrap branch protection forbids it and history rewrites would break every consumer's `git pull`. + +--- + +## 3. Roll back a release + +You cannot delete or overwrite a Maven Central artifact (Sonatype policy). The only valid rollback is a **new patch release** that returns the behaviour to the prior version. + +1. **Block downloads of the bad version** *(best-effort)*: + - GitHub Release: `gh release edit vX.Y.Z --prerelease --notes "Marked pre-release: see vX.Y.(Z+1) for fix"`. + - SECURITY.md / README badges: add a one-line advisory linking the follow-up version. +2. **Cut a hotfix patch release** per [`release.md`](release.md) §5. The hotfix MUST contain only the minimum changes needed to restore the prior behaviour, plus a regression test. +3. **GHSA advisory** if the bad version contains a security regression: `gh api -X POST repos/RandomCodeSpace/codeiq/security-advisories -f severity=high -f summary="..." -f description="..."`. Coordinate with the OpenSSF Scorecard advisory feed. +4. **Update CHANGELOG.md** under `[X.Y.(Z+1)]` with `### Fixed (rollback)` and link the bad-version commit + the GHSA. + +Do NOT delete the bad git tag. Yanking tags after they have been seen by consumers breaks `git fetch --tags` and reproducible builds. + +--- + +## 4. Roll back CI / branch protection / security config + +These are driven by `gh api` calls (see RAN-46 inventory). They are not in version control by themselves, so rollback is by re-running the prior call. + +- **Branch protection**: snapshot before any change with `gh api /repos/RandomCodeSpace/codeiq/branches/main/protection > /tmp/bp-before.json`. The GET payload is a denormalized view that GitHub's PUT endpoint does **not** accept verbatim (PUT flattens the nested objects: `enforce_admins.enabled` → bare boolean, `required_status_checks.checks[].context` strings → flat `contexts[]`, `*.url` fields are rejected). Reshape with the jq filter below before piping into PUT (Reviewer finding fd559a54, R5-5): + + ```bash + jq '{ + required_status_checks: ( + if .required_status_checks == null then null + else { + strict: .required_status_checks.strict, + contexts: ([.required_status_checks.checks[]?.context] // []) + } + end + ), + enforce_admins: (.enforce_admins.enabled // false), + required_pull_request_reviews: ( + if .required_pull_request_reviews == null then null + else { + dismiss_stale_reviews: (.required_pull_request_reviews.dismiss_stale_reviews // false), + require_code_owner_reviews: (.required_pull_request_reviews.require_code_owner_reviews // false), + required_approving_review_count: (.required_pull_request_reviews.required_approving_review_count // 1) + } + end + ), + restrictions: null, + required_linear_history: (.required_linear_history.enabled // false), + allow_force_pushes: (.allow_force_pushes.enabled // false), + allow_deletions: (.allow_deletions.enabled // false), + block_creations: (.block_creations.enabled // false), + required_conversation_resolution: (.required_conversation_resolution.enabled // false), + lock_branch: (.lock_branch.enabled // false), + allow_fork_syncing: (.allow_fork_syncing.enabled // false) + }' /tmp/bp-before.json \ + | gh api -X PUT /repos/RandomCodeSpace/codeiq/branches/main/protection --input - + ``` + + The transform unwraps the `{enabled: bool}` envelopes, projects `checks[].context` strings out into the flat `contexts[]` PUT expects, drops `*.url` fields, and forces `restrictions: null` (apps/teams/users restrictions are out of scope for this repo). If you need to *change* a field instead of rolling back, edit the transformed payload before piping. +- **CodeQL default setup**: re-toggle via Repository Settings → Code security → Code scanning. The disabled state is the safe default. +- **Dependabot security updates**: `gh api -X PUT /repos/RandomCodeSpace/codeiq/automated-security-fixes` to enable, `-X DELETE` to disable. +- **Workflow files** (`.github/workflows/*.yml`): revert via §2 — they are version-controlled. + +If a change to branch protection prevents you from merging the rollback PR (e.g., you made the wrong status check required and it never passes), `aksOps` is the only account with bypass; coordinate over the board channel before bypassing — every bypass is logged. + +--- + +## 5. User-side data rollback (analysis cache / Neo4j store) + +For users who hit a bad cache shape after upgrading: + +```bash +codeiq cache clear # drops .codeiq/cache/*.h2.db +rm -rf .codeiq/graph/graph.db # drops the enriched Neo4j store +codeiq index # rebuild +codeiq enrich +``` + +The `AnalysisCache.CACHE_VERSION` constant must be bumped in any release that changes the cache schema. If a release fails to bump it, rollback per §3 and ship a follow-up that bumps it. + +--- + +## 6. Secret rotation + +If a CI secret leaked (push protection alert, secret scanning hit, or external report): + +1. Rotate at the source: GitHub Settings → Secrets, regenerate at the upstream provider (Sonatype, Sonar, etc.). +2. Re-run the latest failing workflows after rotation: `gh run rerun `. +3. Force a new release if the leaked secret signed an artifact (re-cut the release with a fresh GPG key). +4. Open a tracking issue (`type:security`, `priority:high`) with the rotation timestamp, scope of exposure, and affected runs. + +--- + +## 7. After every rollback + +- Add a one-line entry to `CHANGELOG.md` describing what was rolled back and why. +- Open a follow-up Paperclip issue tagged `type:postmortem` referencing the rolled-back PR/release. The postmortem MUST land before the next release. +- Update [`engineering-standards.md`](engineering-standards.md) if the rollback exposed a missing CI gate or test layer.