- Project: Apache Tapestry — a component-based Java web application framework. Developers build pages
and components; Tapestry handles request dispatch, rendering, form/event processing, asset serving, and
serialization of some server-side state into the client (page activation context, form data) which it
validates and deserializes on postback (documented — README; source
tapestry-core,tapestry-http). - Modelled against:
apache/tapestry-5master/HEAD (2026-05-31). - Status: DRAFT — under Tapestry PMC review (thiagohp, benweidig; 2026-06); wave-1/2 questions
answered and folded. Produced by the ASF Security team via the
threat-model-producerrubric (https://gist.github.com/potiuk/da14a826283038ddfe38cc9fe6310573). - Reporting / version-binding / legend as in the sibling models. Draft confidence: ~12 documented / ~10 maintainer / ~36 inferred (wave-1/2 §14 answers folded from PR #61 review). Each (inferred) routes to §14.
Framing note: Tapestry is a framework. The application developer authors pages, components, templates, and event handlers — that code is trusted (§3). The untrusted web client sending requests, form posts, and activation-context URLs is the adversary (§7). The single most security-load-bearing mechanism is the HMAC-protected serialized client state: Tapestry round-trips serialized objects through the browser and deserializes them on return, so their integrity rests on a configured HMAC secret.
Intended use (documented): build and serve a Java web application; clients interact over HTTP(S) with rendered pages, forms, and component events.
Caller roles:
- Web client (untrusted) — any browser/agent issuing requests, form posts, activation-context URLs.
- Application developer — authors pages/components/templates/handlers and chooses where to use raw output, uploads, whitelisting, HTTPS. Trusted; out of model as adversary (§3).
- Operator/deployer — sets
tapestry.hmac-passphrase, production mode, and deployment hardening. Trusted; out of model (§3).
Component-family table:
| Family | Entry point | Touches outside process | In model? |
|---|---|---|---|
| Request dispatch + page activation | URL → page/event, activation context | — | Yes |
| Serialized client state + HMAC | t:formdata / activation serialization, deserialize on postback |
deserialization | Yes (critical) |
| Rendering / template output | component render, output escaping | — | Yes (XSS) |
| Forms + file upload | form submit, multipart upload | fs (temp) | Yes |
| Asset serving | classpath/context asset URLs | filesystem/classpath | Yes (traversal) |
| Access whitelisting | @WhitelistAccessOnly, ClientWhitelist/LocalhostOnly |
client address | Yes |
| Transport/link security | RequestSecurityManager, LinkSecurity (HTTP↔HTTPS) |
network | Yes |
| Tests / sample apps / docs | all src/test/** across modules (incl. tapestry-core/src/test), samples, docs — none of it deploys |
— | No → §3 |
- The application developer and operator as adversaries, and the application's own page/component/handler code — that is trusted authored code, not an adversary surface (§7) (inferred).
- Misconfiguration (no HMAC passphrase set, raw output of untrusted data, exposing a whitelisted page) — Tapestry provides the controls; using them is the developer/operator's job (§9/§10/§11).
- The application's business-logic authorization beyond the framework's whitelist/secure-link mechanisms.
- All internal test code (
src/test/**in every module, e.g.tapestry-core/src/test), sample apps (app1), and documentation — none of it is deployed, so it is not an adversary surface (maintainer — thiagohp). - The JVM serialization/JCE internals except as Tapestry selects and uses them.
The boundary is the HTTP request: parameters, form data, activation context, and serialized client state are untrusted until validated (inferred).
Trust transitions:
- URL → page activation/event: path/query map to a page, an event, and an activation context. Untrusted context values reach handler parameters (inferred).
- Postback → deserialize serialized state (the critical one): Tapestry deserializes the serialized
object stream it previously sent to the client. It is accepted only if its HMAC verifies against the
configured passphrase — this is what stops an attacker from submitting an arbitrary serialized object and
achieving deserialization RCE (inferred —
RequestSecurityManager, HMAC mechanism; load-bearing, §14). - Render → output: component output is HTML-escaped by default; raw output is an explicit developer opt-in (inferred).
- Asset URL → file: asset requests resolve to classpath/context resources; path canonicalization must prevent traversal/arbitrary read (inferred).
- Whitelist gate:
@WhitelistAccessOnlypages/services are served only to whitelisted clients (e.g. localhost) (documented —ClientWhitelist,LocalhostOnly).
Reachability precondition: in-model if reachable from an untrusted request before the framework's
HMAC/whitelist/escaping controls; a finding requiring the operator to have left the HMAC passphrase unset or
the developer to have emitted raw untrusted output is OUT-OF-MODEL: non-default-build / misconfig (§5a/§3).
- A servlet container hosting the Tapestry app; a JVM.
tapestry.hmac-passphraseis configured to a strong secret by the operator (inferred — wave-1).- Production mode disables developer conveniences (detailed exception pages, component reload) (inferred).
- TLS is provided by the container; Tapestry's secure-link/
RequestSecurityManagerenforces HTTPS for pages marked secure (inferred). - What Tapestry does to its host ((inferred) — wave-2): reads classpath/context assets; writes temp files for uploads; deserializes HMAC-validated client state; not assumed to open arbitrary sockets or run host commands.
| Knob | Effect | Ruling needed |
|---|---|---|
tapestry.hmac-passphrase |
Integrity of serialized client state ⇒ deserialization-RCE protection | Answered (maintainer): unset is currently a loud error (logged + AlertManager client alert), non-fatal; a startup failure in production mode is planned (TAP5-2834). |
| Production mode vs. dev | Exposure of stack traces, component source, reload | Prod disables dev conveniences (§5). |
| Output escaping default | XSS posture (raw output opt-in) | Answered (maintainer): escaped by default; raw is explicit opt-in via MarkupWriter.writeRaw / OutputRaw. |
Asset path / tapestry.asset-path-prefix + protection |
Traversal/arbitrary-read posture | Answered (maintainer): container path normalization + Tapestry's own sensitive-extension exclusions; could be improved further. |
@WhitelistAccessOnly analyzer (default LocalhostOnly) |
Who may reach whitelisted pages | Answered (maintainer): default LocalhostOnly (localhost IPv4/IPv6). |
Secure-link / RequestSecurityManager |
HTTPS enforcement for secure pages | Answered (maintainer): decided by the @Secure annotation; on by default in production mode; configurable via tapestry.secure-enabled. |
| Entry point | Parameter | Attacker-controllable? | Caller/operator must enforce |
|---|---|---|---|
| page/event request | activation context, event context, query/form params | yes | type coercion; handler validation |
| postback | t:formdata / serialized client state |
yes | HMAC verification before deserialization |
| asset request | asset path/URL | yes | path canonicalization; no traversal |
| file upload | filename, body, content-type | yes | size/type limits; safe temp handling |
| whitelisted page/service | request origin/address | yes | whitelist analyzer (default localhost) |
tapestry.* config (hmac passphrase, mode) |
all | no — operator-trusted | never sourced from a request |
- Primary adversary: an untrusted web client. Capabilities: submit crafted activation/event contexts, tampered or replayed serialized client state, malicious asset paths, oversized/typed uploads, and content intended to reflect as XSS.
- Goals: deserialization RCE via forged client state (defeated only by the HMAC); arbitrary file read via asset traversal; reach a whitelisted/admin page; stored/reflected XSS; DoS via large uploads/contexts.
- Out of model: the application developer and operator; anyone holding the HMAC passphrase or filesystem access.
(Conditional on configuration; (inferred) pending §14.)
- Serialized-state integrity (deserialization-RCE protection). Serialized client state is deserialized only after its HMAC verifies against the configured passphrase, so an attacker cannot submit an arbitrary serialized object (inferred — load-bearing; the post-CVE-2021-27850 protection). Symptom: accepted forged serialized object ⇒ RCE. Severity: critical.
- Output escaping by default. Rendered component output is HTML-escaped; raw output is an explicit
opt-in via
MarkupWriter.writeRawor theOutputRawcomponent, where the developer must escape/validate beforehand. (The PMC notes any unchecked passthrough of data in an internal component should itself be treated as a bug.) (maintainer — benweidig.) Symptom: reflected/stored XSS from framework-rendered values. Severity: high–critical. - Asset access control. Asset URLs resolve only to intended classpath/context resources; traversal is prevented by a mix of the servlet container's path normalization and Tapestry's own checks (e.g. explicit exclusion of sensitive file extensions). The PMC notes these checks could be improved further. (maintainer — benweidig.) Symptom: read of files outside the asset roots. Severity: critical.
- Whitelist enforcement.
@WhitelistAccessOnlyresources are served only to whitelisted clients; by default onlyLocalhostOnlyis contributed (localhost IPv4/IPv6) (maintainer — benweidig). Symptom: a whitelisted page reachable by a non-whitelisted client. Severity: high. (The PMC noted a correctness bug where certain shortened IPv6 addresses are falsely denied — over-restrictive, not a bypass: TAP5-2832.) - Secure-link / HTTPS enforcement.
RequestSecurityManagerdecides whether HTTPS is required from a page's@Secureannotation; enforcement is on by default in production mode and configurable via thetapestry.secure-enabledsymbol (maintainer — benweidig). Symptom: secure page served over plain HTTP. Severity: medium–high.
- No serialized-state protection without a configured HMAC passphrase — if unset/weak, §8.1 is void and the deserialization surface reopens. An unset passphrase is currently a loud error (logged + an AlertManager client-side alert), not yet a hard startup failure; making it fail-closed in production mode is planned (TAP5-2834) (maintainer — benweidig).
- No built-in CSRF protection. Tapestry ships no CSRF module/component. The HMAC guards invalid requests, but a valid request is still subject to CSRF, so CSRF defence is the application's responsibility until a built-in lands (the PMC is planning one; prior art: https://github.com/porscheinformatik/tapestry-csrf-protection, primarily by a Tapestry committer/PMC member) (maintainer — benweidig).
- No XSS protection for developer-emitted raw output —
OutputRaw/raw markup of untrusted data is the developer's responsibility (§10). - No defence against the application developer or operator (§3).
- No application-level authorization beyond the whitelist/secure-link mechanisms; page-level access control for normal pages is the app's job.
False friends:
- The serialized client state looks like opaque framework plumbing but is an attacker-reachable deserialization channel — its safety is entirely the HMAC; protect the passphrase like a key.
- Whitelist "access only" looks like authentication but is an address/origin filter (default localhost), not user authn.
- Default escaping protects framework-rendered values, not raw output a component deliberately emits.
Well-known attack classes to keep in view: Java deserialization (gated by the HMAC); XSS via raw output; path traversal via asset URLs; CSRF on form/event posts; open redirect via link/redirect parameters; upload-based DoS / content-type confusion.
- Set a strong
tapestry.hmac-passphraseand protect it; treat it as a cryptographic key. - Run in production mode; never expose dev exception pages / component sources publicly.
- Never emit raw (unescaped) output of untrusted data; rely on default escaping otherwise.
- Use
@WhitelistAccessOnlyfor admin/diagnostic pages and confirm the whitelist analyzer fits the deployment (default localhost). - Mark sensitive pages secure (HTTPS) and run behind TLS; set upload size/type limits.
- Deploying without configuring
tapestry.hmac-passphrase(reopens the deserialization surface). - Emitting untrusted data as raw markup (XSS).
- Running in development mode in production (information disclosure).
- Exposing diagnostic/whitelisted pages to the public.
- Trusting activation/event context values without validation in handlers.
(v0 seed — the PMC will own the authoritative list — §14.)
- "Java deserialization in Tapestry" reports that ignore the HMAC gate —
KNOWN-NON-FINDINGwhen the HMAC verification is in place (the post-CVE-2021-27850 design); only an HMAC bypass or unset passphrase isVALID. - XSS attributed to a developer's raw output — developer responsibility (§9/§10), not a framework default.
- Findings in any
src/test/**(internal test code, e.g.tapestry-core/src/test) / samples / docs — out of scope (§3). - Whitelisted page reachable from localhost — by design (§8.4).
- Dev-mode information disclosure against a dev configuration — operator posture (§5a/§11).
- A change to the HMAC/serialized-state protection or default passphrase handling.
- A change to default output escaping or asset path protection.
- A new client-reachable serialization/deserialization path.
- A change to the whitelist analyzer default or secure-link defaults.
- Any report not cleanly routable to a §13 disposition.
| Disposition | Meaning | Licensed by |
|---|---|---|
VALID |
Violates a claimed property via an in-scope adversary/input in a default/secure config. | §8, §6, §7 |
VALID-HARDENING |
No §8 property broken, but a §11 misuse warrants a safer default/guard. | §11 |
OUT-OF-MODEL: trusted-input |
Requires control of app code / config / HMAC passphrase. | §6, §3 |
OUT-OF-MODEL: adversary-not-in-scope |
Requires developer/operator/key capability. | §7, §3 |
OUT-OF-MODEL: unsupported-component |
Lands in tests, sample apps, docs. | §3 |
OUT-OF-MODEL: non-default-build |
Only when the HMAC passphrase was unset or raw output / dev mode used. | §5a |
BY-DESIGN: property-disclaimed |
Concerns a §9-disclaimed property (raw output; whitelist ≠ authn). | §9 |
KNOWN-NON-FINDING |
Matches a §11a entry. | §11a |
MODEL-GAP |
Routes to none of the above → revise the model. | §12 |
Wave 1 — the deserialization gate + defaults (§5a/§8) — answered (benweidig, PR #61):
- HMAC passphrase when unset — answered. Not currently fail-closed: an unset passphrase is a loud error (logged + an AlertManager client-side alert), non-fatal. A startup failure in production mode is planned (TAP5-2834). Folded into §5a / §8.1 / §9.
- Output escaping — answered. Escaped by default; raw output is an explicit opt-in via
MarkupWriter.writeRaw/ theOutputRawcomponent, where the developer must escape/validate. Folded into §8.2. - Asset traversal — answered. Mitigated by the servlet container's path normalization plus Tapestry's own checks (explicit sensitive-extension exclusions); "could be improved further." Folded into §8.3.
Wave 2 — whitelist, secure-link, CSRF (§8/§9) — answered (benweidig, PR #61):
4. Whitelist + secure-link — answered. Default analyzer is LocalhostOnly (localhost IPv4/IPv6; note the
over-restrictive IPv6 bug TAP5-2832).
RequestSecurityManager requires HTTPS based on the @Secure annotation, enforced in production mode by
default and configurable via tapestry.secure-enabled. Folded into §8.4 / §8.5.
5. CSRF — answered. No built-in CSRF protection; it is the application's responsibility until one lands
(planned; prior art porscheinformatik/tapestry-csrf-protection). Folded into §9.
Wave 3 — §11a (still with the PMC): 6. The component categorization you shared earlier — please confirm the §2 family table and the §11a non-findings list (especially the "deserialization is HMAC-gated" entry). Proposed: per §2/§11a above. 7. What do scanners most often (re)report that the PMC considers a non-finding? (Seeds §11a.)
Meta:
8. Confirm this model lives as root THREAT_MODEL.md referenced from a new SECURITY.md. Proposed: yes.
Deferred for v0; a threat-model.yaml can later encode the §6 trust table, §2/§3 scoping, §8 rows, §9 false
friends, §11a non-findings, and §13 dispositions.