Alex Dunmow 8418d2535b allowlist CMS registry browse/detail fetch() to orchestrator endpoints
The CMS registry browse tab and new Phase-5 detail view call the
orchestrator's public Connect endpoints (PluginRegistryService /
PluginReviewService) on a different origin. The CMS transport and generated
hooks only cover the CMS's own proto surface, so these are plain JSON POSTs
to the registry URL — same rationale routes/admin/plugins.tsx carried before
the BrowseRegistryTab extraction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:42:08 +08:00
2026-06-18 16:46:53 +08:00
2026-06-18 16:46:53 +08:00

check-safety

Static safety checker for the BlockNinja codebase. Walks a target tree, runs 31 invariant checks across Go and frontend sources, and exits non-zero on any violation.

Lives at ~/src/blockninja/check-safety/ as a standalone Go module, alongside cms/, orchestrator/, core/, the plugins, and the sites. It is intentionally standalone — no imports from sibling repos except two vendored packages (see Vendored packages).

Run

# Scan CMS (default target)
make run

# Scan a different repo
make run TARGET=../orchestrator

# Direct invocation — first positional arg is the target dir (default ".")
go run . ../sites/bidbuddy
go run . ~/src/blockninja/cms

If you run it against the top of the consolidated BlockNinja repo, it prints a hint to pass --plugin-dir <path> (or run inside the plugin directory) so plugin-specific checks fire.

CLI flags

Flag Meaning
<target-dir> (positional) Repo/subtree to scan. Defaults to .. If it contains plugin.mod, it is auto-registered as a plugin root so plugin checks run.
--orchestrator Scan the orchestrator backend only (resolved from the hardcoded consolidated layout), regardless of the positional target.
--plugin-dir <path> [more…] Register one or more plugin roots (dirs with plugin.mod). Alias: --plugin-dirs. Consumes args until the next --flag.
--plugin-pages <dir> [more…] Register frontend source dirs directly as plugin page targets (for the frontend checks). Consumes args until the next --flag.
--verbose / -v Print every check (including OK/SKIP), not just failures and warnings.
--all-any The any-usage check (2e) defaults to only the unstaged working-tree diff (newly introduced any); --all-any scans every source file instead.

The CLI contract (check-safety <target-dir> [--flags]) is stable — the CMS Makefile safety-check / install-safety-checker targets shell into this directory and depend on it.

Output

Output is concise by design (it is usually fed back into an agent's context). Passing and skipped checks print nothing; only FAIL/WARN/ERR checks print a <STATUS> <id> <summary> line with their findings indented beneath, followed by one tally line. A clean run is two lines:

check-safety /home/alex/src/blockninja/cms
32 checks: 20 ok 12 skip -> OK

A run with problems:

check-safety /home/alex/src/blockninja/cms
FAIL 15 1 err.Error() leak(s) to HTTP clients — log via slog.Error() and return a user-friendly message
  internal/service/handler.go:27 http.Error(w, err.Error(), ...) — leaks internal error
WARN 2e 1 any usage in changed lines (showing 1, --all-any to scan all)
  web/src/api.ts:12 [go] data: any

32 checks: 27 ok 3 skip 1 warn 1 fail -> FAIL

Pass --verbose to see every check's status. Exit code is 1 on any FAIL, 2 on a hard ERR, 0 otherwise (warnings do not fail the run).

Install globally

make install   # go install . → $GOPATH/bin/check-safety
check-safety ~/src/blockninja/cms

Test

make test         # go test -count=1 ./... (some tests shell out to npm/tsc/golangci-lint and skip if absent)
make test-short   # go test -short — skip the slow ones
make test-update  # regenerate the golden snapshots after intentional output changes
make tidy         # go mod tidy
make build        # go build -o check-safety .
make clean        # remove the built binary

golden_test.go is a characterisation snapshot test: it runs the whole binary against the fixtures under testdata/golden/ and diffs stdout + exit code. If you intentionally change a check's output, run make test-update and commit the regenerated fixture.

What it checks

Checks self-register via init() and run in Seq order. The full set (the ID is what the tool prints in each check header):

ID Check What it enforces
1 Secret env var reads Secret env vars (JWT_SECRET, VAULT_*, encryption keys, …) are read only inside config.Load() (config/config.go) or _test.go files.
2 RBAC registration Every RPC method is registered in the RBAC interceptor. Internal / plugin-managed services (e.g. ManagementService, Symposium's WikiService) are exempted.
2b Plugin proto ownership Plugins own their proto/RBAC definitions correctly (no poaching of core proto packages).
2c Plugin SDK boundaries Standalone plugins import only the published block/core SDK boundary — verified via imports, go.mod, and version.
2d sqlc UUID overrides sqlc UUID overrides in standalone plugins and cmd configs must use github.com/google/uuid.
2e any usage Warns on any usage in Go and TypeScript (capped at 120 printed warnings).
2f Codegen freshness sqlc compile and buf generate must succeed for every scanned root that defines them (generated code is up to date).
3 Go lint Go code compiles and passes go fix, golangci-lint --fix, go vet, and the strict lint checks.
3b Orchestrator tests Orchestrator backend tests must pass when checking the core BlockNinja repo.
4 Frontend lint Frontend code is auto-formatted, lint-clean, and typecheck-clean.
5 ConnectRPC hooks Frontend uses the generated ConnectRPC hooks — no hand-crafted API clients.
6 No hardcoded colors No hardcoded colors in the frontend — use theme tokens.
7 Tab state in URL No useState for tab state in route files — use URL ?tab= params.
8 API subpath imports Import @block-ninja/api from subpaths only, never the package root.
9 pnpm only No npm/yarn lockfiles — pnpm is the only package manager.
10 Button automation Interactive buttons carry the required automation attributes.
10b No eslint-disable No eslint-disable-next-line comments (fix the lint, don't suppress it).
10disc Plugin discovery Plugin frontend pages are discoverable by the loader.
11 No placeholder code No placeholder / stub code — only shipped features.
12 No reinvented utils No reinvented utilities — use the shared helpers.
13 RPC error handling RPC query/mutation error handling is present and correct.
14 No raw SQL No raw SQL outside sqlc / Bob.
15 No leaked errors No err.Error() leaked to HTTP clients.
16 Directory structure Services and handlers live in their correct directories.
17 No TODO markers No TODO markers in production code.
18 Plugin segmentation Plugin segmentation is respected (safety-rules.yml).
19 Auth precedence Bearer token takes precedence over cookies in the auth middleware.
20 Tailwind v4 Tailwind v4 configuration is correct (PostCSS plugin, CSS directives, @config).
21 Preset validation Plugin presets.json type-safe-unmarshals against theme.Theme.
22 HTML sanitization No hand-rolled HTML sanitization — use bluemonday.
28 Admin toolbar Public page handlers inject the admin toolbar data.

The canonical numbered list also lives in the comment block at the top of main.go; keep the two in sync when adding or renumbering a check.

How it stays in sync with CMS

internal/helpers/deferlog.go and internal/theme/*.go are vendored copies from CMS (cms/backend/internal/{helpers,theme}/). Go's internal/ rule blocks direct imports across modules, so they are copied in rather than imported. When CMS changes the theme schema or LogDeferredError, re-copy them — that drift is precisely what the preset-validation check (21) surfaces.

blockNinjaRepoRoot() and orchestratorRepoRoot() in lint_pipeline.go hardcode the consolidated layout (~/src/blockninja/{cms,orchestrator}). Update them if the tree layout changes.

Adding a new check

  1. Add a check_<name>.go file with a runCheck<Name> func and an init() that calls register(Check{Seq, ID, Title, Run}).
  2. Add the rule logic in a <rule>.go file, plus a <rule>_test.go for unit coverage.
  3. Pick a Seq value that slots it into the desired run order.
  4. Update the numbered comment block at the top of main.go and the table above.
  5. If the output is deterministic, add a fixture under testdata/golden/<case>/ and run make test-update to regenerate the snapshot.
Description
No description provided
Readme 436 KiB
Languages
Go 99.9%