This commit is contained in:
BillyOutlast 2026-08-26 09:30:32 -04:00 committed by GitHub
commit fdc3bd556c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 404 additions and 6 deletions

73
.github/workflows/security.yml vendored Normal file
View file

@ -0,0 +1,73 @@
name: Security
on:
push:
branches: [develop]
pull_request:
branches: [develop]
schedule:
# Weekly Monday 06:06 UTC — catches new advisories between PRs.
- cron: "6 6 * * 1"
workflow_dispatch:
permissions:
contents: read
jobs:
gitleaks:
name: Secret scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
with:
fetch-depth: 0 # full history for leak detection
# Direct CLI invocation instead of gitleaks/gitleaks-action, which
# requires a paid license key for organization repos.
- name: Install gitleaks
run: |
curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz | tar -xz -C /usr/local/bin gitleaks
- name: Run gitleaks
run: gitleaks detect --redact --verbose
cargo-audit:
name: Rust advisory audit
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
crate:
- cli
- torrential
- desktop/src-tauri
- libraries/droplet
- libraries/droplet_types
- libraries/libarchive
- libraries/native_model
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
- uses: dtolnay/rust-toolchain@7c8d7d138f5c09cef361f8214cf96882cd029cdb # dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@4b8992af23a2be34fd58f8d9b50d3f4939ad1891 # taiki-e/install-action@cargo-audit
- name: Audit ${{ matrix.crate }}
# Some vendored crates gitignore their Cargo.lock; generate one on the
# fly so `cargo audit` has a manifest to scan.
run: |
if [ ! -f Cargo.lock ]; then cargo generate-lockfile; fi
cargo audit --file Cargo.lock
working-directory: ${{ matrix.crate }}
golangci:
name: Go lint (backend)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
with:
fetch-depth: 0 # needed for --new-from-rev against origin/develop
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # actions/setup-go@v5
with:
go-version-file: backend/go.work
- uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # golangci/golangci-lint-action@v8
with:
working-directory: backend
# Rollout mode: only report issues on changed lines. Remove once the
# existing debt (2 issues in core/database.go) is cleaned up.
args: --new-from-rev=origin/develop ./core/...

27
.github/workflows/semgrep-scheduled.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: Semgrep (scheduled)
# Deep security scan on a weekly schedule — deliberately not wired into git
# hooks or push/PR CI, where its runtime would be too disruptive.
on:
schedule:
- cron: "30 5 * * 1" # weekly Monday 05:30 UTC
workflow_dispatch:
permissions:
contents: read
security-events: write
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
- name: Scan
run: semgrep scan --config p/default --sarif --output semgrep.sarif
- name: Upload SARIF
if: always()
uses: github/codeql-action/upload-sarif@6d786de4d6f3531a740e445b53a42b622bbbace8 # github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif

View file

@ -29,13 +29,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # pnpm/action-setup@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # actions/setup-node@v4
with:
node-version: lts/*
cache: "pnpm"
@ -52,13 +52,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # pnpm/action-setup@v4
- name: Setup Node.js environment
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # actions/setup-node@v4
with:
node-version: lts/*
cache: "pnpm"
@ -69,3 +69,32 @@ jobs:
- name: Lint
working-directory: server
run: pnpm run lint
knip:
name: Dependency & export report (non-blocking)
runs-on: ubuntu-latest
continue-on-error: true # report-only rollout phase; remove once triaged
steps:
- name: Check out the repo
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # pnpm/action-setup@v4
- name: Setup Node.js environment
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # actions/setup-node@v4
with:
node-version: lts/*
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Knip report
run: pnpm exec knip --reporter json > knip-report.json || true
- name: Upload knip report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # actions/upload-artifact@v4
with:
name: knip-report
path: knip-report.json

5
.prettierrc.json Normal file
View file

@ -0,0 +1,5 @@
{
"jsonRecursiveSort": true,
"jsonSortOrder": "{\"/.*/\": \"lexical\"}",
"plugins": ["prettier-plugin-sort-json"]
}

63
CLAUDE.md Normal file
View file

@ -0,0 +1,63 @@
# Drop
Self-hosted game distribution platform. Polyglot monorepo:
- `server/` — Nuxt 3 + Vue 3 app (TypeScript, Prisma, Tailwind, buf/protobuf)
- `backend/` — Go backend (go.work, module under `core/`)
- Rust crates: `cli/` (downpour), `torrential/`, `desktop/src-tauri/`, `libraries/{droplet,droplet_types,libarchive,native_model}`
- pnpm workspace: root + `server/`, `libraries/base/`, `sites/*`, `desktop/`
## Quality gates — what runs when
| Layer | When | What |
| ------------------- | ---------- | ------------------------------------------------------------------------------------- |
| Claude Code hooks | every edit | format-on-edit (advisory) |
| lefthook pre-commit | commit | prettier + eslint --fix on staged files, ast-grep scan, gitleaks |
| lefthook pre-push | push | server typecheck, clippy (changed crates), golangci-lint, knip report |
| GitHub Actions | PR/push | typecheck/lint/clippy + gitleaks history scan + cargo-audit ×7 crates + golangci-lint |
| GitHub Actions | weekly | semgrep deep scan → Code Scanning |
Hooks are early feedback; **CI is the authority**. Never disable a hook to make a
failure go away — read the output and fix it.
### Escape hatches
```sh
git commit --no-verify # skip pre-commit once
git push --no-verify # skip pre-push once
LEFTHOOK=0 git commit # same via env var
```
## Commands
```sh
pnpm exec lefthook run pre-commit --all-files # dry-run all pre-commit checks
pnpm exec lefthook run pre-push --all-files # dry-run all pre-push checks
pnpm exec ast-grep scan [paths] # structural lint (sgconfig.yml)
pnpm exec ast-grep scan --json # machine-readable output
pnpm exec knip --reporter compact # unused deps/exports/files report
cd backend && golangci-lint run ./core/... # Go lint (.golangci.yml in backend/)
gitleaks protect --staged # secret scan staged changes
```
Native binaries NOT installable via pnpm:
- `gitleaks``brew install gitleaks`
- `cargo-audit``cargo install cargo-audit`
- `golangci-lint``go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest`
(must be built with the same Go version as `backend/go.work` or typechecking fails)
## Rollout status (flip these when clean)
- **knip**: report-only (`continue-on-error` in CI, `|| true` in hook). Baseline:
117 unused files / 31 unused exports / 4 unused deps. Remove the escape hatches
to enforce.
- **golangci-lint**: `--new-from-rev=origin/develop` (new issues only). Baseline:
2 legacy issues in `core/database.go`. Remove flag when count is 0.
- **ast-grep** rules: `severity: warning`. Promote per-rule to `error` after cleanup.
## Conventions
- Rust: nightly toolchain (matches CI); clippy with `-D warnings`.
- Formatting: Prettier config at repo root (shared by all JS/TS/Vue packages).
- Go: format with gofmt/goimports; keep modules inside `backend/` consistent with `go.work`.

27
backend/.golangci.yml Normal file
View file

@ -0,0 +1,27 @@
# golangci-lint v2 configuration.
# Run: cd backend && golangci-lint run
# CI: golangci-lint-action@v8 with --new-from-rev=origin/develop (rollout mode).
version: "2"
run:
timeout: 5m
linters:
default: none
enable:
- gosec # security-focused linter
- errcheck
- govet
- staticcheck
- ineffassign
- unused
exclusions:
rules:
- path: _test\.go
linters:
- gosec # test fixtures routinely trip G201/G404 etc.
formatters:
enable:
- gofmt
- goimports

31
knip.json Normal file
View file

@ -0,0 +1,31 @@
{
"$schema": "./node_modules/knip/schema.json",
"workspaces": {
".": {
"entry": ["lefthook.yml"],
"ignoreDependencies": [
"lefthook",
"knip",
"@ast-grep/cli",
"prettier",
"prettier-plugin-sort-json"
]
},
"desktop": {},
"libraries/base": {},
"server": {
"entry": [
"app.vue",
"error.vue",
"nuxt.config.ts",
"plugins/**/*.{ts,js}",
"middleware/**/*.{ts,js}",
"layouts/**/*.vue"
],
"ignore": [".nuxt/**", ".output/**", ".data/**", "build/**"],
"project": ["**/*.{ts,js,mjs,vue}"]
},
"sites/docs": {},
"sites/promo": {}
}
}

74
lefthook.yml Normal file
View file

@ -0,0 +1,74 @@
# Lefthook git hooks for the Drop monorepo.
# Docs: https://lefthook.dev
# Escape hatches: git commit/push --no-verify, or LEFTHOOK=0
#
# Commands with a glob that matches no staged files are skipped entirely,
# so e.g. a Markdown-only commit only runs prettier + gitleaks.
pre-commit:
parallel: true
commands:
# Format only staged files, then re-stage them.
prettier:
glob: "*.{js,cjs,mjs,ts,tsx,vue,json,jsonc,css,scss,html,yaml,yml,md}"
exclude:
- "pnpm-lock.yaml"
- "**/generated/**"
run: pnpm exec prettier --write --ignore-unknown {staged_files}
stage_fixed: true
# Scoped to server/ — the only package with an eslint flat config today.
# root: makes {staged_files} relative to server/, so the config resolves.
eslint:
root: server/
glob: "server/**/*.{js,cjs,mjs,ts,tsx,vue}"
run: pnpm exec eslint --fix {staged_files}
stage_fixed: true
# Structural lint rules (sgconfig.yml). Warnings only during rollout.
ast-grep:
glob: "*.{ts,tsx,vue,go}"
run: pnpm exec ast-grep scan {staged_files}
# Secret scan on the diff about to be committed. Warns if the binary is
# missing locally — CI enforces this authoritatively (security.yml).
gitleaks:
run: |
if command -v gitleaks >/dev/null 2>&1; then
gitleaks protect --staged --redact --verbose
else
echo "::warning:: gitleaks not installed locally; CI enforces. Install: brew install gitleaks"
exit 0
fi
pre-push:
parallel: true
commands:
# Whole-project typecheck; gated on any server TS/Vue change.
typecheck:
root: server/
glob: "server/**/*.{ts,tsx,vue}"
run: pnpm run typecheck
# Per-crate clippy over changed crates only (-D warnings, matching droplet-ci).
# desktop/src-tauri is excluded locally (needs Tauri system libs); CI covers it.
clippy:
glob: "*.rs"
run: ./scripts/clippy-changed.sh
# New-issues-only during rollout: backend/ has never been linted before.
# Compares against origin/develop; remove --new-from-rev once clean.
golangci-lint:
root: backend/
glob: "backend/**/*.go"
run: |
base="$(git rev-parse --verify origin/develop 2>/dev/null || echo "")"
if [ -n "$base" ]; then
golangci-lint run --new-from-rev="$base" ./...
else
golangci-lint run ./...
fi
# Report-only by design (rollout phase): prints findings, never fails.
knip:
run: pnpm exec knip --reporter compact || true

12
rules/no-console-log.yml Normal file
View file

@ -0,0 +1,12 @@
id: no-console-log
message: Use the project logger (pino) instead of console.log
severity: warning # promote to error once codebase is clean
language: TypeScript
rule:
pattern: console.log($$$ARGS)
files:
- server/**
ignores:
- "**/*.d.ts"
- "**/*.spec.ts"
- "**/*.test.ts"

6
rules/no-fmt-println.yml Normal file
View file

@ -0,0 +1,6 @@
id: no-fmt-println
message: Use a structured logger instead of fmt.Println
severity: warning
language: Go
rule:
pattern: fmt.Println($$$ARGS)

46
scripts/clippy-changed.sh Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Run `cargo clippy -D warnings` only over crates that contain changed .rs files.
# Called by lefthook pre-push. desktop/src-tauri is intentionally excluded:
# it needs Tauri system libraries that contributors on minimal setups won't have.
# CI covers it in droplet-ci.yml / torrential-ci.yml.
set -uo pipefail
# Resolve the repo root regardless of where the hook runs.
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root" || exit 1
# :colon: curls are handled by set -e semantics below.
crates=(
cli
torrential
desktop/src-tauri
libraries/droplet
libraries/droplet_types
libraries/libarchive
libraries/native_model
)
# Accumulate changed .rs files relative to repo root.
changed_rs="$(git diff --name-only HEAD -- '*.rs' 2>/dev/null; git diff --cached --name-only -- '*.rs' 2>/dev/null)"
# Falls back to previous-branch commit when @{push} isn't resolvable.
if [ -z "$changed_rs" ]; then
upstream="$(git rev-parse --verify '@{push}' 2>/dev/null || git rev-parse --verify 'HEAD~1' 2>/dev/null || echo "")"
if [ -n "$upstream" ]; then
changed_rs="$(git diff --name-only "$upstream..HEAD" -- '*.rs' 2>/dev/null)"
fi
fi
if [ -z "$changed_rs" ]; then
echo "clippy-changed: no .rs changes; skipping"
exit 0
fi
status=0
for dir in "${crates[@]}"; do
if printf '%s\n' "$changed_rs" | grep -q "^${dir}/"; then
echo "==> cargo clippy (${dir})"
(cd "$dir" && cargo clippy --all-targets --all-features -- -D warnings) || status=1
fi
done
exit "$status"

5
sgconfig.yml Normal file
View file

@ -0,0 +1,5 @@
# ast-grep configuration. Rules live in ./rules.
# Scan: pnpm exec ast-grep scan [paths]
# Machine-readable: pnpm exec ast-grep scan --json
ruleDirs:
- rules