Conversation
The backend's Temporal client was resolving the temporal hostname to IPv6 ::1 (loopback) instead of the Docker network IPv4 address 172.18.0.x. This caused ALL workflow starts to silently fail (D6 pattern: ECONNREFUSED swallowed by catch block).
The Build workflow has been red on every push to dev and main since at least 2026-09-10 (PRs #33-#36). The failing step is "Test bootstrap / OAuth consent (jest)": those suites guard against running on a shared database and throw 'Use isolated local test services' unless DATABASE_URL points at 127.0.0.1:15491 (or :15432) and REDIS_URL at 127.0.0.1:16391 (or :16379). CI provided neither, so beforeAll threw, prisma stayed undefined, and afterAll then failed with "Cannot read properties of undefined (reading 'oAuthAuthorization')" — surfacing as 42 failed / 11 passed of 53. Add Postgres 17 and Redis 7.2 service containers on exactly those ports, push the Prisma schema into the throwaway database before the run, and add --runInBand because the suites share one database and create/delete rows by known ids. Verified locally against the same images, ports, env and commands: Test Suites: 5 passed, 5 total Tests: 53 passed, 53 total Jest exits cleanly, so no --forceExit is needed. This does not make CI a trustworthy gate on its own. The ESLint workflow is still red (eslint 8.57 cannot consume the flat eslint.config.mjs, and fixing that needs eslint 9 plus @typescript-eslint 8), and it is "Build & Publish Crove Containers" — not this workflow — that produces the deployable image. Those are separate follow-ups.
GET /oauth-mobile-callback holds a live OAuth authorization code and redirected it to `process.env.MOBILE_APP_SCHEME || 'postiz://auth/callback'`. Deployments that never set the variable therefore handed this instance's authorization codes to whatever app on the user's device registered the upstream `postiz://` scheme. Crove ships no mobile app and leaves the variable empty, so that fallback was the live behaviour, not a theoretical one. Return 501 instead of redirecting when the scheme is unset. The frontend had the same defect from the other side: it hardcoded `postiz://integrations` as the mobile deep link and never read MOBILE_APP_SCHEME, so the two halves could not agree even when the variable was configured. Add getMobileAppScheme, which derives just the scheme prefix from the same variable the backend consumes, thread it through the variable context and the three layouts, and omit redirectUrl entirely when no scheme is configured. Also drop the upstream scheme from the .env.example default, since the documented example value was the exact string that caused the leak. Eight contract tests in the branding guard cover the derivation, including that an unset, absent, whitespace-only or separator-less value yields '' so callers fail closed rather than falling back.
D3 — verifying the MCP server actually answers with a real credential — could not be done from a dev machine: DNS to the deployed domains is unavailable there, and booting the full backend locally needs Temporal plus its own Postgres and Elasticsearch. GitHub Actions runners have public internet access, so the probe runs from there instead, for production and beta. It asserts: - RFC 9728 discovery is served and names the correct protected resource - an unauthenticated initialize is refused with a real WWW-Authenticate header carrying resource_metadata — not the swallowed, empty 401 shape that made DOSClaw misread the 2026-09-08 database outage as an OAuth failure - an unknown bearer token is rejected as invalid_token (fail closed) - /mcp/:id with a bogus key answers the distinct 400 'Invalid API Key' - when the MCP_PROBE_TOKEN secret is set: initialize with a pos_ token, a serverInfo.name that is branded and never 'Postiz MCP', tools/list, and public API is-connected — the exact route DOSClaw hit in that incident All requests are read-only (discovery, initialize, tools/list), so it is safe on a 30-minute schedule. It is deliberately not a push/PR gate: a transient production blip must not fail unrelated work. Set the MCP_PROBE_TOKEN repository secret to upgrade from "the surface fails closed" to "a real credential completes the agent path".
Bugbot couldn't run - usage limit reachedBugbot 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_5cc0e47d-a875-44e4-b18e-18fb6db44b66) |
…handoff and beta 401 remediation
There was a problem hiding this comment.
Code Review
This pull request enhances security and environment isolation by ensuring that mobile OAuth callbacks fail closed when the MOBILE_APP_SCHEME is unconfigured, preventing unauthorized code redirection. It introduces a utility to dynamically extract the scheme prefix to avoid hardcoding, configures a dedicated Temporal namespace for the beta environment, sets the default DNS resolution order to IPv4 first, and adds an MCP probing script for deployment validation. The review feedback correctly identifies that the regular expression used to parse the mobile app scheme is overly restrictive under RFC 3986, as it assumes the presence of ://, and provides a robust suggestion to support schemes without double slashes.
| env.MOBILE_APP_SCHEME || env.NEXT_PUBLIC_MOBILE_APP_SCHEME || '' | ||
| ).trim(); | ||
| // RFC 3986 scheme: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) ":" | ||
| const match = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/.exec(raw); |
There was a problem hiding this comment.
The current regular expression is a bit too restrictive as it requires the scheme to be followed by ://. According to RFC 3986, a scheme is simply scheme:, and the // part is optional (indicating an authority component).
This implementation will fail for valid schemes like myapp:callback.
To make this function more robust and compliant with the RFC, I suggest updating the regex to handle both cases (with and without //). This will improve flexibility for self-hosters configuring their mobile app schemes. You may also want to add a test case in scripts/branding-guard.ts for a scheme without // to ensure correctness.
For example:
const match = /^([a-zA-Z][a-zA-Z0-9+.-]+:(?:\/\/)?)/.exec(raw);This updated regex will correctly extract:
postiz://frompostiz://auth/callbackpostiz:frompostiz:auth/callback
| const match = /^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/.exec(raw); | |
| const match = /^([a-zA-Z][a-zA-Z0-9+.-]+:(?:\/\/)?)/.exec(raw); |
…amespace
- Replace FlatCompat extends in root eslint.config.mjs with direct flat
imports of eslint-config-next v16 (eslintrc validator crashes on flat
plugin objects, which broke both ESLint Analysis jobs)
- Update validate-beta-compose environment contract to require exactly
{MASTRA_DISABLE_STORAGE_INIT: 'true', TEMPORAL_NAMESPACE: 'beta'} so
the namespace isolation change passes the deployment gate
Bugbot couldn't run - usage limit reachedBugbot 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_8c10d4a4-f07f-49c6-aa60-7e223f27f2be) |
Summary
TEMPORAL_NAMESPACE: 'beta'inscripts/docker-compose.beta.yamland provisions dedicatedbetanamespace on Temporal cluster to prevent Beta worker from colliding with Production task queues (No Postincident U4).docs/audit-2026-09-08.htmlwith root cause analysis and resolution status for U3 (beta DB) and U4 (temporal task queue isolation).Changes
scripts/docker-compose.beta.yaml: AddedTEMPORAL_NAMESPACE: 'beta'underenvironmentforcrove-post-beta.docs/audit-2026-09-08.html: Documented resolution of U3 (Beta Supabase connectivity) and U4 (Temporal task queue isolation between Prod and Beta).Verification
crove-server:crove-post-betarecreated and active in namespacebeta.crove-post(Production) exclusively polls default namespace.Note
Medium Risk
Changes Beta/Prod workflow isolation and OAuth mobile redirect behavior (authorization codes); misconfiguration could break Beta posting or mobile connect flows, while CI/probe changes affect release signal and external monitoring only.
Overview
Beta Temporal isolation sets
TEMPORAL_NAMESPACE: 'beta'oncrove-post-beta(compose +validate-beta-compose.mjs) so Beta workers no longer share Production’s default namespace/task queue—addressing the “No Post” cross-environment workflow incident.Mobile OAuth no longer falls back to
postiz://: unsetMOBILE_APP_SCHEMEmakesGET /oauth-mobile-callbackreturn 501,.env.exampledefaults to empty, and the frontend uses sharedgetMobileAppScheme/ context so mobile integration OAuth only sendsredirectUrlwhen a deployment-specific scheme is configured.CI & ops: the Build workflow adds Postgres/Redis on the ports the bootstrap suites require,
prisma db push, and Jest--runInBand; a scheduled MCP Surface Probe workflow runsscripts/probe-mcp.mjsagainst prod/beta (optionalMCP_PROBE_TOKEN). Backend startup sets DNSipv4firstfor Temporal gRPC; ESLint flat config importseslint-config-nextdirectly instead of FlatCompat.Docs/changelog/audit HTML are updated for these fixes and the new probe.
Reviewed by Cursor Bugbot for commit a18f55e. Configure here.