diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..9a9d9aa76 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +# CodeQL: static analysis for the whole tree (backend/ + frontend/). +# +# Both halves are interpreted TypeScript, so CodeQL's `build-mode: none` +# extracts them straight from source — no npm ci, no build step, and no +# autobuild guessing at the two-package layout. One analyze job covers +# backend and frontend together. The weekly cron re-runs analysis on an +# otherwise-quiet tree so newly shipped CodeQL queries still get applied. +# Findings land in the Security tab (code scanning), not as CI noise. +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Mondays 04:30 UTC. + - cron: "30 4 * * 1" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze (javascript-typescript) + runs-on: ubuntu-latest + permissions: + contents: read + # Required to upload results to code scanning. + security-events: write + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - uses: github/codeql-action/init@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4 + with: + languages: javascript-typescript + build-mode: none + + - uses: github/codeql-action/analyze@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4 + with: + category: "/language:javascript-typescript" diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml new file mode 100644 index 000000000..af3236529 --- /dev/null +++ b/.github/workflows/gitleaks.yml @@ -0,0 +1,45 @@ +# Secret scan: gitleaks over the full git history. +# +# Deliberately NOT gitleaks/gitleaks-action — that action requires a paid +# GITLEAKS_LICENSE for organization-owned repos. The scanner itself is a +# single MIT-licensed Go binary, so this downloads a pinned release and +# verifies its sha256 (from the upstream release checksums file) before +# running it. fetch-depth: 0 so every commit is scanned, not just the PR +# head — a secret that was committed and later deleted is still leaked. +# Known fake secrets (test fixtures, docs placeholders) are allowlisted +# in .gitleaks.toml at the repo root; anything new fails the run. +name: Secret scan + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + gitleaks: + name: gitleaks (full history) + runs-on: ubuntu-latest + env: + GITLEAKS_VERSION: 8.30.1 + # sha256 of gitleaks_8.30.1_linux_x64.tar.gz, from + # gitleaks_8.30.1_checksums.txt on the upstream release. + GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + # Full history — gitleaks scans commits, not the working tree. + fetch-depth: 0 + + - name: Download and verify gitleaks + run: | + curl -sSfL -o gitleaks.tar.gz \ + "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + echo "${GITLEAKS_SHA256} gitleaks.tar.gz" | sha256sum -c - + tar -xzf gitleaks.tar.gz gitleaks + + - name: Scan full history + run: ./gitleaks detect --no-banner --redact diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000..32e462e0a --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,43 @@ +# OpenSSF Scorecard: automated checks of this repo's own security posture +# (branch protection, token permissions, pinned dependencies, ...). +# +# Runs on pushes to main plus a weekly cron — Scorecard rates the repo, not +# a PR's diff, so there is no pull_request trigger. publish_results: true +# sends the score to the OpenSSF public API (which enables the README badge +# and is what other tools query); that upload is signed via OIDC, hence +# id-token: write. Results also land in the Security tab as SARIF. +name: Scorecard + +on: + push: + branches: [main] + schedule: + # Mondays 05:30 UTC. + - cron: "30 5 * * 1" + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to sign the published results (OIDC). + id-token: write + # Needed to upload SARIF to code scanning. + security-events: write + contents: read + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + with: + persist-credentials: false + + - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - uses: github/codeql-action/upload-sarif@a2983b8bed1923f44751c5c43237f479442827b3 # v3.37.4 + with: + sarif_file: results.sarif diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 000000000..2f585551e --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,37 @@ +# gitleaks config for the Secret scan workflow (.github/workflows/gitleaks.yml). +# +# Default rules apply everywhere. The allowlist below suppresses EXACT known +# fake secret values (anchored regexes tested against the found secret), not +# files: every entry is one verified fixture string. A real credential added +# anywhere — including to the files that hold these fixtures — would still +# fail the scan, because its value cannot equal a known fake. +# +# Deliberately NOT path-based. Verified against gitleaks 8.30.1 by planting +# a high-entropy secret in an allowlisted fixture file and scanning history: +# a path-only allowlist swallows it (0 leaks reported), this config flags it. +# Path entries also make `gitleaks dir` skip the whole file unread, so even +# a path+value AND combination is a trap for anyone scanning the worktree. +# +# The scan covers full git history, so entries must keep matching fixtures +# in old commits even if the files that hold them later move or change. + +[extend] +useDefault = true + +[[allowlists]] +description = "safeError unit-test fixtures: fake OpenAI-style key and fake token used to prove secrets get scrubbed from error output (backend/src/lib/__tests__/safeError.test.ts)" +regexes = [ + '''^sk-proj-abc123def456ghi789\.?$''', + '''^abcdef123456$''', +] + +[[allowlists]] +description = "Word add-in e2e fixture: mocked session token for the hermetic auth spec (word-addin/e2e/auth.spec.ts)" +regexes = ['''^valid-jwt-123$'''] + +[[allowlists]] +description = "Supabase's public demo JWTs (iss: supabase-demo, HS256-signed with the published demo secret — the well-known local-dev anon/service_role keys from Supabase's own docs) in .env.example / docker-compose.yml history" +regexes = [ + '''^eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9\.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0$''', + '''^eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0\.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU$''', +] diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..b6065bd75 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,45 @@ +# Security Policy + +## Supported versions + +| Version | Supported | +| --- | --- | +| tip of `main` | ✅ | +| tagged releases (`v0.1.0`–`v0.4.0`) | ❌ | + +Security fixes land only on `main`; the existing tags are historical +snapshots and do not receive backported fixes. If you are self-hosting, +please update to the latest `main` before reporting — the issue may already +be fixed. + +## Reporting a vulnerability + +Please report vulnerabilities privately through GitHub: + +**Security tab → Report a vulnerability** on this repository +(GitHub's private vulnerability reporting). + +Please do not open a public issue for anything security-sensitive. + +This project has a solo maintainer. You can expect an acknowledgment within +7 days; fixes are prioritized by severity after that. + +## Scope + +- Reports about the code, default configuration, and deployment guidance in + this repo are all in scope. +- The **hosted service at `app.mikeoss.com`** (the cloud version offered from + the official Mike website) is in scope — report issues affecting it through + the same private channel above, and please keep testing non-destructive: + only accounts and data you own, no denial of service, no access to other + users' data beyond the minimum proof needed. +- **Independent self-hosted installations** run by third parties are the one + exclusion: findings that only apply to how a specific outside operator has + deployed Mike (their infrastructure, their configuration) should go to + whoever operates that deployment. +- Mike is an **LLM legal product**, so LLM-specific reports are explicitly + welcome: prompt injection (including via uploaded documents), getting the + model to ignore its guardrails, leaking another user's data or system + prompts through model output, and similar. +- Secrets accidentally committed to this repository's history are also worth + a private report, even though CI runs a secret scanner.