Skip to content

sitetile: form coral gains thanks= for an owner-editable page after a successful inbox submit - #32

Merged
cverorg merged 3 commits into
mainfrom
feat/form-thanks-page
Sep 9, 2026
Merged

sitetile: form coral gains thanks= for an owner-editable page after a successful inbox submit#32
cverorg merged 3 commits into
mainfrom
feat/form-thanks-page

Conversation

@cverorg

@cverorg cverorg commented Sep 9, 2026

Copy link
Copy Markdown
Member

Site owners often want a page of their own after a form is submitted, to change the copy and add their own scripts. Today form action=inbox returns to the same page with ?inbox=<outcome> and shows a built-in success card.

  • Parser: optional thanks=<path> on the form coral. The value must be a path on this site (leading single slash, no scheme, no double slash, no ..); anything else is dropped with a drop warning. A backslash is normalised like a slash before the check.
  • Renderer: with thanks= set, the forwarder redirects a successful submit to that page with ?inbox=sent appended (existing query preserved); failures still return to the original page so the inline retry message keeps working. The target travels as a hidden field and is re-validated by the forwarder itself.
  • Without thanks= the built output is byte-identical except one new, otherwise dead branch in the shared inline script.
  • Docs: README/grammar section for the form coral.

Tests: parser accept/drop table, renderer with/without the attribute, forwarder Location logic, isolated hostile-fixture build; full scripts/test.sh green (187 / 70 / 166).

@cverorg
cverorg force-pushed the feat/form-thanks-page branch from ebc56b1 to 08464c5 Compare September 9, 2026 14:05
…n still resolve off-site (#32 review round 1)

A leading single-dot path segment or an ASCII control character is removed by a URL
parser before it resolves a value, so an input that passed isSiteRelativePath/isSafeInternalPath
on its raw, as-posted spelling could still resolve into a Location beginning with two slashes -
a network-path reference a browser reads as a host, not a path. inboxRedirectResponse now checks
the RESOLVED value instead: the origin must still be this site's, and the pathname must not start
with //; either failing falls back to the site root. Applies to both return_to and thanks_to
since they share the one function. Both isSiteRelativePath and isSafeInternalPath also strip
ASCII tab/LF/CR up front, the same way isSafeImageSrc already does, so the two gates and the
result-side check stay in step on that class.

Form.astro's progressive-enhancement script no longer navigates to the forwarder's res.url
unconditionally; it checks the resolved origin first and falls back to a full-page reload when it
does not match. The no-JS path is covered by the forwarder's own result-side check.

Added a property test (a reconstructed corpus of single-dot segments and their percent-encoded
spellings, control characters, backslashes, and percent-encoded slashes) asserting that every
Location this route can produce resolves on the site's own origin, plus the reviewer's pinned
13-shape set and the three known isSiteRelativePath/isSafeInternalPath entity-decoding
divergences (safe direction). Also pins the return_to/thanks_to locale-rewrite asymmetry with a
zh-tw fixture, softens two over-claiming sentences in the change's own write-up, and notes that
takeDropWarnings has no consumer in this repo yet.
@cverorg

cverorg commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Round 1 fixes pushed (bdbc201): the forwarder now checks the resolved redirect target against the site origin and the pathname prefix and falls back when the check fails, for both fields that share the function; both gates strip control characters; the in-page script only navigates to a same-origin result; property-based tests over a generated corpus with non-vacuousness guards. Round 2 review in progress.

@cverorg

cverorg commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Round 2 review: mergeable. Independent 592-input corpus through the emitted worker: 0 off-site after the fix (206 before), and the 206 changed Locations were exactly the off-site ones — safe values unchanged. The redirect base is a build-time constant, so the same-origin check is not a tautology and preview deploys keep working; failure outcomes return to the original page and only a tampered target falls back to the site root. One test added before merge so the forwarder's control-character stripping has its own ruler.

…mutation-killing case (#32 review round 2)

isSiteRelativePath's tab/LF/CR strip had no assertion that would fail if removed: the pinned-
shape property loop continues past any value the gate rejects, and the corpus property only
checks the final Location's origin/pathname, which the result-side check in
inboxRedirectResponse already protects on its own regardless of this gate's own stripping.

Added three end-to-end cases, one per control character, with it placed right after the leading
slash of an otherwise ordinary thanks_to. That is the one position where stripping changes the
gate's accept/reject decision: stripped, the value reveals a network-path reference and the gate
rejects it before it reaches inboxRedirectResponse, so a successful submit lands on return_to.
Without the strip, the gate admits the raw value and it is only caught later, by the result-side
check on the origin a real URL parser resolves it to (dropping the control character itself) -
whose own fallback is the site root, not return_to. Removing the strip changes which page a real
visitor's successful submit lands on, and now goes red on exactly these three (100/103, matching
the reviewer's count).
@cverorg
cverorg merged commit 9d3346f into main Sep 9, 2026
2 checks passed
@cverorg
cverorg deleted the feat/form-thanks-page branch September 9, 2026 15:27
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.

1 participant