Skip to content

release: promote first-party bootstrap, Manifest V3 extension, and infrastructure hardening to main - #24

Merged
JOY (JOY) merged 9 commits into
mainfrom
dev
Aug 31, 2026
Merged

release: promote first-party bootstrap, Manifest V3 extension, and infrastructure hardening to main#24
JOY (JOY) merged 9 commits into
mainfrom
dev

Conversation

@JOY

@JOY JOY (JOY) commented Aug 31, 2026

Copy link
Copy Markdown

What kind of change does this PR introduce?

Release & Architectural Alignment

Why was this change needed?

Promotes verified dev branch features to main:

  1. First-Party Bootstrap & Single-Use Launch: Signed M2M bootstrap, 60-second opaque tickets, host-only secure cookies (__Host-crove-auth / __Host-crove-org), and atomic single-use ticket consumption preventing replay attacks.
  2. Session-Bound OAuth Consent: Exact verified session marker (firstPartyConsentId) bound to organization, client, and state, with race condition protection and clean fallback for superseded sessions.
  3. Chrome Extension Manifest V3: Full Manifest V3 upgrade for apps/extension, multi-platform build script (build.mjs), declarative permissions for 7+ social platforms, and connectable domains (*.crove.com, *.crove.io, *.dos.me).
  4. Clean Open Source Decoupling: Modularized EcosystemModule and EcosystemService with ENABLE_ECOSYSTEM_SYNC feature flag in libraries/helpers/src/utils/ecosystem.config.ts.
  5. Database Connection Pool Optimization: Tuned connection_limit, pool_timeout, and connect_timeout parameters on Supabase Transaction Pooler (port 6543) and direct connection (port 5432).
  6. Infrastructure & Compose Alignment: App-only deployment guards, named volume persistence (crove_postgres-beta-volume, crove_postiz-redis-beta-data), and zero-downtime dual-alias routing in Cloudflare Tunnel.

Technical Details & Scope

  • apps/backend/src/api/routes/provision.controller.ts: Redis Lua script atomic consume.
  • apps/backend/src/ecosystem/: Isolated ecosystem synchronization service and module.
  • apps/extension/: Manifest V3 compliance and cross-platform build tooling.
  • scripts/: Docker Compose and Cloudflare Tunnel naming standardization (crove-post, crove-post-beta).
  • libraries/nestjs-libraries/src/database/prisma/schema.prisma: Cleaned dynamic Mastra tables from Prisma schema.

Verification & Testing

  • Automated integration tests and TypeScript builds passed.
  • Branding Guard validated 100%.
  • Live verification on beta-post.crove.com and post.crove.com: Health HTTP 200 OK, atomic ticket provision & single-use consumption confirmed.

QA

  1. Checkout main and run pnpm install and pnpm run build.
  2. Verify GET https://post.crove.com/api/health and GET https://beta-post.crove.com/api/health return HTTP 200 OK.
  3. Verify pnpm dlx tsx scripts/branding-guard.ts passes with 0 failures.

Checklist:

  • My code follows the project's code style and architectural conventions.
  • Local build passes (pnpm run build).
  • Branding guard validation passes (pnpm dlx tsx scripts/branding-guard.ts).
  • Tests and typecheck have been verified without errors.
  • Documentation has been updated (if applicable).
  • No secrets or sensitive credentials are included in this PR.
  • I have filled in the QA / Verification section above with real steps to verify this change.

Note

High Risk
Touches authentication, OAuth approval, ticket exchange, and session cookies with new Redis-backed consent rules; misconfiguration or edge cases could block DOS-Me connects or weaken replay protection.

Overview
Adds a signed first-party bootstrap path for DOS-Me: HMAC-protected POST /internal/first-party/bootstrap projects user/org in Postgres, returns a short-lived launch_url with an fpt_ ticket, and wires ticket exchange through /v1/ticket/consume and the Next.js proxy so tickets never hit the client UI or logs.

OAuth consent is session-bound for the configured first-party client via firstPartyConsentId in the auth JWT and Redis consent tuples (supersession across tabs, no PKCE/redirect override, fail-closed 503 when misconfigured). Legacy JWT provisioning tickets are unchanged.

Ops and packaging: CI and deploy-beta.ps1 run validate:beta-deploy; beta compose starts PM2 only (no Prisma db push), sets MASTRA_DISABLE_STORAGE_INIT, and renames/pins volumes; nginx uses a safer log format and dedicated /oauth/authorize handling. Crove Post extension rebrands, widens platform host permissions, and uses build.mjs. Sentry filters bootstrap/ticket/oauth authorize traffic. Large integration test suites cover bootstrap, consent, proxy, and telemetry.

Reviewed by Cursor Bugbot for commit 9410cf8. Configure here.

#19)

* feat(provision): implement signed first-party bootstrap and bound tickets

* fix(provision): preserve cookie scope and verify browser ticket exchange

* fix(provision): exclude launch tickets from server telemetry
* fix(oauth): bind first-party consent to the verified launch session

* fix(oauth): revoke superseded consent and fail closed on partial config
fix(beta): guard app-only deployment
fix(oauth): show stale consent errors
feat(extension): upgrade Chrome Extension Manifest V3 and add cross-platform build tooling
Comment thread apps/extension/build.mjs
console.log('4. Creating extension.zip...');
try {
if (process.platform === 'win32') {
execSync(`powershell -Command "Compress-Archive -Path '${distDir}/*' -DestinationPath '${zipFile}' -Force"`, {
@cursor

cursor Bot commented Aug 31, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_281160b7-88c4-48c7-b445-a31ac07e8ccc)

@JOY
JOY (JOY) merged commit 0414e4a into main Aug 31, 2026
19 checks passed

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a secure first-party bootstrap flow for provisioning and authentication, including new backend services, controllers, and repositories, along with proxy middleware, extensive integration tests, and Sentry telemetry filtering. A critical issue was identified in the BootstrapService where comparing stringified JSON directly in a Redis Lua script is fragile due to non-guaranteed object property ordering; it is recommended to retrieve the JSON and perform field-by-field validation in TypeScript instead.

Comment on lines +298 to +320
const expected: ConsentBinding = {
consentId: input.consentId,
subject: user.providerId,
userId: user.id,
orgId: input.orgId,
clientId: input.clientId,
state: input.state,
appId: input.appId,
redirectUri: input.registeredRedirectUri,
codeChallenge: null,
codeChallengeMethod: null,
};
const consumed = await this.redis(() =>
ioRedis.eval(
"local v = redis.call('GET', KEYS[1]); local active = redis.call('GET', KEYS[2]); if v == ARGV[1] and active == ARGV[2] then redis.call('DEL', KEYS[1], KEYS[2]); return 1; end; return 0",
2,
consentKey(input.consentId),
activeConsentKey(user.providerId),
JSON.stringify(expected),
input.consentId
)
);
if (consumed !== 1) throw invalid();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Relying on stringified JSON equality (v == ARGV[1]) in the Redis Lua script for object comparison is highly fragile. In JavaScript/TypeScript, object property ordering is not strictly guaranteed across different runtimes or refactorings. If a developer reorders the properties of the ConsentBinding object literal in either consume or consumeConsent, or if a new optional property is added, the stringified JSON representations will differ, causing consumeConsent to silently fail with an UnauthorizedException ("Consent session changed or expired; reconnect to continue").

To make this robust and maintainable, retrieve the stored JSON string from Redis, verify that the active consent ID matches atomically, and then parse and validate the individual fields in TypeScript. This completely eliminates the dependency on JSON string serialization order while maintaining atomicity.

    const storedJson = await this.redis(() =>
      ioRedis.eval(
        "local v = redis.call('GET', KEYS[1]); local active = redis.call('GET', KEYS[2]); if active == ARGV[1] then redis.call('DEL', KEYS[1], KEYS[2]); return v; end; return nil",
        2,
        consentKey(input.consentId),
        activeConsentKey(user.providerId),
        input.consentId
      )
    ) as string | null;

    if (!storedJson) throw invalid();

    const stored: ConsentBinding = JSON.parse(storedJson);
    if (
      stored.consentId !== input.consentId ||
      stored.subject !== user.providerId ||
      stored.userId !== user.id ||
      stored.orgId !== input.orgId ||
      stored.clientId !== input.clientId ||
      stored.state !== input.state ||
      stored.appId !== input.appId ||
      stored.redirectUri !== input.registeredRedirectUri ||
      stored.codeChallenge !== null ||
      stored.codeChallengeMethod !== null
    ) {
      throw invalid();
    }

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