Skip to content

feat (cloud): cloud bridge + shared session plugin + guest-report backend#663

Open
arberx wants to merge 5 commits into
mainfrom
arberx/aero-owner-view
Open

feat (cloud): cloud bridge + shared session plugin + guest-report backend#663
arberx wants to merge 5 commits into
mainfrom
arberx/aero-owner-view

Conversation

@arberx
Copy link
Copy Markdown
Member

@arberx arberx commented May 30, 2026

Adds the OSS-side foundation for Canonry Hosted — all additive and env-gated off, so OSS/local behavior is unchanged. Rebased onto current main; the /aero UI experiment and cloud-mode SPA churn from the original branch are dropped (net backend only).

What's included

  • Track 3 cloud bridge/api/v1/cloud/* routes (bootstrap, events, google-tokens, bing-key) gated behind CANONRY_ENABLE_CLOUD_BOOTSTRAP + X-Admin-Scope (return 404 in OSS, fingerprint-resistant). cloud_metadata singleton table. Outbound emitCloudEvent/emitConnectionEvent signed webhooks — inert when no subscriber exists.
  • Track 1 cloud runtimeprovider_token_usage telemetry table, populated from provider usage blocks; consumed by the control plane for billing.
  • Shared session plugin/session* extracted into packages/api-routes so the local daemon and apps/api (Cloud Run) share it; apps/api persists the dashboard password in the new app_settings table.
  • Guest-report backend/api/v1/guest/report* (anonymous audit + SSE), users + guest_reports tables. Backend only; the /aero UI ships separately.
  • Quota-lease client (OSS→control-plane) — degrades to standalone when no control plane is configured.

Migrations

The 5 hosted migrations are renumbered v73–77 to sit after main's GBP migrations (v67–72) — no version collisions.

Tests

Cloud bootstrap/bing/google routes, outbound emit gating + envelope contract, session, and guest-report all covered. Full suite green: 4110 tests pass, typecheck + lint clean. Version 4.67.0 → 4.68.0.

🤖 Generated with Claude Code

…gin + guest-report backend

Rebases the long-running aero-owner-view work onto current main (the /aero
UI experiment and cloud-mode SPA churn are dropped — net backend only). The
5 hosted migrations are renumbered v73–77 to sit cleanly after main's GBP
migrations (v67–72).

What this adds (all additive, OSS-safe, env-gated off by default):

- Track 3 cloud bridge — `/api/v1/cloud/*` routes (bootstrap, events,
  google-tokens, bing-key) gated behind `CANONRY_ENABLE_CLOUD_BOOTSTRAP` +
  `X-Admin-Scope`; return 404 in OSS. `cloud_metadata` singleton table.
  Outbound `emitConnectionEvent`/`emitCloudEvent` signed webhooks — inert
  with no subscriber (standalone OSS emits nothing).
- Track 1 cloud runtime — `provider_token_usage` telemetry table, populated
  from provider usage blocks; read by the control plane for billing.
- Session plugin — extracted `/session*` into packages/api-routes so the
  local daemon and apps/api (Cloud Run) share it; apps/api persists the
  dashboard password in the new `app_settings` table.
- Guest-report backend — `/api/v1/guest/report*` (anonymous /aero audit +
  SSE), `users` + `guest_reports` tables. Backend only; the /aero UI ships
  on a separate track.
- Quota-lease client (OSS→control-plane), degrades to standalone when no
  control plane is configured.
- Tests: cloud bootstrap/bing/google + outbound emit gating & envelope
  contract + session + guest-report. Full suite green (4110 tests).

Migrations renumbered once (not per-commit) to avoid version collisions
with main. SDK regenerated. Version 4.67.0 → 4.68.0.
Comment thread apps/api/src/app.ts
const DASHBOARD_PASSWORD_KEY = 'dashboard_password_hash'

function hashApiKey(key: string): string {
return crypto.createHash('sha256').update(key).digest('hex')
Comment thread packages/api-routes/src/guest-report.ts Fixed
Comment thread packages/api-routes/src/guest-report.ts Fixed
const DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024

function hashApiKey(key: string): string {
return crypto.createHash('sha256').update(key).digest('hex')
Comment thread packages/api-routes/src/session.ts Fixed
Comment thread packages/api-routes/src/session.ts Fixed
@arberx arberx changed the title Canonry Hosted v1 (OSS-side): cloud bridge + shared session plugin + guest-report backend feat (cloud): cloud bridge + shared session plugin + guest-report backend May 30, 2026
arberx added 2 commits May 30, 2026 20:04
Resolves the legit findings from the GitHub Advanced Security review:

- ReDoS (CodeQL #74) — `normalizeDomain` (anonymous /guest/report) rewritten
  from a backtracking regex to linear string ops (indexOf/slice/split). Added
  a unit test that a 200k-char all-slashes input returns in <100ms.
- Missing rate limiting (CodeQL #78/#79/#80) — added a small per-registration
  in-memory fixed-window limiter (`createRateLimiter` in helpers.ts) and wired
  it to the brute-force-exposed routes: password login + first-time setup
  (10/min), logout (30/min), guest-report create (15/min) + claim (20/min).
  Limits sit well above legitimate use; tests cover trip-at-11 and a normal
  burst staying under the cap.

Documented (not changed) the two "insufficient hash effort" findings
(CodeQL #75/#76): `hashApiKey` SHA-256 is correct for 128-bit random `cnry_`
tokens (no wordlist to brute-force; a slow KDF would only add per-request
latency). Dashboard passwords already use salted scrypt. False positive for
opaque tokens — added a comment in session.ts + apps/api/app.ts.

No version bump (same unreleased 4.68.0 PR). api-routes suite: 1153 pass.
@arberx
Copy link
Copy Markdown
Member Author

arberx commented May 31, 2026

Addressed the CodeQL review in f2c5806:

Fixed

Won't fix — false positive (documented in code)

  • Insufficient hash computational effort (Indexing sweep — site: operator queries for content coverage monitoring #75/Auto-discover and sync sitemaps from GSC Sitemaps API #76) — the flagged hashApiKey hashes the cnry_… API key, which is a 128-bit cryptographically-random token, not a user password. Fast SHA-256 is the correct choice for opaque high-entropy tokens — there is no wordlist to brute-force, and a slow KDF would only add latency to every authenticated request. Dashboard passwords already use salted scrypt (hashDashboardPassword). Added clarifying comments in session.ts + apps/api/app.ts. (The legacy SHA-256 password-verify path that the dataflow also touches is intentional and self-healing — it accepts a pre-scrypt hash once, then rehashes to scrypt on successful login.)

Suggest dismissing #75/#76 as "won't fix (false positive — opaque token, not a password)".

Comment thread packages/api-routes/src/guest-report.ts Fixed
Comment thread packages/api-routes/src/session.ts Fixed
Comment thread packages/api-routes/src/session.ts Fixed
…limiting

The re-scan kept flagging "missing rate limiting" (#81/#82/#83) on the
login / logout / guest-claim handlers — CodeQL's query only recognizes known
rate-limit libraries, not the custom in-handler limiter from the previous
commit. Swap to @fastify/rate-limit (Fastify 5 → v10), which CodeQL detects.

- Registered scoped to the session + guest-report plugin encapsulations only,
  so the main dashboard/API routes are untouched. In-memory store matches the
  single-tenant posture.
- Per-route caps via `config.rateLimit`: login + setup 10/min, logout 30/min
  (plugin default), guest create 15/min, claim 20/min, guest reads 60/min.
- Removed the custom `createRateLimiter` from helpers.ts (now unused).

Same brute-force behavior, now via a CodeQL-recognized mechanism. Tests
unchanged and green (login trips 429 after the cap). 2431 tests pass across
api-routes + canonry + apps/api; typecheck + lint clean.
@arberx
Copy link
Copy Markdown
Member Author

arberx commented May 31, 2026

Follow-up in e8263a9 — the re-scan kept flagging "missing rate limiting" (#81/#82/#83) because CodeQL only recognizes known rate-limit libraries, not the custom in-handler limiter I added first. Swapped to @fastify/rate-limit (Fastify 5 → v10), which CodeQL detects:

  • Registered scoped to the session + guest-report plugin encapsulations only — the main dashboard/API routes are untouched.
  • Per-route caps via config.rateLimit: login + setup 10/min, logout 30/min, guest create 15/min, claim 20/min, guest reads 60/min.
  • In-memory store (matches the single-tenant posture; for multi-replica hosted, point it at a shared store later).

Same brute-force behavior, now via a mechanism CodeQL recognizes — these three should clear on the next scan. 2431 tests pass; typecheck + lint clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants