Skip to content

Add draft threat model + SECURITY.md + AGENTS.md for security-model discoverability#61

Merged
benweidig merged 3 commits into
apache:masterfrom
potiuk:asf-security/threat-model-2026-05-31
Jun 30, 2026
Merged

Add draft threat model + SECURITY.md + AGENTS.md for security-model discoverability#61
benweidig merged 3 commits into
apache:masterfrom
potiuk:asf-security/threat-model-2026-05-31

Conversation

@potiuk

@potiuk potiuk commented May 31, 2026

Copy link
Copy Markdown
Member

This is a draft proposal for the Tapestry PMC to review — please correct, reject, or discuss as needed. Nothing here is a requirement; the maintainers are the decision-makers.

This PR adds THREAT_MODEL.md + SECURITY.md + AGENTS.md, wiring AGENTS.md -> SECURITY.md -> THREAT_MODEL.md.

Framing: Tapestry is a framework — the application developer's pages/components/handlers and the operator's config are trusted, while the web client is the adversary. The most load-bearing mechanism is the HMAC-protected serialized client state: Tapestry round-trips serialized objects through the browser and deserializes them on return, gated by tapestry.hmac-passphrase (the post-CVE-2021-27850 protection).

Draft-first, mostly inferred (~12 documented / 0 maintainer / ~46 inferred); every *(inferred)* claim routes to a numbered §14 question. The wave-1 rulings:

  • Is a configured tapestry.hmac-passphrase effectively required (fail-closed) so the serialized-state deserialization is always HMAC-gated — and what happens if it is unset?
  • Is framework-rendered output HTML-escaped by default (raw output opt-in)?
  • Are assets protected against path traversal / arbitrary classpath read by default?

I also tried to fold in the component categorization shared earlier — please confirm the §2 family table and the §11a "deserialization is HMAC-gated" non-finding.

Context: the ASF Security team is preparing the project for an automated agentic security scan we're piloting. Drafted via the threat-model-producer rubric. If you'd rather author it yourselves, close this PR and we'll regroup.

…l discoverability

Adds a draft (v0) threat model plus SECURITY.md and AGENTS.md so an automated
scan agent can discover the model via AGENTS.md -> SECURITY.md -> THREAT_MODEL.md.
The model is a proposal for the PMC to review; most claims are (inferred) and
route to open questions in its section 14.

Generated-by: Claude Code (Claude Opus 4.8)
Comment thread THREAT_MODEL.md Outdated
| 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 | `tapestry-core/src/test/app1`, samples | — | No → §3 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, @potiuk ! Why not tapestry-core/src/test (i.e. all internal testing code)? Nothing there ever gets deployed.

thiagohp (PR apache#61): exclude all internal test code (src/test/** across
modules, not just tapestry-core/src/test/app1) — none of it deploys.
Generalized §2/§3/§11a accordingly.

Generated-by: Claude Opus 4.8
@potiuk

potiuk commented Jun 17, 2026

Copy link
Copy Markdown
Member Author

Thanks thiagohp — good catch, fixed. Broadened the exclusion from just tapestry-core/src/test/app1 to all internal test code (src/test/** across every module, including tapestry-core/src/test), since none of it deploys. Updated §2 (component table), §3 (out-of-scope), and §11a (known non-findings) so a finding anywhere under src/test routes to OUT-OF-MODEL: unsupported-component.

Pushed — if anything else on the model looks off, just flag it inline.

Comment thread THREAT_MODEL.md Outdated
Comment on lines +239 to +241
1. Is a configured **`tapestry.hmac-passphrase`** effectively **required** (fail-closed / hard warning) so the
serialized-state deserialization (§8.1) is always HMAC-gated in production? What happens if it is unset?
*Proposed:* required; unset = startup failure/loud warning, not silent insecure default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, it's a "loud, but non-failure".

A message is logged to error level, and an alert is pushed to the framework’s AlertManager, which will display it on the client-side if the related component/mechanism is used.
From internal discussions, we believe we should keep the loud error during development, but change it to a startup failure if production mode is active.

See: https://issues.apache.org/jira/browse/TAP5-2834

Comment thread THREAT_MODEL.md Outdated
1. Is a configured **`tapestry.hmac-passphrase`** effectively **required** (fail-closed / hard warning) so the
serialized-state deserialization (§8.1) is always HMAC-gated in production? What happens if it is unset?
*Proposed:* required; unset = startup failure/loud warning, not silent insecure default.
2. Is framework-rendered output **HTML-escaped by default**, with raw output an explicit opt-in? *Proposed:* yes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an explicit opt-in, either by using the org.apache.tapestry5.MarkupWriter directly (method named writeRaw), or using the org.apache.tapestry5.corelib.components.OutputRaw component.

If used (internal or external), the developer must know what they are doing and either manually escape or validate beforehand.

Any unchecked passthrough of data in an internal component should be treated as an issue.

Comment thread THREAT_MODEL.md Outdated
serialized-state deserialization (§8.1) is always HMAC-gated in production? What happens if it is unset?
*Proposed:* required; unset = startup failure/loud warning, not silent insecure default.
2. Is framework-rendered output **HTML-escaped by default**, with raw output an explicit opt-in? *Proposed:* yes.
3. Are **assets** protected against path traversal / arbitrary classpath read by default? *Proposed:* yes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tapestry's rely on a mix of the servlet container’s path normalization and additional, more specific checks of its own, e.g. explicit exclusions of sensitive file extensions.

However, the checks could be improved further.

Comment thread THREAT_MODEL.md Outdated
Comment on lines +246 to +247
4. Confirm the default **whitelist analyzer** (localhost-only) for `@WhitelistAccessOnly`, and how
`RequestSecurityManager` decides "secure". *Proposed:* default localhost; secure pages per annotation/config.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, only org.apache.tapestry5.internal.services.security.LocalhostOnly is contributed, restricting access to localhost or equivalent IPv4 and IPv6 addresses.
However, while checking this code, we found a bug that falsely denies access to certain shortened IPv6 addresses.
See: https://issues.apache.org/jira/browse/TAP5-2832

The RequestSecurityManager decides based on the @Secure annotation on pages if HTTPS is required.
By default, this is only enabled/enforced in production mode, but is configurable via the tapestry.secure-enabled symbol.

Comment thread THREAT_MODEL.md Outdated
Comment on lines +248 to +249
5. Does Tapestry provide **CSRF** protection for form/event posts, or is that the application's responsibility?
*Proposed:* application responsibility unless a built-in token exists — confirm.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, there's no CSRF module/component available directly in Tapestry.

Only the HMAC provides some form of protection against invalid requests in general, but valid requests are affected by possible CRSF.
So it’s application responsibility until added.

We’re already planning on adding CSRF protection at a later point, but are still working on a concept to integrate it cleanly, flexible, and backward-compatible.

My company has implemented CSRF internally, and there’s https://github.com/porscheinformatik/tapestry-csrf-protection that was primarily developed by a Tapestry committer and a PMC member, so there's prior work to take inspiration from.

…ror not fail-closed (TAP5-2834 planned), escaping default + writeRaw/OutputRaw opt-in, asset traversal via container+own checks, LocalhostOnly + @Secure/secure-enabled (TAP5-2832 IPv6 bug), and no built-in CSRF (app responsibility)
@potiuk

potiuk commented Jun 21, 2026

Copy link
Copy Markdown
Member Author

Thanks @benweidig — that's a thorough set of answers, folded into THREAT_MODEL.md (pushed). What changed:

  • HMAC passphrase when unset (Q1) — corrected my "fail-closed" assumption: it's currently a loud error (logged + an AlertManager client-side alert), not a hard startup failure. Noted the planned prod-mode startup-failure (TAP5-2834) in §5a/§8.1/§9.
  • Output escaping (Q2) — confirmed escaped-by-default; raw is explicit opt-in via MarkupWriter.writeRaw/OutputRaw, and I added your point that any unchecked internal-component passthrough should be treated as a bug. §8.2.
  • Asset traversal (Q3) — captured the "container normalization + Tapestry's own sensitive-extension exclusions, could be improved" framing. §8.3.
  • Whitelist + secure-link (Q4)LocalhostOnly default; @Secure-driven HTTPS, prod-default, tapestry.secure-enabled toggle. Also noted the over-restrictive shortened-IPv6 bug (TAP5-2832) as a correctness (not bypass) issue. §8.4/§8.5.
  • CSRF (Q5) — added a clear §9 disclaimer: no built-in CSRF; HMAC guards invalid requests but valid-request CSRF is the app's responsibility until a built-in lands, with the porscheinformatik prior-art link.

Still open for the PMC when you have a moment: the §11a non-findings list (wave 3, Q6/Q7) — happy to refine it from whatever scanners most often mis-report against Tapestry.

(@thiagohp — your test-scope answer is already folded into §3/§11a.)

Comment thread THREAT_MODEL.md
Comment on lines +270 to +271
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Handling invalid/unset/weak HMAC passphrase was mentioned in earlier comment and will be addressed.
Any HMAC bypass should be treated as a finding.

Comment thread THREAT_MODEL.md
**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.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far, we don't have automatic scanning tools in our CI pipeline.

I've experimented with CodeQL locally, to see if it would make sense to activate it on GitHub.
The findings with the default Java queries were mostly false positives.
Primarily findings based on "user-provided data," like "cross-site scripting" via the output of our JSON module, which doesn't make sense.

@potiuk

potiuk commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Thanks @benweidig — much appreciated, and good that the review surfaced the IPv6 LocalhostOnly bug and the HMAC hardening items on your side. Agreed: any HMAC bypass should be treated as a finding, and CSRF being app-responsibility / the path-normalization details are noted. Nothing blocking from our side — merge whenever you're ready and we'll verify discoverability and queue Tapestry. Thanks for the thorough read.

@benweidig benweidig merged commit 291ab2a into apache:master Jun 30, 2026
2 checks passed
@benweidig

Copy link
Copy Markdown
Contributor

Thanks, @potiuk, for guiding us through!

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.

3 participants