diff --git a/.bestpractices.json b/.bestpractices.json new file mode 100644 index 0000000..4185c78 --- /dev/null +++ b/.bestpractices.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://bestpractices.coreinfrastructure.org/projects.schema.json", + "_comment": "OpenSSF Best Practices self-assessment for RandomCodeSpace/otelcontext (RAN-53). Project page: https://www.bestpractices.dev/en/projects/12646. The badge level transition from in_progress -> passing requires a board admin to log into bestpractices.dev with the OSS-Random identity and confirm the evidence pointers against the questionnaire. The self-assessment below is the evidence map for that review.", + "project_id": 12646, + "name": "otelcontext", + "description": "Self-hosted OTLP observability platform in a single Go binary — OTLP gRPC + HTTP ingest, GraphRAG-powered root-cause analysis, multi-tenant storage, and a built-in MCP server for AI agents.", + "homepage_url": "https://github.com/RandomCodeSpace/otelcontext", + "repo_url": "https://github.com/RandomCodeSpace/otelcontext", + "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", + "engineering_standards": "CLAUDE.md", + "license_file": "LICENSE.md", + "build_reproducible": "go build -o otelcontext .", + "ci_workflow": ".github/workflows/ci.yml", + "supply_chain_scorecard": ".github/workflows/scorecard.yml", + "dependency_updates": ".github/dependabot.yml", + "signed_commits": "scripts/setup-git-signed.sh", + "secret_scanning": ".github/workflows/security.yml (gitleaks job, full git history) + GitHub repo setting (secret_scanning + push_protection enabled — escalated to board)", + "static_analysis": "golangci-lint (.golangci.yml) + Semgrep (.github/workflows/security.yml, p/security-audit + p/owasp-top-ten + p/golang)", + "vulnerability_scanning": "OSV-Scanner (go.mod + ui/package-lock.json) + Trivy (filesystem) + Dependabot (.github/dependabot.yml)", + "sbom": ".github/workflows/security.yml (anchore/sbom-action — SPDX + CycloneDX, 90-day artifact retention)", + "duplication": ".github/workflows/security.yml (jscpd, threshold 3%, scoped to internal/ + ui/src/)" + }, + "audit": { + "self_assessment_date": "2026-04-26", + "self_assessment_author": "TechLead (RAN-53)", + "registration_blocker": "Badge promotion in_progress -> passing requires board admin OAuth at https://www.bestpractices.dev/. Tracked under RAN-53 acceptance item #1." + } +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..263f736 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,123 @@ +# Dependabot configuration for otelcontext. +# 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-53 AC #5 reactive channel. 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: + # ----- Go modules (the otelcontext binary) ----- + - package-ecosystem: "gomod" + 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: + otel: + patterns: + - "go.opentelemetry.io/*" + grpc-protobuf: + patterns: + - "google.golang.org/grpc" + - "google.golang.org/protobuf" + - "google.golang.org/genproto*" + gorm: + patterns: + - "gorm.io/*" + azure: + patterns: + - "github.com/Azure/*" + - "github.com/AzureAD/*" + prometheus: + patterns: + - "github.com/prometheus/*" + test-libs: + patterns: + - "github.com/stretchr/testify" + - "github.com/google/go-cmp" + + # ----- 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 ui/) ----- + - package-ecosystem: "npm" + directory: "/ui" + 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" + - "react-window" + - "@types/react*" + - "@types/react-window" + mantine: + patterns: + - "@mantine/*" + vite: + patterns: + - "vite" + - "@vitejs/*" + vitest-testing: + patterns: + - "vitest" + - "@testing-library/*" + - "jsdom" + echarts: + patterns: + - "echarts" + - "echarts-for-react" + eslint: + patterns: + - "eslint" + - "@eslint/*" + - "eslint-plugin-*" + - "typescript-eslint" + - "globals" + typescript: + patterns: + - "typescript" + - "@types/*" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..1a92bc1 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,67 @@ +# OpenSSF Scorecard supply-chain analysis. +# RAN-53 AC #4. Best-effort target — no hard numeric floor; Scorecard does not gate merge. +# Stretch target: >= 8.0/10. See CLAUDE.md "Security & Supply Chain" for baseline tracking. +# 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/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..963ea25 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,180 @@ +name: Security (OSS-CLI) +# OSS-CLI security stack per RAN-53 AC #5 (mirrors codeiq RAN-46 path B). +# Replaces Sonar + CodeQL + OWASP Dependency-Check. +# +# Six independent jobs — fail-fast off so all signals surface on a single run. +# All actions SHA-pinned per Scorecard `Pinned-Dependencies`. Top-level +# `permissions: read-all` per Scorecard `Token-Permissions`; jobs scope up +# only when needed (gitleaks needs full git history; sbom job uploads). +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '21 4 * * 1' # Mondays 04:21 UTC — catch newly-disclosed CVEs + +permissions: read-all + +jobs: + osv-scanner: + name: OSV-Scanner (SCA) + runs-on: ubuntu-latest + permissions: + contents: read + env: + OSV_SCANNER_VERSION: 2.3.5 + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + # Install osv-scanner from the official GitHub release (binary, not the + # action — google/osv-scanner-action's `action.yml` is composite-only and + # fails when invoked as a job step). Using the preinstalled `gh` CLI + # avoids any external `curl`/`wget`. + - name: Install osv-scanner + run: | + gh release download "v${OSV_SCANNER_VERSION}" \ + --repo google/osv-scanner \ + --pattern 'osv-scanner_linux_amd64' \ + --clobber + mv osv-scanner_linux_amd64 osv-scanner + chmod +x osv-scanner + ./osv-scanner --version + - name: Run osv-scanner (Go module + npm lockfile) + # Go's `gomod` lockfile parser in osv-scanner is stable (unlike v2's + # `pomxml` plugin which depends on the deps.dev gRPC service). Both + # ecosystems are covered here directly. + # + # The repo-root `package-lock.json` is an empty stub (no root + # package.json exists) and is intentionally excluded — scanning it + # only adds noise without coverage. + # + # AC §5 ("Zero High/Critical CVEs in dependency tree") is satisfied + # by the union of OSV-Scanner (Go + ui/ npm) + Trivy (filesystem, OS) + + # Dependabot (cross-ecosystem advisories on the Security tab). + run: | + ./osv-scanner \ + --lockfile=go.mod \ + --lockfile=ui/package-lock.json + + trivy: + name: Trivy (filesystem + container scan) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: fs + scan-ref: . + severity: HIGH,CRITICAL + exit-code: '1' + ignore-unfixed: true + + semgrep: + name: Semgrep (SAST) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.12' + - name: Install semgrep + run: python -m pip install --quiet --upgrade pip semgrep + - name: Run semgrep (security-audit + owasp-top-ten + golang) + run: | + semgrep scan \ + --error \ + --config p/security-audit \ + --config p/owasp-top-ten \ + --config p/golang \ + --severity ERROR \ + --metrics off + + gitleaks: + name: Gitleaks (secret scan) + runs-on: ubuntu-latest + permissions: + contents: read + env: + GITLEAKS_VERSION: 8.30.1 + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + with: + fetch-depth: 0 + # The official `gitleaks/gitleaks-action` requires a paid license for + # GitHub organisations. The underlying gitleaks CLI is MIT-licensed and + # free; install it directly from the upstream release. + - name: Install gitleaks + run: | + gh release download "v${GITLEAKS_VERSION}" \ + --repo gitleaks/gitleaks \ + --pattern "gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + --output gitleaks.tar.gz + tar -xzf gitleaks.tar.gz gitleaks + chmod +x gitleaks + - name: Run gitleaks (full git history) + run: ./gitleaks detect --source . --redact --no-banner --exit-code 1 + + jscpd: + name: jscpd (duplication < 3% on touched code) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: '20' + - run: | + # Scope jscpd to production code only: + # - internal/ — Go production code + # - ui/src/ — React/TS production code + # Tests, generated code, vendored deps, build artifacts, and the + # legacy internal/graph/ package (CLAUDE.md: "use internal/graphrag/ + # for all new graph work") are excluded — they would otherwise + # surface intentional shape-parallelism rather than refactoring + # opportunities. + # + # `--min-tokens 100` is a sensible Go floor: a meaningful Go + # function body is ~40-80 tokens; 100 corresponds to a real + # method body or a non-trivial code block, not import/struct + # boilerplate that 200+ Go files share by convention. + npx --yes jscpd@4 \ + --threshold 3 \ + --min-tokens 100 \ + --reporters consoleFull \ + --format "go,javascript,typescript,tsx" \ + --ignore "**/vendor/**,**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/internal/graph/**,**/*_test.go,**/*.test.ts,**/*.test.tsx,**/*.spec.ts,**/*.spec.tsx,**/internal/ui/embed_*.go" \ + internal ui/src + + sbom: + name: SBOM (SPDX + CycloneDX) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + - name: Generate SPDX SBOM + uses: anchore/sbom-action@fc46e51fd3cb168ffb36c6d1915723c47db58abb # v0.17.7 + with: + format: spdx-json + output-file: sbom.spdx.json + upload-artifact: false + - name: Generate CycloneDX SBOM + uses: anchore/sbom-action@fc46e51fd3cb168ffb36c6d1915723c47db58abb # v0.17.7 + with: + format: cyclonedx-json + output-file: sbom.cdx.json + upload-artifact: false + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4.6.2 + with: + name: sbom + path: | + sbom.spdx.json + sbom.cdx.json + retention-days: 90 diff --git a/CLAUDE.md b/CLAUDE.md index 564eb8c..3f98896 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -234,6 +234,47 @@ Failure-mode gauges (prefix `OtelContext_`): - `retention_last_success_timestamp` — Unix seconds; alert when stale relative to the hourly tick - `retention_rows_purged_total`, `retention_purge_duration_seconds`, `retention_vacuum_duration_seconds` — throughput and latency +## Security & Supply Chain + +OtelContext targets the OpenSSF Best Practices `passing` badge (project [12646](https://www.bestpractices.dev/en/projects/12646)) and ships a six-job OSS-CLI security stack — no Sonar, no CodeQL, no NVD-direct tooling. Cost: $0. + +### OSS-CLI security stack (`.github/workflows/security.yml`) + +| Concern | Tool | Gate | +|---|---|---| +| SCA (Go modules + npm) | OSV-Scanner against `go.mod` + `ui/package-lock.json` (OSV.dev / GHSA / ecosystem feeds; **not NVD**) | Block merge on High/Critical | +| SCA (filesystem + OS) + container scan | Trivy filesystem scan; Dependabot surfaces advisories on the Security tab | Block merge on `severity: HIGH,CRITICAL`, `exit-code: 1`, `ignore-unfixed: true` | +| SAST | Semgrep (`p/security-audit` + `p/owasp-top-ten` + `p/golang`) | Block merge on `--severity ERROR` | +| Secret scan | Gitleaks (full git history) | Block merge on any finding | +| Duplication | jscpd, threshold 3%, `--min-tokens 100`, scoped to `internal/` + `ui/src/`, excludes tests, vendor, build artifacts, and the legacy `internal/graph/` package | Block merge above threshold | +| SBOM | `anchore/sbom-action` (SPDX + CycloneDX) | Surface as 90-day artifact; do **not** gate merge | +| Lint (Go) | `golangci-lint` (existing `.golangci.yml`) | Wired into `ci.yml`, not security.yml | + +All actions are SHA-pinned per Scorecard `Pinned-Dependencies`. Top-level `permissions: read-all`; jobs scope up only when needed (gitleaks needs full history; sbom uploads). + +**Not used (do not re-introduce without an explicit board reversal):** SonarCloud / SonarQube, CodeQL (GHAS-paid for non-public repos), OWASP Dependency-Check (or any NVD-direct tool — NVD has analysis-backlog and rate-limit reliability problems). + +### OpenSSF Scorecard (`.github/workflows/scorecard.yml`) + +- **Schedule:** push to `main` + Mondays 06:00 UTC + manual `workflow_dispatch`. +- **Output:** SARIF → Security tab; results published to public Scorecard dashboard. +- **Hardening:** `step-security/harden-runner` (egress: audit), `actions/checkout` with `persist-credentials: false`. +- **Baseline:** to be measured after first push to `main`. Track via the Scorecard dashboard linked from the README badge. +- **Stretch target:** ≥ 8.0/10. Best-effort — Scorecard does **not** gate merge per the board ruling. The `passing` Best Practices badge is the only hard supply-chain gate. + +### Vulnerability reporting + +See [`SECURITY.md`](SECURITY.md). Preferred channel: GitHub Security Advisories at `https://github.com/RandomCodeSpace/otelcontext/security/advisories/new`. Email fallback: `ak.nitrr13@gmail.com` with subject prefix `[otelcontext security]`. + +### Signed commits & branch protection + +- Repo-local config helper: [`scripts/setup-git-signed.sh`](scripts/setup-git-signed.sh) — supports ssh, openpgp, and x509 signing; honours the contributor's existing global git identity. +- Branch protection on `main` requiring signed commits is configured at the GitHub repo level (board-admin action; not file-driven). When toggled on, every commit landing on `main` must verify. + +### Self-assessment evidence + +- [`.bestpractices.json`](.bestpractices.json) — OpenSSF Best Practices evidence map (project 12646, level `passing`, six categories self-assessed). The badge level transition from `in_progress` → `passing` requires a board admin to log into bestpractices.dev with the OSS-Random identity. + ## Build & Run ```bash diff --git a/README.md b/README.md index 27ac5de..927cf93 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # OtelContext -[![Security Scan](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/ci.yml) -[![OpenSSF Scan](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/ci.yml/badge.svg?branch=main&label=OpenSSF%20scan)](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/ci.yml) -[![OpenSSF Score](https://api.scorecard.dev/projects/github.com/RandomCodeSpace/otelcontext/badge)](https://scorecard.dev/viewer/?uri=github.com/RandomCodeSpace/otelcontext) +[![CI](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/ci.yml) +[![Security (OSS-CLI)](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/security.yml/badge.svg?branch=main)](https://github.com/RandomCodeSpace/otelcontext/actions/workflows/security.yml) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/RandomCodeSpace/otelcontext/badge)](https://scorecard.dev/viewer/?uri=github.com/RandomCodeSpace/otelcontext) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/12646/badge)](https://www.bestpractices.dev/projects/12646) [![Release](https://img.shields.io/github/v/release/RandomCodeSpace/otelcontext)](https://github.com/RandomCodeSpace/otelcontext/releases) [![Beta](https://img.shields.io/github/v/release/RandomCodeSpace/otelcontext?include_prereleases&label=beta)](https://github.com/RandomCodeSpace/otelcontext/releases) ![Go Version](https://img.shields.io/github/go-mod/go-version/RandomCodeSpace/otelcontext) @@ -112,6 +113,10 @@ See `docs/otel-collector-example.yaml` for a complete example. - **DLQ** — durable typed envelopes with disk-bounded replay. - **Self-instrumentation** — export OtelContext's own spans via `OTEL_EXPORTER_OTLP_ENDPOINT`. +## Security + +See [`SECURITY.md`](SECURITY.md) for the vulnerability reporting process. The security posture (OSV-Scanner, Trivy, Semgrep, Gitleaks, jscpd, SBOM, Scorecard) is described in [`CLAUDE.md`](CLAUDE.md) under "Security & Supply Chain". + ## License -See [LICENSE](LICENSE). +See [LICENSE.md](LICENSE.md). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d86d573 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,68 @@ +# Security Policy + +Thanks for helping keep otelcontext and its users safe. + +## Supported versions + +otelcontext is pre-1.0 and ships from `main`. The `main` branch is the only branch that receives security fixes; tagged releases are advisory snapshots. + +| Version | Supported | +|---|---| +| `main` (HEAD) | ✅ | +| Tagged pre-releases | Best-effort — please upgrade to `main` HEAD | + +If you are running an older snapshot, please pull `main` and reproduce against HEAD before reporting — the issue may already be fixed. + +## 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/otelcontext/security/advisories/new` (you must be signed in to GitHub). The advisory channel is monitored by the maintainer. +- **Email** — `ak.nitrr13@gmail.com`. Put `[otelcontext security]` in the subject so the report is triaged ahead of normal mail. + +Please include: + +- The otelcontext commit SHA or release tag (`./otelcontext --version` if available, otherwise `git rev-parse HEAD`). +- The shortest reproducer you can produce — a curl command, OTLP payload, or test case is ideal. +- Your assessment of impact (e.g., RCE, auth bypass, tenant isolation breakage, 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 **5 business days** of report receipt. +- A fix triage decision within **10 business days** for High/Critical issues. +- A coordinated disclosure timeline negotiated with you. The default embargo is **90 days** from acknowledgement, or until a patched release is published, whichever is sooner. +- Credit in the release notes (or anonymously, at your preference). + +## Scope + +In scope: + +- The otelcontext binary (single Go process serving OTLP gRPC `:4317`, HTTP API + OTLP HTTP + UI + MCP `:8080`). +- All packages under `internal/` (ingestion, storage, GraphRAG, MCP, API, telemetry). +- The embedded React frontend under `ui/`. +- The OTLP/MCP wire protocol surface as exposed by this binary. +- The DLQ (Dead Letter Queue) on-disk format. + +Out of scope (please report upstream): + +- Vulnerabilities in third-party OTLP clients or SDKs that send to otelcontext — report to the OpenTelemetry project. +- Vulnerabilities in supported relational databases (SQLite, PostgreSQL, MySQL, MSSQL) themselves — report to the database vendor. +- Misconfiguration that exposes the platform without `API_KEY` set in production — that is operator error, not a vulnerability. The README is explicit about TLS + `API_KEY` for any non-dev deployment. + +## Hardening references + +- [`CLAUDE.md`](CLAUDE.md) — architecture, multi-tenancy model, configuration surface. +- [`.bestpractices.json`](.bestpractices.json) — OpenSSF Best Practices self-assessment evidence map. +- [`.github/workflows/scorecard.yml`](.github/workflows/scorecard.yml) — OpenSSF Scorecard supply-chain analysis (push + weekly). +- [`.github/workflows/security.yml`](.github/workflows/security.yml) — OSS-CLI security stack (OSV-Scanner, Trivy, Semgrep, Gitleaks, jscpd, SBOM). +- [`.github/dependabot.yml`](.github/dependabot.yml) — dependency update + security update channels. +- [`scripts/setup-git-signed.sh`](scripts/setup-git-signed.sh) — repo-local config for signed commits on `main`. + +## Changelog + +| Date | Change | +|---|---| +| 2026-04-26 | Initial policy under [RAN-53](/RAN/issues/RAN-53). | diff --git a/go.mod b/go.mod index 3c6679a..010b936 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/RandomCodeSpace/otelcontext -go 1.25.0 +go 1.25.9 require github.com/RandomCodeSpace/central-ops v0.1.0 @@ -10,7 +10,7 @@ require ( github.com/coder/websocket v1.8.14 github.com/glebarez/go-sqlite v1.21.2 github.com/glebarez/sqlite v1.11.0 - github.com/jackc/pgx/v5 v5.7.2 + github.com/jackc/pgx/v5 v5.9.2 github.com/joho/godotenv v1.5.1 github.com/klauspost/compress v1.18.5 github.com/microsoft/go-mssqldb v1.9.7 @@ -19,14 +19,14 @@ require ( github.com/testcontainers/testcontainers-go/modules/postgres v0.42.0 github.com/tmc/langchaingo v0.1.14 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 - go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 - go.opentelemetry.io/otel/metric v1.42.0 - go.opentelemetry.io/otel/sdk v1.42.0 - go.opentelemetry.io/otel/sdk/metric v1.42.0 - go.opentelemetry.io/otel/trace v1.42.0 + go.opentelemetry.io/otel/metric v1.43.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/sdk/metric v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 go.opentelemetry.io/proto/otlp v1.9.0 golang.org/x/sync v0.19.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 diff --git a/go.sum b/go.sum index 754dd16..246796c 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= -github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -257,22 +257,22 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/scripts/setup-git-signed.sh b/scripts/setup-git-signed.sh new file mode 100755 index 0000000..038f1d4 --- /dev/null +++ b/scripts/setup-git-signed.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +# scripts/setup-git-signed.sh +# +# Apply the repo-local git config required by RAN-53 acceptance #6 (signed +# commits on main). 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. +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) +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/ui/package-lock.json b/ui/package-lock.json index 9cb4d4d..9a0542e 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -2128,9 +2128,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2483,9 +2483,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -3706,9 +3706,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -3719,9 +3719,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -4482,9 +4482,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": {