You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2026/10/03 12:52:09 UTC

(roller) branch security-model-doc created (now b53584cf4)

This is an automated email from the ASF dual-hosted git repository.

snoopdave pushed a change to branch security-model-doc
in repository https://gitbox.apache.org/repos/asf/roller.git


      at b53584cf4 Add security model and SECURITY.md

This branch includes the following new commits:

     new b53584cf4 Add security model and SECURITY.md

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(roller) 01/01: Add security model and SECURITY.md

Posted by sn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

snoopdave pushed a commit to branch security-model-doc
in repository https://gitbox.apache.org/repos/asf/roller.git

commit b53584cf4a15e28f7101f5bba556b3adbc8ada84
Author: David M. Johnson <sn...@apache.org>
AuthorDate: Sat Oct 3 08:51:51 2026 -0400

    Add security model and SECURITY.md
    
    Document who Roller trusts, what each kind of user may do, the
    boundaries Roller keeps, and what is out of scope. Add a SECURITY.md
    that explains how to report issues and links the model.
---
 SECURITY.md            |  50 +++++++
 docs/README.md         |   1 +
 docs/security-model.md | 360 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 411 insertions(+)

diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..165754983
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,50 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Security policy
+
+## Reporting a vulnerability
+
+Report suspected security vulnerabilities in Apache Roller privately to the
+Apache Security Team at [security@apache.org](mailto:security@apache.org).
+Please do **not** report them in the public issue tracker, in GitHub issues or
+pull requests, or on the public mailing lists.
+
+A useful report says which Roller version you tested, how the site is
+configured in any way that matters to the issue, and what steps reproduce the
+behaviour. If you are not sure whether what you found is a vulnerability,
+report it privately anyway and we will work it out with you.
+
+The Apache Security Team forwards the report to the Roller PMC. See the
+[ASF security page](https://www.apache.org/security/) for the foundation-wide
+process.
+
+## Security model
+
+The [Roller security model](docs/security-model.md) explains who Roller trusts,
+what each kind of user may do, and what counts as a vulnerability. Please read
+it before you report.
+
+## Supported versions
+
+Security fixes are made on the current release line and shipped in a new
+release. Older releases are not patched. If you run an older version, upgrade
+to the [current release](https://roller.apache.org/downloads/downloads.html).
+
+## Advisories
+
+Advisories are listed at <https://roller.apache.org/security.html>.
diff --git a/docs/README.md b/docs/README.md
index 8036aaf46..6991392fa 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,6 +5,7 @@ In this directory you'll find Roller docs in AsciiDoc format, various examples a
 * `roller-install-guide.adoc` - How to install Roller
 * `roller-user-guide.adoc` - How to use Roller
 * `roller-template-guide.adoc` - How to create Roller templates and themes
+* `security-model.md` - Who Roller trusts, what each user may do, and what counts as a vulnerability
 * `examples/` - example configuration and script files
 * `testing/` - Various test scripts
 
diff --git a/docs/security-model.md b/docs/security-model.md
new file mode 100644
index 000000000..93ad08bee
--- /dev/null
+++ b/docs/security-model.md
@@ -0,0 +1,360 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Apache Roller security model
+
+## About this document
+
+- **Version**: this model describes the Apache Roller 6.1.x release line.
+- **Purpose**: it tells operators what Roller protects and what it leaves to them, and tells security researchers what counts as a vulnerability.
+- **Reporting**: report a suspected vulnerability privately to security@apache.org, as described in [SECURITY.md](../SECURITY.md).
+- **Vulnerabilities**: an issue is a vulnerability when an untrusted user crosses one of the boundaries below in a supported configuration.
+- **Questions**: ask about the model on the Roller dev list, but never post details of a suspected vulnerability there.
+
+## The one rule
+
+Roller is one server that hosts many weblogs for people who do not
+necessarily trust each other. The whole model follows from one rule:
+
+> **What you may do in one weblog gives you nothing in another weblog, and
+> nothing on the server.**
+
+Only two parties are trusted: the **global admin**, who runs the site, and the
+**operator**, who runs the server. Everyone else is a possible attacker. That
+includes the admin of a weblog.
+
+## Principles
+
+Every change to Roller must keep these rules:
+
+1. Rights in one weblog give nothing in another weblog or on the server.
+2. Weblog admins are untrusted. Only global admins and the operator are
+   trusted.
+3. Every object named in a request is checked against the weblog the action
+   runs on.
+4. Content that users write never runs as script for other users.
+5. Templates run in a sandbox. They cannot call arbitrary Java code or read
+   files outside their theme.
+6. Request data is data. Roller never runs it as code, and it never resolves
+   external entities in XML it receives.
+7. Every state-changing request from a logged-in user needs a one-time token
+   (the *salt*).
+8. Users below global admin cannot make the server fetch a URL of their
+   choice.
+
+## What Roller is made of
+
+```mermaid
+flowchart TB
+  subgraph Public["Public, no login"]
+    PR["Weblog pages, feeds, search"]
+    CM["Comments"]
+  end
+  subgraph Members["Logged-in users"]
+    AU["Authoring UI<br/>(/roller-ui/authoring)"]
+    API["Remote publishing APIs<br/>XML-RPC, AtomPub, OAuth"]
+  end
+  subgraph Admins["Global admins"]
+    AD["Admin UI<br/>(/roller-ui/admin)"]
+    PL["Planet feed aggregator"]
+    PG["Ping targets"]
+  end
+  subgraph Setup["First run only"]
+    IN["Installer + setup token"]
+  end
+  TH["Bundled themes and templates"]
+  DB[("Database, media files,<br/>search index")]
+  PR --> TH
+  PR --> DB
+  CM --> DB
+  AU --> DB
+  API --> DB
+  AD --> DB
+  IN --> DB
+  PL -->|fetches| NET(("Network"))
+  PG -->|pings| NET
+```
+
+Everything in the Roller WAR is in scope, including the bundled themes. Build
+and test tooling (`db-utils/`, `it-selenium/`, `testing/`, `docker/`,
+`assembly-release/`) is not part of the product and is out of scope. The
+Docker files are examples for development, not a supported deployment.
+
+## The users
+
+Roller has two layers of roles:
+
+- **Global roles** apply to the whole site.
+- **Weblog roles** apply to one weblog. A person can hold a different weblog
+  role in each weblog they belong to, or no role at all.
+
+```mermaid
+flowchart TB
+  subgraph Outside["Outside Roller"]
+    OP["Operator<br/>container, database, config, logs<br/>TRUSTED, out of scope"]
+  end
+  subgraph Site["Site-wide: global roles"]
+    GA["Global admin<br/>global role 'admin'<br/>TRUSTED"]
+    RU["Registered user<br/>global role 'editor'<br/>UNTRUSTED"]
+    AN["Anonymous visitor<br/>no account<br/>UNTRUSTED"]
+  end
+  subgraph Weblog["Inside one weblog: weblog roles"]
+    WA["Admin<br/>permission 'admin'<br/>UNTRUSTED"]
+    AU["Author<br/>permission 'post'"]
+    LI["Limited<br/>permission 'edit_draft'"]
+  end
+  OP -->|creates first admin at setup| GA
+  AN -->|registers, if allowed| RU
+  RU -->|creates a weblog| WA
+  RU -->|is invited to a weblog| AU
+  RU -->|is invited to a weblog| LI
+  GA -.->|holds every weblog role<br/>in every weblog| WA
+```
+
+Weblog roles stack: an Admin can do everything an Author can, and an Author can
+do everything a Limited member can.
+
+| User | Can do | Cannot do | Trusted? |
+| --- | --- | --- | --- |
+| **Anonymous visitor** | Read published entries, feeds and search; post comments; register, when registration is on | See drafts, settings or anything unpublished; change anything | No |
+| **Registered user** (global `editor`) | Log in; edit own profile; comment; create weblogs, when the site allows it | Act in a weblog where they hold no role | No |
+| **Limited** (`edit_draft`) | Create and edit draft entries in that weblog | Publish; manage comments, categories or media; act in other weblogs | No |
+| **Author** (`post`) | Everything Limited can do; publish entries; manage comments and categories; manage media, when uploads are on | Change weblog settings, members, bookmarks, pings or design | No |
+| **Admin** (`admin`) | Everything Author can do; weblog settings, members, bookmarks (including OPML import), pings, maintenance; theme and templates, when custom themes are on | Touch other weblogs; read server files; run Java code; read other users' private data; run script in other users' browsers | **No** |
+| **Global admin** (global `admin`) | Everything in every weblog; users; site settings; Planet feeds; ping targets | Get shell or file access on the host | Yes |
+| **Network attacker** | Read or change traffic when the site runs over plain HTTP | Anything, once the site runs over HTTPS | No |
+| **Operator** | Owns the container, JVM, database, configuration files and logs | — | Yes, out of scope |
+
+### Why the weblog Admin is untrusted
+
+This is the most important line in the model. On a multi-user site, anyone who
+can register can create a weblog and become its Admin. If weblog Admins were
+trusted, every registered user would be trusted.
+
+Roller's defaults reflect this. `weblogAdminsUntrusted=true` turns on HTML
+sanitizing, and templates run in a sandbox built for untrusted authors.
+
+A request forged cross-site that makes a logged-in user's browser act is an
+attack by an anonymous visitor, not by that user.
+
+## Where untrusted data enters
+
+| Input | Who controls it |
+| --- | --- |
+| Any request to public pages, feeds and search | Anyone |
+| Comments: name, email, URL, text | Anyone |
+| Entry text, titles and summaries | Weblog members |
+| Categories, bookmarks, profiles, weblog settings | Weblog members and Admins |
+| Templates and stylesheets | Weblog Admins, when custom themes are on |
+| Uploaded files: content, name, declared type | Weblog members, when uploads are on |
+| OPML bookmark import | Weblog Admins |
+| Remote API requests (XML-RPC, AtomPub, OAuth) | Anyone, before authentication |
+| Feeds fetched by Planet | Whoever runs the feed's host |
+| Responses from ping targets | Whoever runs the ping target |
+| Installer forms | Anyone, until setup is complete |
+
+Only the operator controls `roller-custom.properties`, JNDI resources and the
+database.
+
+## The four boundaries
+
+```mermaid
+flowchart LR
+  V["Visitor / commenter"] -->|"① request data"| R["Public pages,<br/>comments, search"]
+  M["Member of weblog A"] -->|"② allowed"| A[("Weblog A")]
+  M -. "② blocked" .-> B[("Weblog B")]
+  WA["Weblog Admin"] -->|"③ HTML, templates, uploads"| S["Sanitizer,<br/>sandbox,<br/>media policy"]
+  S --> P["Pages that every<br/>visitor's browser runs"]
+  U["Any user below<br/>global admin"] -. "④ blocked" .-> N(("Network"))
+  GA["Global admin"] -->|"④ allowed"| N
+```
+
+1. **Visitor → server.** All request data is untrusted. Roller escapes what it
+   writes back into pages, never runs request data as code, and reads XML as
+   data only. One request that causes runaway CPU or memory use (for example
+   catastrophic regex backtracking) is a vulnerability. A slow response is not.
+2. **Weblog → weblog.** A role in weblog A gives nothing in weblog B. This
+   includes every object named by id in a request: entries, comments, media,
+   templates, categories, bookmarks and members. A global admin holds every role
+   in every weblog.
+3. **Weblog author → everyone else.** Content that members and Admins write
+   runs in other people's browsers. Roller keeps it from attacking visitors,
+   other users or the server:
+   - HTML in entries, profiles and settings is sanitized when
+     `weblogAdminsUntrusted=true`.
+   - Comments are always escaped, or limited to a small HTML subset. Comment
+     links and author URLs must be `http` or `https`.
+   - Templates run in the sandbox.
+   - Uploaded files are never served as active content (see below).
+4. **Server → network.** Users below global admin cannot make the server fetch
+   a URL of their choice. Entry enclosure URLs are stored, not fetched. Global
+   admins can point the server at any URL through Planet feeds and ping targets.
+   That is by design.
+
+Two more rules protect the whole site:
+
+- **Accounts.** Passwords are stored as bcrypt hashes. Roller takes the user's
+  identity from the login, never from request data. A password change ends the
+  user's other sessions.
+- **Setup.** Before setup, the installer needs a one-time token that Roller
+  writes to the server log. Once setup is complete, the installer is closed for
+  good.
+
+## How a request is checked
+
+Every request to the authoring UI passes these checks in order. A missing check
+creates a predictable class of bug.
+
+```mermaid
+flowchart TD
+  Q["Request to /roller-ui/authoring/..."] --> G1{"Logged in with a global role?"}
+  G1 -- no --> X1["Redirect to login"]
+  G1 -- yes --> G2{"Holds the required role<br/>in THIS weblog?"}
+  G2 -- no --> X2["Denied<br/>(missing: privilege escalation)"]
+  G2 -- yes --> G3{"Does every object named<br/>in the request belong<br/>to this weblog?"}
+  G3 -- no --> X3["Denied<br/>(missing: cross-weblog access)"]
+  G3 -- yes --> G4{"Valid one-time salt<br/>on a state change?"}
+  G4 -- no --> X4["Rejected<br/>(missing: CSRF)"]
+  G4 -- yes --> OK["Action runs"]
+```
+
+The remote APIs (XML-RPC, AtomPub) authenticate with a username and password,
+or OAuth, instead of a session and salt. They apply the same role and object
+checks.
+
+## How content is kept safe
+
+```mermaid
+flowchart LR
+  subgraph In["Written by untrusted users"]
+    E["Entry HTML,<br/>profiles, settings"]
+    C["Comments"]
+    T["Templates"]
+    U["Uploads"]
+  end
+  E --> SAN["HTML sanitizer<br/>(weblogAdminsUntrusted=true)"]
+  C --> ESC["Escaped, or HTML subset;<br/>links http/https only"]
+  T --> SB["Velocity sandbox:<br/>no arbitrary Java calls,<br/>no reads outside the theme"]
+  U --> MT["Type taken from file name;<br/>active types never inline;<br/>nosniff"]
+  SAN --> OUT["Page sent to visitors"]
+  ESC --> OUT
+  SB --> OUT
+  MT --> OUT
+```
+
+Uploaded files get their type from the file name, not from what the browser
+claims. Only passive types such as images, audio, video, PDF and plain text are
+shown inline. Everything else, including HTML and SVG, is sent as a download.
+
+## Settings
+
+```mermaid
+flowchart LR
+  subgraph Supported["Optional features: supported when enabled"]
+    S1["XML-RPC"]
+    S2["AtomPub"]
+    S3["Uploads"]
+    S4["Custom themes"]
+    S5["Self-registration"]
+    S6["HTML comments"]
+    S7["LDAP / OpenID login"]
+    S8["Remember-me"]
+    S9["Planet aggregator"]
+  end
+  subgraph Unsupported["Unsupported on a multi-user site"]
+    U1["weblogAdminsUntrusted=false"]
+    U2["authentication.method=cma"]
+    U3["passwds.encryption.enabled=false"]
+  end
+```
+
+A bug in an optional feature is a vulnerability when the feature is enabled.
+
+| Setting | What it changes |
+| --- | --- |
+| `weblogAdminsUntrusted` | `true` (default) sanitizes HTML from weblog members. Set `false` only when every weblog admin is trusted, such as on a solo blog. |
+| `themes.customtheme.allowed` | Lets weblog Admins edit templates. Templates stay in the sandbox. |
+| `uploads.enabled` | Lets members upload media files. |
+| `webservices.enableXmlRpc`, `webservices.enableAtomPub` | Turn on the remote publishing APIs. They use the same weblog roles as the UI. |
+| `users.registration.enabled` | Lets anyone create an account. |
+| `users.comments.htmlenabled` | Allows a small HTML subset in comments. |
+| `authentication.method` | `db`, `ldap`, `openid` or `db-openid`. `cma` is not usable. |
+| `rememberme.enabled` | Keeps users logged in with a cookie. Use only over HTTPS. |
+| `passwds.encryption.enabled` | `false` stores plaintext passwords. Not supported. |
+
+## Libraries
+
+Roller decides how it configures and calls each library. A library bug hit
+while Roller uses the library correctly belongs to that library's project. Roller
+passing untrusted input to a library in an unsafe way is a Roller bug.
+
+## Not vulnerabilities
+
+Roller does not protect against:
+
+- **The operator or host**, or anyone who can edit the configuration or the
+  database.
+- **A global admin misusing the admin UI.** A request forged against a logged-in
+  global admin is still a vulnerability.
+- **Plain HTTP.** TLS is the operator's job.
+- **Floods, brute-force login attempts and spam.** Roller has no rate limit or
+  login lockout. The comment math question is an anti-spam measure, not
+  authentication.
+- **Browsers out of vendor support.** Script that runs only in such browsers
+  (for example old Internet Explorer modes) is not a vulnerability. A payload
+  that also runs in a current browser is.
+- **Forged anonymous comments.** A comment forged cross-site carries no
+  identity, so it gains the attacker nothing.
+- **Unsupported settings**, listed under Settings above.
+- **Partial writes** when a multi-step operation fails. That is a bug, not a
+  vulnerability.
+
+## Operator checklist
+
+- Serve Roller only over HTTPS, and set the session cookie `Secure` flag in the
+  servlet container. Roller sets `HttpOnly` but not `Secure`.
+- Keep `weblogAdminsUntrusted=true` unless every weblog admin is trusted.
+- Enable custom themes, uploads, XML-RPC and AtomPub only when you need them.
+- Restrict access to the server log. It holds the setup token during
+  installation and upgrades.
+- Keep the database, data directories and `roller-custom.properties` private.
+- If the server can reach internal services, put egress rules on Roller's
+  outbound traffic (Planet, pings).
+- Run the current release. Older releases are not patched.
+
+## Common mistakes
+
+- Setting `weblogAdminsUntrusted=false` on a multi-user site to allow rich HTML.
+- Enabling custom themes and assuming templates are harmless. They are
+  sandboxed, but they run on every page view.
+- Running over plain HTTP with remember-me enabled.
+- Running setup on a host where other people can read the log.
+
+## Judging a report
+
+```mermaid
+flowchart TD
+  A["Report"] --> B{"Who is the attacker?<br/>(see The users)"}
+  B -- "Operator, or a global admin<br/>acting directly" --> OUT1["Not a vulnerability"]
+  B -- "Anyone else, or a request<br/>forged against an admin" --> C{"Does it need an<br/>unsupported setting?"}
+  C -- yes --> OUT2["Not a vulnerability"]
+  C -- no --> D{"Does it cross one of<br/>the four boundaries?"}
+  D -- yes --> VALID["Vulnerability"]
+  D -- no --> E{"Listed under<br/>Not vulnerabilities?"}
+  E -- yes --> BD["Not a vulnerability"]
+  E -- no --> GAP["Unclear: the PMC decides<br/>and updates this model"]
+```