Compare commits

..
Author SHA1 Message Date
clawbot c31404184a Core Rule Set: the package registries' searches of gitea and Forgejo pass (closes #354)
check / check (push) Waiting to run
Recorded from gitea 1.27.3 and Forgejo 17.0.1 with each registry's own
client, and curl for the Chef cookbook list, NuGet's Packages() and the
counts at Search()/$count and Packages()/$count, with python3,
powershell, ls -la, .htaccess, file names and plain words. q on the
Cargo, Chef, Composer and Conan searches and NuGet's search service
joins 900603. 900605 lets /bin/sh through on Composer and Conan. New:
900609 for npm's text; 900611, in phase 2, lets one q shaped as a Conan
reference with no */, such as zlib/*, skip 942100; 900612 for NuGet's
Search(), Packages() and their counts: searchTerm, $filter, and the
names $filter, $orderby and $top for 942290.

Model: opus-5-5
2026-11-12 13:01:32 +00:00
clawbot b84a33fb57 Core Rule Set: the API searches gitea and Forgejo share pass (closes #353)
check / check (push) Waiting to run
Recorded with python3, powershell, ls -la, .htaccess, file names and plain
words from gitea 1.27.3 and Forgejo 17.0.1. q on the API's searches of
topics, of an organisation's teams and of emails joins 900603, which skips
930120, 932120, 932250 and 932260. The same four rules are skipped by
query on the search of unadopted repositories (900607) and by name on the
search of a repository's milestones (900608). 900605 also lets /bin/sh
through in q on the API's search of packages, since a package name can
hold a slash. The searches of repositories, users and issues already
passed.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-11-12 12:28:33 +01:00
clawbot 4dad4fff43 Rename the settings whose names do not say what they control (closes #234)
check / check (push) Failing after 2m3s
Applies the setting renames of the naming audit under the SWW_WAF_ and
SWW_RPROXY_ prefixes, with sneak's names for the IP reputation cache
time and the two time limits, which now take milliseconds. The rproxy
log field default_disposition becomes waf_backend_no_answer, and
config.Config and config.FromEnvironment become config.WAF and
config.WAFFromEnvironment. Go fields, test names and prose follow. No
aliases; nothing reads the old names.

Model: opus-5-5
2026-11-12 11:13:27 +01:00
clawbot 94d60358ee Core Rule Set: Ghost's remaining fields pass with words that name commands (closes #359)
check / check (push) Failing after 4m5s
Ghost 6.76.0 was sent the fields not yet recorded with python3,
powershell, ls -la and .htaccess. Fixes in ghost.conf:

- eight rules that refused words naming commands and files, 932235 among
  them, for staff profiles, setup, invitations, tiers, offers,
  recommendations, integrations, webhooks, feature images, comments, the
  CSV import's labels, ActivityPub, and source and the utm_ fields;
- 932235 for the existing plain-text fields and code injection;
- filter skips six more rules, for title:~'powershell & python3';
- pathname skips 930120, 932120 and 932260;
- invitation addresses, email_segment, a tag's canonical_url, and
  ActivityPub's handles, actorUri and domain;
- a tag's code injection skips what a post's does.

Model: opus-5-5
2026-11-12 10:58:48 +01:00
11 changed files with 1501 additions and 65 deletions

No files matched your search

+3 -3
View File
@@ -609,9 +609,9 @@ lowers a client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
- `nextcloud`, whose fixes let through its sync client, its WebDAV methods,
its searches, its file names, its account names, its comments and its
documents;
- `ghost`, whose fixes let through its filters, its searches, its names, its
email addresses, its code injection, its settings and a post saved from
its editor, HTML included.
- `ghost`, whose fixes let through the text its admin and members type, its
analytics, its code injection, ActivityPub and a post saved from its
editor, HTML included.
- 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, gitea and Ghost;
+119 -25
View File
@@ -883,11 +883,13 @@ one entry of `apps` in `internal/coreruleset/coreruleset.go`. gitea's fixes are:
which refused `org.apache.commons.logging`.
A branch, tag or package name can hold a slash, as `feat/docker-support`
does. So `q` skips the rule for shell paths (932160) too on these pages,
where it refused `/bin/sh`:
does. So `q` skips the rule for shell paths (932160) too on these
searches, where it refused `/bin/sh`:
- a repository's branches, tags and packages pages;
- an owner's packages page;
- the admin page of packages.
- the admin page of packages;
- the API's search of packages;
- the searches of the Composer and Conan registries.
- `q` on gitea's other searches, for the same four rules. Words such as
`python3`, `powershell`, `ls -la` and `.htaccess` were refused there. The
@@ -902,12 +904,43 @@ one entry of `apps` in `internal/coreruleset/coreruleset.go`. gitea's fixes are:
- the runner lists of the site, a user, an organisation and a
repository;
- the admin lists of users, organisations, repositories, unadopted
repositories and emails.
repositories and emails;
- the API's searches of topics, of an organisation's teams and of
emails;
- the searches of the Cargo, Chef, Composer and Conan registries, and
the NuGet registry's search service.
Most send `q` in the query string. The admin forms that adopt a
repository or activate an email send it in the body. gitea serves its
admin pages under `/-/admin/` and Forgejo under `/admin/`.
- Two fields of the API's searches other than `q`, for the same four rules:
- `query` of the search of unadopted repositories, which holds an owner
and a name, such as `alice/python3-app`;
- `name` of the search of a repository's milestones.
- Fields of the package registries' searches:
- `text` of the npm registry's search, for the same four rules and the
rule for shell paths (932160). An npm name can hold a slash, as
`@alice/tools-cli` does.
- `searchTerm` of the NuGet registry's older searches, for the same four
rules. These are the searches:
- `Search()` and `Packages()`;
- `Search()/$count` and `Packages()/$count`, which count their
results.
- `$filter` of those searches, for the lists of system files and Windows
commands (930120, 932120). It holds an expression such as
`tolower(Id) eq 'python3'`.
- The names `$filter`, `$orderby` and `$top` on those searches, for the
rule for MongoDB operators (942290). It refused every such search.
- `q` of a Conan search, for the rule for SQL injection (942100), which
refused the pattern `zlib/*`. `q` skips it only when the request has
one `q`, in the query string or the body. That `q` has the shape of a
Conan reference and holds no `*/`, which ends an SQL comment. A
reference is a name, then optionally `/` and a version, then
optionally `@` and a user, then optionally `/` and a channel. Each
part is made of letters, digits, `_`, `.`, `+`, `-` and `*`.
- The path of these requests, which names a file:
- a file's page;
- its raw file;
@@ -1756,35 +1789,87 @@ Ghost's fixes: on Ghost's paths, some fields skip the rules that refused
ordinary Ghost requests there. `internal/coreruleset/ghost.conf` lists each rule
and the request it refused. Ghost's admin and integrations call the Admin API
under `/ghost/api/admin/`. Themes and Ghost's scripts call the Content API under
`/ghost/api/content/`, and members call `/members/api/`.
`/ghost/api/content/`, and members call `/members/api/`. The admin calls
ActivityPub, a service of its own, under `/.ghost/activitypub/`.
Three query fields skip some rules:
Eight rules refused words that name commands and files, such as `python3`,
`ls -la`, `.htaccess` and the title `powershell & python3`. They are 930120,
932120, 932125, 932230, 932235, 932250, 932260 and 932380.
- `filter`, a query in Ghost's own filter language, skips 932120 on the Admin
API and the Content API, so `tag:powershell` passes;
These query fields skip some rules:
- `filter`, a query in Ghost's own filter language, skips those eight rules
except 930120, on the Admin API and the Content API;
- `search`, a search for a member, skips 930120, 932120, 932250 and 932260 on
the Admin API;
- `url`, the address the editor looks up for an embed or a bookmark, skips
930120 and 932120.
930120 and 932120;
- `source` and the `utm_` fields, which filter the analytics, skip those eight
rules under `/ghost/api/admin/stats/`;
- `pathname`, the page the analytics show, skips 930120, 932120 and 932260
there;
- `email_segment`, the members a post is sent to as a newsletter, skips 932120
on the Admin API's posts;
- `query`, a search for an account in ActivityPub, skips those eight rules;
- `email`, when an invitation is checked, skips 930120, 932120 and 932260.
The fields of JSON bodies where a person types plain text skip 930120, 932120,
932125, 932230, 932250, 932260 and 932380. They are these fields of posts,
pages, tags, labels, members, newsletters and snippets, on the Admin API:
The fields of JSON bodies where a person types plain text skip those eight
rules. They are these fields of posts, pages, tags, labels, members, newsletters
and snippets, on the Admin API:
- `title`, `name` and `slug`;
- a post's `tags` and a member's `labels`, by name or slug;
- `custom_excerpt`, `description`, `note` and `sender_name`;
- the `meta_`, `og_` and `twitter_` titles and descriptions.
A member's `name` at `/members/api/` skips them too. A member's `email` skips
930120, 932120 and 932260, at `/members/api/` and when the admin adds or edits a
member. A staff user's address skips them too: `username` when signing in, and
`email` when creating the first staff user, asking for a password reset or
saving a profile.
These fields on the Admin API skip them too:
The `canonical_url` of a post or a page skips 930120 and 932120, as `url` does.
A post's skips them under `/ghost/api/admin/posts/`, and a page's under
`/ghost/api/admin/pages/`.
- a staff user's `name`, `slug`, `bio`, `location` and `website`;
- `blogTitle` and `name` when creating the first staff user;
- `name` when accepting an invitation;
- `name`, `description` and `benefits` of tiers;
- `name`, `code`, `display_title` and `display_description` of offers;
- `title`, `description` and `excerpt` of recommendations;
- `name` and `description` of integrations, and `name` of webhooks;
- `feature_image_alt` and `feature_image_caption` of posts and pages.
These fields skip them on their own paths:
- a member's `name` at `/members/api/`;
- `html` of a member's comment at `/members/api/comments/`;
- `labels`, when the admin imports members from a CSV file;
- `content` of a note or a reply in ActivityPub, and `image.altText`, the
description of its image;
- `name`, `username` and `bio` of the account in ActivityPub.
A member's `email` skips 930120, 932120 and 932260, at `/members/api/` and when
the admin adds or edits a member. A staff user's address skips them too. It is
sent as `username` when signing in, and as `email` in these requests:
- creating the first staff user;
- asking for a password reset;
- saving a profile;
- inviting a staff user;
- accepting an invitation.
In ActivityPub, an account's handle and its address skip 930120 and 932120. The
domain of the site's handle skips 930120, 932120 and 932260. Each field skips
its rules only on its own path:
- `sourceHandle`, an account that moves here, when it is added at
`/.ghost/activitypub/v1/aliases`;
- `actorUri`, that account's address, when the admin removes it there;
- `targetHandle`, the account this one moves to, at
`/.ghost/activitypub/v1/migration`;
- `domain`, saved at `/.ghost/activitypub/v1/domain` and checked at
`/.ghost/activitypub/v1/domain/validate`.
The `canonical_url` of a post, a page or a tag skips 930120 and 932120, as `url`
does. Each skips them only on its own path:
- a post's under `/ghost/api/admin/posts/`;
- a page's under `/ghost/api/admin/pages/`;
- a tag's under `/ghost/api/admin/tags/`.
The articles carry HTML on purpose. These fields on the Admin API hold them:
@@ -1804,12 +1889,14 @@ The articles skip these rules:
Code injection is the HTML that Ghost adds to the head and the foot of a page.
These fields on the Admin API hold it:
- `codeinjection_head` and `codeinjection_foot` of posts and pages;
- `codeinjection_head` and `codeinjection_foot` of posts, pages and tags;
- `value` of settings, since the site's settings hold its code injection.
Code injection skips these rules:
- 930120, 932120, 932125, 932230, 932250, 932260 and 932380, as plain text does;
- 930120, 932120, 932125, 932230, 932235, 932250, 932260 and 932380, which
refused words that name commands and files, such as the site's title
`powershell & python3`;
- 921130, 941100, 941160 and 941270, which refused `<meta>` and `<link>`
elements.
@@ -1818,15 +1905,22 @@ card can embed a widget and code injection can load analytics. These are the
fields, on the Admin API:
- `lexical`, `mobiledoc` and `html` of posts and pages;
- `codeinjection_head` and `codeinjection_foot` of posts and pages;
- `codeinjection_head` and `codeinjection_foot` of posts, pages and tags;
- `value` of settings.
`<script>alert(1)</script>` is still refused in snippets and newsletters.
`javascript:alert(1)` is still refused in the articles and code injection
(941170 and 941210). Path traversal and SQL injection are still refused in every
field above. Script injection is too, outside the articles and code injection.
`; wget http://example.com/x.sh` is still refused in the query fields, the plain
text, the addresses and the canonical URLs.
`; wget http://example.com/x.sh` is still refused in these fields:
- the addresses, the handles and the domain;
- the `canonical_url` of posts, pages and tags;
- `search`, `url`, `pathname`, `email_segment` and `email`.
The other fields above skip 932235, which refuses that command but also refuses
`powershell & python3`.
Coraza names a staff user's `profile_image` field `json.users.0.profile_image`.
That name holds `.profile`, a file on the list of system files, so the name
+4
View File
@@ -919,6 +919,10 @@ const retypeKey = "retype"
// system files (930120) holds.
const htaccess = ".htaccess"
// binSh is the shell path /bin/sh as a query or form value, which the rule for
// shell paths (932160) refuses.
const binSh = "%2Fbin%2Fsh"
// hardValues are words that name commands and files, as a query or form
// value. An app's tests check that its fields let them through.
//
+1 -1
View File
@@ -145,7 +145,7 @@ func TestForgejoExclusionsAreNarrow(t *testing.T) {
"/alice/-/packages/npm/x?q=",
"/alice/tools/branches?other=",
} {
for _, value := range slices.Concat([]string{"%2Fbin%2Fsh"}, hardValues) {
for _, value := range slices.Concat([]string{binSh}, hardValues) {
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
t.Errorf("%s%s let through: %+v", target, value, got)
}
+249 -7
View File
@@ -7,7 +7,8 @@
#
# Ghost's admin and its integrations call the Admin API under /ghost/api/admin/,
# themes and Ghost's own scripts call the Content API under /ghost/api/content/,
# and members call /members/api/. Coraza names a field of a JSON body by its
# and members call /members/api/. The admin calls ActivityPub, a service of its
# own, under /.ghost/activitypub/. Coraza names a field of a JSON body by its
# path, so the title of the first post saved is json.posts.0.title.
# Every rule below is skipped unless Inspect has set tx.app_ghost. It sets it
@@ -17,11 +18,22 @@
SecRule &TX:app_ghost "@eq 0" \
"id:900365,phase:1,pass,nolog,skipAfter:END_GHOST"
# filter, a query in Ghost's own filter language, skips 932120 on the Admin API
# and the Content API: filter=tag:powershell, filter=name:~'powershell'.
# filter, a query in Ghost's own filter language, skips seven rules on the
# Admin API and the Content API:
# 932120: filter=tag:powershell, filter=name:~'powershell'
# 932125, 932230, 932250: filter=title:~'powershell & ls -la'
# 932235, 932260: filter=title:~'powershell & python3', which the admin's
# post picker sends when a person types that title
# 932380: filter=title:~'python3 & powershell'
SecRule REQUEST_FILENAME "@rx ^/ghost/api/(?:admin|content)/" \
"id:900350,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=932120;ARGS:filter"
ctl:ruleRemoveTargetById=932120;ARGS:filter,\
ctl:ruleRemoveTargetById=932125;ARGS:filter,\
ctl:ruleRemoveTargetById=932230;ARGS:filter,\
ctl:ruleRemoveTargetById=932235;ARGS:filter,\
ctl:ruleRemoveTargetById=932250;ARGS:filter,\
ctl:ruleRemoveTargetById=932260;ARGS:filter,\
ctl:ruleRemoveTargetById=932380;ARGS:filter"
# search, the search for a member by name or address, skips four rules on the
# Admin API:
@@ -45,10 +57,61 @@ SecRule REQUEST_FILENAME "@streq /ghost/api/admin/oembed/" \
ctl:ruleRemoveTargetById=930120;ARGS:url,\
ctl:ruleRemoveTargetById=932120;ARGS:url"
# The fields a person types plain text into skip seven rules on the Admin API:
# The query fields that filter the analytics skip the eight rules that 900353
# lists, on /ghost/api/admin/stats/:
# 932120: source=powershell.example.com
# 932235, 932260: utm_campaign=powershell & python3
# 932250: utm_term=ls -la
# These fields filter the figures by the site a visit came from, or a
# campaign: source, utm_source, utm_medium, utm_campaign, utm_content and
# utm_term.
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/stats/" \
"id:900367,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/"
# pathname, the page the analytics show, skips three rules on
# /ghost/api/admin/stats/, which refused the path of a post and of tag pages:
# 930120: pathname=/tag/proc/
# 932120: pathname=/python3-and-powershell/
# 932260: pathname=/tag/python3/
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/stats/" \
"id:900385,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:pathname,\
ctl:ruleRemoveTargetById=932120;ARGS:pathname,\
ctl:ruleRemoveTargetById=932260;ARGS:pathname"
# email_segment, the members a post is sent to as a newsletter, skips 932120
# on the Admin API's posts: email_segment=label:powershell-users.
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/posts/" \
"id:900368,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=932120;ARGS:email_segment"
# query, a search for an account in ActivityPub, skips the eight rules that
# 900353 lists: query=python3, query=@powershell@example.com.
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/actions/search" \
"id:900369,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:query,\
ctl:ruleRemoveTargetById=932120;ARGS:query,\
ctl:ruleRemoveTargetById=932125;ARGS:query,\
ctl:ruleRemoveTargetById=932230;ARGS:query,\
ctl:ruleRemoveTargetById=932235;ARGS:query,\
ctl:ruleRemoveTargetById=932250;ARGS:query,\
ctl:ruleRemoveTargetById=932260;ARGS:query,\
ctl:ruleRemoveTargetById=932380;ARGS:query"
# The fields a person types plain text into skip eight rules on the Admin API,
# which refused words that name commands and files:
# 930120: the title "Using python3 and ls -la to tidy .htaccess files"
# 932120: the page's slug about-python3-powershell-htaccess
# 932125, 932230, 932250: the tag "Linux & ls -la"
# 932235: the title "powershell & python3"
# 932260: the tag python3
# 932380: the newsletter "Shell tips weekly: python3 & powershell"
# These fields hold plain text:
@@ -64,11 +127,12 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/"
# name, the name a member saves at /members/api/, skips the same seven rules.
# name, the name a member saves at /members/api/, skips the same eight rules.
SecRule REQUEST_FILENAME "@beginsWith /members/api/" \
"id:900359,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.name$/ "@eq 0" \
@@ -76,10 +140,99 @@ SecRule REQUEST_FILENAME "@beginsWith /members/api/" \
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.name$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.name$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.name$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.name$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.name$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.name$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.name$/"
# More fields of plain text skip the same eight rules on the Admin API, which
# refused "python3 & powershell club", "Weekly ls -la and .htaccess tips" and
# "powershell & python3 deal" in them:
# users.<n>.name, slug, bio, location and website, a staff user's profile
# setup.<n>.blogTitle and name, creating the first staff user
# invitation.<n>.name, accepting a staff invitation
# tiers.<n>.name, description and benefits
# offers.<n>.name, code, display_title and display_description
# recommendations.<n>.title, description and excerpt
# integrations.<n>.name and description, and webhooks.<n>.name
# posts.<n>.feature_image_alt and feature_image_caption, and the same fields
# of pages
# Coraza matches a field's name in lower case, so blogTitle is blogtitle.
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
"id:900370,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/"
# A member's comment, html at /members/api/comments/, skips the same eight
# rules, which refused "I ran ls -la and fixed my .htaccess with python3". The
# rules for scripts and HTML still refuse a <script> element, an event handler
# and a javascript: link in it.
SecRule REQUEST_FILENAME "@beginsWith /members/api/comments/" \
"id:900371,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.comments\.\d+\.html$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.comments\.\d+\.html$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.comments\.\d+\.html$/"
# labels, which the admin gives the members imported from a CSV file at
# /ghost/api/admin/members/upload/, skips the same eight rules, which refused
# "python3 import" and "powershell & .htaccess".
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/members/upload/" \
"id:900372,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:labels "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:labels,\
ctl:ruleRemoveTargetById=932120;ARGS:labels,\
ctl:ruleRemoveTargetById=932125;ARGS:labels,\
ctl:ruleRemoveTargetById=932230;ARGS:labels,\
ctl:ruleRemoveTargetById=932235;ARGS:labels,\
ctl:ruleRemoveTargetById=932250;ARGS:labels,\
ctl:ruleRemoveTargetById=932260;ARGS:labels,\
ctl:ruleRemoveTargetById=932380;ARGS:labels"
# In ActivityPub, a note's or a reply's text and the description of its image,
# and an account's name, username and bio, skip the same eight rules. They
# refused "Trying python3 & powershell", the image's description "A terminal
# running ls -la on .htaccess with python3" and the bio "ls -la and .htaccess
# tips". Each field skips them on its own path:
# content and image.altText, posting a note at
# /.ghost/activitypub/v1/actions/note or a reply under
# /.ghost/activitypub/v1/actions/reply/
# name, username and bio, saving the account at /.ghost/activitypub/v1/account
SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/actions/(?:note$|reply/)" \
"id:900373,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.(?:content|image\.alttext)$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:content|image\.alttext)$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:content|image\.alttext)$/"
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/account" \
"id:900374,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.(?:name|username|bio)$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:name|username|bio)$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:name|username|bio)$/"
# A member's email address skips 930120, 932120 and 932260, which refused
# erin.htaccess@example.com, dave.powershell@example.com and
# python3.dev@example.com. These fields hold it:
@@ -132,6 +285,63 @@ SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.users\.\d+\.email$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.users\.\d+\.email$/"
# The address of a staff invitation skips the same three rules. Each of these
# fields skips them only on its own path:
# invites.<n>.email, inviting a staff user at /ghost/api/admin/invites/
# email in the query, checking an invitation at
# /ghost/api/admin/authentication/invitation/
# invitation.<n>.email, accepting it at the same path
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/invites/" \
"id:900375,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.invites\.\d+\.email$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.invites\.\d+\.email$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.invites\.\d+\.email$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.invites\.\d+\.email$/"
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/invitation/" \
"id:900376,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:email,\
ctl:ruleRemoveTargetById=932120;ARGS:email,\
ctl:ruleRemoveTargetById=932260;ARGS:email"
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/invitation/" \
"id:900377,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.invitation\.\d+\.email$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.invitation\.\d+\.email$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.invitation\.\d+\.email$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.invitation\.\d+\.email$/"
# In ActivityPub, an account's handle and its address skip 930120 and 932120.
# They refused the handles @erin.htaccess@example.com and
# @powershell@example.com, and the addresses
# https://example.com/users/erin.htaccess and
# https://example.com/users/powershell. A handle starts with an @, so 932260
# does not refuse @python3.dev@example.com. The domain of the site's handle
# skips the same three rules as an email address, which refused
# erin.htaccess.example.com, powershell.example.com and python3.example.com.
# Each of these fields skips them only on its own path:
# sourceHandle, an account that moves here, added at
# /.ghost/activitypub/v1/aliases
# actorUri, that account's address, when the admin removes it there
# targetHandle, the account this one moves to, at
# /.ghost/activitypub/v1/migration
# domain, saved at /.ghost/activitypub/v1/domain and checked at
# /.ghost/activitypub/v1/domain/validate
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/aliases" \
"id:900379,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.(?:sourcehandle|actoruri)$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:sourcehandle|actoruri)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:sourcehandle|actoruri)$/"
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/migration" \
"id:900380,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.targethandle$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.targethandle$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.targethandle$/"
SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/domain(?:/validate)?$" \
"id:900381,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.domain$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.domain$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.domain$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.domain$/"
# The articles skip these rules on the Admin API:
# 930120: ls -la /var/www/html/.htaccess in a code block
# 932120: powershell -ExecutionPolicy Bypass -File build.ps1
@@ -178,9 +388,10 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
# Code injection, the HTML Ghost adds to the head and the foot of a page, and
# the site's settings, which hold the site-wide code injection besides the
# title and the description, skip the rules of plain text above and these:
# title and the description, skip the eight rules that 900353 lists and these:
# 921130, 941100, 941160: <meta name="google-site-verification" ...>
# 941270: <link rel="preconnect" href="https://fonts.example.com">
# 932235, one of the eight, refused the site's title "powershell & python3".
# These fields hold it, and skip the rules anywhere on the Admin API:
# posts.<n>.codeinjection_head and _foot, and the same fields of pages
# settings.<n>.value, the value of a setting
@@ -194,6 +405,7 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
@@ -240,6 +452,36 @@ SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
SecRule &ARGS_GET:/^json\.users\.\d+\.profile_image$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS_NAMES:/^json\.users\.\d+\.profile_image$/"
# A tag's code injection, tags.<n>.codeinjection_head and _foot under
# /ghost/api/admin/tags/, skips the rules that 900356 and 900366 switch off in
# the code injection of posts. A javascript: link is still refused in it.
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
"id:900378,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/ "@eq 0" \
"ctl:ruleRemoveTargetById=921130;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=930120;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=941100;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=941110;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=941160;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
ctl:ruleRemoveTargetById=941270;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/"
# A tag's canonical URL, tags.<n>.canonical_url under /ghost/api/admin/tags/,
# skips the two rules that url skips:
# 930120: https://example.com/tag/.htaccess/
# 932120: https://example.com/tag/powershell/
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
"id:900382,phase:1,pass,nolog,chain"
SecRule &ARGS_GET:/^json\.tags\.\d+\.canonical_url$/ "@eq 0" \
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.tags\.\d+\.canonical_url$/,\
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.tags\.\d+\.canonical_url$/"
# The passwords, which skip each rule that random passwords set off:
# password, signing in at /ghost/api/admin/session/
# setup.<n>.password, creating the first staff user
+439 -23
View File
@@ -28,6 +28,19 @@ const (
ghostUser = "/ghost/api/admin/users/6af4570fd281b00001a59891/"
ghostMember = "/members/api/member/"
ghostMagicLink = "/members/api/send-magic-link/"
ghostInvites = "/ghost/api/admin/invites/"
ghostInvitation = "/ghost/api/admin/authentication/invitation/"
ghostTiers = "/ghost/api/admin/tiers/6af45710d281b00001a598a3/"
ghostOffers = "/ghost/api/admin/offers/"
ghostRecommended = "/ghost/api/admin/recommendations/"
ghostTags = "/ghost/api/admin/tags/"
ghostImport = "/ghost/api/admin/members/upload/"
ghostComments = "/members/api/comments/"
ghostNote = "/.ghost/activitypub/v1/actions/note"
ghostAccount = "/.ghost/activitypub/v1/account"
ghostAliases = "/.ghost/activitypub/v1/aliases"
ghostMigration = "/.ghost/activitypub/v1/migration"
ghostDomain = "/.ghost/activitypub/v1/domain"
)
func TestGhostFixesApplyOnlyToAHostnameThatServesGhost(t *testing.T) {
@@ -64,7 +77,7 @@ func (f ghostField) with(t *testing.T, value string) string {
)
}
// ghostTextFields hold plain text and skip the rules of 900353 and 900359.
// ghostTextFields hold plain text and skip the eight rules that 900353 lists.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostTextFields = []ghostField{
@@ -116,8 +129,111 @@ var ghostTextFields = []ghostField{
`{"snippets":[{"name":"VALUE"}]}`,
},
{http.MethodPut, ghostMember, ghostMemberName},
// A staff user's profile, the first staff user, an invitation accepted.
{http.MethodPut, ghostUser, `{"users":[{"name":"VALUE"}]}`},
{http.MethodPut, ghostUser, `{"users":[{"slug":"VALUE"}]}`},
{http.MethodPut, ghostUser, `{"users":[{"bio":"VALUE"}]}`},
{http.MethodPut, ghostUser, `{"users":[{"location":"VALUE"}]}`},
{http.MethodPut, ghostUser, `{"users":[{"website":"VALUE"}]}`},
{http.MethodPost, ghostSetup, `{"setup":[{"blogTitle":"VALUE"}]}`},
{http.MethodPost, ghostSetup, `{"setup":[{"name":"VALUE"}]}`},
{http.MethodPost, ghostInvitation, `{"invitation":[{"name":"VALUE"}]}`},
// Tiers, offers, recommendations, integrations and webhooks.
{http.MethodPut, ghostTiers, `{"tiers":[{"name":"VALUE"}]}`},
{http.MethodPut, ghostTiers, `{"tiers":[{"description":"VALUE"}]}`},
{
http.MethodPut,
ghostTiers,
`{"tiers":[{"benefits":["Tips","VALUE"]}]}`,
},
{http.MethodPost, ghostOffers, `{"offers":[{"name":"VALUE"}]}`},
{http.MethodPost, ghostOffers, `{"offers":[{"code":"VALUE"}]}`},
{http.MethodPost, ghostOffers, `{"offers":[{"display_title":"VALUE"}]}`},
{
http.MethodPost,
ghostOffers,
`{"offers":[{"display_description":"VALUE"}]}`,
},
{
http.MethodPost,
ghostRecommended,
`{"recommendations":[{"title":"VALUE"}]}`,
},
{
http.MethodPost,
ghostRecommended,
`{"recommendations":[{"description":"VALUE"}]}`,
},
{
http.MethodPost,
ghostRecommended,
`{"recommendations":[{"excerpt":"VALUE"}]}`,
},
{
http.MethodPost,
"/ghost/api/admin/integrations/",
`{"integrations":[{"name":"VALUE","description":"VALUE"}]}`,
},
{
http.MethodPost,
"/ghost/api/admin/webhooks/",
`{"webhooks":[{"event":"post.published","name":"VALUE"}]}`,
},
// A feature image's alt text and caption.
{http.MethodPut, ghostPost, `{"posts":[{"feature_image_alt":"VALUE"}]}`},
{
http.MethodPut,
ghostPost,
`{"posts":[{"feature_image_caption":` +
`"Photo: <a href=\"https://example.com/\">VALUE</a>"}]}`,
},
{http.MethodPut, ghostPage, `{"pages":[{"feature_image_alt":"VALUE"}]}`},
// A member's comment, written and edited.
ghostNewComment,
{
http.MethodPut,
ghostComments + "6af5426ae56ecb0001938894/",
`{"comments":[{"id":"6af5426ae56ecb0001938894","html":"<p>VALUE</p>"}]}`,
},
// ActivityPub: a note, a reply, the description of their image, and the
// account.
{http.MethodPost, ghostNote, `{"content":"VALUE"}`},
{http.MethodPost, ghostReply, `{"content":"VALUE"}`},
{http.MethodPost, ghostNote, ghostNoteImage},
{http.MethodPost, ghostReply, ghostNoteImage},
{http.MethodPut, ghostAccount, `{"name":"VALUE","username":"alice"}`},
{http.MethodPut, ghostAccount, `{"username":"VALUE"}`},
{http.MethodPut, ghostAccount, `{"bio":"VALUE"}`},
}
// ghostNewComment is a member's comment on a post, with VALUE in a paragraph.
//
//nolint:gochecknoglobals // a constant cannot be a struct
var ghostNewComment = ghostField{
http.MethodPost,
ghostComments,
`{"comments":[{"post_id":"6af458a5d281b00001a59969","html":"<p>VALUE</p>"}]}`,
}
// ghostComment is the HTML of an ordinary comment, with a link.
const ghostComment = `Try <strong>python3</strong>, then ` +
`<a href="https://example.com/ls-la" rel="ugc">ls -la</a>.`
// ghostNoteImage is the body of a note or a reply in ActivityPub with an
// image, with VALUE as the image's description.
const ghostNoteImage = `{"content":"My desk","image":{"url":` +
`"https://example.com/content/images/desk.png","altText":"VALUE"}}`
// ghostReply is the path that posts a reply to a note in ActivityPub.
const ghostReply = "/.ghost/activitypub/v1/actions/reply/" +
"https%3A%2F%2Fexample.com%2F.ghost%2Factivitypub%2Fnote%2F" +
"0d6e3b4c-2a1f-4b5e-9c8d-7e6f5a4b3c2d"
// ghostMemberName is the body that saves a member's name at /members/api/.
const ghostMemberName = `{"name":"VALUE"}`
@@ -135,6 +251,8 @@ var ghostAddressFields = []ghostField{
{http.MethodPost, ghostMembers, `{"members":[{"email":"VALUE"}]}`},
{http.MethodPost, ghostSession, `{"username":"VALUE"}`},
{http.MethodPost, ghostSetup, `{"setup":[{"email":"VALUE"}]}`},
{http.MethodPost, ghostInvites, `{"invites":[{"email":"VALUE"}]}`},
{http.MethodPost, ghostInvitation, `{"invitation":[{"email":"VALUE"}]}`},
{
http.MethodPost,
ghostPasswordReset,
@@ -143,14 +261,41 @@ var ghostAddressFields = []ghostField{
{http.MethodPut, ghostUser, `{"users":[{"email":"VALUE"}]}`},
}
// ghostCanonicalURLFields hold the canonical URL of a post or a page, and skip
// 930120 and 932120, as url does.
// ghostHandleFields hold an account's handle in ActivityPub, which the admin
// sends as an @ followed by an address, with no spaces. They skip 930120 and
// 932120.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostCanonicalURLFields = []ghostField{
var ghostHandleFields = []ghostField{
{http.MethodPost, ghostAliases, `{"sourceHandle":"@VALUE"}`},
{http.MethodPost, ghostMigration, `{"targetHandle":"@VALUE"}`},
}
// ghostDomainFields hold the domain of the site's handle in ActivityPub and
// skip 930120, 932120 and 932260.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostDomainFields = []ghostField{
{http.MethodPut, ghostDomain, `{"domain":"VALUE"}`},
{http.MethodPost, ghostDomain + "/validate", `{"domain":"VALUE"}`},
}
// ghostURLFields hold the canonical URL of a post, a page or a tag, or the
// address of an account removed from those that move here in ActivityPub.
// They skip 930120 and 932120, as url does.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostURLFields = []ghostField{
{http.MethodPut, ghostPost, `{"posts":[{"canonical_url":"VALUE"}]}`},
{http.MethodPost, ghostPosts, `{"posts":[{"canonical_url":"VALUE"}]}`},
{http.MethodPut, ghostPage, `{"pages":[{"canonical_url":"VALUE"}]}`},
{http.MethodPost, ghostTags, `{"tags":[{"canonical_url":"VALUE"}]}`},
{
http.MethodPut,
ghostTags + "6af45d4ed281b00001a599a8/",
`{"tags":[{"canonical_url":"VALUE"}]}`,
},
{http.MethodDelete, ghostAliases, `{"actorUri":"VALUE"}`},
}
// ghostPostFields hold a post or a page, skip the rules of 900355, and take a
@@ -193,8 +338,8 @@ var ghostArticleFields = slices.Concat(
ghostSnippetAndNewsletterFooterFields,
)
// ghostCodeFields hold code injection or a setting, skip the rules of 900356,
// and take a <script> element by 900366.
// ghostCodeFields hold code injection or a setting, skip the rules of 900356
// and 900378, and take a <script> element by 900366 and 900378.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostCodeFields = []ghostField{
@@ -213,6 +358,16 @@ var ghostCodeFields = []ghostField{
`{"settings":[{"key":"title","value":"Blog"},` +
`{"key":"codeinjection_head","value":"VALUE"}]}`,
},
{
http.MethodPost,
ghostTags,
`{"tags":[{"codeinjection_head":"VALUE"}]}`,
},
{
http.MethodPut,
ghostTags + "6af45d4ed281b00001a599a8/",
`{"tags":[{"codeinjection_foot":"VALUE"}]}`,
},
}
// The query fields that skip some rules, each ending in "=".
@@ -221,17 +376,42 @@ const (
ghostContentFilter = "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter="
ghostSearch = ghostMembers + "?limit=100&search="
ghostEmbed = "/ghost/api/admin/oembed/?type=bookmark&url="
ghostStats = "/ghost/api/admin/stats/top-content/?date_from=2026-10-13"
ghostPathname = ghostStats + "&pathname="
ghostSource = ghostStats + "&source="
ghostCampaign = ghostStats + "&utm_campaign="
ghostSegment = ghostPost + "?newsletter=default-newsletter&email_segment="
ghostAccountSearch = "/.ghost/activitypub/v1/actions/search?query="
ghostInvitedEmail = ghostInvitation + "?email="
)
// ghostAnalyticsFilters are source and the utm_ fields, which filter the
// analytics, each ending in "=".
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostAnalyticsFilters = []string{
ghostSource,
ghostStats + "&utm_source=",
ghostStats + "&utm_medium=",
ghostCampaign,
ghostStats + "&utm_content=",
ghostStats + "&utm_term=",
}
// ghostQueryFields are the query fields that skip some rules.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostQueryFields = []string{
ghostAdminFilter, ghostContentFilter, ghostSearch, ghostEmbed,
ghostPathname, ghostSource, ghostCampaign, ghostSegment,
ghostAccountSearch, ghostInvitedEmail,
}
// ghostURLs are addresses 930120 and 932120 refused in url and in a canonical
// URL.
// ghostCommandNames joins two command names, as an ordinary title can.
const ghostCommandNames = "powershell & python3"
// ghostURLs are addresses 930120 and 932120 refused in url and in
// ghostURLFields.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostURLs = []string{
@@ -251,6 +431,15 @@ var ghostAddresses = []string{
"dave.powershell@example.com",
}
// ghostDomains are domains 930120, 932120 and 932260 refused.
//
//nolint:gochecknoglobals // a constant cannot be a list
var ghostDomains = []string{
"erin.htaccess.example.com",
"python3.example.com",
"powershell.example.com",
}
// ghostArticle is an ordinary article with a code block, an HTML card and an
// embed, which each of the rules of 900355 refused.
const ghostArticle = `<h2>Tidy .htaccess with python3</h2>` +
@@ -341,17 +530,23 @@ func TestGhostOrdinaryValuesPassInTheExcludedFields(t *testing.T) {
for _, address := range ghostURLs {
wantGhostQuery(t, ghostEmbed+url.QueryEscape(address), false)
for _, f := range ghostCanonicalURLFields {
for _, f := range ghostURLFields {
wantGhostField(t, f, address, false)
}
}
for _, address := range ghostAddresses {
for _, f := range ghostAddressFields {
for _, f := range slices.Concat(ghostAddressFields, ghostHandleFields) {
wantGhostField(t, f, address, false)
}
}
for _, domain := range ghostDomains {
for _, f := range ghostDomainFields {
wantGhostField(t, f, domain, false)
}
}
for _, f := range ghostArticleFields {
wantGhostField(t, f, ghostArticle, false)
}
@@ -360,6 +555,14 @@ func TestGhostOrdinaryValuesPassInTheExcludedFields(t *testing.T) {
wantGhostField(t, f, ghostCodeInjection, false)
}
wantGhostField(t, ghostNewComment, ghostComment, false)
wantGhostField(
t,
ghostField{http.MethodPost, ghostNote, ghostNoteImage},
"A terminal running ls -la on .htaccess with python3",
false,
)
wantGhostQuery(t, "/ghost/api/admin/posts/?filter="+
url.QueryEscape(
"tag:powershell+status:[draft,scheduled,published,sent]",
@@ -393,7 +596,8 @@ func TestGhostScriptsPassInPostsAndCodeInjection(t *testing.T) {
wantGhostField(t, f, ghostWidget, false)
}
// Site code injection, saved in settings, and a post's or a page's own.
// Site code injection, saved in settings, and a post's, a page's or a
// tag's own.
for _, f := range ghostCodeFields {
wantGhostField(t, f, ghostAnalytics, false)
}
@@ -412,7 +616,8 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
for _, attack := range ghostAttacks {
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields,
ghostCanonicalURLFields, ghostArticleFields, ghostCodeFields) {
ghostHandleFields, ghostDomainFields, ghostURLFields,
ghostArticleFields, ghostCodeFields) {
wantGhostField(t, f, attack, true)
}
@@ -424,7 +629,8 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
// A script injection stays refused in every field that does not take a
// <script> element.
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields,
ghostCanonicalURLFields, ghostSnippetAndNewsletterFooterFields) {
ghostHandleFields, ghostDomainFields, ghostURLFields,
ghostSnippetAndNewsletterFooterFields) {
wantGhostField(t, f, scriptTag, true)
}
@@ -432,15 +638,19 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
wantGhostQuery(t, target+url.QueryEscape(scriptTag), true)
}
// A command after a separator stays refused wherever its rules do not
// misfire: in the query fields, the plain text, the addresses and the
// canonical URLs.
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields,
ghostCanonicalURLFields) {
// A command after a separator stays refused wherever 932235 does not
// misfire: in the addresses, handles, the domain, ghostURLFields, search,
// url, pathname, email_segment and email. The other fields skip 932235,
// which refuses "powershell & python3" as it refuses this.
for _, f := range slices.Concat(ghostAddressFields, ghostHandleFields,
ghostDomainFields, ghostURLFields) {
wantGhostField(t, f, commandAfterASeparator, true)
}
for _, target := range ghostQueryFields {
for _, target := range []string{
ghostSearch, ghostEmbed, ghostPathname, ghostSegment,
ghostInvitedEmail,
} {
wantGhostQuery(
t,
target+url.QueryEscape(commandAfterASeparator),
@@ -448,6 +658,15 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
)
}
// A comment keeps the rules for scripts and HTML.
wantGhostField(t, ghostNewComment, `<img src=x onerror=alert(1)>`, true)
wantGhostField(
t,
ghostNewComment,
`<a href="javascript:alert(1)">x</a>`,
true,
)
// The articles and code injection carry HTML on purpose, but a
// javascript: link is still refused there, and a script element in
// snippets and a newsletter's footer.
@@ -459,12 +678,138 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
wantGhostField(t, f, `<p>Hi</p><script src="/x.js"></script>`, true)
}
// An address keeps 932250, which refuses a listing of files.
for _, f := range ghostAddressFields {
// An address and the domain keep 932250, which refuses a listing of
// files. A handle keeps it too, but its @ comes before any command.
for _, f := range slices.Concat(ghostAddressFields, ghostDomainFields) {
wantGhostField(t, f, "ls -la /root", true)
}
}
func TestGhostTwoCommandNamesPass(t *testing.T) {
t.Parallel()
// The site's title, saved through settings.
siteTitle := ghostField{
http.MethodPut,
ghostSettings,
`{"settings":[{"key":"title","value":"VALUE"}]}`,
}
wantGhostField(t, siteTitle, ghostCommandNames, false)
// Values that join two command names, as a person types them.
for _, typed := range []string{
ghostCommandNames, "python3 & powershell", "powershell & ls -la",
"ls -la & python3",
} {
for _, f := range slices.Concat(ghostTextFields, ghostCodeFields) {
wantGhostField(t, f, typed, false)
}
wantGhostImportLabel(t, ghostImport, typed, false)
for _, target := range slices.Concat(
ghostAnalyticsFilters,
[]string{ghostAccountSearch},
) {
wantGhostQuery(t, target+url.QueryEscape(typed), false)
}
// filter as the admin's post picker writes it, after a person types
// such a title.
filter := "status:published+title:~'" + typed + "'"
wantGhostQuery(t, ghostAdminFilter+url.QueryEscape(filter), false)
}
}
// TestGhostOrdinaryQueriesPass checks the query fields of the analytics
// pages, email_segment, the search of ActivityPub and the check of an
// invitation.
func TestGhostOrdinaryQueriesPass(t *testing.T) {
t.Parallel()
for _, encoded := range hardValues {
value, _ := url.QueryUnescape(encoded)
text := url.QueryEscape("Notes on " + value + " & tidy files")
for _, target := range ghostAnalyticsFilters {
wantGhostQuery(t, target+encoded, false)
}
wantGhostQuery(t, ghostCampaign+text, false)
wantGhostQuery(t, ghostAccountSearch+text, false)
}
// pathname, the page the analytics show: a post and two tag pages.
for _, path := range []string{
"/tag/proc/", "/python3-and-powershell/", "/tag/python3/",
} {
wantGhostQuery(t, ghostPathname+url.QueryEscape(path), false)
}
wantGhostQuery(
t,
ghostCampaign+url.QueryEscape(ghostCommandNames),
false,
)
wantGhostQuery(
t,
ghostAccountSearch+"%40powershell%40example.com",
false,
)
for _, segment := range []string{
"label:powershell-users", "label:python3", "label:ls-la",
"label:htaccess", "status:free,tier:powershell-club",
} {
wantGhostQuery(t, ghostSegment+url.QueryEscape(segment), false)
}
for _, address := range ghostAddresses {
wantGhostQuery(t, ghostInvitedEmail+url.QueryEscape(address), false)
}
}
// wantGhostImportLabel checks that the Core Rule Set, reading bodies,
// refuses label, sent as labels in a CSV import to target, or lets it
// through.
func wantGhostImportLabel(
t *testing.T, target, label string, refused bool,
) {
t.Helper()
r := request{http.MethodPost, target, []string{multipart}}
body := field("labels", label) + field("mapping[email]", "email") + end
got, _ := inspectBody(t, readingBodies(t), r, body)
if refused && got.Score < ghostThreshold {
t.Errorf("labels=%q at %s let through: %+v", label, target, got)
}
if !refused && got.Score >= ghostThreshold {
t.Errorf("labels=%q at %s refused: %+v", label, target, got)
}
}
func TestGhostImportLabelsPass(t *testing.T) {
t.Parallel()
for _, encoded := range hardValues {
value, _ := url.QueryUnescape(encoded)
wantGhostImportLabel(t, ghostImport, value+" import", false)
}
wantGhostImportLabel(t, ghostImport, ghostCommandNames, false)
for _, attack := range ghostAttacks {
wantGhostImportLabel(t, ghostImport, attack, true)
}
// labels keeps its rules on other paths, and when the query has a field
// of the same name.
wantGhostImportLabel(t, ghostMembers, "python3 import", true)
wantGhostImportLabel(t, ghostImport+"?labels=x", "python3 import", true)
}
func TestGhostExclusionsAreNarrow(t *testing.T) {
t.Parallel()
@@ -502,7 +847,6 @@ func TestGhostExclusionsAreNarrow(t *testing.T) {
`{"settings":[{"value":"<meta name=x content=y>"}]}`,
},
{"/ghost/api/admin/members/", `{"members":[{"geolocation":"python3"}]}`},
{"/ghost/api/admin/invites/", `{"invites":[{"email":"python3.dev@example.com"}]}`},
{ghostPosts, email},
{ghostMembers, email},
{ghostMember, `{"members":[{"email":"python3.dev@example.com"}]}`},
@@ -560,6 +904,7 @@ func TestGhostScriptExclusionIsNarrow(t *testing.T) {
script = `<script defer src=\"/script.js\"></script>`
scriptInPost = `{"posts":[{"lexical":"` + script + `"}]}`
scriptInSettings = `{"settings":[{"value":"` + script + `"}]}`
scriptInTag = `{"tags":[{"codeinjection_head":"` + script + `"}]}`
)
for _, refused := range []struct{ target, body string }{
@@ -569,6 +914,8 @@ func TestGhostScriptExclusionIsNarrow(t *testing.T) {
{"/ghost/api/admin/users/me/", `{"users":[{"bio":"` + script + `"}]}`},
{"/api/settings/", scriptInSettings},
{ghostSettings + "?json.settings.0.value=x", scriptInSettings},
{ghostPosts, scriptInTag},
{ghostTags + "?json.tags.0.codeinjection_head=x", scriptInTag},
} {
r := request{http.MethodPut, refused.target, []string{jsonBody}}
@@ -590,7 +937,8 @@ func TestGhostQueryExclusionsAreNarrow(t *testing.T) {
// The query fields keep their rules off Ghost's paths, and in other
// query fields there. search skips its rules only on the Admin API, and
// url only on the editor's lookup of embeds.
// url only on the editor's lookup of embeds. pathname, email_segment,
// query and email skip theirs only on their own paths.
for _, target := range []string{
"/api/posts/?filter=powershell",
"/ghost/posts/?filter=powershell",
@@ -599,11 +947,79 @@ func TestGhostQueryExclusionsAreNarrow(t *testing.T) {
"/members/api/member/?search=python3",
"/ghost/api/admin/posts/?url=powershell",
"/oembed/?url=powershell",
"/ghost/api/admin/posts/?pathname=%2Fpowershell%2F",
"/ghost/api/admin/stats/top-content/?order=powershell",
"/ghost/api/admin/pages/?email_segment=label%3Apowershell-users",
"/ghost/api/admin/posts/?query=python3",
"/.ghost/activitypub/v1/actions/note?query=python3",
"/ghost/api/admin/invites/?email=python3.dev%40example.com",
} {
wantGhostQuery(t, target, true)
}
}
// TestGhostStaffCommentAndActivityPubExclusionsAreNarrow checks the body
// fields that 900370 to 900382 name, as TestGhostExclusionsAreNarrow checks
// the others: each keeps its rules on another path, and when the query has
// a field of the same name.
func TestGhostStaffCommentAndActivityPubExclusionsAreNarrow(t *testing.T) {
t.Parallel()
const (
bio = `{"users":[{"bio":"python3"}]}`
comment = `{"comments":[{"html":"<p>Notes on .htaccess</p>"}]}`
content = `{"content":"python3"}`
altText = `{"image":{"altText":"python3"}}`
accountBio = `{"bio":"python3"}`
invited = `{"invites":[{"email":"python3.dev@example.com"}]}`
accepted = `{"invitation":[{"email":"python3.dev@example.com"}]}`
sourceHandle = `{"sourceHandle":"@powershell@example.com"}`
actorURI = `{"actorUri":"https://example.com/users/powershell"}`
targetHandle = `{"targetHandle":"@powershell@example.com"}`
domain = `{"domain":"python3.example.com"}`
tagCode = `{"tags":[{"codeinjection_head":"<meta name=x content=y>"}]}`
tagURL = `{"tags":[{"canonical_url":"https://example.com/tag/powershell/"}]}`
)
for _, refused := range []struct{ target, body string }{
{"/api/users/1/", bio},
{ghostMember, bio},
{ghostUser + "?json.users.0.bio=x", bio},
{ghostPosts, comment},
{ghostComments + "?json.comments.0.html=x", comment},
{ghostAccount, content},
{ghostNote + "?json.content=x", content},
{ghostAccount, altText},
{ghostNote + "?json.image.altText=x", altText},
{ghostNote, accountBio},
{ghostAccount + "?json.bio=x", accountBio},
{ghostPosts, invited},
{ghostInvites + "?json.invites.0.email=x", invited},
{ghostInvites, accepted},
{ghostInvitation + "?json.invitation.0.email=x", accepted},
{ghostMigration, sourceHandle},
{ghostAliases + "?json.sourceHandle=x", sourceHandle},
{ghostMigration, actorURI},
{ghostAliases + "?json.actorUri=x", actorURI},
{ghostAliases, targetHandle},
{ghostMigration + "?json.targetHandle=x", targetHandle},
{ghostAccount, domain},
{ghostDomain + "?json.domain=x", domain},
{ghostPosts, tagCode},
{ghostTags + "?json.tags.0.codeinjection_head=x", tagCode},
{ghostPosts, tagURL},
{ghostTags + "?json.tags.0.canonical_url=x", tagURL},
} {
r := request{http.MethodPut, refused.target, []string{jsonBody}}
got, _ := inspectBody(t, readingBodies(t), r, refused.body)
if got.Score < ghostThreshold {
t.Errorf("PUT %s %s let through: %+v", refused.target,
refused.body, got)
}
}
}
func TestGhostPasswordsPass(t *testing.T) {
t.Parallel()
+97 -5
View File
@@ -172,11 +172,13 @@ SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
# A branch, tag or package name can hold a slash, as feat/docker-support and
# npm's @scope/name do. So q on a repository's branches, tags and packages
# pages, on an owner's packages page and on the admin page of packages also
# skips this rule:
# pages, on an owner's packages page, on the admin page of packages, on the
# API's search of packages and on the Composer and Conan registries' searches
# also skips this rule:
# 932160: /bin/sh
SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+/[^/]+/(?:branches|tags|packages)|\
admin/packages)$" \
admin/packages|api/v1/packages/[^/]+|\
api/packages/[^/]+/(?:composer/search\.json|conan/v[12]/conans/search))$" \
"id:900605,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932160;ARGS:q"
# gitea's other searches send a name or a few words in q too, which skips only
@@ -188,7 +190,11 @@ SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+/[^/]+/(?:branches|tags|packages)|\
# The searches:
# - the dashboard's filter of repositories;
# - the author filter of the issue and pull request lists;
# - the lists of users and organisations, and the API's search for users;
# - the lists of users and organisations;
# - the API's searches of users, of topics, of an organisation's teams and of
# emails;
# - the searches of the Cargo, Chef, Composer and Conan registries, and the
# NuGet registry's search service;
# - the list of repositories a user watches;
# - an organisation's milestones of one team, and its search for teams;
# - the milestones of a repository or an organisation, and the projects of a
@@ -205,7 +211,11 @@ SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+/[^/]+/(?:branches|tags|packages)|\
# versions holds the package's name, which can hold a slash, as npm's
# @scope/name does.
SecRule REQUEST_FILENAME "@rx ^/(?:repo/search|notifications/watching|\
explore/(?:users|organizations)|api/v1/users/search|\
explore/(?:users|organizations)|\
api/v1/(?:users/search|topics/search|orgs/[^/]+/teams/search|\
admin/emails/search)|\
api/packages/[^/]+/(?:cargo/api/v1/crates|chef/api/v1/(?:search|cookbooks)|\
composer/search\.json|conan/v[12]/conans/search|nuget/query)|\
org/[^/]+/(?:milestones/[^/]+|teams/-/search)|\
[^/]+/-/packages/[^/]+/.+/versions|\
[^/]+/[^/]+/(?:(?:issues|pulls)/posters|issues/suggestions|milestones|\
@@ -219,6 +229,88 @@ SecRule REQUEST_FILENAME "@rx ^/(?:repo/search|notifications/watching|\
ctl:ruleRemoveTargetById=932250;ARGS:q,\
ctl:ruleRemoveTargetById=932260;ARGS:q"
# Two of the API's searches send their words in a field other than q, which
# skips these rules there:
# 930120: .htaccess
# 932120: powershell
# 932250: ls -la
# 932260: python3, alice/python3-app
# The search of unadopted repositories sends an owner and a name, such as
# alice/python3-app, in query.
SecRule REQUEST_FILENAME "@rx ^/api/v1/admin/unadopted$" \
"id:900607,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:query,\
ctl:ruleRemoveTargetById=932120;ARGS:query,\
ctl:ruleRemoveTargetById=932250;ARGS:query,\
ctl:ruleRemoveTargetById=932260;ARGS:query"
# The search of a repository's milestones sends a milestone's name in name.
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/milestones$" \
"id:900608,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:name,\
ctl:ruleRemoveTargetById=932120;ARGS:name,\
ctl:ruleRemoveTargetById=932250;ARGS:name,\
ctl:ruleRemoveTargetById=932260;ARGS:name"
# The npm registry's search sends its words in text, which skips these rules
# there:
# 930120: .htaccess
# 932120: powershell
# 932160: /bin/sh, as an npm name such as @alice/tools-cli holds a slash
# 932250: ls -la
# 932260: python3
SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/npm/-/v1/search$" \
"id:900609,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:text,\
ctl:ruleRemoveTargetById=932120;ARGS:text,\
ctl:ruleRemoveTargetById=932160;ARGS:text,\
ctl:ruleRemoveTargetById=932250;ARGS:text,\
ctl:ruleRemoveTargetById=932260;ARGS:text"
# A Conan search takes a pattern such as zlib/*, which the rule for SQL
# injection (942100) refused in q. q skips that rule only when the request has
# one q, in the query string or the body, with the shape of a Conan reference:
# a name, then optionally / and a version, then optionally @ and a user, then
# optionally / and a channel. Each part is made of letters, digits, _, ., +, -
# and *. q must also hold no */, which ends an SQL comment. Such a q has no
# quote, no space and no comment between two words, so SQL injection such as
# 1' OR '1'='1, 1/**/or/**/1 or 1/*@*/or-1 stays refused.
# This rule is in phase 2, because a phase 1 rule runs before the body is read
# and cannot see a q there. It checks tx.app_gitea, since the skip above does
# not reach phase 2.
SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/conan/v[12]/conans/search$" \
"id:900611,phase:2,pass,nolog,chain"
SecRule &TX:app_gitea "@eq 1" "chain"
SecRule &ARGS:q "@eq 1" "chain"
SecRule ARGS:q "!@contains */" "chain"
SecRule ARGS:q "@rx ^[\w.+*-]+(?:/[\w.+*-]+)?(?:@[\w.+*-]+(?:/[\w.+*-]+)?)?$" \
"ctl:ruleRemoveTargetById=942100;ARGS:q"
# The NuGet registry's older searches, Search() and Packages(), send their words
# in searchTerm, or in $filter as an expression such as
# tolower(Id) eq 'python3'. Search()/$count and Packages()/$count count a
# search's results and read the same fields. searchTerm skips these rules:
# 930120: .htaccess
# 932120: powershell
# 932250: ls -la
# 932260: python3
# $filter skips these:
# 930120: .htaccess
# 932120: powershell
# The names $filter, $orderby and $top skip the rule for MongoDB operators
# (942290), which refused every such search.
SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/nuget/(?:Search|Packages)\(\)(?:/\$count)?$" \
"id:900612,phase:1,pass,nolog,\
ctl:ruleRemoveTargetById=930120;ARGS:searchTerm,\
ctl:ruleRemoveTargetById=932120;ARGS:searchTerm,\
ctl:ruleRemoveTargetById=932250;ARGS:searchTerm,\
ctl:ruleRemoveTargetById=932260;ARGS:searchTerm,\
ctl:ruleRemoveTargetById=930120;ARGS:$filter,\
ctl:ruleRemoveTargetById=932120;ARGS:$filter,\
ctl:ruleRemoveTargetById=942290;ARGS_NAMES:$filter,\
ctl:ruleRemoveTargetById=942290;ARGS_NAMES:$orderby,\
ctl:ruleRemoveTargetById=942290;ARGS_NAMES:$top"
# The repositories on a user's or an organisation's page are searched with q
# too. Their path has one segment, so q skips these rules on every path of one
# segment, other apps' pages such as /index.php included:
+238 -1
View File
@@ -3,6 +3,7 @@ package coreruleset_test
import (
"net/http"
"slices"
"strings"
"testing"
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
@@ -182,7 +183,7 @@ func TestGiteaSearchExclusionsAreNarrow(t *testing.T) {
}
}
attacks := []string{"%2Fbin%2Fsh", commandInjection, pathTraversal}
attacks := []string{binSh, commandInjection, pathTraversal}
// /bin/sh, a shell command after a ";", a path traversal and the hard
// values stay refused in q on paths near the searches, and in another
@@ -248,3 +249,239 @@ func TestGiteaSearchExclusionsAreNarrow(t *testing.T) {
}
}
}
// giteaAPISearches are the API's searches that gitea and Forgejo share whose
// field skips the lists of system files and command names. Each ends in the
// field it sends its words in.
//
//nolint:gochecknoglobals // a constant cannot be a list
var giteaAPISearches = []string{
"/api/v1/users/search?q=",
"/api/v1/topics/search?q=",
"/api/v1/orgs/acme/teams/search?q=",
"/api/v1/admin/emails/search?q=",
"/api/v1/admin/unadopted?query=",
"/api/v1/repos/alice/tools/milestones?name=",
}
// giteaAPIPackageSearch is the API's search of an owner's packages, whose q
// also lets /bin/sh through, since a package name can hold a slash.
const giteaAPIPackageSearch = "/api/v1/packages/alice?q="
func TestGiteaAttacksAreRefusedInItsAPISearches(t *testing.T) {
t.Parallel()
checkSearchesRefuseAttacks(
t,
giteaAPISearches,
[]string{giteaAPIPackageSearch},
)
}
// checkSearchesRefuseAttacks checks that SQL injection, a path traversal,
// script injection and a shell command stay refused in the field of each
// search, in the query string and in a form data body. Each search ends in
// its field. /bin/sh stays refused too in the field of each of
// refusingBinSh, but not of lettingBinSh.
func checkSearchesRefuseAttacks(
t *testing.T, refusingBinSh, lettingBinSh []string,
) {
t.Helper()
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
defaults, bodies := atDefaults(t), readingBodies(t)
for _, search := range slices.Concat(refusingBinSh, lettingBinSh) {
path, field, _ := strings.Cut(search, "?")
for _, attack := range []string{
sqlInjection, pathTraversal, scriptInjection, commandInjection,
} {
if got := inspect(t, defaults, get(search+attack)); got.Score < threshold {
t.Errorf("GET %s%s let through: %+v", search, attack, got)
}
// The same field in a form data body.
r := request{http.MethodPost, path, []string{formData}}
if got, _ := inspectBody(t, bodies, r, field+attack); got.Score < threshold {
t.Errorf(
"POST %s %s%s let through: %+v",
path,
field,
attack,
got,
)
}
}
}
for _, search := range refusingBinSh {
if got := inspect(t, defaults, get(search+binSh)); got.Score < threshold {
t.Errorf("%s%s let through: %+v", search, binSh, got)
}
}
}
func TestGiteaAPISearchExclusionsAreNarrow(t *testing.T) {
t.Parallel()
// Each search's field on paths near the searches, and another field on
// them.
checkValuesStayRefused(t, []string{
"/api/v1/topics/search/x?q=",
"/api/v1/orgs/acme/teams?q=",
"/api/v1/orgs/acme/teams/search/x?q=",
"/api/v1/admin/emails?q=",
"/api/v1/admin/unadopted/alice/legacy-site?query=",
"/api/v1/admin/users?query=",
"/api/v1/repos/alice/tools/milestones/1?name=",
"/api/v1/repos/alice/tools/labels?name=",
"/api/v1/packages/alice/generic/tools-cli?q=",
"/api/v1/topics/search?other=",
"/api/v1/orgs/acme/teams/search?other=",
"/api/v1/admin/emails/search?other=",
"/api/v1/admin/unadopted?q=",
"/api/v1/repos/alice/tools/milestones?q=",
"/api/v1/packages/alice?other=",
})
}
// checkValuesStayRefused checks that /bin/sh, a shell command after a ";", a
// path traversal and the hard values stay refused at the end of each target.
func checkValuesStayRefused(t *testing.T, targets []string) {
t.Helper()
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
crs := atDefaults(t)
for _, target := range targets {
for _, value := range slices.Concat(
[]string{binSh, commandInjection, pathTraversal},
hardValues,
) {
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
t.Errorf("%s%s let through: %+v", target, value, got)
}
}
}
}
// giteaRegistrySearches are the package registries' searches whose field
// skips the lists of system files and command names. Each ends in the field
// it sends its words in.
//
//nolint:gochecknoglobals // a constant cannot be a list
var giteaRegistrySearches = []string{
"/api/packages/alice/cargo/api/v1/crates?q=",
"/api/packages/alice/chef/api/v1/search?q=",
"/api/packages/alice/chef/api/v1/cookbooks?q=",
"/api/packages/alice/nuget/query?q=",
"/api/packages/alice/nuget/Search()?searchTerm=",
"/api/packages/alice/nuget/Packages()?$filter=",
"/api/packages/alice/nuget/Search()/$count?searchTerm=",
"/api/packages/alice/nuget/Packages()/$count?$filter=",
}
// giteaRegistryNameSearches are the package registries' searches whose field
// also lets /bin/sh through, since a Composer, Conan or npm name can hold a
// slash.
//
//nolint:gochecknoglobals // a constant cannot be a list
var giteaRegistryNameSearches = []string{
"/api/packages/alice/composer/search.json?q=",
"/api/packages/alice/conan/v1/conans/search?q=",
"/api/packages/alice/conan/v2/conans/search?q=",
"/api/packages/alice/npm/-/v1/search?text=",
}
func TestGiteaAttacksAreRefusedInItsRegistrySearches(t *testing.T) {
t.Parallel()
checkSearchesRefuseAttacks(
t,
giteaRegistrySearches,
giteaRegistryNameSearches,
)
}
func TestGiteaRegistrySearchExclusionsAreNarrow(t *testing.T) {
t.Parallel()
// Each search's field on paths near the searches, and another field on
// them.
checkValuesStayRefused(t, []string{
"/api/packages/alice/cargo/api/v1/crates/tools-cli?q=",
"/api/packages/alice/chef/api/v1/cookbooks/tools?q=",
"/api/packages/alice/composer/packages.json?q=",
"/api/packages/alice/conan/v1/conans/tools/1.0/alice/stable/search?q=",
"/api/packages/alice/nuget/query/x?q=",
"/api/packages/alice/nuget/FindPackagesById()?id=",
"/api/packages/alice/nuget/FindPackagesById()/$count?id=",
"/api/packages/alice/nuget/Search()/$count/x?searchTerm=",
"/api/packages/alice/npm/tools-cli?text=",
"/api/packages/alice/cargo/api/v1/crates?other=",
"/api/packages/alice/chef/api/v1/search?other=",
"/api/packages/alice/composer/search.json?type=",
"/api/packages/alice/conan/v2/conans/search?other=",
"/api/packages/alice/nuget/query?other=",
"/api/packages/alice/nuget/Search()?targetFramework=",
"/api/packages/alice/nuget/Packages()?$orderby=",
"/api/packages/alice/nuget/Search()/$count?targetFramework=",
"/api/packages/alice/nuget/Packages()/$count?$orderby=",
"/api/packages/alice/npm/-/v1/search?q=",
})
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
crs := atDefaults(t)
// The rule for MongoDB operators (942290) still refuses the name of
// another operator on NuGet's searches, and $filter on NuGet's other
// paths. The rule for SQL injection (942100) still refuses tools/* in a
// field other than a Conan search's, SQL injection in a second q after a
// Conan pattern, and SQL comments that stand in for spaces.
for _, target := range []string{
"/api/packages/alice/nuget/Search()?$where=1",
"/api/packages/alice/nuget/Packages()?$ne=1",
"/api/packages/alice/nuget/Search()/$count?$where=1",
"/api/packages/alice/nuget/FindPackagesById()?$filter=IsLatestVersion",
"/api/packages/alice/nuget/FindPackagesById()/$count?$filter=IsLatestVersion",
"/api/packages/alice/composer/search.json?q=tools%2F%2A",
"/api/packages/alice/conan/v2/conans/search?other=tools%2F%2A",
"/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A&q=" +
sqlInjection,
"/api/packages/alice/conan/v2/conans/search?q=1%2F%2A%2A%2For%2F%2A%2A%2F1",
"/api/packages/alice/conan/v2/conans/search?q=1%2F%2A%40%2A%2For-1",
} {
if got := inspect(t, crs, get(target)); got.Score < threshold {
t.Errorf("%s let through: %+v", target, got)
}
}
// SQL injection in a q in the body is refused, even after a Conan
// pattern in the query string.
search := request{
http.MethodPost,
"/api/packages/alice/conan/v2/conans/search?q=zlib",
[]string{formData},
}
body := "q=" + sqlInjection
if got, _ := inspectBody(t, readingBodies(t), search, body); got.Score < threshold {
t.Errorf("POST %s %s let through: %+v", search.target, body, got)
}
// The rule that lets a Conan pattern through is in phase 2, where it
// checks tx.app_gitea itself: a hostname that does not serve gitea
// refuses the pattern.
pattern := get(
"/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A",
)
apps := servingApps(t, map[string][]string{
giteaHost: {gitea}, otherHost: {wordpress},
})
wantResult(t, apps, withHost(pattern, giteaHost), coreruleset.Result{})
wantResult(t, apps, withHost(pattern, otherHost), matched(942100))
}
+160
View File
@@ -974,3 +974,163 @@
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
{"method": "GET", "target": "/alice/tools/milestone/1?q=SELECT%20%2A%20FROM%20users%20WHERE", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
# The API's searches that gitea and Forgejo share, sent with curl to Forgejo
# 17.0.1. Each searches for words that name commands and files, such as python3
# and .htaccess, and for plain words. A package name can hold a slash, as npm's
# @alice/tools-cli does, so the search of packages is also sent /bin/sh. The
# search of unadopted repositories takes an owner and a name.
{"method": "GET", "target": "/api/v1/repos/search?q=python3&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/search?q=powershell&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/search?q=ls%20-la&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/search?q=.htaccess&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/search?q=docker-compose.yml&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/search?q=tools&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/users/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/users/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/users/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/users/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/users/search?q=bob", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/issues/search?q=python3&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/issues/search?q=powershell&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/issues/search?q=ls%20-la&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/issues/search?q=.htaccess&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/issues/search?q=docker-compose.yml&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/issues/search?q=build&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=install-script", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/packages/alice?q=%40alice%2Ftools-cli", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/topics/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/topics/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/topics/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/topics/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/topics/search?q=docker", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=developers", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/emails/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/emails/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/emails/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/emails/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/emails/search?q=alice", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/unadopted?query=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/unadopted?query=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/unadopted?query=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/unadopted?query=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/unadopted?query=alice%2Fpython3-app", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/admin/unadopted?query=alice%2Flegacy-site", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=v1.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
# The package registries' searches, sent to Forgejo 17.0.1 by each registry's
# own client: npm, composer, conan 2 and 1, cargo, dotnet and knife. curl sent
# the requests no client sends: the Chef cookbook list with a search, and
# NuGet's Packages() with the search in $filter, as NuGet's client builds it for
# a feed without Search(). curl also sent each NuGet Search() and Packages()
# search to Search()/$count and Packages()/$count, which count its results. Each
# searches for words that name commands and files, such as python3 and
# .htaccess, and for plain words. A Composer, Conan or npm name can hold a
# slash, so those searches are also sent /bin/sh.
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=python3&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=powershell&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=ls+-la&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=.htaccess&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=%2Fbin%2Fsh&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=%40alice%2Ftools-cli&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=docker-compose.yml&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=tools&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=python3&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=powershell&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=ls+-la&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=.htaccess&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=%2Fbin%2Fsh&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=alice%2Ftools-cli&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=docker-compose.yml&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=tools&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=python3", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=powershell", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=ls+-la", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=tools%2F1.0%40alice%2Fstable", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=tools", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=python3", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=powershell", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=ls+-la", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=tools%2F%2A", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=tools", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=python3&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=powershell&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=ls%20%2Dla&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=%2Ehtaccess&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=docker%2Dcompose%2Eyml&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=tools&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=python3&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: bc433449-14e0-4cb0-907b-fcca1ead302b", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=powershell&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: e691cf8b-1859-45b1-aa67-e0987eb7474a", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=ls%20-la&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: 725a8ab1-a2c3-4924-84dd-a741bd6b8c07", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=.htaccess&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: 5b1d333f-6583-4f78-9c23-d776ebbde5cf", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=docker-compose.yml&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: 23a81a25-0828-4311-8d2d-66f0d5161b6b", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=tools&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: ebf39a79-969d-4ac5-b5a0-5119c2d43ccc", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: 825cbe04-63e7-414a-8394-2ecf74c68211", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: f61bfff6-1c26-4024-bc17-6b78dcef2edc", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: e894f984-8abf-4a23-960a-d1a09c94eaa3", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: 33e3aa1b-14cb-4854-821a-c3eb9e5be7ee", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: c478a564-7cf9-4282-9f00-bf5b7966607d", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: 8dfa5583-4005-4134-9343-ddb4c68c125b", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=python3&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: e7210054-9f40-4a58-8228-beeab1e3bb05", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=powershell&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: 0f3b26a8-7ca2-4b52-a9bb-c23a25c4172c", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=ls%20-la&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: d0fa60d5-6948-4149-845f-e38d340cc09a", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=.htaccess&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: 8c22d5d2-1691-4488-89ca-a405dd7a15cc", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=docker-compose.yml&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: 0a1bd4b1-0009-4d1d-931d-d1c19b26dd8b", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=tools&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: f858e4b5-10bc-4633-a517-f165a65ac4cb", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=python3&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=powershell&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=ls%20-la&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=.htaccess&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=docker-compose.yml&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=tools&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'python3'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'powershell'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'tools'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'python3'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'powershell'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'tools'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
+30
View File
@@ -762,3 +762,33 @@
{"method": "POST", "target": "/ghost/api/admin/posts/", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA2OTQsImV4cCI6MTc5NDQwMDk5NCwiYXVkIjoiL2FkbWluLyJ9.ayqaBc1Hg4fbMZj_XbvUGlhloCnW_D5KDMN8A6uM68M", "Accept-Version: v6.76", "Content-Type: application/json", "Content-Length: 465"], "body": "{\"posts\": [{\"title\": \"Older editor format: python3 notes\", \"mobiledoc\": \"{\\\"version\\\": \\\"0.3.1\\\", \\\"atoms\\\": [], \\\"markups\\\": [[\\\"code\\\"]], \\\"cards\\\": [[\\\"code\\\", {\\\"code\\\": \\\"ls -la /var/www\\\\npython3 tidy.py .htaccess\\\", \\\"language\\\": \\\"bash\\\"}]], \\\"sections\\\": [[1, \\\"h2\\\", [[0, [], 0, \\\"Mobiledoc post about powershell\\\"]]], [1, \\\"p\\\", [[0, [], 0, \\\"Run \\\"], [0, [0], 1, \\\"ls -la\\\"], [0, [], 0, \\\" and keep .htaccess tidy.\\\"]]], [10, 0]]}\", \"status\": \"draft\"}]}"}
{"method": "POST", "target": "/ghost/api/admin/snippets/?formats=mobiledoc%2Clexical", "headers": ["Host: example.com", "Content-Length: 519", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A8kdYjooVavGjq0rNroahDCvgTG3XukhZ.ioil5%2Fg42yqQcdl%2B%2FlhvXc4HoOV6hGDetkdr5aPXvuo"], "body": "{\"snippets\":[{\"name\":\"python3 & .htaccess tip\",\"mobiledoc\":\"{}\",\"lexical\": \"{\\\"root\\\": {\\\"children\\\": [{\\\"children\\\": [{\\\"detail\\\": 0, \\\"format\\\": 0, \\\"mode\\\": \\\"normal\\\", \\\"style\\\": \\\"\\\", \\\"text\\\": \\\"Run ls -la, then python3 tidy.py .htaccess; on Windows use powershell.\\\", \\\"type\\\": \\\"extended-text\\\", \\\"version\\\": 1}], \\\"direction\\\": \\\"ltr\\\", \\\"format\\\": \\\"\\\", \\\"indent\\\": 0, \\\"type\\\": \\\"paragraph\\\", \\\"version\\\": 1}], \\\"direction\\\": \\\"ltr\\\", \\\"format\\\": \\\"\\\", \\\"indent\\\": 0, \\\"type\\\": \\\"root\\\", \\\"version\\\": 1}}\"}]}"}
{"method": "POST", "target": "/ghost/api/admin/themes/upload/", "headers": ["Host: example.com", "Content-Length: 2138", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzhPTWHSPscackPlN", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A8kdYjooVavGjq0rNroahDCvgTG3XukhZ.ioil5%2Fg42yqQcdl%2B%2FlhvXc4HoOV6hGDetkdr5aPXvuo"], "body": "------WebKitFormBoundaryzhPTWHSPscackPlN\r\nContent-Disposition: form-data; name=\"file\"; filename=\"notes-theme.zip\"\r\nContent-Type: application/zip\r\n\r\nUEsDBAoAAAAAAM9la10AAAAAAAAAAAAAAAAMABwAbm90ZXMtdGhlbWUvVVQJAAOmY/RqpmP0anV4CwABBOgDAAAE6AMAAFBLAwQUAAAACADPZWtdXfnn43sAAACmAAAAFQAcAG5vdGVzLXRoZW1lL2luZGV4Lmhic1VUCQADpmP0aqZj9Gp1eAsAAQToAwAABOgDAAAtjlEKAyEMRP/3FHZ7AKHfqVcpwcYqWF00C4WQu1dtP+fNZCYiFzBPCnhmVt3gjak4kWuojdBHc9TOXRWwcfKZjM/Y+30XmcZjKdXdQbw5QBMbhWmeLU8qwokzjXOLDuzIiNDHUzt4sV/ngPY/pzqK8ZUKcqplZtY/2xdQSwMECgAAAAAAz2VrXQAAAAAAAAAAAAAAABUAHABub3Rlcy10aGVtZS9wYXJ0aWFscy9VVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAAUEsDBBQAAAAIAM9la11tuH02lQAAAPgAAAAUABwAbm90ZXMtdGhlbWUvcGFnZS5oYnNVVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAAXU+7DsMgDNz7FZTMVdSd5lcQoiaxBEkFzmTx77XTdGi3e+l0x3x15gkp7Jl6vzAPr60JcqESxgwm5tDawzKr7g/Wu53ccp+YCSmDhMeDDZhMgkB7BY8lzOpgmU2rUQsE+r3m/4g1IZP6P7oPusdK64hJehpEwm39zpmXW9xWgpU0ckIdcuYmN54HDrsUsWW3tH3uXd5QSwMEFAAAAAgAz2VrXY4bR/y4AAAABwEAABgAHABub3Rlcy10aGVtZS9wYWNrYWdlLmpzb25VVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAALY/BbsIwDIbvPIWVM6uKEJdJm+BJKis1baTEjuIAG6jvjpPtZvv7/Vl+OcZE7hMcSyX9qCtZuwc3k/oScg3CjV5AE8YIncNVCvQ8CEP+ravwEZBnyPKgoivF2Bx3q//3D8M4jG6/A0e8BCa14cstq2ht+Pvr1AObbeHNfKVzShhi4xiDpzP9YMqRBi/Jkubywtew9Gg2k06ZypRxaQ8dRnOFZM2k4fl3sOk9lnlCVaptVMuNtm33BlBLAwQUAAAACADPZWtdbbh9NpUAAAD4AAAAFAAcAG5vdGVzLXRoZW1lL3Bvc3QuaGJzVVQJAAOmY/RqpmP0anV4CwABBOgDAAAE6AMAAF1Puw7DIAzc+xWUzFXUneZXEKImsQRJBc5k8e+103Rot3vpdMd8deYJKeyZer8wD6+tCXKhEsYMJubQ2sMyq+4P1rud3HKfmAkpg4THgw2YTIJAewWPJczqYJlNq1ELBPq95v+INSGT+j+6D7rHSuuISXoaRMJt/c6Zl1vcVoKVNHJCHXLmJjeeBw67FLFlt7R97l3eUEsDBBQAAAAIAM9la12WkoJXiwAAAMEAAAAXABwAbm90ZXMtdGhlbWUvZGVmYXVsdC5oYnNVVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAANY6xDsIwDET3fkXIDqwMboQEzDCwMEWmdZtKKZGIGVDkf6cO6mTdO92dYXO+nu6P28UEnqNrQI+J+BpbW8oxT0y7mDqMJGIdBMLewUyMpgv4zsSt/fCwPSweTxzJlaKur0IE9isdQ8rsNa+09jTwTP3XdBFz1jVVvirdKn8gImt4SIk1rNgtHfXhH1BLAQIeAwoAAAAAAM9la10AAAAAAAAAAAAAAAAMABgAAAAAAAAAEAD9QQAAAABub3Rlcy10aGVtZS9VVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADPZWtdXfnn43sAAACmAAAAFQAYAAAAAAABAAAAtIFGAAAAbm90ZXMtdGhlbWUvaW5kZXguaGJzVVQFAAOmY/RqdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAAAAAAz2VrXQAAAAAAAAAAAAAAABUAGAAAAAAAAAAQAP1BEAEAAG5vdGVzLXRoZW1lL3BhcnRpYWxzL1VUBQADpmP0anV4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAM9la11tuH02lQAAAPgAAAAUABgAAAAAAAEAAAC0gV8BAABub3Rlcy10aGVtZS9wYWdlLmhic1VUBQADpmP0anV4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAM9la12OG0f8uAAAAAcBAAAYABgAAAAAAAEAAAC0gUICAABub3Rlcy10aGVtZS9wYWNrYWdlLmpzb25VVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADPZWtdbbh9NpUAAAD4AAAAFAAYAAAAAAABAAAAtIFMAwAAbm90ZXMtdGhlbWUvcG9zdC5oYnNVVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADPZWtdlpKCV4sAAADBAAAAFwAYAAAAAAABAAAAtIEvBAAAbm90ZXMtdGhlbWUvZGVmYXVsdC5oYnNVVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwUGAAAAAAcABwB3AgAACwUAAAAA\r\n------WebKitFormBoundaryzhPTWHSPscackPlN--\r\n"}
# The requests below were sent to Ghost by a script with the admin's or a
# member's headers. They hold words that name commands and files in more fields.
{"method": "POST", "target": "/ghost/api/admin/authentication/setup/", "headers": ["Host: example.com", "Content-Length: 158", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"setup\":[{\"blogTitle\":\"Notes on python3, powershell & .htaccess\",\"name\":\"Alice ls -la Example\",\"email\":\"alice@example.com\",\"password\":\"Hv4-mQz8-Lr2x-Tp9w\"}]}"}
{"method": "POST", "target": "/ghost/api/admin/session/", "headers": ["Host: example.com", "Content-Length: 64", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"username\":\"alice@example.com\",\"password\":\"Hv4-mQz8-Lr2x-Tp9w\"}"}
{"method": "PUT", "target": "/ghost/api/admin/users/6af54436a509340001efcee2/?include=roles", "headers": ["Host: example.com", "Content-Length: 1256", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"users\":[{\"id\":\"6af54436a509340001efcee2\",\"name\":\"Alice python3 & powershell\",\"slug\":\"alice-python3-powershell\",\"email\":\"alice@example.com\",\"profile_image\":null,\"cover_image\":null,\"bio\":\"I write about ls -la, python3, powershell and .htaccess files.\",\"website\":\"https://example.com/python3/powershell\",\"location\":\"Berlin & the python3 meetup\",\"facebook\":null,\"twitter\":null,\"threads\":null,\"bluesky\":null,\"mastodon\":null,\"tiktok\":null,\"youtube\":null,\"instagram\":null,\"linkedin\":null,\"accessibility\":\"{\\\"nightShift\\\":\\\"system\\\"}\",\"status\":\"active\",\"meta_title\":null,\"meta_description\":null,\"tour\":null,\"last_seen\":\"2026-11-12T04:44:42.000Z\",\"comment_notifications\":true,\"free_member_signup_notification\":true,\"paid_subscription_started_notification\":true,\"paid_subscription_canceled_notification\":false,\"mention_notifications\":true,\"recommendation_notifications\":true,\"milestone_notifications\":true,\"donation_notifications\":true,\"gift_subscription_notifications\":true,\"created_at\":\"2026-11-12T04:44:38.000Z\",\"updated_at\":\"2026-11-12T04:44:42.000Z\",\"roles\":[{\"id\":\"6af54436a509340001efcee7\",\"name\":\"Owner\",\"description\":\"Blog Owner\",\"created_at\":\"2026-11-12T04:44:38.000Z\",\"updated_at\":\"2026-11-12T04:44:38.000Z\"}],\"url\":\"http://example.com/author/alice/\"}]}"}
{"method": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 130", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"invites\":[{\"email\":\"erin.htaccess@example.com\",\"role_id\":\"6af54436a509340001efcee6\",\"expires\":null,\"status\":null,\"token\":null}]}"}
{"method": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 128", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"invites\":[{\"email\":\"python3.dev@example.com\",\"role_id\":\"6af54436a509340001efcee6\",\"expires\":null,\"status\":null,\"token\":null}]}"}
{"method": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 132", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"invites\":[{\"email\":\"dave.powershell@example.com\",\"role_id\":\"6af54436a509340001efcee6\",\"expires\":null,\"status\":null,\"token\":null}]}"}
{"method": "GET", "target": "/ghost/api/admin/authentication/invitation/?email=erin.htaccess%40example.com", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
{"method": "POST", "target": "/ghost/api/admin/authentication/invitation/", "headers": ["Host: example.com", "Content-Length: 243", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"invitation\":[{\"token\":\"MTc5NTA2MzQ4MjU5MnxlcmluLmh0YWNjZXNzQGV4YW1wbGUuY29tfDUxbkp4aWJ4SzA4NzN0eUtIQWl5YzhyWFhKTjA0aWJ1ZDExendTVHQrSU09\",\"name\":\"Erin python3 & .htaccess\",\"password\":\"Rk7-Wq2z-Np5c-Lx8v\",\"email\":\"erin.htaccess@example.com\"}]}"}
{"method": "PUT", "target": "/ghost/api/admin/tiers/6af54437a509340001efcef1/", "headers": ["Host: example.com", "Content-Length: 474", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"tiers\":[{\"id\":\"6af54437a509340001efcef1\",\"name\":\"python3 & powershell club\",\"description\":\"Weekly ls -la and .htaccess tips\",\"slug\":\"default-product\",\"active\":true,\"type\":\"paid\",\"welcome_page_url\":null,\"created_at\":\"2026-11-12T04:44:39.000Z\",\"updated_at\":\"2026-11-12T04:44:39.000Z\",\"visibility\":\"public\",\"benefits\":[\"python3 scripts\",\"powershell recipes\",\"ls -la cheat sheet\",\".htaccess examples\"],\"currency\":\"USD\",\"monthly_price\":500,\"yearly_price\":5000,\"trial_days\":0}]}"}
{"method": "POST", "target": "/ghost/api/admin/offers/", "headers": ["Host: example.com", "Content-Length: 391", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"offers\":[{\"name\":\"python3 launch\",\"code\":\"python3-launch\",\"display_title\":\"powershell & python3 deal\",\"display_description\":\"Half off for ls -la and .htaccess fans\",\"type\":\"percent\",\"cadence\":\"month\",\"amount\":50,\"duration\":\"once\",\"duration_in_months\":null,\"currency_restriction\":false,\"currency\":null,\"status\":\"active\",\"redemption_type\":\"signup\",\"tier\":{\"id\":\"6af54437a509340001efcef1\"}}]}"}
{"method": "POST", "target": "/ghost/api/admin/recommendations/", "headers": ["Host: example.com", "Content-Length: 288", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"recommendations\":[{\"title\":\"python3 & powershell weekly\",\"description\":\"Tips on ls -la and .htaccess files\",\"excerpt\":\"A newsletter about python3, powershell, ls -la and .htaccess.\",\"featured_image\":null,\"favicon\":null,\"url\":\"https://example.com/python3/\",\"one_click_subscribe\":false}]}"}
{"method": "POST", "target": "/ghost/api/admin/integrations/?include=api_keys%2Cwebhooks", "headers": ["Host: example.com", "Content-Length: 55", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"integrations\":[{\"name\":\"python3 & powershell sync\"}]}"}
{"method": "PUT", "target": "/ghost/api/admin/integrations/6af5443aa509340001efcfc6/?include=api_keys%2Cwebhooks", "headers": ["Host: example.com", "Content-Length: 128", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"integrations\":[{\"name\":\"python3 & powershell sync\",\"description\":\"Runs ls -la and tidies .htaccess files\",\"icon_image\":null}]}"}
{"method": "POST", "target": "/ghost/api/admin/webhooks/", "headers": ["Host: example.com", "Content-Length: 202", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"webhooks\":[{\"event\":\"post.published\",\"target_url\":\"https://example.com/hooks/python3\",\"name\":\"python3 & powershell hook\",\"secret\":null,\"api_version\":\"v6\",\"integration_id\":\"6af5443aa509340001efcfc6\"}]}"}
{"method": "POST", "target": "/ghost/api/admin/tags/?include=count.posts", "headers": ["Host: example.com", "Content-Length: 702", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3AWHEHmHzgM_62oH0uUMxh6TgpQCaauWmB.h%2FuJNd7nfwPSaFGmMarZNVguG8U1ZIIJsaLFE4%2BqXMA"], "body": "{\"tags\":[{\"name\":\"Shell\",\"slug\":\"shell\",\"description\":null,\"feature_image\":null,\"meta_title\":\"python3 & powershell tips\",\"meta_description\":\"Run ls -la and tidy .htaccess files with python3.\",\"canonical_url\":\"https://example.com/tag/powershell/\",\"twitter_image\":null,\"twitter_title\":\"powershell & python3\",\"twitter_description\":\"ls -la, .htaccess and more\",\"og_image\":null,\"og_title\":\"python3 & powershell\",\"og_description\":\"ls -la and .htaccess tips\",\"codeinjection_head\":\"<meta name=\\\"google-site-verification\\\" content=\\\"x\\\"><link rel=\\\"preconnect\\\" href=\\\"https://fonts.example.com\\\">\",\"codeinjection_foot\":\"<style>.tag-python3 { color: #333; }</style>\",\"accent_color\":null,\"visibility\":\"public\"}]}"}
{"method": "PUT", "target": "/ghost/api/admin/posts/6af5443ba509340001efcfd1/?formats=mobiledoc%2Clexical", "headers": ["Host: example.com", "Content-Length: 315", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"posts\":[{\"updated_at\":\"2026-11-12T04:44:43.000Z\",\"title\":\"Shell notes\",\"feature_image\":\"https://images.example.com/terminal.png\",\"feature_image_alt\":\"A terminal running ls -la on .htaccess with python3\",\"feature_image_caption\":\"python3 &amp; powershell, photo by <a href=\\\"https://example.com/ls-la\\\">Erin</a>\"}]}"}
{"method": "POST", "target": "/ghost/api/admin/members/upload/", "headers": ["Host: example.com", "Content-Length: 990", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: multipart/form-data; boundary=----WebKitFormBoundaryq3Lm8vXc2TzR7wKd", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"membersfile\"; filename=\"members.csv\"\r\nContent-Type: text/csv\r\n\r\nemail,name,note,labels\r\npython3.dev@example.com,Grace python3,Asked about ls -la,powershell\r\nerin.htaccess@example.com,Erin,Tidies .htaccess files,python3\r\n\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"labels\"\r\n\r\npython3 import\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"labels\"\r\n\r\npowershell & .htaccess\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[email]\"\r\n\r\nemail\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[name]\"\r\n\r\nname\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[note]\"\r\n\r\nnote\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[labels]\"\r\n\r\nlabels\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd--\r\n"}
{"method": "GET", "target": "/ghost/api/admin/stats/top-content/?date_from=2026-10-13&date_to=2026-11-12&timezone=Etc%2FUTC&member_status=all&pathname=%2Fpython3-and-powershell%2F", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
{"method": "GET", "target": "/ghost/api/admin/stats/top-content/?date_from=2026-10-13&date_to=2026-11-12&timezone=Etc%2FUTC&member_status=all&source=powershell.example.com", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
{"method": "GET", "target": "/ghost/api/admin/stats/top-content/?date_from=2026-10-13&date_to=2026-11-12&timezone=Etc%2FUTC&member_status=all&utm_campaign=powershell+%26+python3", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
{"method": "PUT", "target": "/ghost/api/admin/posts/6af5443ba509340001efcfd1/?formats=mobiledoc%2Clexical&newsletter=default-newsletter&email_segment=label%3Apowershell-users", "headers": ["Host: example.com", "Content-Length: 74", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"posts\":[{\"updated_at\":\"2026-11-12T04:44:43.000Z\",\"status\":\"published\"}]}"}
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Apublished%2Btitle%3A%7E%27powershell+%26+python3%27&limit=25&fields=id%2Ctitle&order=published_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Apublished%2Btitle%3A%7E%27ls+-la+%26+.htaccess%27&limit=25&fields=id%2Ctitle&order=published_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
{"method": "POST", "target": "/members/api/comments/", "headers": ["Host: example.com", "Content-Length: 161", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": "{\"comments\":[{\"post_id\":\"6af5443ba509340001efcfd1\",\"status\":\"published\",\"html\":\"<p>Thanks! I ran <code>ls -la</code> and fixed my .htaccess with python3.</p>\"}]}"}
{"method": "PUT", "target": "/members/api/comments/6af5443ba509340001efd00b/", "headers": ["Host: example.com", "Content-Length: 140", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": "{\"comments\":[{\"id\":\"6af5443ba509340001efd00b\",\"html\":\"<p>Thanks! I ran ls -la, then powershell, and fixed my .htaccess with python3.</p>\"}]}"}
{"method": "POST", "target": "/members/api/comments/", "headers": ["Host: example.com", "Content-Length: 220", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": "{\"comments\":[{\"post_id\":\"6af5443ba509340001efcfd1\",\"parent_id\":\"6af5443ba509340001efd00b\",\"in_reply_to_id\":\"6af5443ba509340001efd00b\",\"status\":\"published\",\"html\":\"<p>Same here: python3 &amp; powershell both work.</p>\"}]}"}
{"method": "GET", "target": "/members/api/comments/post/6af5443ba509340001efcfd1/?limit=20&filter=created_at%3A%3C%3D2026-11-12T04%3A44%3A43.000Z&page=2&order=created_at+desc", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": ""}
{"method": "GET", "target": "/members/api/comments/6af5443ba509340001efd00b/replies/?limit=5&filter=id%3A%3E%276af5443ba509340001efd010%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": ""}
+161
View File
@@ -381,6 +381,167 @@
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE&search_mode=exact
/alice/tools/milestone/1?q=SELECT%20%2A%20FROM%20users%20WHERE
# The API's searches that gitea and Forgejo share, sent with curl to gitea
# 1.27.3. Each searches for words that name commands and files, such as python3
# and .htaccess, and for plain words. A package name can hold a slash, as npm's
# @alice/tools-cli does, so the search of packages is also sent /bin/sh. The
# search of unadopted repositories takes an owner and a name.
/api/v1/repos/search?q=python3&limit=10
/api/v1/repos/search?q=powershell&limit=10
/api/v1/repos/search?q=ls%20-la&limit=10
/api/v1/repos/search?q=.htaccess&limit=10
/api/v1/repos/search?q=docker-compose.yml&limit=10
/api/v1/repos/search?q=tools&limit=10
/api/v1/users/search?q=python3
/api/v1/users/search?q=powershell
/api/v1/users/search?q=ls%20-la
/api/v1/users/search?q=.htaccess
/api/v1/users/search?q=bob
/api/v1/repos/issues/search?q=python3&type=issues&state=open
/api/v1/repos/issues/search?q=powershell&type=issues&state=open
/api/v1/repos/issues/search?q=ls%20-la&type=issues&state=open
/api/v1/repos/issues/search?q=.htaccess&type=issues&state=open
/api/v1/repos/issues/search?q=docker-compose.yml&type=issues&state=open
/api/v1/repos/issues/search?q=build&type=issues&state=open
/api/v1/repos/alice/tools/issues?state=open&q=python3
/api/v1/repos/alice/tools/issues?state=open&q=powershell
/api/v1/repos/alice/tools/issues?state=open&q=ls%20-la
/api/v1/repos/alice/tools/issues?state=open&q=.htaccess
/api/v1/repos/alice/tools/issues?state=open&q=docker-compose.yml
/api/v1/packages/alice?q=python3
/api/v1/packages/alice?q=powershell
/api/v1/packages/alice?q=ls%20-la
/api/v1/packages/alice?q=.htaccess
/api/v1/packages/alice?q=%2Fbin%2Fsh
/api/v1/packages/alice?q=install-script
/api/v1/packages/alice?q=%40alice%2Ftools-cli
/api/v1/topics/search?q=python3
/api/v1/topics/search?q=powershell
/api/v1/topics/search?q=ls%20-la
/api/v1/topics/search?q=.htaccess
/api/v1/topics/search?q=docker
/api/v1/orgs/acme/teams/search?q=python3
/api/v1/orgs/acme/teams/search?q=powershell
/api/v1/orgs/acme/teams/search?q=ls%20-la
/api/v1/orgs/acme/teams/search?q=.htaccess
/api/v1/orgs/acme/teams/search?q=developers
/api/v1/admin/emails/search?q=python3
/api/v1/admin/emails/search?q=powershell
/api/v1/admin/emails/search?q=ls%20-la
/api/v1/admin/emails/search?q=.htaccess
/api/v1/admin/emails/search?q=alice
/api/v1/admin/unadopted?query=python3
/api/v1/admin/unadopted?query=powershell
/api/v1/admin/unadopted?query=ls%20-la
/api/v1/admin/unadopted?query=.htaccess
/api/v1/admin/unadopted?query=alice%2Fpython3-app
/api/v1/admin/unadopted?query=alice%2Flegacy-site
/api/v1/repos/alice/tools/milestones?state=open&name=python3
/api/v1/repos/alice/tools/milestones?state=open&name=powershell
/api/v1/repos/alice/tools/milestones?state=open&name=ls%20-la
/api/v1/repos/alice/tools/milestones?state=open&name=.htaccess
/api/v1/repos/alice/tools/milestones?state=open&name=v1.0
# The package registries' searches, sent to gitea 1.27.3 by each registry's own
# client: cargo, knife, composer, conan 1 and 2, dotnet and npm. curl sent the
# requests no client sends: the Chef cookbook list with a search, and NuGet's
# Packages() with the search in $filter, as NuGet's client builds it for a feed
# without Search(). curl also sent each NuGet Search() and Packages() search to
# Search()/$count and Packages()/$count, which count its results. Each searches
# for words that name commands and files, such as python3 and .htaccess, and
# for plain words. A Composer, Conan or npm name can hold a slash, so those
# searches are also sent /bin/sh.
/api/packages/alice/cargo/api/v1/crates?q=python3&per_page=10
/api/packages/alice/cargo/api/v1/crates?q=powershell&per_page=10
/api/packages/alice/cargo/api/v1/crates?q=ls%20%2Dla&per_page=10
/api/packages/alice/cargo/api/v1/crates?q=%2Ehtaccess&per_page=10
/api/packages/alice/cargo/api/v1/crates?q=docker%2Dcompose%2Eyml&per_page=10
/api/packages/alice/cargo/api/v1/crates?q=tools&per_page=10
/api/packages/alice/chef/api/v1/search?q=python3&items=9999999&start=0
/api/packages/alice/chef/api/v1/search?q=powershell&items=9999999&start=0
/api/packages/alice/chef/api/v1/search?q=ls%20-la&items=9999999&start=0
/api/packages/alice/chef/api/v1/search?q=.htaccess&items=9999999&start=0
/api/packages/alice/chef/api/v1/search?q=docker-compose.yml&items=9999999&start=0
/api/packages/alice/chef/api/v1/search?q=tools&items=9999999&start=0
/api/packages/alice/chef/api/v1/cookbooks?q=python3&items=9999999&start=0
/api/packages/alice/chef/api/v1/cookbooks?q=powershell&items=9999999&start=0
/api/packages/alice/chef/api/v1/cookbooks?q=ls%20-la&items=9999999&start=0
/api/packages/alice/chef/api/v1/cookbooks?q=.htaccess&items=9999999&start=0
/api/packages/alice/chef/api/v1/cookbooks?q=docker-compose.yml&items=9999999&start=0
/api/packages/alice/chef/api/v1/cookbooks?q=tools&items=9999999&start=0
/api/packages/alice/composer/search.json?q=python3&type=
/api/packages/alice/composer/search.json?q=powershell&type=
/api/packages/alice/composer/search.json?q=ls+-la&type=
/api/packages/alice/composer/search.json?q=.htaccess&type=
/api/packages/alice/composer/search.json?q=%2Fbin%2Fsh&type=
/api/packages/alice/composer/search.json?q=alice%2Ftools-cli&type=
/api/packages/alice/composer/search.json?q=docker-compose.yml&type=
/api/packages/alice/composer/search.json?q=tools&type=
/api/packages/alice/conan/v1/conans/search?q=python3
/api/packages/alice/conan/v1/conans/search?q=powershell
/api/packages/alice/conan/v1/conans/search?q=ls+-la
/api/packages/alice/conan/v1/conans/search?q=.htaccess
/api/packages/alice/conan/v1/conans/search?q=%2Fbin%2Fsh
/api/packages/alice/conan/v1/conans/search?q=tools%2F%2A
/api/packages/alice/conan/v1/conans/search?q=docker-compose.yml
/api/packages/alice/conan/v1/conans/search?q=tools
/api/packages/alice/conan/v2/conans/search?q=python3
/api/packages/alice/conan/v2/conans/search?q=powershell
/api/packages/alice/conan/v2/conans/search?q=ls+-la
/api/packages/alice/conan/v2/conans/search?q=.htaccess
/api/packages/alice/conan/v2/conans/search?q=%2Fbin%2Fsh
/api/packages/alice/conan/v2/conans/search?q=tools%2F1.0%40alice%2Fstable
/api/packages/alice/conan/v2/conans/search?q=docker-compose.yml
/api/packages/alice/conan/v2/conans/search?q=tools
/api/packages/alice/nuget/query?q=python3&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
/api/packages/alice/nuget/query?q=powershell&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
/api/packages/alice/nuget/query?q=ls%20-la&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
/api/packages/alice/nuget/query?q=.htaccess&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
/api/packages/alice/nuget/query?q=docker-compose.yml&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
/api/packages/alice/nuget/query?q=tools&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'python3'&$top=30
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'powershell'&$top=30
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'tools'&$top=30
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'python3'&$top=30
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'powershell'&$top=30
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'tools'&$top=30
/api/packages/alice/npm/-/v1/search?text=python3&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=powershell&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=ls+-la&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=.htaccess&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=%2Fbin%2Fsh&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=%40alice%2Ftools-cli&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=docker-compose.yml&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
/api/packages/alice/npm/-/v1/search?text=tools&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
# Pages, raw files and edit pages of files, and the API's call for their
# contents.
/owner/repo/src/branch/main/package.json