Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions .github/workflows/semgrep-scheduled.yml
Original file line number Diff line number Diff line change
@@ -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
41 changes: 35 additions & 6 deletions .github/workflows/server-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"jsonRecursiveSort": true,
"jsonSortOrder": "{\"/.*/\": \"lexical\"}",
"plugins": ["prettier-plugin-sort-json"]
}
63 changes: 63 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions backend/.golangci.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 31 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
@@ -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 changes: 74 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions rules/no-console-log.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions rules/no-fmt-println.yml
Original file line number Diff line number Diff line change
@@ -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)
Loading