You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-auth: opt-in Cloudflare Turnstile on email sign-up (server verify + Console widget), off unless a site key and secret are configured #28100
plugin-auth pins better-auth 1.7.12. better-auth ships a captcha plugin with a cloudflare-turnstile provider, which verifies the x-captcha-response header on the endpoints it guards.
No captcha is wired anywhere in packages/ today. ui/packages/plugin-form has a captchaToken for embeddable forms only.
The sign-up rate limit is better-auth's default (3 per 10s per IP, cloud#3367 measurement).
The public auth config (getPublicConfig, features.*) is how the Console learns sign-in options. The termsUrl/privacyUrl pattern is auth-manager.ts:4517-4530.
Work
Server. When both OS_TURNSTILE_SITE_KEY (public) and OS_TURNSTILE_SECRET_KEY (secret) are set, mount better-auth's captcha plugin with provider cloudflare-turnstile on the email sign-up endpoint. Add sign-in and password reset only if the evaluation's measurement supports it, and say which in the PR. When either is unset, mount nothing. The secret is never logged, never served, and never echoed in an error.
Public config. Serve features.turnstileSiteKey (the public site key only) when enabled.
Console. When the config carries a site key, the sign-up form renders the Turnstile widget and sends its token as x-captcha-response. A failed or expired challenge shows a retry message and no raw provider error. Allow https://challenges.cloudflare.com (script and frame) in the Console CSP, only when enabled if the CSP is per-config.
Tests. Enabled: sign-up without a token is refused, and with a token is verified through a stubbed verifier. Disabled: sign-up is unchanged. The config serves the site key and never the secret. Include an ablation.
A changeset and a docs line naming the two env vars (names only).
Done when
With the two vars set, sign-up needs a valid Turnstile token. Without them, nothing changes.
Read fresh. The maintainer ruled 「同意 A」. Turnstile on email sign-up is opt-in: it is off unless both a site key and a secret are configured, and when it is off, behaviour is byte-identical to today. better-auth's captcha plugin with the cloudflare-turnstile provider guards the sign-up endpoint. p1: it is a pre-launch abuse guard.
Granted (SKILL.md 跨域例外路径): the domain:server lane claims the one PR, and the claim declares the Console sign-up widget file(s) under ui/packages/app-shell. The widget renders only when the site key is served.
⛔ The secret is never served to the client, and no captcha is added to any other endpoint in this card. The new settings keys owe their Clause-② line, and a contract review is owed.
Claim: PM loop round 2 · 2026-10-30T13:07Z
Session: session_016WV4cxKwptVz3gdUgWLyNb (card thread; seat session_016WV4czuTWNgKR22fKv7jia)
Account: os-elon-musk
Branch: claude/project-thread-7tkjc8 (the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread reported it)
Worktree: objectstack-issue-28100
Domain: domain:server
Seat: domain:server#3
File surface: packages/plugins/plugin-auth/src (mounting better-auth's captcha plugin with the cloudflare-turnstile provider on email sign-up when both keys are configured; getPublicConfigfeatures.turnstileSiteKey), tests beside them; cross-domain exception granted by triage 6523361600: ui/packages/app-shell/src/console/auth/RegisterPage.tsx (the Turnstile widget, rendered only when the site key is served) and its tests, and the Console CSP file that admits https://challenges.cloudflare.com if the CSP lives in this repo; one docs line naming the two env vars; .changeset/*. ⛔ No captcha on any other endpoint unless the PR measures and states it; the secret is never served, logged or echoed.
Container & model: M, mode:thread, model: opus (TIER_DEFAULT), effort: xhigh
Clause-②: yes (widening: the public auth config gains features.turnstileSiteKey, and new settings/env keys; the dev confirms the exact members from the diff)
Metadata-protocol: none
Responsibility: opt-in Cloudflare Turnstile on email sign-up in plugin-auth and the Console sign-up form, byte-identical behaviour when unconfigured.
Thread-read: 6523361600
Premise: holds on main @ 8cb8c7f4: no captcha is wired in packages/plugins/plugin-auth/src or ui/packages/app-shell/src.
Serial constraints cleared: no open PR touches this surface at claim time (checked against the open PR list).
Dispatch-note: p1 · security · maintainer ruling 「同意 A」 (2026-10-30 12:46Z, cloud#3367). A contract review is owed before the queue.
pm:retriage · 2026-10-30T13:12Z · cross-domain exception: the measured file surface · ⛔ not a release
The card thread re-measured on main @ b6c2ed9c. The exception granted in 6523361600 (ui/packages/app-shell sign-up widget) does not reach the files the Done-when needs. Asking triage to extend the one-PR exception to exactly these, or to name which become their own card:
packages/spec (2 lines, domain:spec, no seat sitting).packages/spec/src/kernel/public-auth-features.tsPUBLIC_AUTH_CONFIG_NON_FLAG_KEYS gains turnstileSiteKey (plugin-auth's public-feature drift guard requires served = declared), and AuthFeaturesConfigSchema (packages/spec/src/api/auth-endpoints.zod.ts) gains it as optional. Clause-②: yes (widening); a CONTRACT_REVIEW_TIER review follows from the schema face.
The Console's real sign-up page and the token plumbing (domain:ui)./register is ui/apps/console/src/pages/auth/RegisterPage.tsx (app-shell's is the exported default). Both render RegisterForm from ui/packages/auth, which is where the challenge token has to be sent: ui/packages/auth/src/{types.ts, createAuthClient.ts, AuthProvider.tsx, AuthContext.ts, RegisterForm.tsx}.
First-owner setup.ui/apps/console/src/pages/auth/SetupPage.tsx also posts the email sign-up endpoint; with both keys set it would be refused unless it carries the widget. Plan: the widget at RegisterForm-level plumbing so Setup gets it too.
CSP (packages/cli). The Console CSP is packages/cli/src/utils/console.ts (consoleContentSecurityPolicy, per request): admit challenges.cloudflare.com in script-src + frame-src only when both keys are set.
Server side stays as ordered: the captcha plugin guards the email sign-up endpoint only (its default set is overridden), and in-process sign-up callers are unaffected.
The thread keeps building locally and pushes nothing until this is answered.
Triage: retriage answer · cross-domain exception extended to the measured file surface · 2026-10-30T14:01Z
⛔ Not a release. The claim stands.
The four items in 6523679528 are one feature: opt-in Turnstile, inert when unconfigured. The ruling 「同意 A」 already includes the public config serving the site key (card Work item 2). Splitting it would leave a key that is served and not declared, or a widget with no token path. So the one-PR exception granted in 6523361600 is extended to exactly these files:
packages/spec/src/kernel/public-auth-features.ts (PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS gains turnstileSiteKey) and packages/spec/src/api/auth-endpoints.zod.ts (AuthFeaturesConfigSchema gains it as optional). Clause-②: yes (widening). A contract review is owed before the merge queue.
ui/packages/auth/src/{types.ts, createAuthClient.ts, AuthProvider.tsx, AuthContext.ts, RegisterForm.tsx} and ui/apps/console/src/pages/auth/RegisterPage.tsx.
ui/apps/console/src/pages/auth/SetupPage.tsx, through the RegisterForm-level plumbing, so that first-owner setup is not refused once both keys are set.
packages/cli/src/utils/console.ts (consoleContentSecurityPolicy): challenges.cloudflare.com goes into script-src and frame-src only when both keys are set.
Conditions:
The PR states the file surface in its body, matching this list.
Any file outside this list stops the thread and comes back here.
The secret is never served, logged or echoed.
The unconfigured path stays byte-identical, with an ablation that shows it.
The server guards only the email sign-up endpoint, as ordered.
pm:retriage · 2026-10-30T15:13Z · cross-domain exception: the remainder of the measured file surface · ⛔ not a release
Thank you for 6524682167. Under its condition "any file outside this list stops the thread and comes back here", the card thread stopped before pushing. Its one local commit (39 files, nothing pushed) holds the listed files and the original ui/packages/app-shell widget grant (6523361600). It also holds these files outside both. Asking once to extend the exception to exactly these, or to name which to drop:
ui/packages/auth/src/TurnstileWidget.tsx (new: the widget component that RegisterForm.tsx renders) and ui/packages/auth/src/index.ts (exports it, for /setup and the app-shell page).
ui/packages/app-shell/src/console/auth/signUpRefusalMessages.ts and ui/packages/app-shell/src/index.ts: the challenge's refusal codes map to one retry message, and the helper is exported. This may already sit under the app-shell widget grant; listed so the record is explicit.
ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts: two keys each (the widget's retry and failure sentences). The all-locales parity test needs all ten.
content/docs/deployment/environment-variables.mdx: the one row naming the two variables, which the card orders.
Ride-alongs, which the seat reads as implied (say if not): the generated packages/spec/authorable-surface/api.json and content/docs/references/api/auth-endpoints.mdx (regenerated from the two spec lines; check:generated and check:docs are red without them), the tests beside the changed files (including two existing /setup test doubles that gain getAuthConfig), and the changeset.
Every other condition of 6524682167 is kept: the secret is never served, logged or echoed; the unconfigured path is byte-identical, with an ablation; only /sign-up/email is guarded; Clause-②: yes (widening), with a CONTRACT_REVIEW_TIER review owed before the queue. The PR body will state the full file surface.
Triage: retriage answer · exception extended to the remainder · 2026-10-30T15:59Z
⛔ Not a release. The claim stands.
All four items, and the ride-alongs, belong to the same opt-in feature. The exception in 6524682167 is extended to exactly these:
ui/packages/auth/src/TurnstileWidget.tsx (new) and ui/packages/auth/src/index.ts.
ui/packages/app-shell/src/console/auth/signUpRefusalMessages.ts and ui/packages/app-shell/src/index.ts.
ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts: two keys each, no others.
content/docs/deployment/environment-variables.mdx: one row, which names the variables only.
The ride-alongs are confirmed as implied:
the generated packages/spec/authorable-surface/api.json and content/docs/references/api/auth-endpoints.mdx, regenerated by the repo's tooling and never written by hand;
the tests beside the changed files, including the two /setup doubles that gain getAuthConfig;
the changeset.
Every condition of 6524682167 is kept. The full file surface goes in the PR body, and the contract review is owed before the queue. This is the last extension on this card: anything more is a split. pm:retriage removed.
{
"issue": 28100,
"status": "done",
"branch": "claude/project-thread-7tkjc8",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28365",
"session": "session_016WV4cxKwptVz3gdUgWLyNb",
"premise_still_valid": true,
"summary": "Opt-in Cloudflare Turnstile on email sign-up per the maintainer's option A (cloud#3367). plugin-auth mounts better-auth's captcha plugin (provider cloudflare-turnstile, endpoints overridden to ['/sign-up/email'] because the vendor default also guards /sign-in/email and /request-password-reset) only when OS_TURNSTILE_SITE_KEY and OS_TURNSTILE_SECRET_KEY are both set and non-blank; one resolver feeds both the mount and getPublicConfig, which serves features.turnstileSiteKey (the public key only). A half configuration warns naming the variables only. The Console CSP admits https://challenges.cloudflare.com (script-src, frame-src) only while both are set. @object-ui/auth gains TurnstileWidget; RegisterForm, the Console /register, DefaultRegisterPage and the first-run /setup render it exactly when the key is served, hold submit until solved, send the token as the x-captcha-response header, and redraw after every server answer. With neither key set, nothing is mounted, served, or rendered, and the CSP is unchanged.",
"tests": "plugin-auth src/turnstile-sign-up.test.ts: 12 passed on a real engine harness with siteverify stubbed via fetch (ON: no token 400 MISSING_RESPONSE + no row + no Cloudflare call; valid token creates the account; rejected token 403 VERIFICATION_FAILED + no row; Cloudflare unreachable 500 with the secret in neither body nor any log line; sign-in and password reset need no token. OFF: neither / one / blank secret, sign-up succeeds, Cloudflare never called, no key served; half-config warn). cli console.content-security-policy.test.ts 50 passed. UI: registerTurnstile-28100 (4, on the wire via the real AuthProvider + createAuthClient), console turnstileWidget-28100 (4), app-shell defaultRegisterTurnstile-28100 (2). Ablations via scripts/ablation-replace.mjs: dropping the plugin mount reds the 4 ON cases; dropping the endpoints override reds the sign-in/reset case; dropping the header in createAuthClient reds the 2 wire cases; each restore equals HEAD. Full suites: plugin-auth 206 files green; UI auth/app-shell/console 400 files green; cli utils 121 files green.",
"gates": "On main a0f86549 after a whole-tree build (exit 0, generated outputs unchanged): dispatch-gates --commands derived 113 families, all 113 run, every one exit 0; --ran reconciliation 0 NOT-MEASURED. pnpm lint exit 0. Typecheck plugin-auth/cli/spec green; UI type-check 74 tasks, check:i18n-keys and check:i18n-drift green (measured pre-rebase on b6c2ed9c; CI re-runs). Head 2cbc7763. CI on PR #28365: pending at report time.",
"line_budget": "1311 changed lines (+1298/-13, 39 files) vs the 3000 human-merge threshold: under.",
"deviations": "1) File surface wider than the order's list: spec (2 lines + regenerated outputs), @object-ui/auth, the Console /register and /setup pages, i18n (2 keys x 10 locales) and the env-vars docs row, each granted by triage 6524682167 and 6527231164; nothing outside that list. 2) /setup carries the widget because the first owner registers through the same /sign-up/email door. 3) The captcha plugin is a plain push, not an optional plugin: a guard that fails to build fails the auth build rather than leaving sign-up open while the Console renders a widget. 4) No allowedHostnames / expectedAction set (no configuration surface in the card).",
"files_changed": [
".changeset/28100-turnstile-sign-up.md",
"content/docs/deployment/environment-variables.mdx",
"content/docs/references/api/auth-endpoints.mdx",
"packages/cli/src/utils/console.content-security-policy.test.ts",
"packages/cli/src/utils/console.ts",
"packages/plugins/plugin-auth/src/auth-manager-plugin-list.ts",
"packages/plugins/plugin-auth/src/auth-manager.ts",
"packages/plugins/plugin-auth/src/turnstile-sign-up.test.ts",
"packages/plugins/plugin-auth/src/turnstile-sign-up.ts",
"packages/spec/authorable-surface/api.json",
"packages/spec/src/api/auth-endpoints.zod.ts",
"packages/spec/src/kernel/public-auth-features.ts",
"ui/apps/console/src/pages/auth/RegisterPage.tsx",
"ui/apps/console/src/pages/auth/SetupPage.tsx",
"ui/apps/console/src/pages/auth/__tests__/authExitBasename.test.tsx",
"ui/apps/console/src/pages/auth/__tests__/setupCreateTimezone-11908.test.tsx",
"ui/apps/console/src/pages/auth/__tests__/turnstileWidget-28100.test.tsx",
"ui/packages/app-shell/src/console/auth/RegisterPage.tsx",
"ui/packages/app-shell/src/console/auth/__tests__/defaultRegisterTurnstile-28100.test.tsx",
"ui/packages/app-shell/src/console/auth/signUpRefusalMessages.ts",
"ui/packages/app-shell/src/index.ts",
"ui/packages/auth/src/AuthContext.ts",
"ui/packages/auth/src/AuthProvider.tsx",
"ui/packages/auth/src/RegisterForm.tsx",
"ui/packages/auth/src/TurnstileWidget.tsx",
"ui/packages/auth/src/__tests__/registerTurnstile-28100.test.tsx",
"ui/packages/auth/src/createAuthClient.ts",
"ui/packages/auth/src/index.ts",
"ui/packages/auth/src/types.ts",
"ui/packages/i18n/src/locales/ar.ts",
"ui/packages/i18n/src/locales/de.ts",
"ui/packages/i18n/src/locales/en.ts",
"ui/packages/i18n/src/locales/es.ts",
"ui/packages/i18n/src/locales/fr.ts",
"ui/packages/i18n/src/locales/ja.ts",
"ui/packages/i18n/src/locales/ko.ts",
"ui/packages/i18n/src/locales/pt.ts",
"ui/packages/i18n/src/locales/ru.ts",
"ui/packages/i18n/src/locales/zh.ts"
],
"dogfood_battery": "n/a: no '!' in the PR title and no (narrowing) arm",
"mcp_calls": "read-only mcp__github__issue_read on #28100; no write tool",
"api_writes": "git push (branch); relay pr_create (objectstack-ai/objectstack#28365, read-back identical 12452 bytes); relay comment (this report on #28100)",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 登录与身份 | none | P②
Triage: p1 ·
domain:server·area:auth·security· cross-domain exception, one PR · 2026-10-30T12:55Z⛔ Not a claim.
captchaplugin with thecloudflare-turnstileprovider guards the sign-up endpoint. p1: it is a pre-launch abuse guard.domain:serverlane claims the one PR, and the claim declares the Console sign-up widget file(s) underui/packages/app-shell. The widget renders only when the site key is served.Clause-②line, and a contract review is owed.enhancement,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
Claim: PM loop round 2 · 2026-10-30T13:07Z
Session:
session_016WV4cxKwptVz3gdUgWLyNb(card thread; seatsession_016WV4czuTWNgKR22fKv7jia)Account:
os-elon-muskBranch:
claude/project-thread-7tkjc8(the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread reported it)Worktree:
objectstack-issue-28100Domain:
domain:serverSeat:
domain:server#3File surface:
packages/plugins/plugin-auth/src(mounting better-auth'scaptchaplugin with thecloudflare-turnstileprovider on email sign-up when both keys are configured;getPublicConfigfeatures.turnstileSiteKey), tests beside them; cross-domain exception granted by triage 6523361600:ui/packages/app-shell/src/console/auth/RegisterPage.tsx(the Turnstile widget, rendered only when the site key is served) and its tests, and the Console CSP file that admitshttps://challenges.cloudflare.comif the CSP lives in this repo; one docs line naming the two env vars;.changeset/*. ⛔ No captcha on any other endpoint unless the PR measures and states it; the secret is never served, logged or echoed.Container & model:
M,mode:thread,model: opus (TIER_DEFAULT),effort: xhighClause-②: yes (widening: the public auth config gains
features.turnstileSiteKey, and new settings/env keys; the dev confirms the exact members from the diff)Metadata-protocol: none
Responsibility: opt-in Cloudflare Turnstile on email sign-up in
plugin-authand the Console sign-up form, byte-identical behaviour when unconfigured.Thread-read: 6523361600
Premise: holds on
main@8cb8c7f4: no captcha is wired inpackages/plugins/plugin-auth/srcorui/packages/app-shell/src.Serial constraints cleared: no open PR touches this surface at claim time (checked against the open PR list).
Dispatch-note: p1 ·
security· maintainer ruling 「同意 A」 (2026-10-30 12:46Z, cloud#3367). A contract review is owed before the queue.objectstack-fleet commentedon Oct 30, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
pm:retriage · 2026-10-30T13:12Z · cross-domain exception: the measured file surface · ⛔ not a release
The card thread re-measured on
main@b6c2ed9c. The exception granted in 6523361600 (ui/packages/app-shellsign-up widget) does not reach the files the Done-when needs. Asking triage to extend the one-PR exception to exactly these, or to name which become their own card:packages/spec(2 lines,domain:spec, no seat sitting).packages/spec/src/kernel/public-auth-features.tsPUBLIC_AUTH_CONFIG_NON_FLAG_KEYSgainsturnstileSiteKey(plugin-auth's public-feature drift guard requires served = declared), andAuthFeaturesConfigSchema(packages/spec/src/api/auth-endpoints.zod.ts) gains it as optional.Clause-②: yes (widening); aCONTRACT_REVIEW_TIERreview follows from the schema face.domain:ui)./registerisui/apps/console/src/pages/auth/RegisterPage.tsx(app-shell's is the exported default). Both renderRegisterFormfromui/packages/auth, which is where the challenge token has to be sent:ui/packages/auth/src/{types.ts, createAuthClient.ts, AuthProvider.tsx, AuthContext.ts, RegisterForm.tsx}.ui/apps/console/src/pages/auth/SetupPage.tsxalso posts the email sign-up endpoint; with both keys set it would be refused unless it carries the widget. Plan: the widget atRegisterForm-level plumbing so Setup gets it too.packages/cli). The Console CSP ispackages/cli/src/utils/console.ts(consoleContentSecurityPolicy, per request): admitchallenges.cloudflare.cominscript-src+frame-srconly when both keys are set.Server side stays as ordered: the captcha plugin guards the email sign-up endpoint only (its default set is overridden), and in-process sign-up callers are unaffected.
The thread keeps building locally and pushes nothing until this is answered.
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 登录与身份 | none | P②
Triage: retriage answer · cross-domain exception extended to the measured file surface · 2026-10-30T14:01Z
⛔ Not a release. The claim stands.
The four items in 6523679528 are one feature: opt-in Turnstile, inert when unconfigured. The ruling 「同意 A」 already includes the public config serving the site key (card Work item 2). Splitting it would leave a key that is served and not declared, or a widget with no token path. So the one-PR exception granted in 6523361600 is extended to exactly these files:
packages/spec/src/kernel/public-auth-features.ts(PUBLIC_AUTH_CONFIG_NON_FLAG_KEYSgainsturnstileSiteKey) andpackages/spec/src/api/auth-endpoints.zod.ts(AuthFeaturesConfigSchemagains it as optional).Clause-②: yes (widening). A contract review is owed before the merge queue.ui/packages/auth/src/{types.ts, createAuthClient.ts, AuthProvider.tsx, AuthContext.ts, RegisterForm.tsx}andui/apps/console/src/pages/auth/RegisterPage.tsx.ui/apps/console/src/pages/auth/SetupPage.tsx, through theRegisterForm-level plumbing, so that first-owner setup is not refused once both keys are set.packages/cli/src/utils/console.ts(consoleContentSecurityPolicy):challenges.cloudflare.comgoes intoscript-srcandframe-srconly when both keys are set.Conditions:
pm:retriageremoved.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
pm:retriage · 2026-10-30T15:13Z · cross-domain exception: the remainder of the measured file surface · ⛔ not a release
Thank you for 6524682167. Under its condition "any file outside this list stops the thread and comes back here", the card thread stopped before pushing. Its one local commit (39 files, nothing pushed) holds the listed files and the original
ui/packages/app-shellwidget grant (6523361600). It also holds these files outside both. Asking once to extend the exception to exactly these, or to name which to drop:ui/packages/auth/src/TurnstileWidget.tsx(new: the widget component thatRegisterForm.tsxrenders) andui/packages/auth/src/index.ts(exports it, for/setupand the app-shell page).ui/packages/app-shell/src/console/auth/signUpRefusalMessages.tsandui/packages/app-shell/src/index.ts: the challenge's refusal codes map to one retry message, and the helper is exported. This may already sit under the app-shell widget grant; listed so the record is explicit.ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts: two keys each (the widget's retry and failure sentences). The all-locales parity test needs all ten.content/docs/deployment/environment-variables.mdx: the one row naming the two variables, which the card orders.Ride-alongs, which the seat reads as implied (say if not): the generated
packages/spec/authorable-surface/api.jsonandcontent/docs/references/api/auth-endpoints.mdx(regenerated from the two spec lines;check:generatedandcheck:docsare red without them), the tests beside the changed files (including two existing/setuptest doubles that gaingetAuthConfig), and the changeset.Every other condition of 6524682167 is kept: the secret is never served, logged or echoed; the unconfigured path is byte-identical, with an ablation; only
/sign-up/emailis guarded;Clause-②: yes (widening), with aCONTRACT_REVIEW_TIERreview owed before the queue. The PR body will state the full file surface.objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 登录与身份 | none | P②
Triage: retriage answer · exception extended to the remainder · 2026-10-30T15:59Z
⛔ Not a release. The claim stands.
All four items, and the ride-alongs, belong to the same opt-in feature. The exception in 6524682167 is extended to exactly these:
ui/packages/auth/src/TurnstileWidget.tsx(new) andui/packages/auth/src/index.ts.ui/packages/app-shell/src/console/auth/signUpRefusalMessages.tsandui/packages/app-shell/src/index.ts.ui/packages/i18n/src/locales/{ar,de,en,es,fr,ja,ko,pt,ru,zh}.ts: two keys each, no others.content/docs/deployment/environment-variables.mdx: one row, which names the variables only.The ride-alongs are confirmed as implied:
packages/spec/authorable-surface/api.jsonandcontent/docs/references/api/auth-endpoints.mdx, regenerated by the repo's tooling and never written by hand;/setupdoubles that gaingetAuthConfig;Every condition of 6524682167 is kept. The full file surface goes in the PR body, and the contract review is owed before the queue. This is the last extension on this card: anything more is a split.
pm:retriageremoved.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:server#3 · os-elon-musk · session_016WV4cxKwptVz3gdUgWLyNb
os-dev-report
{ "issue": 28100, "status": "done", "branch": "claude/project-thread-7tkjc8", "pr": "https://github.com/objectstack-ai/objectstack/pull/28365", "session": "session_016WV4cxKwptVz3gdUgWLyNb", "premise_still_valid": true, "summary": "Opt-in Cloudflare Turnstile on email sign-up per the maintainer's option A (cloud#3367). plugin-auth mounts better-auth's captcha plugin (provider cloudflare-turnstile, endpoints overridden to ['/sign-up/email'] because the vendor default also guards /sign-in/email and /request-password-reset) only when OS_TURNSTILE_SITE_KEY and OS_TURNSTILE_SECRET_KEY are both set and non-blank; one resolver feeds both the mount and getPublicConfig, which serves features.turnstileSiteKey (the public key only). A half configuration warns naming the variables only. The Console CSP admits https://challenges.cloudflare.com (script-src, frame-src) only while both are set. @object-ui/auth gains TurnstileWidget; RegisterForm, the Console /register, DefaultRegisterPage and the first-run /setup render it exactly when the key is served, hold submit until solved, send the token as the x-captcha-response header, and redraw after every server answer. With neither key set, nothing is mounted, served, or rendered, and the CSP is unchanged.", "tests": "plugin-auth src/turnstile-sign-up.test.ts: 12 passed on a real engine harness with siteverify stubbed via fetch (ON: no token 400 MISSING_RESPONSE + no row + no Cloudflare call; valid token creates the account; rejected token 403 VERIFICATION_FAILED + no row; Cloudflare unreachable 500 with the secret in neither body nor any log line; sign-in and password reset need no token. OFF: neither / one / blank secret, sign-up succeeds, Cloudflare never called, no key served; half-config warn). cli console.content-security-policy.test.ts 50 passed. UI: registerTurnstile-28100 (4, on the wire via the real AuthProvider + createAuthClient), console turnstileWidget-28100 (4), app-shell defaultRegisterTurnstile-28100 (2). Ablations via scripts/ablation-replace.mjs: dropping the plugin mount reds the 4 ON cases; dropping the endpoints override reds the sign-in/reset case; dropping the header in createAuthClient reds the 2 wire cases; each restore equals HEAD. Full suites: plugin-auth 206 files green; UI auth/app-shell/console 400 files green; cli utils 121 files green.", "gates": "On main a0f86549 after a whole-tree build (exit 0, generated outputs unchanged): dispatch-gates --commands derived 113 families, all 113 run, every one exit 0; --ran reconciliation 0 NOT-MEASURED. pnpm lint exit 0. Typecheck plugin-auth/cli/spec green; UI type-check 74 tasks, check:i18n-keys and check:i18n-drift green (measured pre-rebase on b6c2ed9c; CI re-runs). Head 2cbc7763. CI on PR #28365: pending at report time.", "line_budget": "1311 changed lines (+1298/-13, 39 files) vs the 3000 human-merge threshold: under.", "deviations": "1) File surface wider than the order's list: spec (2 lines + regenerated outputs), @object-ui/auth, the Console /register and /setup pages, i18n (2 keys x 10 locales) and the env-vars docs row, each granted by triage 6524682167 and 6527231164; nothing outside that list. 2) /setup carries the widget because the first owner registers through the same /sign-up/email door. 3) The captcha plugin is a plain push, not an optional plugin: a guard that fails to build fails the auth build rather than leaving sign-up open while the Console renders a widget. 4) No allowedHostnames / expectedAction set (no configuration surface in the card).", "files_changed": [ ".changeset/28100-turnstile-sign-up.md", "content/docs/deployment/environment-variables.mdx", "content/docs/references/api/auth-endpoints.mdx", "packages/cli/src/utils/console.content-security-policy.test.ts", "packages/cli/src/utils/console.ts", "packages/plugins/plugin-auth/src/auth-manager-plugin-list.ts", "packages/plugins/plugin-auth/src/auth-manager.ts", "packages/plugins/plugin-auth/src/turnstile-sign-up.test.ts", "packages/plugins/plugin-auth/src/turnstile-sign-up.ts", "packages/spec/authorable-surface/api.json", "packages/spec/src/api/auth-endpoints.zod.ts", "packages/spec/src/kernel/public-auth-features.ts", "ui/apps/console/src/pages/auth/RegisterPage.tsx", "ui/apps/console/src/pages/auth/SetupPage.tsx", "ui/apps/console/src/pages/auth/__tests__/authExitBasename.test.tsx", "ui/apps/console/src/pages/auth/__tests__/setupCreateTimezone-11908.test.tsx", "ui/apps/console/src/pages/auth/__tests__/turnstileWidget-28100.test.tsx", "ui/packages/app-shell/src/console/auth/RegisterPage.tsx", "ui/packages/app-shell/src/console/auth/__tests__/defaultRegisterTurnstile-28100.test.tsx", "ui/packages/app-shell/src/console/auth/signUpRefusalMessages.ts", "ui/packages/app-shell/src/index.ts", "ui/packages/auth/src/AuthContext.ts", "ui/packages/auth/src/AuthProvider.tsx", "ui/packages/auth/src/RegisterForm.tsx", "ui/packages/auth/src/TurnstileWidget.tsx", "ui/packages/auth/src/__tests__/registerTurnstile-28100.test.tsx", "ui/packages/auth/src/createAuthClient.ts", "ui/packages/auth/src/index.ts", "ui/packages/auth/src/types.ts", "ui/packages/i18n/src/locales/ar.ts", "ui/packages/i18n/src/locales/de.ts", "ui/packages/i18n/src/locales/en.ts", "ui/packages/i18n/src/locales/es.ts", "ui/packages/i18n/src/locales/fr.ts", "ui/packages/i18n/src/locales/ja.ts", "ui/packages/i18n/src/locales/ko.ts", "ui/packages/i18n/src/locales/pt.ts", "ui/packages/i18n/src/locales/ru.ts", "ui/packages/i18n/src/locales/zh.ts" ], "dogfood_battery": "n/a: no '!' in the PR title and no (narrowing) arm", "mcp_calls": "read-only mcp__github__issue_read on #28100; no write tool", "api_writes": "git push (branch); relay pr_create (objectstack-ai/objectstack#28365, read-back identical 12452 bytes); relay comment (this report on #28100)", "open_questions": [], "out_of_scope_findings": [] }