Skip to content

console: PublicFormsPage's redirect-URL authoring door accepts any non-empty string, so out-of-contract submitBehavior.url metadata can be saved from the console itself #4990

Description

@yinlianghui

Found while implementing objectui#4190. Filed unassigned and NOT fixed there: #4190's dispatched scope is the CONSUMER side (FormPage's redirect arm), and this is the producer side — a different half of the same ruling.

What

apps/console/src/pages/developer/PublicFormsPage.tsx, saveEdit (around :222-250), builds the submitBehavior that gets written to the view metadata. The redirect branch validates one thing — that the field is not empty:

case 'redirect':
if (!editBehaviorUrl) {
toast.error('Redirect URL is required');
return;
}
submitBehavior = { kind: 'redirect', url: editBehaviorUrl };
break;

and then client.meta.saveItem('view', editRow.name, next).

os#7496 (ruled 2026-08-11, landed by os#7657, live on this repo's @objectstack/spec 17.0.0 GA pin) rules this key relative-only, with interpolation restricted to {{record.field_name}} and every interpolated value URL-escaped when the redirect is built. The spec refines the value and refuses seven families with author-facing prescriptions: empty, a leading scheme (https:, and equally javascript: / data:), a leading //, any backslash, whitespace or C0/DEL controls, a brace shape that is not a well-formed token, and anything not starting with /.

This door enforces exactly the first of those seven. An admin can type https://example.com/thanks — or javascript:alert(1) — into this dialog and it is saved as view metadata, unexamined.

Why it matters even though the renderer now refuses

The consumer side is being hardened in objectui#4190 (the renderer refuses out-of-contract values loudly instead of following them), so the resulting navigation is safe. What remains is worse-shaped than a navigation bug:

  • the metadata corpus accumulates values the contract rejects, discovered only at submit time by an end user seeing a refusal for something an admin was allowed to save;
  • the admin gets no feedback at the moment they can actually fix it, and the console is the surface that taught them the value was acceptable;
  • "declared = enforced" is inverted: the authoring door is more permissive than the schema it authors for. This is precisely the shape that lets AI-authored metadata errors hide and multiply — the door is where a wrong value should be refused.

Whether the server rejects the save is unmeasured (I did not exercise meta.saveItem against a live server). If it does, the defect is a poor error path rather than a silent save; if it does not, the value lands in the corpus. Either way this door should state the rule.

Shape of the fix, for whoever takes it

The contract's own schema is importable and is what apps/console/src/components/submitRedirect.ts (from #4190) already uses at the consumer: parsing a minimal form view carrying { kind: 'redirect', url } yields the spec's own author-facing refusal text, which is exactly what this dialog wants to display next to the field. So the fix is likely a shared reuse of that parse rather than a second copy of the rule — a hand-written mirror in the dialog is what scripts/check-spec-symbol-derivation.mjs exists to discourage.

Note the same dialog writes title / message for thank-you with no validation either; that is a much weaker case (both are free-form strings in the spec) and is mentioned only so the scope question is answered in advance.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions