Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ node_modules/
packages/sitetile/astro/dist-smoke/
packages/sitetile/astro/dist/
packages/sitetile/astro/.astro/
# the smoke's SECOND, isolated build — a throwaway rsync'd copy of astro/ with its own hostile
# content/blog fixture (site-level fields a single-page fixture can't exercise; see
# smoke-build.mjs's buildHostileBlogFixture for why). Rebuilt every smoke run, never committed.
packages/sitetile/.smoke-hostile-blog/
# @tile/build stashes the renderer's own content/, blog/, pagetile/ and public/ HERE while it builds
# somebody's site, and moves them back afterwards — including when the build throws. The directory
# should never outlive a build; it is ignored because the one time it does (a killed process) the
Expand Down
151 changes: 151 additions & 0 deletions packages/sitetile/astro/content/scheme-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
sitetile-page: scheme-check
title: Scheme check — disallowed destinations must never reach a live sink
lang: en-US
# round 4 (R3-P2-1/R3-P3-1/R3-P2-2): every field below is set on THIS page's own frontmatter
# (never the shared _site.md), so none of it leaks onto any other fixture page — page keys
# override site keys in the merged meta (see pages/[...path].astro's siteConfig merge).
favicon: javascript:void(0)
fonts: javascript:void(0)
header-cta: Buy now=javascript:void(0)
# header-actions-cart-href (R3-P2-1, second instance): reaches window.location.href in
# header-actions-cart.js, not an href=/src= attribute — the sink the R3 sweep's literal
# `href=|src=` grep could not see. Requires the guild + a `cart` toggle to wire at all.
header-actions: Cart=cart toggle cart
header-actions-cart-guild: scheme-check-cart
header-actions-cart-href: javascript:void(0)
# round 5 (R4-P3-1): share-image:/og-image: reaching a live <meta property=og:image>/
# <meta name=twitter:image> with no gate — absUrl only makes a relative path absolute, it does
# not touch a value that already carries its own scheme.
share-image: javascript:void(0)
---

## Contact — disallowed scheme in the form action
%% sitetile: form action="javascript:void(0)" submit="Send" %%
R3-P2-1: `action=` reaching a live `<form action>` with no gate. A disallowed scheme must
degrade to no backend (disabled submit), never a live `action="javascript:…"`.

### Name

## Contact — safe sibling (control)
%% sitetile: form action="/safe-contact" submit="Send" %%
The safe sibling: same coral, a normal destination, proving the check above did not just
turn every form off.

### Name

## Workshop tools
%% sitetile: collection eyebrow="Scheme check" %%
A grouped collection: a hostile item (disallowed scheme in the whole-card GH-style link, an
entity-encoded scheme in its `learn:` secondary link) beside a safe sibling.

### Group
#### Hostile item →javascript:void(0) "Bad"
The card-wide GH-style link is a disallowed scheme.
learn: javascript&#58;void(0)

#### Safe item →https://github.com/example/safe-scheme-check "Good"
The safe sibling — same coral, live destinations, control for the check above.
learn: /safe-learn

## Linked cells
%% sitetile: grid cols=2 %%
### Hostile cell →javascript:void(0) "Bad"
A whole-cell link with a disallowed scheme.

### Safe cell →/safe-grid "Good"
A whole-cell link with a safe destination — control.

## Gallery
%% sitetile: gallery %%
### Hostile gallery cell →javascript:void(0)
A disallowed scheme on a gallery cell.

### Safe gallery cell →/safe-gallery
A safe sibling — control.

## Carousel
%% sitetile: carousel %%
### Hostile carousel cell →javascript:void(0)
A disallowed scheme on a carousel cell.

### Safe carousel cell →/safe-carousel
A safe sibling — control.

## Roster
%% sitetile: people %%
### Hostile person →javascript:void(0)
![Hostile portrait](data:image/svg+xml;base64,PHN2Zz4=)
An SVG `data:` portrait (excluded from the raster allowlist — an SVG can carry its own
`<script>`) beside a disallowed-scheme name link and a disallowed-scheme entry in `links:`.
links: Bad=javascript:void(0), Good=/safe-person-link

### Safe person →/safe-person
![Safe portrait](data:image/png;base64,iVBORw0KGgo=)
A raster PNG `data:` portrait — allowed by policy (round 2, P1-2) — the control for the SVG
case above: the check must be able to tell these two `data:` images apart.

## Tag cloud
%% sitetile: tagcloud %%
- [Bad tag](javascript:void(0))
- [Good tag](/safe-tag)
- [Protocol-relative tag](//example.test/x)

## Background image
%% sitetile: hero bg=javascript:void(0) %%
A disallowed scheme in `bg=` reaching a live CSS `url()` with no gate before round 4 — the
same sink class as `logo=`/`layers=` elsewhere in this coral (found sweeping every `url(` in
this file, not from the round 3 review). Must degrade to no background image at all, on
both the reference renderer (site-core.js) and this Astro build.

## Safe background image (control)
%% sitetile: hero bg=/safe-bg.jpg %%
round 5 (R4-P3-6): a SAFE `bg=` must still emit a QUOTED, CSS-string-escaped `url()` — the fix
changed what a SAFE destination renders as too, not only what a disallowed one degrades to.

## CSS injection guard
%% sitetile: hero bg=/safe-bg.jpg);position:fixed;inset:0;background:red %%
round 5 (R4-P3-6): the review's own reproduction — an otherwise-ALLOWED destination (a plain
relative path) reaching an UNQUOTED CSS `url()` token, so its own `)`/`;` closed the declaration
early and opened new ones: a full-viewport defacement primitive from a page parameter, no script
involved. The whole value must stay inert inside one quoted CSS string now.

## Call to action
%% sitetile: cta button="Hostile CTA"→javascript:void(0) %%
[Safe CTA](/safe-cta)

The primary button (`button=`) is a disallowed scheme and must drop to plain text; the secondary
— an ordinary body link-only paragraph — is safe (control), proving the coral did not just turn
every button off.

## Follow us
%% sitetile: social %%
[Bad social](javascript:evil) · [Safe social](/safe-social)

Same button-row helper as `cta` (`linkButtonsHtml`), reached through a DIFFERENT coral and body
convention (a links-only paragraph, not a `button=` param) — proving the gate lives on the shared
helper, not duplicated per caller.

## Company history
%% sitetile: timeline %%
### 2026
No href/src of its own — a structural coral (heading + prose only), listed here purely to reach
14/14 KNOWN_TYPES coverage (R4-P3-5).

## Frequently asked questions
%% sitetile: faq %%
### Does this coral emit a destination?
No href/src of its own either — same reason as `timeline`, above.

## Closing thoughts
%% sitetile: prose %%
Ordinary prose paragraph, unaffected by any of this.

[Bad prose link](javascript:evil) · [Safe prose](/safe-prose)

## Embed passthrough (accepted residual)
%% sitetile: embed %%
Embed passthrough control. `embed` is the ONE documented raw-HTML escape hatch
(`Embed.astro`'s own `set:html={section.body}`) — `sweep2` reports it "STILL UNSAFE" by
accepted policy, not by an omission this round closes. Kept as plain text here (never a working
payload) so this fixture's presence at 14/14 does not itself carry one.
Loading
Loading