From 3144ae85d0ce63bdeb3ed990d9e4e0c24403bc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 00:47:34 -0300 Subject: [PATCH 001/124] Ignore the add-on per checkout, and derive the loopback file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things the eighth simplification pass argued for and demonstrated, and one it argued against. A .gitignore block is branch-scoped. Adopt hostshift on one branch and every pre-existing branch still shows the add-on's files as untracked — which is where `git add -A` commits them, and PLAN §3 exists because of what that did to 42 repos carrying 14 pinned SHAs of the same submodule. Measured: not one of the four pilot checkouts carries the block on its default branch; it lives only on the unmerged feature branch, and replaying master's .gitignore against the real footprint leaves five untracked files. $GIT_COMMON_DIR/info/exclude is checkout-scoped, which is the right scope for machine-local tool files, and git shares it between a repository and all its linked worktrees — so one write from pre_install_actions covers every worktree on every branch, and the repo commits nothing at all. Verified on a realistic layout: `git status` is empty before and after a real `ddev add-on get`, the root hostshift.yaml is still not ignored, and removal_actions takes the block back out. `ddev hostshift loopback` derives the containment file from the map instead of shipping a placeholder to hand-edit. The production hostnames are already declared in hostshift.yaml's canonical and aliases; writing them a second time by hand is how the two come to disagree, and the failure of disagreeing is wp-cron POSTing to the client's live site from a box holding a production database. Same shape as wp-cli — it prints, you redirect — and on a DDEV-canonical project it declines and says why, which is every project but the production-canonical ones. `hostshift map --canonical-hosts` is the new flag behind it: canonical *and* aliases, because an alias is a production hostname the content may still name. Not done, and the pass was right to reject it: the ${HOSTSHIFT_ARGS:-${HOSTSHIFT_MAP_ARGS:-…}} back-compat chain stays. v0.1.0 is the only release, its host command has no #ddev-generated marker and writes the old variable names, so everyone who has installed the add-on is the stranded population — and the chain is the only thing keeping their proxy serving through the upgrade that fixes it. It can go one release after v0.2.0, not at it. --- cmd/hostshift/main.go | 13 ++++++++++ ddev/commands/host/hostshift | 46 ++++++++++++++++++++++++++---------- ddev/install.yaml | 30 +++++++++++++++++++++++ test/integration-ddev.sh | 18 ++++++++++++++ 4 files changed, 95 insertions(+), 12 deletions(-) diff --git a/cmd/hostshift/main.go b/cmd/hostshift/main.go index 882abc2..b3e140a 100644 --- a/cmd/hostshift/main.go +++ b/cmd/hostshift/main.go @@ -391,6 +391,7 @@ func cmdMap(args []string) (int, error) { asJSON := fs.Bool("json", false, "emit the map as JSON") pairs := fs.Bool("pairs", false, "emit canonical=variant, one per line, for --map") hosts := fs.Bool("variant-hosts", false, "emit the variant hostnames, one per line") + canon := fs.Bool("canonical-hosts", false, "emit every canonical-side hostname, aliases included, one per line") if describe(fs, args, "print the resolved map, and where it came from") { return exitOK, nil } @@ -406,6 +407,18 @@ func cmdMap(args []string) (int, error) { // python3 to read --json, an undeclared dependency whose absence produced // "could not resolve a map to hand the proxy" and named neither python3 nor // the cause. Printing its own map flat is as generic as printing it as JSON. + // Every hostname the *content* may name, which is the canonical of each site + // plus its aliases. The variant side is what the browser uses; this side is + // what the application would reach out to, which is what loopback + // containment has to cover. + if *canon { + for _, s := range res.Map.Sites { + for _, o := range s.CanonicalSet() { + fmt.Println(o.Host) + } + } + return exitOK, nil + } if *pairs || *hosts { for _, s := range res.Map.Sites { if *pairs { diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index 4a1d0b0..01e863c 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -1,7 +1,7 @@ #!/usr/bin/env bash #ddev-generated ## Description: configure hostshift for this DDEV project -## Usage: hostshift [init|check|copy-db|env|wp-cli] [--slug NAME] [--dry-run|--quiet] +## Usage: hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME] ## Example: "ddev hostshift init" ## ## Scaffolding a DDEV project is opinionated work, and this is where it is @@ -51,16 +51,16 @@ while [ $# -gt 0 ]; do # Silently discarding an unknown flag is how `--dry-run` came to write the # files it was passed to prevent. *) echo "unknown argument: $1" >&2 - echo "usage: ddev hostshift [init|check|copy-db|env|wp-cli] [--slug NAME] [--dry-run|--force|--quiet]" >&2 + echo "usage: ddev hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME]" >&2 exit 2 ;; esac done case "$cmd" in - init|check|copy-db|env|wp-cli) ;; + init|check|copy-db|loopback|env|wp-cli) ;; -h|--help|help) - echo "usage: ddev hostshift [init|check|copy-db|env|wp-cli] [--slug NAME] [--dry-run|--force|--quiet]"; exit 0 ;; - *) echo "usage: ddev hostshift [init|check|copy-db|env|wp-cli] [--slug NAME] [--dry-run|--force|--quiet]" >&2; exit 2 ;; + echo "usage: ddev hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME]"; exit 0 ;; + *) echo "usage: ddev hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME]" >&2; exit 2 ;; esac # The post-start hook runs `check` on every start. Between `ddev add-on get` and @@ -409,13 +409,6 @@ fi # HOSTSHIFT_MAP_ARGS= line that reads like a bug. args="${hsmap:---slug $slug}" -# A digest of hostshift.yaml, so that editing it is drift like any other. -# The file is mounted and the proxy reads it once, at startup. Nothing else in -# .ddev/.env moves when it changes — not the args, not the variants, not -# VIRTUAL_HOST — so adding the alias the file exists for, or repointing -# canonical at production, left every comparison equal and `check` reporting -# success while the running proxy used the old map. - # #ddev-silent-no-warn, because DDEV otherwise prints a four-line "Custom # configuration detected" block naming this file on every single start. It is a # legal dotenv comment and DDEV documents the marker in that very message. @@ -559,6 +552,35 @@ if [ "$cmd" = "init" ] && [ -n "$unreadable_parent" ] && [ ! -f hostshift.yaml ] exit 2 fi +# The production hostnames are already declared, in hostshift.yaml's canonical +# and aliases. Writing them a second time by hand into a compose file is how the +# two come to disagree — and the failure of disagreeing is wp-cron POSTing to the +# client's live site from a box holding a production database. Same shape as +# wp-cli: it prints, you redirect. +if [ "$cmd" = "loopback" ]; then + # Canonical *and* aliases: an alias is a production hostname the content may + # still name, so leaving it out is the same leak with a different spelling. + hosts_out="$(hostshift map --slug "$slug" "${map_args[@]+"${map_args[@]}"}" \ + --canonical-hosts | sort -u)" + # Off-box means "not one this DDEV project answers to". The project's own TLD + # is the tail of its primary hostname, which `hostshift hosts` prints first. + tld="$(printf '%s\n' "$hosts" | head -1 | cut -d. -f2-)" + offbox="$(printf '%s\n' "$hosts_out" | grep -v "\.${tld:-ddev.site}\$" || true)" + if [ -z "$offbox" ]; then + echo "hostshift: every canonical hostname is a local DDEV one, so the" >&2 + echo " application's own internal requests already stay on this machine." >&2 + echo " Nothing to contain." >&2 + exit 0 + fi + echo "#ddev-silent-no-warn" + echo "# generated by \`ddev hostshift loopback\` from hostshift.yaml — do not edit" + echo "services:" + echo " web:" + echo " extra_hosts:" + printf ' - "%s:127.0.0.1"\n' $offbox + exit 0 +fi + if [ "$cmd" = "env" ]; then printf '%s\n' "$env_out" exit 0 diff --git a/ddev/install.yaml b/ddev/install.yaml index 38458ac..6f48072 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -18,6 +18,28 @@ pre_install_actions: echo "without it, but 'ddev hostshift' and 'hostshift map|check|diff' need it." >&2 fi +# Ignore what this add-on installs, in the checkout rather than in a commit. +# +# A .gitignore block is *branch*-scoped: adopt hostshift on one branch and every +# pre-existing branch still shows the add-on's files as untracked, which is where +# `git add -A` commits them. $GIT_COMMON_DIR/info/exclude is checkout-scoped, +# which is the right scope for machine-local tool files, and it is shared by a +# repository and all its linked worktrees — so one write covers them all, on +# every branch, and the repo commits nothing. +# +# Deliberately not the root hostshift.yaml, which is a project's own committed +# statement about its hostnames. + - | + #ddev-nodisplay + ex="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)/info/exclude" + [ -d "$(dirname "$ex")" ] || exit 0 + grep -qxF '# hostshift (ddev add-on)' "$ex" 2>/dev/null && exit 0 + { + echo '# hostshift (ddev add-on)' + echo '.ddev/**/*hostshift*' + echo '.ddev/.env' + } >> "$ex" + project_files: - config.hostshift.yaml - docker-compose.hostshift.yaml @@ -87,6 +109,14 @@ post_install_actions: echo removal_actions: + - | + #ddev-nodisplay + ex="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)/info/exclude" + [ -f "$ex" ] || exit 0 + tmp="$(mktemp)" + grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' -e '.ddev/.env' \ + "$ex" > "$tmp" && cat "$tmp" > "$ex" + rm -f "$tmp" - | #ddev-nodisplay echo "hostshift removed. wp-cli.local.yml, if you generated one, is yours to delete." diff --git a/test/integration-ddev.sh b/test/integration-ddev.sh index b8c1eb9..c02da0a 100755 --- a/test/integration-ddev.sh +++ b/test/integration-ddev.sh @@ -262,6 +262,24 @@ install_out="$(cd "$m4" && ddev add-on get "$repo/ddev" 2>&1)" \ projects+=("$m4") contains "the add-on installs the host command" "commands/host/hostshift" "$install_out" + +# The files it installs are ignored in the *checkout*, not in a commit. A +# .gitignore block is branch-scoped, so adopting hostshift leaves them untracked +# on every branch that predates the adoption — which is where `git add -A` +# commits them. +if [ -z "$(git -C "$m4" status --porcelain -- .ddev)" ]; then + pass "and nothing it installs shows up as untracked" +else + fail "and nothing it installs shows up as untracked" "$(git -C "$m4" status --porcelain -- .ddev)" +fi +# The project's own committed statement about its hostnames is not ignored. +printf 'sites:\n - canonical: https://x.example\n' > "$m4/hostshift.yaml" +if git -C "$m4" check-ignore -q hostshift.yaml; then + fail "but the repo's own hostshift.yaml still is not" "it is ignored" +else + pass "but the repo's own hostshift.yaml still is not" +fi +rm -f "$m4/hostshift.yaml" case "$install_out" in *"predates this add-on"*) fail "a fresh install is not told its command is stale" "$install_out" ;; From 7ba9d632f964502f1035afee59d9b6dc14619ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 01:00:25 -0300 Subject: [PATCH 002/124] Fix the install regression, and test the three fixes nothing tested MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ddev add-on get` aborted in any .ddev project that is not a git checkout. The previous commit's info/exclude step calls `git rev-parse`, which exits 128 outside a repository; DDEV runs install actions under `set -eu -o pipefail`, so the assignment inherited that exit and nothing installed at all. `|| true` goes inside the substitution, and the integration suite grows a project with no .git — `newsite()` always `git init`s, so only the succeeding branch was exercised. Then coverage for three fixes that all four suites stayed green without: * `check` refusing a hostname another project claims. Deleting the refusal passed everything. Two projects on one hostname is an error nowhere in DDEV — traefik breaks the tie by rule length and the loser is silently unreachable — so this is what stands between a developer and reviewing the wrong branch's code at the right URL. A bare sibling directory stages it. * `copy-db` replacing rather than merging. The existing assertion — the parent's row arrives — passed before the fix too. The one that distinguishes them needs a table the *worktree* has and the parent does not, asserted gone. * `init`'s EXIT trap. Without it a failed init leaves a `.ddev/.env.XXXXXX` behind, holding whatever credentials .env held, in a repo whose generated .ddev/.gitignore does not cover it. Smaller, from the same round: * The sweep asked for `explain=true` unconditionally, materialising a string per *skipped* candidate over every byte of every HTML body. Text is populated for rewrites regardless, which is all the WARN reads. Pinned, because nothing asserted the WARN's attributes and the degraded form — `origin=""` — is still a warning and still counted. * README: `.ddev/.env`'s first line is `#ddev-silent-no-warn`, not a comment, and the add-on now handles the ignore itself. * `stripRange` claimed Accept-Ranges was dropped from every response. It is dropped from rewritten ones. --- README.md | 12 ++++-- ddev/install.yaml | 12 +++++- internal/proxy/transport.go | 6 ++- internal/rewrite/sweep.go | 8 +++- internal/rewrite/sweep_warn_test.go | 66 +++++++++++++++++++++++++++++ test/addon-command.sh | 16 +++++++ test/integration-ddev.sh | 48 +++++++++++++++++++++ 7 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 internal/rewrite/sweep_warn_test.go diff --git a/README.md b/README.md index b478cf6..e458fe6 100644 --- a/README.md +++ b/README.md @@ -117,14 +117,20 @@ The one file that comes out of it: ```sh # .ddev/.env +#ddev-silent-no-warn HOSTSHIFT_ARGS=--from https://acme.ddev.site --to https://wt-a--acme.ddev.site HOSTSHIFT_VARIANTS=wt-a--acme.ddev.site HOSTSHIFT_WEB_HOSTS=acme-wt-a.ddev.site ``` -Ignore it in the project's own `.gitignore` — DDEV's generated -`.ddev/.gitignore` does not cover `.env`. `init` merges into the file rather -than truncating it, so anything else already in there survives. +The first line is not a comment DDEV ignores: without it every `ddev start` +prints a four-line "Custom configuration detected" block. `init` merges into +the file rather than truncating it, so anything else already in there survives. + +You do not need to gitignore it. Installing the add-on adds its files to +`.git/info/exclude`, which is per checkout and shared with linked worktrees, so +the ignore travels with the machine rather than with the branch. Removing the +add-on takes the entry back out. After `ddev restart`, `https://wt-a--acme.ddev.site` serves the worktree and `https://acme.ddev.site` goes on serving the parent. diff --git a/ddev/install.yaml b/ddev/install.yaml index 6f48072..bf98a22 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -31,7 +31,13 @@ pre_install_actions: # statement about its hostnames. - | #ddev-nodisplay - ex="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)/info/exclude" + # `|| true` inside the substitution: DDEV runs actions under `set -eu -o + # pipefail`, git rev-parse exits 128 outside a repository, and the assignment + # inherits that — which aborted the whole install and left the project with + # nothing, in any .ddev project that is not a git checkout. + common="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)" + [ -n "$common" ] || exit 0 + ex="$common/info/exclude" [ -d "$(dirname "$ex")" ] || exit 0 grep -qxF '# hostshift (ddev add-on)' "$ex" 2>/dev/null && exit 0 { @@ -111,7 +117,9 @@ post_install_actions: removal_actions: - | #ddev-nodisplay - ex="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)/info/exclude" + common="$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null || true)" + [ -n "$common" ] || exit 0 + ex="$common/info/exclude" [ -f "$ex" ] || exit 0 tmp="$(mktemp)" grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' -e '.ddev/.env' \ diff --git a/internal/proxy/transport.go b/internal/proxy/transport.go index d01997e..2dcb3c2 100644 --- a/internal/proxy/transport.go +++ b/internal/proxy/transport.go @@ -228,8 +228,10 @@ func isPartial(resp *http.Response) bool { // so in the fleet this costs nothing on a page; it costs a media file being // re-fetched from zero on a seek, over loopback or the Docker bridge. // -// RFC 9110 lets a server ignore Range, and Accept-Ranges is dropped from every -// response so a well-behaved client does not ask again. +// RFC 9110 lets a server ignore Range and answer 200, which is what a client +// gets here. Accept-Ranges is dropped from *rewritten* responses only, along +// with the other validators — an untouched body keeps it, so a client may still +// ask for a range on a media file and be answered in full. func stripRange(h http.Header) { h.Del("Range") h.Del("If-Range") diff --git a/internal/rewrite/sweep.go b/internal/rewrite/sweep.go index d026ef0..ff6a951 100644 --- a/internal/rewrite/sweep.go +++ b/internal/rewrite/sweep.go @@ -125,7 +125,11 @@ func (s *Sweep) flush(b []byte, limit int) int { if len(b) == 0 { return 0 } - out, consumed, events := s.m.RewritePrefix(b, limit, s.prev, SurfaceStraggler, true) + // s.stats.Explain(), not a bare true. Text is materialised for every + // ActionRewrote event regardless, which is all the WARN below reads, so + // forcing explain on bought nothing but a string per *skipped* candidate — + // and the sweep runs over every byte of every HTML body. + out, consumed, events := s.m.RewritePrefix(b, limit, s.prev, SurfaceStraggler, s.stats.Explain()) // Events arrive in increasing offset order, which is what the mapper's // cursor needs, and each is mapped individually — the drift is not a // constant per flush, it accumulates with every rewrite upstream. The @@ -183,7 +187,7 @@ func SweepBytes(b []byte, m *origin.Matcher, st *Stats, log *slog.Logger) []byte if log == nil { log = slog.Default() } - out, events := m.Rewrite(b, SurfaceStraggler, true) + out, events := m.Rewrite(b, SurfaceStraggler, st.Explain()) for _, e := range events { if e.Action != origin.ActionRewrote { continue diff --git a/internal/rewrite/sweep_warn_test.go b/internal/rewrite/sweep_warn_test.go new file mode 100644 index 0000000..d370142 --- /dev/null +++ b/internal/rewrite/sweep_warn_test.go @@ -0,0 +1,66 @@ +package rewrite + +import ( + "bytes" + "io" + "log/slog" + "strings" + "testing" + + "github.com/generoi/hostshift/internal/origin" +) + +// The straggler WARN names the origin it swept, and that has to hold with +// --explain off — which is every proxy in normal operation. +// +// The sweep asks the matcher for events with explain=false so a skipped +// candidate costs no string. Event.Text is populated for ActionRewrote +// regardless, and the WARN only fires for ActionRewrote, so the two fit. If +// that ever stops being true the WARN degrades to `origin=""`: still a warning, +// still counted, but no longer naming the URL that leaked — and a straggler +// nobody can locate is a bug report nobody can act on. Nothing else asserts it. +func TestStragglerWarnNamesTheOriginWithoutExplain(t *testing.T) { + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://wt-a--example.ddev.site"), + }}) + if err != nil { + t.Fatal(err) + } + body := []byte(`leaked https://www.example.fi/a here`) + + for _, c := range []struct { + name string + run func(*slog.Logger, *Stats) []byte + }{ + {"SweepBytes", func(log *slog.Logger, st *Stats) []byte { + return SweepBytes(body, m, st, log) + }}, + {"Sweep", func(log *slog.Logger, st *Stats) []byte { + s := NewSweep(bytes.NewReader(body), m, nil, Options{Stats: st, Log: log}) + out, err := io.ReadAll(s) + if err != nil { + t.Fatal(err) + } + return out + }}, + } { + t.Run(c.name, func(t *testing.T) { + var logBuf bytes.Buffer + log := slog.New(slog.NewTextHandler(&logBuf, nil)) + st := NewStats(false) // --explain off, as in production + + out := c.run(log, st) + if bytes.Contains(out, []byte("www.example.fi")) { + t.Fatalf("the sweep did not rewrite: %s", out) + } + logged := logBuf.String() + if !strings.Contains(logged, "straggler swept") { + t.Fatalf("no WARN at all:\n%s", logged) + } + if !strings.Contains(logged, "www.example.fi") { + t.Errorf("the WARN does not name the origin it swept, so nobody can find it:\n%s", logged) + } + }) + } +} diff --git a/test/addon-command.sh b/test/addon-command.sh index e3766f4..9b4e693 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -233,6 +233,22 @@ chmod 600 "$wt/.ddev/.env" (cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || fail "init exited non-zero" "" check ".env keeps the mode it had" "-rw-------" \ "$(ls -l "$wt/.ddev/.env" | cut -c1-10)" + +# The write goes through a temp file in .ddev/ so the mv is an atomic rename. +# That temp file is only safe because of the EXIT trap: without it, every failed +# init leaves a `.ddev/.env.XXXXXX` behind — a file DDEV's own .ddev/.gitignore +# does not cover, so it shows up as an untracked file in the developer's repo, +# holding whatever credentials .env held. chmod 000 makes the `cp -p` fail, +# which is the failure path. +chmod 000 "$wt/.ddev/.env" +(cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || true +chmod 600 "$wt/.ddev/.env" +leftover="$(ls "$wt"/.ddev/.env.?????? 2>/dev/null || true)" +if [ -n "$leftover" ]; then + fail "a failed init leaves no temp file behind" "$leftover" +else + pass "a failed init leaves no temp file behind" +fi rm -f "$wt/.ddev/.env" (cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || fail "init exited non-zero" "" check ".env is 0644 when init creates it" "-rw-r--r--" \ diff --git a/test/integration-ddev.sh b/test/integration-ddev.sh index c02da0a..036c4a2 100755 --- a/test/integration-ddev.sh +++ b/test/integration-ddev.sh @@ -136,6 +136,26 @@ code="$(curl -sk -o /dev/null -w '%{http_code}' --max-time 20 https://${tag}-wt- out="$(cd "$wt" && ddev hostshift check 2>&1)" && pass "check passes a live worktree" \ || fail "check passes a live worktree" "$out" +# ...and stops passing it the moment a sibling claims the same hostname. Two +# projects on one hostname is an error nowhere in DDEV — traefik breaks the tie +# by rule length and the loser is silently unreachable — so `check` refusing is +# the only thing standing between a developer and reviewing the wrong branch's +# code at the right URL. The scan reads `../*/.ddev/.env`, so a bare directory +# is enough to stage it; no second DDEV project needed. +rival="$work/${tag}-rival" +mkdir -p "$rival/.ddev" +printf 'HOSTSHIFT_VARIANTS=%s-wt-a.ddev.site\n' "$tag" > "$rival/.ddev/.env" +if (cd "$wt" && ddev hostshift check >/dev/null 2>&1); then + fail "check refuses when another project claims the same hostname" \ + "exit 0 — the collision was reported as healthy" +else + pass "check refuses when another project claims the same hostname" +fi +rm -rf "$rival" +(cd "$wt" && ddev hostshift check >/dev/null 2>&1) \ + && pass "and passes again once the rival is gone" \ + || fail "and passes again once the rival is gone" "$(cd "$wt" && ddev hostshift check 2>&1)" + echo "== copy-db" # The only subcommand that destroys something. It streams the parent's database @@ -144,10 +164,24 @@ echo "== copy-db" # would overwrite. Both halves are new and neither had any coverage. sql() { (cd "$1" && ddev exec -s web bash -c "mysql -h db -udb -pdb -N -B -e \"$2\" db" 2>/dev/null) || true; } sql "$main" "create table hs_probe (id int); insert into hs_probe values (42);" >/dev/null +# A table only the worktree has, so the copy has something to drop. `mysqldump db +# | mysql` drops only the tables the dump contains, so this survived — while the +# refusal message promised a replace. +sql "$wt" "create table hs_only_here (id int); insert into hs_only_here values (7);" >/dev/null out="$(cd "$wt" && ddev hostshift copy-db 2>&1)" || fail "copy-db copies the parent's database" "$out" contains "copy-db copies the parent's database" "42" "$(sql "$wt" "select id from hs_probe")" +# Replace, not merge. `mysqldump db | mysql` drops only the tables the dump +# contains, so a worktree from an older pull kept everything the parent no longer +# has — and the refusal message promises a replace. The parent-side assertion +# above passed before the fix too; this is the one that distinguishes them. +if [ -n "$(sql "$wt" "select id from hs_only_here" 2>&1 | grep -x 7 || true)" ]; then + fail "and drops what only the worktree had" "hs_only_here survived, so it merged" +else + pass "and drops what only the worktree had" +fi + # Running it twice is the accident: the second run silently replaced whatever # the first one's work had put there. if (cd "$wt" && ddev hostshift copy-db >/dev/null 2>&1); then @@ -263,6 +297,20 @@ projects+=("$m4") contains "the add-on installs the host command" "commands/host/hostshift" "$install_out" +# A .ddev project that is not a git checkout. DDEV runs install actions under +# `set -eu -o pipefail`, and `git rev-parse` exits 128 outside a repository — so +# an unguarded call aborted the install and left the project with nothing. +nogit="$work/${tag}-nogit" +mkdir -p "$nogit/.ddev" +printf 'name: %s-nogit\ntype: php\n' "$tag" > "$nogit/.ddev/config.yaml" +ng_out="$(cd "$nogit" && ddev add-on get "$repo/ddev" 2>&1)" || true +projects+=("$nogit") +if [ -f "$nogit/.ddev/commands/host/hostshift" ]; then + pass "and installs into a project that is not a git checkout" +else + fail "and installs into a project that is not a git checkout" "$ng_out" +fi + # The files it installs are ignored in the *checkout*, not in a commit. A # .gitignore block is branch-scoped, so adopting hostshift leaves them untracked # on every branch that predates the adoption — which is where `git add -A` From 72d91239fcde4c019ded6fdc4ae7a42716c050e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 01:19:56 -0300 Subject: [PATCH 003/124] The upgrade hook, and asking the container instead of grepping files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two findings from living a week in a real multisite parent and worktree. Both were wrong-and-silent, and both were in the add-on's lifecycle rather than in the engine, which held. Upgrading from v0.1.0 switched the post-start check off, permanently. config.hostshift.yaml carries #ddev-generated, so `ddev add-on get` replaces it; the command it calls only gained that marker after v0.1.0, so DDEV refuses to replace *that*. An upgraded project ran the new hook against the old command, whose parser exits 2 on an unknown flag. Measured: `unknown argument: --quiet`, `Task failed`, `Successfully started`. The proxy kept serving, so nothing looked wrong — and from then on a renamed directory, a changed branch, a stale .env, a crashed proxy or a hijacked hostname all went unreported, on exactly the projects that had been running longest. The hook now passes HOSTSHIFT_HOOK=1 instead: DDEV runs the line through `bash -c` (pkg/ddevapp/task.go), so a parser that only reads "$@" cannot trip over it. Tested against the real v0.1.0 command out of the tag, and against every tag there is. `copy-db` reported success into a database the application does not read. The shared-database guard grepped four files and missed `.ddev/.env.web`, DDEV's own documented place for per-service environment. Measured: DB_HOST set there, the worktree live on the parent's database, `copy-db --force` writing the dump into the worktree's idle db container and printing "copied". The developer who ran it specifically to stop writing to the parent was told they had. It now asks the web container's environment first — which sees every mechanism, including ones DDEV has not invented yet — and falls back to the file scan, extended with `.ddev/.env.*`, for when nothing is running. Also from the same week: * `check` refused over a directory, not a claim. It exited 2 on a leftover `.ddev/.env` from a deleted worktree — and `git worktree remove` refuses while untracked files are present, so the directory outliving the project is the common case. That failed the post-start hook on every start while routing was entirely correct, and the advice to pass --slug was wrong for it. Now only a running rival is fatal; a dead one still warns. * `check` compares the running map, not just the command line that asked for it. Editing hostshift.yaml without restarting left it printing "hostshift is serving" while the new alias rewrote nothing, because the container's command line is `--slug X` however the file changes. The proxy prints its resolved map at startup and `docker logs` still has it, so the comparison needs no recorded state — unlike the checksum and mtime attempts, both of which were worse than the gap. The README claim that nothing detects this was false and is gone. * The interrupted copy said "the copy failed", which reads as "nothing happened". --add-drop-database puts DROP DATABASE at the top of the stream, so it may not be true. * An unparseable parent config now names the file and, mid-rebase, the conflict markers. "No DDEV config could be read there" sent the reader looking for a DDEV project that was there all along. * README: DDEV collapses a host command's exit status to 1, so the documented 0/1/2 holds for the bare binary only. --- README.md | 16 ++-- ddev/commands/host/hostshift | 144 ++++++++++++++++++++++++++++++++--- ddev/config.hostshift.yaml | 26 +++++-- test/addon-command.sh | 62 +++++++++++++++ test/integration-ddev.sh | 44 +++++++---- 5 files changed, 254 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e458fe6..db9c20e 100644 --- a/README.md +++ b/README.md @@ -236,13 +236,10 @@ before. ### `hostshift.yaml` — only for aliases, or for production-canonical -**`ddev restart` after editing it.** The proxy reads the file once, at startup, -and nothing detects that you have changed it since — `ddev hostshift check` -compares `.ddev/.env` and the running container's command line, and neither moves -when the file's contents do. Two attempts at detecting this were worse than the -gap: a checksum recorded at `init` time called a correctly-restarted project -stale, and comparing the file's timestamp against the container's proved -unreliable across platforms. +**`ddev restart` after editing it.** The proxy reads the file once, at startup. +`ddev hostshift check` catches it if you forget: the proxy prints its resolved +map to stderr when it starts, so `check` compares that against what this +checkout resolves to now and refuses to call a stale proxy healthy. Not needed because a site is a multisite. Needed for the two things a DDEV config genuinely cannot say: **alias hostnames**, so a residual `@staging` URL @@ -331,7 +328,10 @@ Flags worth knowing: default; it exists for performance work where transfer size must resemble production. -Exit codes: 0 success, 1 runtime error, 2 invalid configuration. +Exit codes: 0 success, 1 runtime error, 2 invalid configuration. These are the +binary's. DDEV collapses a host command's status to 1, so a script testing +`ddev hostshift check` for `-eq 2` will never match — test for non-zero, or call +`hostshift` directly. `hostshift diff -n 20` is the check that validates a deployment against reality: it crawls N pages canonically, fetches the same N through the proxy, diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index 01e863c..81b111b 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -67,7 +67,28 @@ esac # `ddev hostshift init` — and permanently, for anyone who installed the add-on # and never used it — there is nothing to check, and saying so in nine lines plus # a `Task failed` is how an add-on stops being inert until it is configured. -if [ "$cmd" = "check" ] && [ -n "$quiet" ] && ! grep -qs '^HOSTSHIFT_' .ddev/.env 2>/dev/null; then +# +# $HOSTSHIFT_HOOK, not a flag, and that is not a style choice. +# `config.hostshift.yaml` carries #ddev-generated so `ddev add-on get` replaces +# it on upgrade. This file only gained that marker *after* v0.1.0, so DDEV +# refuses to replace it — an upgraded project runs the new hook against the old +# command, and the old parser exits 2 on any flag it does not know. Measured on a +# real upgrade: `unknown argument: --quiet`, `Task failed`, `Successfully +# started`. The proxy kept serving, so nothing looked wrong, and the post-start +# check was dead from then on: every later drift — a renamed directory, a changed +# branch, a stale .env, a crashed proxy, a hijacked hostname — went unreported on +# exactly the projects that had been running longest. +# +# `bash -c` runs the hook line (ddev pkg/ddevapp/task.go, ExecHostTask.Execute), +# so an environment prefix is a plain assignment there, and a parser that only +# reads "$@" cannot trip over it. The rule the compose file states — anything +# added must work against what is already deployed — covers the hook line too. +# +# Only this short-circuit is gated. `ddev hostshift check` typed by a person on a +# project with no configuration is a question that deserves an answer, and it +# still gets one. +if [ "$cmd" = "check" ] && { [ -n "$quiet" ] || [ -n "${HOSTSHIFT_HOOK:-}" ]; } \ + && ! grep -qs '^HOSTSHIFT_' .ddev/.env 2>/dev/null; then exit 0 fi @@ -193,7 +214,20 @@ elif [ -n "$linked" ]; then case "$ph" in # Nothing readable there. Distinct from the case below, and the two must not # be confused: this one is a problem and that one is not. - "") unreadable_parent="$dir" ;; + # + # A .ddev/config.yaml that is *present* but not parseable is the common + # shape, and mid-rebase it is the common cause: conflict markers make the + # YAML invalid, and "no DDEV config could be read there" then reads as + # "the parent has no DDEV project", which sends the reader looking in the + # wrong place entirely. Say which file, and what is in it. + "") unreadable_parent="$dir" + if [ -f "$dir/.ddev/config.yaml" ]; then + if grep -q '^<<<<<<< ' "$dir/.ddev/config.yaml" 2>/dev/null; then + unreadable_parent="$dir (its .ddev/config.yaml has unresolved conflict markers)" + else + unreadable_parent="$dir (its .ddev/config.yaml could not be parsed)" + fi + fi ;; # A repo that pins `name:` in its tracked config.yaml gives every worktree # the *same* DDEV project — same containers, same hostnames. There is nothing # to derive from elsewhere and nothing to subtract. @@ -211,11 +245,41 @@ if [ "$cmd" = "copy-db" ]; then [ -f "$parent/.ddev/config.yaml" ] && n="$(sed -n 's/^name: *//p' "$parent/.ddev/config.yaml" | head -1)" && [ -n "$n" ] && from="$n" # Sharing the parent's database and copying it are different decisions, and - # doing both means importing a database into itself. - if grep -rqs 'ddev-'"$from"'-db' .ddev/config.yaml .ddev/config.*.yaml .ddev/docker-compose.*.yaml .ddev/.env 2>/dev/null; then - echo "hostshift: this worktree is configured to *use* $from's database" >&2 - echo " (DATABASE_URL points at ddev-$from-db). Copying it into itself is not" >&2 - echo " what you want — remove that override and \`ddev restart\` first." >&2 + # doing both is worse than pointless: the dump goes into this worktree's own + # idle `db` container while the application goes on reading and writing the + # parent's — and copy-db says "copied", exit 0. The developer who ran it + # *specifically to stop writing to the parent* is told they succeeded, and + # carries on activating plugins and uploading media into the checkout they + # were protecting. + # + # Ask the container, not the files. This grepped four paths for the parent's + # db hostname and missed `.ddev/.env.web`, which is DDEV's own documented + # place for per-service environment — measured: DB_HOST=ddev--db there, + # the worktree live on the parent's database, and copy-db reported success. + # `printenv` in the web container sees every mechanism that can set it, + # including ones DDEV has not invented yet, and it is what `check` already does + # for the same reason. + # + # Both, not either. The container is the truth when it is up, and it is down + # exactly when the files are all there is — a project stopped mid-configuration + # is the likeliest moment for someone to reach for copy-db. The file list gains + # `.ddev/.env.*`, which is where the miss was. + shared="" + dbenv="$(ddev exec -s web printenv DB_HOST DATABASE_URL DB_URL 2>/dev/null || true)" + case "$dbenv" in + *"ddev-$from-db"*) shared="$(printf '%s\n' "$dbenv" | grep -F "ddev-$from-db" || true)" ;; + esac + if [ -z "$shared" ]; then + shared="$(grep -rs 'ddev-'"$from"'-db' \ + .ddev/config.yaml .ddev/config.*.yaml .ddev/docker-compose.*.yaml \ + .ddev/.env .ddev/.env.* 2>/dev/null || true)" + fi + if [ -n "$shared" ]; then + echo "hostshift: this worktree is configured to *use* $from's database:" >&2 + printf '%s\n' "$shared" | sed 's/^/ /' >&2 + echo " Copying into it would write the dump to this worktree's own db" >&2 + echo " container, which nothing reads, and report success. Remove that" >&2 + echo " override and \`ddev restart\` first." >&2 exit 2 fi @@ -244,7 +308,17 @@ if [ "$cmd" = "copy-db" ]; then ddev exec -s web bash -c "set -o pipefail; mysqldump --single-transaction --quick \ --no-tablespaces --add-drop-database --databases db -h ddev-$from-db -udb -pdb \ | mysql -h db -udb -pdb" || { - echo "hostshift: the copy failed. Is $from running? \`ddev start $from\`" >&2 + # Not "the copy failed", full stop — that reads as "nothing happened", and + # it is only true if the dump never started. --add-drop-database puts + # `DROP DATABASE` at the top of the stream, so an interruption after that + # point — the laptop sleeping, the parent stopped mid-copy — leaves this + # database dropped and partially reloaded. Saying so is the difference + # between re-running the command and debugging a site that is now missing + # half its tables. + echo "hostshift: the copy failed." >&2 + echo " Is $from running? \`ddev start $from\`" >&2 + echo " This worktree's database may be half-replaced: the dump drops it" >&2 + echo " before reloading. Re-run with --force once $from is up." >&2 exit 1; } echo "hostshift: copied. It still holds $from's hostnames, which is what" >&2 echo " hostshift maps — nothing else to do." >&2 @@ -306,7 +380,26 @@ for other in ../*/.ddev/.env; do echo " Two projects answering to one hostname is not an error anywhere in DDEV;" >&2 echo " the router picks one and the other is silently unreachable. Pass --slug to" >&2 echo " choose a different name." >&2 - claimed="$v" + # The warning is right either way; the *refusal* is not. A worktree that + # was `ddev delete`d but whose directory stayed still has its .ddev/.env, + # and `git worktree remove` refuses while untracked files are present, so + # the directory staying is the common case rather than the odd one. That + # made `check` exit 2 — failing the post-start hook on every single start — + # over a claim nothing was making, while routing was perfectly correct, and + # the advice to pass --slug was wrong for that case. + # + # So only a *live* claim is fatal. If the rival is not running there is + # nothing to lose a routing tie to today. + oname="$(basename "$odir")" + if [ -f "$odir/.ddev/config.yaml" ]; then + n="$(sed -n 's/^name: *//p' "$odir/.ddev/config.yaml" | sed -n 1p)" + [ -n "$n" ] && oname="$n" + fi + if [ "$(docker inspect "ddev-$oname-web" --format '{{.State.Running}}' 2>/dev/null || true)" = "true" ]; then + claimed="$v" + else + echo " ($oname is not running, so nothing is serving it right now.)" >&2 + fi break 2 ;; esac done @@ -535,6 +628,39 @@ if [ "$cmd" = "check" ]; then exit 2 fi + # The map itself, not just the command line that asked for it. + # + # When the container resolves its own map from a mounted hostshift.yaml, the + # command line is `--slug X` and stays `--slug X` however the file changes — so + # editing hostshift.yaml and not restarting left `check` printing "hostshift is + # serving" while the new alias rewrote nothing. Two attempts at closing this + # were worse than the gap (a checksum at init time called a correctly restarted + # project stale; a file mtime against the container's was unreliable across + # platforms), and both were guessing at something the proxy states outright: + # it prints its resolved map to stderr at startup, and `docker logs` still has + # it. Comparing that against `hostshift map` here is exact, needs no recorded + # state, and both sides render through the same Map.String(). + # + # Best-effort by design. A rotated or truncated log yields no pair lines and is + # not evidence of drift, so it says nothing rather than crying wolf. + running_map="$(docker logs "ddev-$name-hostshift" 2>&1 \ + | sed -n 's/^\(.*[[:space:]]\)\{0,1\}\(https:\/\/[^[:space:]]*[[:space:]]*->[[:space:]]*https:\/\/[^[:space:]]*\)$/\2/p' \ + | tr -s ' ' | sort -u || true)" + if [ -n "$running_map" ]; then + want_map="$(hostshift map --slug "$slug" "${map_args[@]+"${map_args[@]}"}" 2>/dev/null \ + | sed -n 's/^\(.*[[:space:]]\)\{0,1\}\(https:\/\/[^[:space:]]*[[:space:]]*->[[:space:]]*https:\/\/[^[:space:]]*\)$/\2/p' \ + | tr -s ' ' | sort -u || true)" + if [ -n "$want_map" ] && [ "$running_map" != "$want_map" ]; then + echo "hostshift: the proxy is running a different map than this checkout" >&2 + echo " now resolves to. It started with:" >&2 + printf '%s\n' "$running_map" | sed 's/^/ /' >&2 + echo " and hostshift.yaml/DDEV config now give:" >&2 + printf '%s\n' "$want_map" | sed 's/^/ /' >&2 + echo " Run \`ddev restart\`." >&2 + exit 2 + fi + fi + printf '\n hostshift is serving:\n' >&2 printf ' https://%s\n' $(printf '%s' "$variants" | tr ',' ' ') >&2 printf '\n' >&2 diff --git a/ddev/config.hostshift.yaml b/ddev/config.hostshift.yaml index c4f0a49..33e1a3c 100644 --- a/ddev/config.hostshift.yaml +++ b/ddev/config.hostshift.yaml @@ -21,10 +21,26 @@ # success — so all the guard did was suppress the one line that made a stale # configuration visible above the rest of the start output. # -# --quiet so that a project which has the add-on installed but has never been -# configured says nothing at all. It has nothing to check, and nine lines of -# advice plus a `Task failed` on a project with nothing wrong with it is how an -# add-on stops being inert until it is configured. +# An environment variable rather than a flag, and that is the whole point. +# +# This file carries #ddev-generated, so `ddev add-on get` replaces it on upgrade. +# The command it calls only gained that marker *after* v0.1.0, so DDEV refuses to +# replace that one — an upgraded project runs this new hook against the old +# command, whose parser exits 2 on any flag it does not know. Measured on a real +# upgrade: `unknown argument: --quiet`, `Task failed`, `Successfully started`. +# The proxy kept serving, so nothing looked wrong, and the post-start check was +# dead from then on. Every later drift went unreported on exactly the projects +# that had been running longest. +# +# DDEV runs this line through `bash -c` (pkg/ddevapp/task.go, ExecHostTask), so +# the prefix is an ordinary assignment, and a command that only reads "$@" cannot +# trip over it. The rule stated in the compose file — anything added here must +# work against what is already deployed — applies to the hook line too. +# +# What it buys: a project that has the add-on installed but has never been +# configured says nothing at all on start. It has nothing to check, and nine +# lines of advice plus a `Task failed` on a project with nothing wrong with it is +# how an add-on stops being inert until it is configured. hooks: post-start: - - exec-host: .ddev/commands/host/hostshift check --quiet + - exec-host: HOSTSHIFT_HOOK=1 .ddev/commands/host/hostshift check diff --git a/test/addon-command.sh b/test/addon-command.sh index 9b4e693..0edfcd6 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -219,6 +219,49 @@ first="$(cat "$wt/.ddev/.env")" (cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || fail "init exited non-zero" "" check "init is idempotent" "$first" "$(cat "$wt/.ddev/.env")" +# The hook line must parse under every released version of the command. +# +# config.hostshift.yaml carries #ddev-generated, so `ddev add-on get` replaces it +# on upgrade. The command only gained that marker after v0.1.0, so DDEV refuses +# to replace *that* — an upgraded project runs the new hook against the old +# command. A flag the old parser does not know makes it exit 2, and since the +# proxy goes on serving, the post-start check is then dead and silent: every +# later drift goes unreported, on exactly the projects running longest. +# +# Run against the real released command out of the tag, not a stub of it, and +# against every tag there is — a stub only proves what its author remembered. +hook="$(sed -n 's/^ *- *exec-host: *//p' "$repo/ddev/config.hostshift.yaml")" +[ -n "$hook" ] || fail "the post-start hook line could not be read" "" +for tag in $(cd "$repo" && git tag -l 'v*'); do + old_cmd="$work/hostshift-$tag" + (cd "$repo" && git show "$tag:ddev/commands/host/hostshift") > "$old_cmd" 2>/dev/null || continue + chmod +x "$old_cmd" + # The hook names its path; point that at the old command and run the line the + # way DDEV does, `bash -c` from the approot (pkg/ddevapp/task.go, ExecHostTask). + mkdir -p "$wt/.ddev/commands/host" + cp "$old_cmd" "$wt/.ddev/commands/host/hostshift" + # `|| true`: the suite runs under `set -e`, and the whole point is to invoke a + # command that may fail. What is asserted is *why* it failed, not that it did. + out="$(cd "$wt" && bash -c "$hook" 2>&1 || true)" + rm -f "$wt/.ddev/commands/host/hostshift" + case "$out" in + *"unknown argument"*|*"usage:"*) + fail "the post-start hook parses under $tag's command" "$out" ;; + *) pass "the post-start hook parses under $tag's command" ;; + esac +done + +# Mid-rebase, a parent's config.yaml has conflict markers and is not valid YAML. +# The outcome is right — fall back to the worktree's own hostnames and warn — but +# "no DDEV config could be read there" reads as "the parent has no DDEV project", +# which sends the reader to look in the wrong place. +cp "$main/.ddev/config.yaml" "$work/parent-config.bak" +printf '<<<<<<< HEAD\nname: acme\n=======\nname: acme2\n>>>>>>> other\n' > "$main/.ddev/config.yaml" +out="$(cd "$wt" && "$cmd" env --slug wt-a 2>&1 || true)" +contains "an unparseable parent config names the conflict markers" \ + "conflict markers" "$out" +cp "$work/parent-config.bak" "$main/.ddev/config.yaml" + out="$(cd "$wt" && "$cmd" env --slug other 2>/dev/null || true)" contains "a second slug does not compound with the first" \ "HOSTSHIFT_VARIANTS=other--acme.ddev.site,other--nat.acme.ddev.site" "$out" @@ -360,6 +403,25 @@ contains "copy-db sees sharing configured in a compose override" \ "configured to *use*" "$out" rm -f "$wt/.ddev/docker-compose.sharedb.yaml" +# ...and in .ddev/.env.web, DDEV's own documented place for per-service +# environment, which the file scan did not read. Measured live: DB_HOST set +# there, the worktree serving off the parent's database, and copy-db reporting +# "copied" into this worktree's idle db container — so the developer who ran it +# specifically to stop writing to the parent was told they had. +printf 'DB_HOST=ddev-acme-db\n' > "$wt/.ddev/.env.web" +out="$(cd "$wt" && "$cmd" copy-db 2>&1 || true)" +contains "copy-db sees sharing configured in .ddev/.env.web" \ + "configured to *use*" "$out" +rm -f "$wt/.ddev/.env.web" + +# Nothing shared: copy-db must get past the guard. A guard that refuses +# everything passes both tests above and is useless. +out="$(cd "$wt" && "$cmd" copy-db 2>&1 || true)" +case "$out" in + *"configured to *use*"*) fail "and does not refuse when nothing is shared" "$out" ;; + *) pass "and does not refuse when nothing is shared" ;; +esac + # Every command this prints must be one DDEV actually accepts. `ddev start -p X` # is not — it fails with "unknown shorthand flag". if grep -n 'ddev start -p' "$repo/ddev/commands/host/hostshift"; then diff --git a/test/integration-ddev.sh b/test/integration-ddev.sh index 036c4a2..b6a2bbc 100755 --- a/test/integration-ddev.sh +++ b/test/integration-ddev.sh @@ -136,25 +136,37 @@ code="$(curl -sk -o /dev/null -w '%{http_code}' --max-time 20 https://${tag}-wt- out="$(cd "$wt" && ddev hostshift check 2>&1)" && pass "check passes a live worktree" \ || fail "check passes a live worktree" "$out" -# ...and stops passing it the moment a sibling claims the same hostname. Two -# projects on one hostname is an error nowhere in DDEV — traefik breaks the tie -# by rule length and the loser is silently unreachable — so `check` refusing is -# the only thing standing between a developer and reviewing the wrong branch's -# code at the right URL. The scan reads `../*/.ddev/.env`, so a bare directory -# is enough to stage it; no second DDEV project needed. -rival="$work/${tag}-rival" -mkdir -p "$rival/.ddev" -printf 'HOSTSHIFT_VARIANTS=%s-wt-a.ddev.site\n' "$tag" > "$rival/.ddev/.env" +# ...and stops passing it the moment a *running* sibling claims the same +# hostname. Two projects on one hostname is an error nowhere in DDEV — traefik +# breaks the tie by rule length and the loser is silently unreachable — so this +# refusal is what stands between a developer and reviewing the wrong branch's +# code at the right URL. +# +# The parent is already up, so it is the live rival: give it the worktree's +# variant and it is genuinely contending for that hostname. +variant="$(sed -n 's/^HOSTSHIFT_VARIANTS=//p' "$wt/.ddev/.env" | cut -d, -f1)" +cp "$main/.ddev/.env" "$work/main-env.bak" 2>/dev/null || : > "$work/main-env.bak" +printf 'HOSTSHIFT_VARIANTS=%s\n' "$variant" >> "$main/.ddev/.env" if (cd "$wt" && ddev hostshift check >/dev/null 2>&1); then - fail "check refuses when another project claims the same hostname" \ - "exit 0 — the collision was reported as healthy" + fail "check refuses when a running project claims the same hostname" \ + "exit 0 — a live collision was reported as healthy" else - pass "check refuses when another project claims the same hostname" + pass "check refuses when a running project claims the same hostname" fi -rm -rf "$rival" -(cd "$wt" && ddev hostshift check >/dev/null 2>&1) \ - && pass "and passes again once the rival is gone" \ - || fail "and passes again once the rival is gone" "$(cd "$wt" && ddev hostshift check 2>&1)" +cp "$work/main-env.bak" "$main/.ddev/.env" + +# But a directory left behind by a deleted worktree is not a claim. `git worktree +# remove` refuses while untracked files are present, so the directory outliving +# the project is the common case — and refusing there failed the post-start hook +# on every start while routing was entirely correct. +dead="$work/${tag}-dead" +mkdir -p "$dead/.ddev" +printf 'HOSTSHIFT_VARIANTS=%s\n' "$variant" > "$dead/.ddev/.env" +out="$(cd "$wt" && ddev hostshift check 2>&1)" \ + && pass "and a stopped project's leftover directory is a warning, not a failure" \ + || fail "and a stopped project's leftover directory is a warning, not a failure" "$out" +contains "which still says what it found" "already claims" "$out" +rm -rf "$dead" echo "== copy-db" From 61a3a41ee27104a7c35f4f0dbdd67195fd584f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 01:42:20 -0300 Subject: [PATCH 004/124] Order the copy-db tests so the first copy still runs against an empty database The worktree-only table proves the copy replaces rather than merges, but creating it before the first copy made that database non-empty, so the unforced copy correctly refused and three assertions failed on a bug in the test rather than in the command. It goes in after the first copy instead. --- test/integration-ddev.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/integration-ddev.sh b/test/integration-ddev.sh index b6a2bbc..e07a4e5 100755 --- a/test/integration-ddev.sh +++ b/test/integration-ddev.sh @@ -176,26 +176,26 @@ echo "== copy-db" # would overwrite. Both halves are new and neither had any coverage. sql() { (cd "$1" && ddev exec -s web bash -c "mysql -h db -udb -pdb -N -B -e \"$2\" db" 2>/dev/null) || true; } sql "$main" "create table hs_probe (id int); insert into hs_probe values (42);" >/dev/null -# A table only the worktree has, so the copy has something to drop. `mysqldump db -# | mysql` drops only the tables the dump contains, so this survived — while the -# refusal message promised a replace. -sql "$wt" "create table hs_only_here (id int); insert into hs_only_here values (7);" >/dev/null - out="$(cd "$wt" && ddev hostshift copy-db 2>&1)" || fail "copy-db copies the parent's database" "$out" contains "copy-db copies the parent's database" "42" "$(sql "$wt" "select id from hs_probe")" -# Replace, not merge. `mysqldump db | mysql` drops only the tables the dump -# contains, so a worktree from an older pull kept everything the parent no longer -# has — and the refusal message promises a replace. The parent-side assertion -# above passed before the fix too; this is the one that distinguishes them. +# Replace, not merge — and this is the assertion that distinguishes them. The one +# above passed before the fix too. `mysqldump db | mysql` drops only the tables +# the dump contains, so a worktree whose database came from an older pull kept +# everything the parent no longer has, while the refusal message promised a +# replace. Needs a table the *worktree* has and the parent does not; it goes in +# after the first copy, so the copy above still runs against an empty database. +sql "$wt" "create table hs_only_here (id int); insert into hs_only_here values (7);" >/dev/null +out="$(cd "$wt" && ddev hostshift copy-db --force 2>&1)" \ + || fail "and drops what only the worktree had" "$out" if [ -n "$(sql "$wt" "select id from hs_only_here" 2>&1 | grep -x 7 || true)" ]; then fail "and drops what only the worktree had" "hs_only_here survived, so it merged" else pass "and drops what only the worktree had" fi -# Running it twice is the accident: the second run silently replaced whatever -# the first one's work had put there. +# Running it twice is the accident this refusal is for: the second run silently +# replaced whatever the first one's work had put there. if (cd "$wt" && ddev hostshift copy-db >/dev/null 2>&1); then fail "copy-db refuses a database that already has tables" "exited 0" else From 4b4c3c240abbfdb08b9fffa9719150f42888cddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 02:22:19 -0300 Subject: [PATCH 005/124] Model the URL parser, not just the bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round nine's breaker found a live test 28 leak, and it is the shape three rounds of entity fuzzing could not reach: both previous campaigns modelled the *HTML* decoder, and neither modelled the URL parser that runs after it. §5.3's three encodings all assume an origin is a contiguous run reading `scheme` `://` `host`. The WHATWG parser requires neither the run to be contiguous nor the separator to be two forward slashes. Verified against it — every one of these resolves to https://www.example.fi/x and was served unrewritten: href="https://www.example.fi/x" (also LF, CR; also ) href="https:\\www.example.fi/x" (also /\, \/, ///, ////, //\) href="//www.example.fi/x" href="https://www.example.fi/x" Two rules produce all of it: tab, LF and CR are deleted from the whole URL before parsing, and the authority is introduced by a run of any length of `/` and `\`. Anything else — a space, a form feed, a NBSP — makes the URL fail to parse instead, so only those three characters are removed. The worst part was the silence. `--json` on every one of these reported zero candidates and zero skips, and the straggler sweep runs through the same matcher, so nothing anywhere suggested the page was worth a second look. The census is the post-condition §4.4 leans on, and it was reporting clean. Closed the way decodeEntityLeak closes the reference gap, and for the same reason — the runs are unbounded, so pattern variants cannot do it: normalise the value the way the parser would, re-match, and splice only when the normalised form carries an origin the raw form did not. It never runs on a page that is already correct, and an identity map still rewrites nothing, so test 24 holds. Single-URL attributes only. The pass deletes whitespace, which is *content* in a title and a *separator* in srcset, imagesrcset and ping. The remaining gap is an obfuscated origin inside a srcset entry; that is a background image rather than a navigation target, and closing it means splitting the list first. Also: ‐ is U+2010 HYPHEN, not U+002D, and this table had it as ASCII. Because a decoded value is spliced back whole whenever any origin in it rewrote, that turned an inert link live — `next=https://staging‐old.prod.fi/` resolves to a punycode host that does not exist on production, and hostshift served `staging-old.prod.fi`, which does. There is no named reference for U+002D. Every entry is now checked against html.UnescapeString by a test, and \ and _ were missing. And three regressions in the add-on command I added last commit, all found by the same round: * The sibling-collision liveness probe read `.ddev/config.yaml` alone, which is *tracked*, so in a worktree it holds the parent repo's pinned name and the probe asked docker about the parent — running by definition. The liveness gate was inert and every stale sibling .ddev/.env was fatal again. It also stopped at the first colliding sibling, so a live rival behind a dead one was never examined and check exited 0. Same mistake this file already fixed for the current project two blocks down. * The running-map comparison pattern-matched pair lines out of the whole log. Every straggler WARN carries a `context=` attribute whose value is raw page bytes, so a page merely mentioning `https://old -> https://new` injected a phantom pair and check exited 2 on every start — with advice (`ddev restart`) that could not help, because the line stays until the container is recreated. It now reads the last delimited map block. That also fixes unioning a previous run's map with the current one, which a hostshift.yaml edit produces exactly, since it does not change the compose definition. And `hostshift map` prints its header to stderr, so the `sed 1d` that dropped it was eating a real pair. * The copy-db guard could not tell "the container says it is not shared" from "there is no container to ask", refused on a commented-out override with no way past (--force does not bypass it), refused on an override renamed aside to `.env.web.disabled`, and still missed the shape most likely in this fleet: Bedrock's project-root `.env`, which `printenv` cannot see either because phpdotenv reads it inside PHP. * `check` used `grep -qs` on `.ddev/.env`, which cannot tell "no HOSTSHIFT_ line" from "cannot read the file", so a fully configured, fully stale project reported healthy on every start. --- ddev/commands/host/hostshift | 139 ++++++++++++++--- internal/rewrite/entity.go | 27 ++-- internal/rewrite/entity_refs_test.go | 27 ++++ internal/rewrite/html.go | 34 +++++ internal/rewrite/stats.go | 10 +- internal/rewrite/urlobf.go | 216 +++++++++++++++++++++++++++ internal/rewrite/urlobf_test.go | 150 +++++++++++++++++++ test/addon-command.sh | 32 ++++ 8 files changed, 602 insertions(+), 33 deletions(-) create mode 100644 internal/rewrite/entity_refs_test.go create mode 100644 internal/rewrite/urlobf.go create mode 100644 internal/rewrite/urlobf_test.go diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index 81b111b..9f0f745 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -87,9 +87,21 @@ esac # Only this short-circuit is gated. `ddev hostshift check` typed by a person on a # project with no configuration is a question that deserves an answer, and it # still gets one. -if [ "$cmd" = "check" ] && { [ -n "$quiet" ] || [ -n "${HOSTSHIFT_HOOK:-}" ]; } \ - && ! grep -qs '^HOSTSHIFT_' .ddev/.env 2>/dev/null; then - exit 0 +# +# `[ ! -e ]` or readable-and-empty, not `grep -qs`. -s swallows the difference +# between "no HOSTSHIFT_ line" and "cannot read the file", so a fully configured +# project whose .ddev/.env had lost its read permission reported healthy on every +# start — the one file the whole check is about, silently unread. +if [ "$cmd" = "check" ] && { [ -n "$quiet" ] || [ -n "${HOSTSHIFT_HOOK:-}" ]; }; then + if [ ! -e .ddev/.env ]; then + exit 0 + elif [ ! -r .ddev/.env ]; then + echo "hostshift: .ddev/.env exists but cannot be read, so nothing here can be" >&2 + echo " checked. \`chmod u+r .ddev/.env\`." >&2 + exit 2 + elif ! grep -q '^HOSTSHIFT_' .ddev/.env; then + exit 0 + fi fi command -v hostshift >/dev/null || { @@ -260,19 +272,61 @@ if [ "$cmd" = "copy-db" ]; then # including ones DDEV has not invented yet, and it is what `check` already does # for the same reason. # - # Both, not either. The container is the truth when it is up, and it is down - # exactly when the files are all there is — a project stopped mid-configuration - # is the likeliest moment for someone to reach for copy-db. The file list gains - # `.ddev/.env.*`, which is where the miss was. + # The container first, and its answer is final either way — including "no". + # Testing `[ -z "$shared" ]` could not tell "the container says it is not + # shared" from "there is no container to ask", so a running project that had + # been un-shared was still blocked by a file left lying around. + # + # The file scan is the fallback for when nothing is running, which is exactly + # when someone reaches for copy-db. It reads assignments, not any line that + # mentions the hostname: a commented-out `# DB_HOST=ddev--db` blocked + # the copy and printed its own `#` back, with no way past — --force does not + # bypass this guard — so a stale note permanently refused a legitimate copy. + # + # The project's own .env is in the list because that is where Bedrock keeps + # DB_HOST. `printenv` in the web container cannot see it (phpdotenv reads it + # inside PHP), and the previous list stopped at .ddev/, so the one shape most + # likely in this fleet was missed by both halves of the guard. shared="" + asked="" dbenv="$(ddev exec -s web printenv DB_HOST DATABASE_URL DB_URL 2>/dev/null || true)" + [ -n "$dbenv" ] && asked=1 case "$dbenv" in *"ddev-$from-db"*) shared="$(printf '%s\n' "$dbenv" | grep -F "ddev-$from-db" || true)" ;; esac - if [ -z "$shared" ]; then - shared="$(grep -rs 'ddev-'"$from"'-db' \ - .ddev/config.yaml .ddev/config.*.yaml .ddev/docker-compose.*.yaml \ - .ddev/.env .ddev/.env.* 2>/dev/null || true)" + # ...but the container's environment cannot see a dotenv file the application + # reads for itself, so that one is checked either way. + if [ -z "$shared" ] && { [ -z "$asked" ] || [ -f .env ]; }; then + # An assignment: optional `export`, the name, `=`, and the parent's db host + # in the value. `# DB_HOST=...` does not match, and neither does prose. + # -E and no -r: a fixed file list, so a directory recursion is not wanted, + # and $from goes through sed first because a project name containing `.` or + # `+` would otherwise widen the pattern. + fromre="$(printf '%s' "$from" | sed 's/[][\.*^$(){}?+|]/\\&/g')" + # A shell assignment, a dotenv line, a YAML mapping value or a compose list + # item — all of them are NAME, a separator, then the value. What none of them + # is, is a comment. + assignment="^[[:space:]]*-?[[:space:]]*(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[:=][^#]*ddev-$fromre-db" + if [ -n "$asked" ]; then + # The container already answered for its own environment; .env is the one + # thing it could not see. + shared="$(grep -sEn "$assignment" .env 2>/dev/null || true)" + else + # DDEV's per-service form is .ddev/.env., and a service name has + # no dot in it. Taking the glob whole made `.ddev/.env.web.disabled` — a + # override renamed aside precisely to turn it off — refuse the copy, with + # no way past. + envfiles="" + for f in .ddev/.env.*; do + [ -f "$f" ] || continue + case "${f#.ddev/.env.}" in *.*) continue ;; esac + envfiles="$envfiles $f" + done + # shellcheck disable=SC2086 + shared="$(grep -sEn "$assignment" \ + .ddev/config.yaml .ddev/config.*.yaml .ddev/docker-compose.*.yaml \ + .ddev/.env $envfiles .env 2>/dev/null || true)" + fi fi if [ -n "$shared" ]; then echo "hostshift: this worktree is configured to *use* $from's database:" >&2 @@ -390,17 +444,32 @@ for other in ../*/.ddev/.env; do # # So only a *live* claim is fatal. If the rival is not running there is # nothing to lose a routing tie to today. + # Every config.*.yaml, in DDEV's own merge order, not config.yaml alone. + # config.yaml is *tracked*, so in a sibling worktree it holds the parent + # repo's pinned `name:` while the worktree's own name lives in a + # config.*.local.yaml — which is the shape this repo's test harness uses + # and the shape a worktree naturally takes. Reading config.yaml alone + # therefore asked docker about the *parent*, which is running by + # definition, so every stale sibling .ddev/.env was fatal again and the + # liveness gate was inert. That is the same mistake this file already + # fixed for the current project two blocks down. oname="$(basename "$odir")" - if [ -f "$odir/.ddev/config.yaml" ]; then - n="$(sed -n 's/^name: *//p' "$odir/.ddev/config.yaml" | sed -n 1p)" + for oc in "$odir/.ddev/config.yaml" "$odir"/.ddev/config.*.yaml; do + [ -f "$oc" ] || continue + n="$(sed -n 's/^name: *//p' "$oc" | sed -n 1p)" [ -n "$n" ] && oname="$n" - fi + done if [ "$(docker inspect "ddev-$oname-web" --format '{{.State.Running}}' 2>/dev/null || true)" = "true" ]; then claimed="$v" - else - echo " ($oname is not running, so nothing is serving it right now.)" >&2 + break 2 fi - break 2 ;; + # Not `break 2`. A dead sibling is not a claim, and stopping at the first + # one meant a *live* rival further down the list was never examined — + # check then printed "hostshift is serving" and exited 0 while traefik + # routed that hostname elsewhere, which is the exact failure this block + # exists to catch. + echo " ($oname is not running, so nothing is serving it right now.)" >&2 + break ;; esac done done @@ -641,15 +710,39 @@ if [ "$cmd" = "check" ]; then # it. Comparing that against `hostshift map` here is exact, needs no recorded # state, and both sides render through the same Map.String(). # - # Best-effort by design. A rotated or truncated log yields no pair lines and is + # Best-effort by design. A rotated or truncated log yields no map block and is # not evidence of drift, so it says nothing rather than crying wolf. - running_map="$(docker logs "ddev-$name-hostshift" 2>&1 \ - | sed -n 's/^\(.*[[:space:]]\)\{0,1\}\(https:\/\/[^[:space:]]*[[:space:]]*->[[:space:]]*https:\/\/[^[:space:]]*\)$/\2/p' \ - | tr -s ' ' | sort -u || true)" + # + # The block is delimited, not pattern-matched out of the whole log, and only + # the *last* block counts. Both matter: + # + # * Every straggler WARN carries a `context=` attribute whose value is raw + # page bytes. A page that merely mentions `https://old -> https://new` + # within 64 bytes of a straggler injected a phantom pair, `check` exited 2 + # on every start, and the advice — `ddev restart` — could not help, because + # the line stays in the log until the container is *recreated*. + # * `docker logs` keeps output across a restart, and a hostshift.yaml edit + # changes the map without changing the compose definition, so the container + # is reused. Unioning the blocks compared the old map against the new one + # and never agreed again. Reading the last block compares what is actually + # running. + # + # `map from` opens the block and the version banner closes it; both are printed + # by the same function, adjacent, before the first request is served. + extract_map=' + /^hostshift: map from / { inblock = 1; delete m; n = 0; next } + inblock && /: listening on / { inblock = 0; next } + inblock { m[++n] = $0 } + END { for (i = 1; i <= n; i++) print m[i] }' + # Same normalisation both sides: drop the site-name column and collapse the + # alignment padding, which depends on the longest hostname in the map. No line + # to skip on the host side — `hostshift map` prints its `map from` header to + # stderr, so stdout is pair lines only, and dropping one ate a real pair. + norm_map() { awk '{ sub(/^[^ \t]+[ \t]+/, ""); gsub(/[ \t]+/, " "); print }' | LC_ALL=C sort -u; } + running_map="$(docker logs "ddev-$name-hostshift" 2>&1 | awk "$extract_map" | norm_map || true)" if [ -n "$running_map" ]; then want_map="$(hostshift map --slug "$slug" "${map_args[@]+"${map_args[@]}"}" 2>/dev/null \ - | sed -n 's/^\(.*[[:space:]]\)\{0,1\}\(https:\/\/[^[:space:]]*[[:space:]]*->[[:space:]]*https:\/\/[^[:space:]]*\)$/\2/p' \ - | tr -s ' ' | sort -u || true)" + | norm_map || true)" if [ -n "$want_map" ] && [ "$running_map" != "$want_map" ]; then echo "hostshift: the proxy is running a different map than this checkout" >&2 echo " now resolves to. It started with:" >&2 diff --git a/internal/rewrite/entity.go b/internal/rewrite/entity.go index 70cec58..80a7371 100644 --- a/internal/rewrite/entity.go +++ b/internal/rewrite/entity.go @@ -8,16 +8,25 @@ import "bytes" // so a query string carrying "©=1" would come back as "©=1" — a corrupted // link on a page that had nothing wrong with it. Numeric references have no // such ambiguity and are decoded in full below. +// +// Every entry is checked against html.UnescapeString by TestNamedRefsMatchHTML5. +// "hyphen" was in this table decoding to '-', and it is not: HTML5 defines +// ‐ as U+2010 HYPHEN, not U+002D. Because a decoded value is spliced back +// whole whenever any origin inside it rewrote, that turned an inert link live — +// `next=https://staging‐old.prod.fi/` resolves to a punycode host that +// does not exist on production, and hostshift served `staging-old.prod.fi`, +// which does. There is no named reference for U+002D; it is spelled -. var urlNamedRefs = map[string]byte{ - "sol": '/', - "colon": ':', - "period": '.', - "quest": '?', - "num": '#', - "percnt": '%', - "lowbar": '_', - "hyphen": '-', - "commat": '@', + "sol": '/', + "bsol": '\\', // the JSON separator's byte + "colon": ':', + "period": '.', + "quest": '?', + "num": '#', + "percnt": '%', + "lowbar": '_', + "UnderBar": '_', // a second spelling of the same character + "commat": '@', } // NewLine is deliberately absent. It decodes to a raw 0x0A, which no origin diff --git a/internal/rewrite/entity_refs_test.go b/internal/rewrite/entity_refs_test.go new file mode 100644 index 0000000..84ffddb --- /dev/null +++ b/internal/rewrite/entity_refs_test.go @@ -0,0 +1,27 @@ +package rewrite + +import ( + "fmt" + "html" + "testing" +) + +// Every entry in urlNamedRefs must decode the way a browser decodes it. +// +// "hyphen" did not: HTML5 defines ‐ as U+2010 HYPHEN, and this table +// had it as U+002D. A decoded value is spliced back whole whenever any origin +// inside it rewrote, so that silently rewrote a neighbouring hostname from one +// that does not resolve on production into one that does — content inert on +// production, made live by the rewriter, which is test 28 exactly. +// +// html.UnescapeString is the HTML5 named table; comparing against it is the +// only way this stays honest as entries are added. +func TestNamedRefsMatchHTML5(t *testing.T) { + for name, want := range urlNamedRefs { + got := html.UnescapeString(fmt.Sprintf("&%s;", name)) + if got != string(rune(want)) { + t.Errorf("&%s; decodes to %q (%U) in HTML5, but this table says %q", + name, got, []rune(got), string(rune(want))) + } + } +} diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index efa7be7..ad202e0 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -204,6 +204,32 @@ var structuredAttrNames = [][]byte{ []byte("srcset"), []byte("imagesrcset"), []byte("ping"), []byte("srcdoc"), []byte("content"), } +// singleURLAttrNames are the attributes whose whole value is one URL, which the +// browser resolves through the URL parser. +// +// Only these get normaliseURLLeak, and the restriction is not caution for its +// own sake: that pass deletes tab, LF and CR, because the URL parser does. In a +// title or an alt those are ordinary text, and in srcset, imagesrcset and ping +// they are *separators* — a list of URLs delimited by whitespace, which deleting +// the whitespace would run together. The remaining gap is therefore an +// obfuscated origin inside a srcset entry; it is a background image rather than +// a navigation target, and closing it means splitting the list first. +var singleURLAttrNames = [][]byte{ + []byte("href"), []byte("src"), []byte("action"), []byte("formaction"), + []byte("cite"), []byte("poster"), []byte("data"), []byte("manifest"), + []byte("longdesc"), []byte("background"), []byte("codebase"), + []byte("profile"), []byte("itemid"), +} + +func singleURLAttr(name []byte) bool { + for _, s := range singleURLAttrNames { + if len(name) == len(s) && bytes.EqualFold(name, s) { + return true + } + } + return false +} + // structuredAttr matches on the raw name bytes, case-insensitively. Lowercasing // every attribute name to a string first cost one allocation per attribute — // 37,280 of them across the corpus — for a check that five byte comparisons @@ -234,6 +260,14 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b w.stats.Record(surface, base, events) if surface == SurfaceHTMLAttr { out = w.decodeEntityLeak(base, out) + // After the entity decode, because the two compose: a host may be + // spelled with references *and* carry a tab, and the reference form of + // the tab is removed here rather than decoded there — decodeURLRefs must + // never emit a control character, which is one of the XSS holes it was + // written to close. Removing one is not emitting one. + if singleURLAttr(name) { + out = w.normaliseURLLeak(base, out) + } } if w.dryRun { return v diff --git a/internal/rewrite/stats.go b/internal/rewrite/stats.go index 9da0116..395cc9a 100644 --- a/internal/rewrite/stats.go +++ b/internal/rewrite/stats.go @@ -33,7 +33,15 @@ const ( // count is worth looking at: it means content is storing origins in a form // §5.3's three encodings do not model. SurfaceHTMLEntity = "html-entity" - SurfaceHeader = "header" + // SurfaceHTMLObfuscated is an attribute value whose origin was only visible + // after the value was normalised the way the WHATWG URL parser normalises + // it: tab, LF and CR removed, and the run of '/' and '\' after the scheme + // collapsed. `https:\\h`, `https:///h` and a tab inside the host all + // dereference to production in a browser while matching none of §5.3's + // three encodings. Like html-entity, a non-zero count means content is + // storing origins in a form the byte model does not cover. + SurfaceHTMLObfuscated = "html-obfuscated" + SurfaceHeader = "header" SurfaceJSONString = "json-string" // SurfaceJSONEscape is a JSON string whose origin was only visible after // the string was unquoted — a \uXXXX-escaped IDN host, an HTML character diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go new file mode 100644 index 0000000..6f40558 --- /dev/null +++ b/internal/rewrite/urlobf.go @@ -0,0 +1,216 @@ +package rewrite + +import "bytes" + +// The second gap between the matcher's model and the browser's: the matcher +// models *bytes*, and the browser runs a URL parser over them first. +// +// §5.3's three encodings all assume the origin is a contiguous run reading +// `scheme` `://` `host`. The WHATWG URL parser requires neither the run to be +// contiguous nor the separator to be two forward slashes, and every shape below +// was served unrewritten — and, worse, uncounted, so `--json` reported a clean +// page and the straggler sweep saw nothing either: +// +// href="https://www.example.fi/x" with a tab, LF or CR anywhere in it +// href="https:\\www.example.fi/x" +// href="https:///www.example.fi/x" (or ////, /\, \/, //\ …) +// href="//www.example.fi/x" with a tab, LF or CR in the host +// href="https://www.example .fi/x" +// +// Verified against the WHATWG parser: every one resolves to +// https://www.example.fi/x. That is test 28 — a production origin the browser +// dereferences — and it is reachable from any attacker-influenced content in the +// database, pointing the developer's authenticated browser at the live site. +// +// Two mechanisms in the URL spec produce all of it: +// +// - Tab, LF and CR are removed from the whole URL before parsing. Nothing +// else is: a space, a form feed or a NBSP makes the URL fail to parse +// instead, so only these three are removed here. +// - After `scheme:`, the parser skips a run of any length of `/` and `\` +// before reading the authority ("special authority ignore slashes state"). +// The same applies at the start of a scheme-relative reference. +// +// So the value is normalised the way the parser would and re-matched, exactly +// as decodeEntityLeak does for character references, and for the same reason: +// pattern variants cannot close it, because the runs are unbounded. When the +// normalised form carries an origin the raw form did not, it replaces the value +// — which drops the obfuscation from the page, and is confined to values that +// would otherwise leak, so it never runs on a page that is already correct. + +// removableRef reports the length of a character reference at b that spells a +// character the URL parser removes, or 0. +// +// These are handled here rather than in decodeURLRefs because that decoder must +// never *emit* a control character — doing so was one of the XSS holes this file +// sits next to. Removing one is not emitting one, so the same characters that +// are unsafe to decode are safe to delete. +func removableRef(b []byte) int { + if len(b) < 4 || b[0] != '&' { + return 0 + } + if b[1] != '#' { + lim := min(len(b), 10) + end := bytes.IndexByte(b[1:lim], ';') + if end < 0 { + return 0 + } + switch string(b[1 : 1+end]) { + case "Tab", "NewLine": + return end + 2 + } + return 0 + } + j, base := 2, 10 + if j < len(b) && (b[j] == 'x' || b[j] == 'X') { + base, j = 16, j+1 + } + start := j + val := 0 + for j < len(b) { + d, ok := digitVal(b[j], base) + if !ok { + break + } + val = val*base + d + if val > 0x10FFFF { + return 0 + } + j++ + } + if j == start { + return 0 + } + if j < len(b) && b[j] == ';' { + j++ + } + if val == '\t' || val == '\n' || val == '\r' { + return j + } + return 0 +} + +func isURLStripped(c byte) bool { return c == '\t' || c == '\n' || c == '\r' } + +func isSlashish(c byte) bool { return c == '/' || c == '\\' } + +// normaliseURL applies the two parser rules to v, returning the normalised +// bytes and whether anything changed. +func normaliseURL(v []byte) ([]byte, bool) { + // The ordinary case, and the one that must stay cheap: no removable + // character and no backslash means neither rule can fire. A lone extra '/' + // still has to be looked at, so the slash run is checked below rather than + // here. + fast := true + for _, c := range v { + if isURLStripped(c) || c == '\\' || c == '&' || c == '/' { + fast = false + break + } + } + if fast { + return v, false + } + + // Rule one: delete tab, LF and CR, in raw and character-reference form. + stripped := make([]byte, 0, len(v)) + changed := false + for i := 0; i < len(v); { + if isURLStripped(v[i]) { + i++ + changed = true + continue + } + if v[i] == '&' { + if n := removableRef(v[i:]); n > 0 { + i += n + changed = true + continue + } + } + stripped = append(stripped, v[i]) + i++ + } + + // Rule two: the authority separator. It is a run of '/' and '\' of length + // two or more, either at the very start of the value or immediately after + // `http:` / `https:`. Anywhere else a slash is a path and must not be + // touched — `/a//b` is a path with an empty segment, not an authority. + at := 0 + if n := schemeLen(stripped); n > 0 { + at = n + } else if len(stripped) > 0 && isSlashish(stripped[0]) { + at = 0 + } else { + return finish(v, stripped, changed) + } + end := at + for end < len(stripped) && isSlashish(stripped[end]) { + end++ + } + if end-at < 2 { + // One slash is a path relative to the base, not an authority, and zero + // is not a separator at all. + return finish(v, stripped, changed) + } + if end-at != 2 || stripped[at] != '/' || stripped[at+1] != '/' { + out := make([]byte, 0, len(stripped)) + out = append(out, stripped[:at]...) + out = append(out, '/', '/') + out = append(out, stripped[end:]...) + return out, true + } + return finish(v, stripped, changed) +} + +func finish(v, stripped []byte, changed bool) ([]byte, bool) { + if !changed { + return v, false + } + return stripped, true +} + +// schemeLen returns the length of a leading "http:" or "https:", or 0. +// +// Case-insensitive, because the URL parser lowercases the scheme, and only +// these two because they are the only schemes hostshift maps. +func schemeLen(b []byte) int { + for _, s := range [][]byte{[]byte("https:"), []byte("http:")} { + if len(b) >= len(s) && hasFoldPrefixASCII(b[:len(s)], s) { + return len(s) + } + } + return 0 +} + +func hasFoldPrefixASCII(b, want []byte) bool { + for i := range want { + c := b[i] + if 'A' <= c && c <= 'Z' { + c += 'a' - 'A' + } + if c != want[i] { + return false + } + } + return true +} + +// normaliseURLLeak is the seam, alongside decodeEntityLeak and with the same +// contract: it returns v untouched unless the normalised form carries an origin +// that the value as written did not. +func (w *HTML) normaliseURLLeak(base int, v []byte) []byte { + norm, ok := normaliseURL(v) + if !ok { + return v + } + out, events := w.m.Rewrite(norm, SurfaceHTMLObfuscated, w.stats.Explain()) + if bytes.Equal(out, norm) { + // The normalised form holds no origin either. Leave the value exactly as + // its author wrote it — this pass exists to stop a leak, not to tidy + // anyone's markup. + return v + } + w.stats.Record(SurfaceHTMLObfuscated, base, events) + return out +} diff --git a/internal/rewrite/urlobf_test.go b/internal/rewrite/urlobf_test.go new file mode 100644 index 0000000..aecde10 --- /dev/null +++ b/internal/rewrite/urlobf_test.go @@ -0,0 +1,150 @@ +package rewrite + +import ( + "io" + "strings" + "testing" + + "github.com/generoi/hostshift/internal/origin" +) + +func obfMatcher(t *testing.T) *origin.Matcher { + t.Helper() + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://wt-a--example.ddev.site"), + }}) + if err != nil { + t.Fatal(err) + } + return m +} + +func rewriteHTML(t *testing.T, m *origin.Matcher, in string, st *Stats) string { + t.Helper() + r := NewResponseBody(io.NopCloser(strings.NewReader(in)), m, nil, Options{Stats: st}) + out, err := io.ReadAll(r) + if err != nil { + t.Fatal(err) + } + return string(out) +} + +// Test 28 through the URL parser rather than through the byte model. +// +// The matcher matches a contiguous `scheme://host`. The WHATWG URL parser needs +// neither the run to be contiguous nor the separator to be two forward slashes, +// so every input here was served unrewritten and — worse — uncounted: --json +// reported a clean page and the straggler sweep saw nothing, because the sweep +// runs through the same matcher. +// +// Each of these was verified against the WHATWG parser (Node's ada, which is +// what Chrome ships) to resolve to https://www.example.fi/x, i.e. to a live +// production origin in the developer's authenticated browser. +func TestObfuscatedOriginsAreRewritten(t *testing.T) { + m := obfMatcher(t) + for _, c := range []struct{ name, in string }{ + {"tab in host", ""}, + {"LF in host", ""}, + {"CR in host", ""}, + {"tab in scheme", ""}, + {"leading tab", ""}, + {"tab inside the separator", ""}, + {"backslashes", ``}, + {"slash backslash", ``}, + {"backslash slash", ``}, + {"three slashes", ``}, + {"four slashes", ``}, + {"scheme relative, three slashes", ``}, + {"scheme relative, backslashes", ``}, + {"scheme relative, tab in host", ""}, + {"tab as a character reference", ``}, + {"LF as a named reference", ``}, + {"hex reference", ``}, + {"src, not only href", ``}, + } { + t.Run(c.name, func(t *testing.T) { + out := rewriteHTML(t, m, c.in, NewStats(false)) + if strings.Contains(out, "www.example.fi") { + t.Errorf("a production origin reached the browser:\n%s", out) + } + if !strings.Contains(out, "wt-a--example.ddev.site") { + t.Errorf("nothing was rewritten:\n%s", out) + } + }) + } +} + +// The census has to see them too. A leak the counters call zero is a leak +// nobody goes looking for, and --json reporting a clean page is what made this +// survive three audit rounds. +func TestObfuscatedOriginsAreCounted(t *testing.T) { + st := NewStats(false) + rewriteHTML(t, obfMatcher(t), ``, st) + if got := st.Rewrites(SurfaceHTMLObfuscated); got != 1 { + t.Errorf("the census counts %d obfuscated rewrites, want 1: %+v", + got, st.Snapshot()) + } +} + +// The pass deletes tab, LF and CR, because the URL parser does. Everywhere else +// those bytes are content, and in srcset and ping they are separators — so it +// must not run there, and must not run on a value that holds no origin. +func TestNormalisationIsConfined(t *testing.T) { + m := obfMatcher(t) + for _, c := range []struct{ name, in, want string }{ + { + "srcset keeps its whitespace separators", + "", + "1x,\thttps://wt-a--example.ddev.site/b.png", + }, + { + "a title is text, not a URL", + "

", + "see\thttps://wt-a--example.ddev.site/x", + }, + { + "a path with an empty segment is not an authority", + ``, + `href="/a//b"`, + }, + { + "an unrelated host is left exactly as written", + ``, + `href="https:\\cdn.other.example/x"`, + }, + { + "a value with nothing to rewrite keeps its tabs", + "", + "q=a\tb", + }, + } { + t.Run(c.name, func(t *testing.T) { + if out := rewriteHTML(t, m, c.in, NewStats(false)); !strings.Contains(out, c.want) { + t.Errorf("got:\n%s\nwant it to contain:\n%s", out, c.want) + } + }) + } +} + +// Test 24: an identity map is byte-identical, and that has to hold for every +// shape above — the pass must never fire when there is nothing to fix. +func TestObfuscationIsIdentitySafe(t *testing.T) { + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://www.example.fi"), + }}) + if err != nil { + t.Fatal(err) + } + for _, in := range []string{ + "", + ``, + ``, + ``, + } { + if out := rewriteHTML(t, m, in, NewStats(false)); out != in { + t.Errorf("identity map changed bytes:\n got %q\nwant %q", out, in) + } + } +} diff --git a/test/addon-command.sh b/test/addon-command.sh index 0edfcd6..7178a5a 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -414,6 +414,38 @@ contains "copy-db sees sharing configured in .ddev/.env.web" \ "configured to *use*" "$out" rm -f "$wt/.ddev/.env.web" +# Bedrock keeps DB_HOST in the project root .env, not under .ddev/. Neither half +# of the guard saw it: the file list stopped at .ddev/, and `printenv` in the web +# container cannot see it either, because phpdotenv reads that file inside PHP. +# It is the shape most likely in this fleet. +printf 'DB_NAME=db\nDB_HOST=ddev-acme-db\nWP_ENV=development\n' > "$wt/.env" +out="$(cd "$wt" && "$cmd" copy-db 2>&1 || true)" +contains "copy-db sees sharing configured in the project root .env" \ + "configured to *use*" "$out" +rm -f "$wt/.env" + +# A comment is not a configuration. This refused, printed the `#` back, and +# --force does not bypass this guard — so a stale note permanently blocked a +# legitimate copy and the message told the developer to remove something they +# already had. +printf '# DB_HOST=ddev-acme-db # disabled, we have our own db now\n' > "$wt/.ddev/.env.web" +out="$(cd "$wt" && "$cmd" copy-db 2>&1 || true)" +case "$out" in + *"configured to *use*"*) fail "a commented-out override does not block the copy" "$out" ;; + *) pass "a commented-out override does not block the copy" ;; +esac +rm -f "$wt/.ddev/.env.web" + +# Same for an override renamed aside to turn it off. DDEV's per-service form is +# .ddev/.env., and a service name has no dot in it. +printf 'DB_HOST=ddev-acme-db\n' > "$wt/.ddev/.env.web.disabled" +out="$(cd "$wt" && "$cmd" copy-db 2>&1 || true)" +case "$out" in + *"configured to *use*"*) fail "an override renamed aside does not block the copy" "$out" ;; + *) pass "an override renamed aside does not block the copy" ;; +esac +rm -f "$wt/.ddev/.env.web.disabled" + # Nothing shared: copy-db must get past the guard. A guard that refuses # everything passes both tests above and is useless. out="$(cd "$wt" && "$cmd" copy-db 2>&1 || true)" From ac2266caa9548d568e885c52640da5508a9e9774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 03:07:35 -0300 Subject: [PATCH 006/124] Make --slug stick, and stop removal reaching into sibling worktrees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round nine's daily-work agent lived a week in a real multisite parent and two worktrees. Two large findings, both wrong-and-silent, both in the lifecycle. `--slug` was a one-shot flag. `check` re-derived the slug from the branch whenever there was one, so `init --slug review-42` on branch `main` deployed correctly, served correctly — and then failed the post-start hook on every start for the life of the project, comparing the deployed `--slug review-42` against a freshly derived `--slug main`. The cost is not the noise: it is that the advice it printed, `ddev hostshift init`, silently reverts the choice when run as written. In the case that motivates `--slug` at all — the collision warning saying "Pass --slug to choose a different name" — following the advice puts you back on the colliding name. An explicit slug is now recorded as HOSTSHIFT_SLUG and outranks the branch; `--slug-from-branch` is the way back, because otherwise hand-editing .ddev/.env is, and `init` says out loud when it keeps one. `ddev add-on remove` in one worktree un-ignored the add-on in every other checkout of the repository. info/exclude is shared by a repo and all its linked worktrees — the property that makes one write cover them all on install — so removal inherited the same reach and said nothing. The parent and the other worktrees, where the add-on is still installed and running, were left with config.hostshift.yaml, the host command and `.ddev/.env` untracked and un-ignored, ready for the next `git add -A`. That is the file this codebase documents as holding project env vars, credentials included. Removal now strips the block only when no other checkout still has the command installed, and says why when it does not. Also from the same week and the same round's breaker: * `name:` was parsed with `sed -n 's/^name: *//p'`, which keeps the quotes on `name: "acme"`, the comment on `name: acme # local`, and the CR on a file edited on Windows. A wrong project name is silent in both places it is used: `docker inspect ddev-"acme"-web` finds nothing, which reads as "the rival is not running" and lets a real hostname collision through. * decodeEntityLeak recorded nothing when it did not rewrite, so a decoded value carrying an unanchored near-miss counted zero candidates and zero skips where the raw spelling of the same thing counts both — the state matcher.go's emit comment says was fixed, on the one surface where a non-zero count is documented as the signal that content is storing origins in an unmodelled form. Same for the new obfuscation pass. * multipart's fallback disposition scan matched `filename=` but not RFC 2231's `filename*=`, so a file part with a malformed disposition had its bytes rewritten. * Two more `| head -1` under `set -o pipefail`, the pattern this file calls out twice elsewhere. --- ddev/commands/host/hostshift | 65 ++++++++++++++++++++++++++++++---- ddev/install.yaml | 28 +++++++++++++++ internal/proxy/multipart.go | 15 ++++++-- internal/rewrite/html.go | 10 +++++- internal/rewrite/urlobf.go | 4 ++- test/addon-command.sh | 41 +++++++++++++++++++++ test/integration-ddev.sh | 24 +++++++++++++ test/integration-proxy-ddev.sh | 6 +++- 8 files changed, 181 insertions(+), 12 deletions(-) diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index 9f0f745..8f9499d 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -36,6 +36,8 @@ set -euo pipefail cmd="${1:-init}"; shift || true slug="" +slug_explicit="" +forget_slug="" branch="" dry_run="" force="" @@ -43,8 +45,12 @@ quiet="" claimed="" while [ $# -gt 0 ]; do case "$1" in - --slug) slug="${2:?--slug needs a value}"; shift 2 ;; - --slug=*) slug="${1#*=}"; shift ;; + --slug) slug="${2:?--slug needs a value}"; slug_explicit=1; shift 2 ;; + --slug=*) slug="${1#*=}"; slug_explicit=1; shift ;; + # The way back. A recorded slug outranks the branch — that is the point of + # recording it — so without this the only way to return to branch-derived + # naming is to hand-edit .ddev/.env. + --slug-from-branch) forget_slug=1; shift ;; --dry-run|-n) dry_run=1; shift ;; --quiet|-q) quiet=1; shift ;; --force) force=1; shift ;; @@ -119,6 +125,22 @@ command -v hostshift >/dev/null || { # copy-db maps nothing, so it needs no slug — and refusing to copy a database # because the branch name has no letters in it, or because HEAD is detached # during a rebase, is a refusal with no reason behind it. +# A slug recorded by `init --slug` outranks the branch, for every subcommand +# that did not get one on the command line. Without this, `check` re-derived from +# the branch and called a correct deployment stale forever, and `init` run as the +# advice said reverted the choice. +if [ -z "$slug" ] && [ -z "$forget_slug" ] && [ "$cmd" != "copy-db" ]; then + slug="$(sed -n '/^HOSTSHIFT_SLUG=/{s///p;q;}' .ddev/.env 2>/dev/null || true)" + if [ -n "$slug" ]; then + slug_explicit=1 + # Said out loud on init, because init is where a developer would expect a + # bare invocation to re-derive, and silently keeping the old name is the + # kind of helpfulness that reads as a bug. + [ "$cmd" = "init" ] && echo "hostshift: keeping the slug you chose, $slug." \ + "\`--slug NAME\` to change it, \`--slug-from-branch\` to go back to the branch." >&2 + fi +fi + if [ -z "$slug" ] && [ "$cmd" != "copy-db" ]; then branch="$(git symbolic-ref --short HEAD 2>/dev/null || true)" # Detached HEAD is ordinary — a rebase, a bisect, a checked-out tag — and it @@ -248,13 +270,28 @@ elif [ -n "$linked" ]; then esac fi +# ddev_name reads `name:` out of a DDEV config file, the way DDEV reads it. +# +# `sed -n 's/^name: *//p'` alone kept the quotes on `name: "acme"` and the +# comment on `name: acme # local`, and a wrong project name is silent in both +# places it is used: `docker inspect ddev-"acme"-web` finds nothing, which reads +# as "the rival is not running" and lets a real hostname collision through, and +# copy-db streams from a db container that does not exist. CRLF for the same +# reason — a config.yaml edited on Windows leaves \r on the value. +ddev_name() { + [ -f "$1" ] || return 0 + sed -n 's/^name:[[:space:]]*//p' "$1" | sed -n 1p | tr -d '\r' \ + | sed -e 's/[[:space:]]*#.*$//' -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'\$/\1/" \ + | sed 's/[[:space:]]*$//' +} + if [ "$cmd" = "copy-db" ]; then [ -n "$parent" ] || { echo "hostshift: no parent checkout to copy from — this is not a linked worktree" >&2 echo " of a different DDEV project. Use \`ddev import-db\` or a fresh pull." >&2 exit 2; } from="$(basename "$parent")" - [ -f "$parent/.ddev/config.yaml" ] && n="$(sed -n 's/^name: *//p' "$parent/.ddev/config.yaml" | head -1)" && [ -n "$n" ] && from="$n" + n="$(ddev_name "$parent/.ddev/config.yaml")" && [ -n "$n" ] && from="$n" # Sharing the parent's database and copying it are different decisions, and # doing both is worse than pointless: the dump goes into this worktree's own @@ -456,7 +493,7 @@ for other in ../*/.ddev/.env; do oname="$(basename "$odir")" for oc in "$odir/.ddev/config.yaml" "$odir"/.ddev/config.*.yaml; do [ -f "$oc" ] || continue - n="$(sed -n 's/^name: *//p' "$oc" | sed -n 1p)" + n="$(ddev_name "$oc")" [ -n "$n" ] && oname="$n" done if [ "$(docker inspect "ddev-$oname-web" --format '{{.State.Running}}' 2>/dev/null || true)" = "true" ]; then @@ -574,7 +611,19 @@ args="${hsmap:---slug $slug}" # #ddev-silent-no-warn, because DDEV otherwise prints a four-line "Custom # configuration detected" block naming this file on every single start. It is a # legal dotenv comment and DDEV documents the marker in that very message. -env_out="#ddev-silent-no-warn +# HOSTSHIFT_SLUG is written only when the slug was *chosen* rather than derived +# from the branch, and it is what makes --slug stick. +# +# `check` re-derives the slug from the current branch, which is right for a slug +# that came from a branch and wrong for one the developer picked: `init --slug +# review-42` on branch `main` deployed correctly, served correctly, and then +# failed the post-start hook on every single start, because check compared the +# deployed `--slug review-42` against a freshly derived `--slug main`. Worse, the +# advice it printed — `ddev hostshift init` — silently reverts to the branch +# slug when run as printed, which in the case that motivates --slug at all is +# back onto the colliding name the collision warning told you to move off. +env_out="#ddev-silent-no-warn${slug_explicit:+ +HOSTSHIFT_SLUG=$slug} HOSTSHIFT_ARGS=$args HOSTSHIFT_VARIANTS=$variants HOSTSHIFT_WEB_HOSTS=$web" @@ -783,7 +832,11 @@ if [ "$cmd" = "loopback" ]; then --canonical-hosts | sort -u)" # Off-box means "not one this DDEV project answers to". The project's own TLD # is the tail of its primary hostname, which `hostshift hosts` prints first. - tld="$(printf '%s\n' "$hosts" | head -1 | cut -d. -f2-)" + # sed, not `| head -1`: this script runs under `set -o pipefail`, and a head + # that exits after one line SIGPIPEs whatever is still writing. The same + # pattern is called out twice elsewhere in this file; $hosts is short enough + # that it has never fired, which is exactly why it would be a surprise. + tld="$(printf '%s\n' "$hosts" | sed -n 1p | cut -d. -f2-)" offbox="$(printf '%s\n' "$hosts_out" | grep -v "\.${tld:-ddev.site}\$" || true)" if [ -z "$offbox" ]; then echo "hostshift: every canonical hostname is a local DDEV one, so the" >&2 diff --git a/ddev/install.yaml b/ddev/install.yaml index bf98a22..ff66b83 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -121,6 +121,34 @@ removal_actions: [ -n "$common" ] || exit 0 ex="$common/info/exclude" [ -f "$ex" ] || exit 0 + # Only when no other checkout of this repository still has the add-on. + # + # info/exclude is shared by the repository and all its linked worktrees — + # which is the property that makes one write cover them all on install, and + # exactly what made removal reach too far. Removing the add-on from one + # worktree un-ignored .ddev/config.hostshift.yaml, the host command and + # .ddev/.env in the *parent and every other worktree*, where it is still + # installed and running, with nothing printed. The next `git add -A` over + # there commits the add-on's files and .ddev/.env, which is where DDEV + # documents putting project env vars, credentials included. The install is + # idempotent-guarded, so it does not come back on its own either. + # + # `git worktree list --porcelain` names every checkout, this one included; + # the block stays while any *other* one still has the command installed. + others=0 + here="$(git rev-parse --show-toplevel 2>/dev/null || true)" + while read -r k v; do + [ "$k" = "worktree" ] || continue + [ "$v" = "$here" ] && continue + [ -f "$v/.ddev/commands/host/hostshift" ] && others=$((others + 1)) || true + done </dev/null || true) + EOT + if [ "$others" -gt 0 ]; then + echo "note: $others other checkout(s) of this repository still have hostshift" >&2 + echo "installed, so .git/info/exclude keeps ignoring its files for them." >&2 + exit 0 + fi tmp="$(mktemp)" grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' -e '.ddev/.env' \ "$ex" > "$tmp" && cat "$tmp" > "$ex" diff --git a/internal/proxy/multipart.go b/internal/proxy/multipart.go index 3e732ee..58354af 100644 --- a/internal/proxy/multipart.go +++ b/internal/proxy/multipart.go @@ -168,12 +168,21 @@ func rewritablePart(headers []byte) bool { } if _, params, err := mime.ParseMediaType(strings.TrimSpace(disposition)); err == nil { // Presence, not non-emptiness. filename="" still means a file part; - // testing the value let an empty file input through. + // testing the value let an empty file input through. ParseMediaType + // folds RFC 2231's filename*= into "filename" itself. if _, isFile := params["filename"]; isFile { return false } - } else if strings.Contains(strings.ToLower(disposition), "filename=") { - return false + } else { + // The parse failed, so this is a hand-rolled scan over a disposition + // that is already malformed — which is exactly when it must be + // pessimistic. "filename=" alone missed RFC 2231's extended form, + // `filename*=UTF-8''photo.jpg`, so a file part with a malformed + // disposition had its bytes rewritten. + lower := strings.ToLower(disposition) + if strings.Contains(lower, "filename=") || strings.Contains(lower, "filename*=") { + return false + } } if ctype == "" { return true diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index ad202e0..30b621b 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -301,10 +301,18 @@ func (w *HTML) decodeEntityLeak(base int, v []byte) []byte { return v } out, events := w.m.Rewrite(dec, SurfaceHTMLEntity, w.stats.Explain()) + // Recorded before the equality check, not after. Returning early skipped the + // *skips*, so a decoded value carrying an unanchored near-miss — + // `https://www.example.fi.evil/x` — counted zero candidates and zero + // skips, where the raw spelling of the same thing counts both. That is the + // state matcher.go's emit comment says was fixed: candidates == rewrites + // reads as "nothing was skipped", on the one surface where a non-zero count + // is documented as the signal that content is storing origins in a form + // §5.3 does not model. + w.stats.Record(SurfaceHTMLEntity, base, events) if bytes.Equal(out, dec) { return v } - w.stats.Record(SurfaceHTMLEntity, base, events) return out } diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go index 6f40558..937762a 100644 --- a/internal/rewrite/urlobf.go +++ b/internal/rewrite/urlobf.go @@ -205,12 +205,14 @@ func (w *HTML) normaliseURLLeak(base int, v []byte) []byte { return v } out, events := w.m.Rewrite(norm, SurfaceHTMLObfuscated, w.stats.Explain()) + // Before the equality check, so a near-miss in the normalised form is + // counted as a skip rather than vanishing — see decodeEntityLeak. + w.stats.Record(SurfaceHTMLObfuscated, base, events) if bytes.Equal(out, norm) { // The normalised form holds no origin either. Leave the value exactly as // its author wrote it — this pass exists to stop a leak, not to tidy // anyone's markup. return v } - w.stats.Record(SurfaceHTMLObfuscated, base, events) return out } diff --git a/test/addon-command.sh b/test/addon-command.sh index 7178a5a..99083a2 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -262,6 +262,47 @@ contains "an unparseable parent config names the conflict markers" \ "conflict markers" "$out" cp "$work/parent-config.bak" "$main/.ddev/config.yaml" +# A slug the developer chose must survive `check` and a bare `init`. +# +# check re-derived the slug from the branch whenever there was one, so +# `init --slug review-42` on branch `main` deployed correctly, served correctly, +# and then failed the post-start hook on every start — comparing the deployed +# `--slug review-42` against a freshly derived `--slug main`. The advice it +# printed, `ddev hostshift init`, silently reverted the choice when run as +# written, which in the case that motivates --slug at all lands back on the +# colliding name the collision warning told you to move off. +rm -f "$wt/.ddev/.env" +(cd "$wt" && "$cmd" init --slug picked >/dev/null 2>&1) || fail "init exited non-zero" "" +contains "an explicit slug is recorded" "HOSTSHIFT_SLUG=picked" "$(cat "$wt/.ddev/.env")" +out="$(cd "$wt" && "$cmd" env 2>&1 || true)" +contains "and a later run with no --slug keeps it" \ + "HOSTSHIFT_VARIANTS=picked--acme.ddev.site" "$out" +# No Docker here, so check gets as far as the missing container and stops. What +# matters is that it does not get there via "out of date" — which is what it +# said before, on a deployment that was correct. +out="$(cd "$wt" && "$cmd" check 2>&1 || true)" +case "$out" in + *"out of date"*) fail "and check does not call it stale" "$out" ;; + *) pass "and check does not call it stale" ;; +esac + +# ...and there is a way back, or the only route to branch-derived naming is +# hand-editing .ddev/.env. +(cd "$wt" && "$cmd" init --slug-from-branch >/dev/null 2>&1) || fail "init exited non-zero" "" +case "$(cat "$wt/.ddev/.env")" in + *HOSTSHIFT_SLUG=*) fail "--slug-from-branch forgets the recorded slug" "$(cat "$wt/.ddev/.env")" ;; + *) pass "--slug-from-branch forgets the recorded slug" ;; +esac + +# ...while a slug that came from the branch still tracks the branch. +out="$(cd "$wt" && "$cmd" env 2>&1 || true)" +case "$out" in + *"picked--acme"*) fail "a branch-derived slug still follows the branch" "$out" ;; + *) pass "a branch-derived slug still follows the branch" ;; +esac +rm -f "$wt/.ddev/.env" +(cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || fail "init exited non-zero" "" + out="$(cd "$wt" && "$cmd" env --slug other 2>/dev/null || true)" contains "a second slug does not compound with the first" \ "HOSTSHIFT_VARIANTS=other--acme.ddev.site,other--nat.acme.ddev.site" "$out" diff --git a/test/integration-ddev.sh b/test/integration-ddev.sh index e07a4e5..f09872f 100755 --- a/test/integration-ddev.sh +++ b/test/integration-ddev.sh @@ -340,6 +340,30 @@ else pass "but the repo's own hostshift.yaml still is not" fi rm -f "$m4/hostshift.yaml" + +# Removing the add-on from one worktree must not un-ignore it in the others. +# +# info/exclude is shared by the repository and all its linked worktrees, which +# is the property that makes one write cover them all on install — and is +# exactly what made removal reach too far. `ddev add-on remove` in a worktree +# un-ignored the add-on's files and .ddev/.env in the parent and in every other +# worktree, where it is still installed and running, and said nothing. The next +# `git add -A` over there commits them, .ddev/.env included. +rmwt="$work/${tag}-rmwt" +git -C "$m4" worktree add -q -b "${tag}-rm" "$rmwt" >/dev/null 2>&1 +mkdir -p "$rmwt/.ddev" +printf 'name: %s-rm\ntype: php\n' "$tag" > "$rmwt/.ddev/config.yaml" +(cd "$rmwt" && ddev add-on get "$repo/ddev" >/dev/null 2>&1) || true +projects+=("$rmwt") +rm_out="$(cd "$rmwt" && ddev add-on remove hostshift 2>&1)" || true +if git -C "$m4" check-ignore -q .ddev/.env; then + pass "removing it from one worktree leaves the others ignored" +else + fail "removing it from one worktree leaves the others ignored" "$rm_out" +fi +git -C "$m4" worktree remove --force "$rmwt" >/dev/null 2>&1 || true +git -C "$m4" branch -D "${tag}-rm" >/dev/null 2>&1 || true + case "$install_out" in *"predates this add-on"*) fail "a fresh install is not told its command is stale" "$install_out" ;; diff --git a/test/integration-proxy-ddev.sh b/test/integration-proxy-ddev.sh index 329bb1b..5daa57a 100755 --- a/test/integration-proxy-ddev.sh +++ b/test/integration-proxy-ddev.sh @@ -396,7 +396,11 @@ if (cd "$wt" && ddev hostshift check --slug wt-z >/dev/null 2>&1); then else pass "check catches a slug the running proxy does not answer on" fi -(cd "$wt" && ddev hostshift init >/dev/null 2>&1) || true +# --slug-from-branch, because a slug passed to `init` is now recorded and sticks: +# a bare `init` that silently reverted it was failing the post-start hook forever +# on every deliberately-slugged project, and telling the developer to run the +# command that undid their choice. +(cd "$wt" && ddev hostshift init --slug-from-branch >/dev/null 2>&1) || true # The compose service is `restart: "no"`, so a proxy that dies stays dead while # `ddev start` has already returned success — which is how an image that parses From cd4d96461f31373d50e023df4cc2e080a9cc78d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 03:09:13 -0300 Subject: [PATCH 007/124] Reject over-long labels, and say what removal leaves behind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A 30-character slug on a 32-character canonical label derives a 67-character DNS label. Nothing rejected it: `check` reported the map injective and anchored and exited 0, and it failed later as a certificate mkcert will not issue a SAN for — which presents as a browser warning rather than as a naming mistake. RFC 1035 caps a label at 63. The hint now names the real reason, since "use only letters, digits and hyphens" is unhelpful advice for a slug that is already only letters, digits and hyphens and is merely too long. `ddev add-on remove` mentioned wp-cli.local.yml and nothing else, but it also leaves docker-compose.hostshift-loopback.yaml — which carries no #ddev-generated marker, deliberately, since `ddev hostshift loopback` writes it per project — and that file goes on pinning production hostnames to 127.0.0.1 inside web after the thing that needed it is gone. It lists what is actually there now, and says nothing extra when there is nothing. --- ddev/install.yaml | 20 ++++++++++++++++++- internal/config/config.go | 42 +++++++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/ddev/install.yaml b/ddev/install.yaml index ff66b83..c331fe4 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -155,4 +155,22 @@ removal_actions: rm -f "$tmp" - | #ddev-nodisplay - echo "hostshift removed. wp-cli.local.yml, if you generated one, is yours to delete." + # Everything DDEV does not take away itself. docker-compose.hostshift-loopback.yaml + # carries no #ddev-generated marker — deliberately, since `ddev hostshift + # loopback` writes it per project — so `add-on remove` leaves it, and a + # loopback file left behind goes on pinning production hostnames to 127.0.0.1 + # inside web after the thing that needed it is gone. The HOSTSHIFT_ block in + # .ddev/.env is inert once the compose service is gone, but it is still this + # add-on's litter in the developer's project file. + left="" + [ -f .ddev/docker-compose.hostshift-loopback.yaml ] && left="$left + .ddev/docker-compose.hostshift-loopback.yaml — pins production hostnames to 127.0.0.1 in web" + grep -qs '^HOSTSHIFT_' .ddev/.env && left="$left + the HOSTSHIFT_ lines in .ddev/.env" + [ -f wp-cli.local.yml ] && left="$left + wp-cli.local.yml" + if [ -n "$left" ]; then + echo "hostshift removed. These are yours to delete:$left" + else + echo "hostshift removed." + fi diff --git a/internal/config/config.go b/internal/config/config.go index 4c4ea74..5363191 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -363,30 +363,56 @@ func deriveVariant(explicit, base, canonical, pattern, slug, name string) (origi name, slug, host, err) } if v.Host != strings.ToLower(host) || !validHostLabels(v.Host) { + // Name the actual problem. "use only letters, digits and hyphens" is + // unhelpful advice for a slug that is already only letters, digits and + // hyphens and is simply too long — and length is the reachable case, + // since the slug prefixes the leftmost label rather than replacing it. + hint := "slugs become hostname labels, so use only letters, digits and hyphens" + if l := longestLabel(strings.ToLower(host)); l > 63 { + hint = fmt.Sprintf( + "that host has a %d-character label and DNS allows 63 — "+ + "the slug prefixes the leftmost label, so shorten the slug", l) + } return origin.Origin{}, fmt.Errorf( - "site %q: --slug %q derives %q, which is not a usable hostname\n"+ - "slugs become hostname labels, so use only letters, digits and hyphens", - name, slug, host) + "site %q: --slug %q derives %q, which is not a usable hostname\n%s", + name, slug, host, hint) } return v, nil } -// validHostLabels checks each dot-separated label is non-empty and starts and -// ends alphanumeric. A slug ending in "." derives "wt-a.--acmecorp.ddev.site", -// whose second label starts with a hyphen — DNS-invalid, and it resolves or not -// depending on the resolver rather than failing at startup where it belongs. +// validHostLabels checks each dot-separated label is non-empty, no longer than +// RFC 1035's 63 octets, and starts and ends alphanumeric. +// +// A slug ending in "." derives "wt-a.--acmecorp.ddev.site", whose second label +// starts with a hyphen — DNS-invalid, and it resolves or not depending on the +// resolver rather than failing at startup where it belongs. The length is the +// same class: the variant prefixes the leftmost label, so a 30-character slug on +// a 32-character label derives a 64-octet one. Nothing rejected it — the map +// reported "injective and anchored" and exit 0 — and it fails later, as a +// certificate mkcert will not issue a SAN for, which presents as a browser +// warning rather than as a naming mistake. func validHostLabels(h string) bool { alnum := func(c byte) bool { return c >= 'a' && c <= 'z' || c >= '0' && c <= '9' } for _, l := range strings.Split(h, ".") { - if l == "" || !alnum(l[0]) || !alnum(l[len(l)-1]) { + if l == "" || len(l) > 63 || !alnum(l[0]) || !alnum(l[len(l)-1]) { return false } } return true } +func longestLabel(h string) int { + n := 0 + for _, l := range strings.Split(h, ".") { + if len(l) > n { + n = len(l) + } + } + return n +} + func portSuffix(o origin.Origin) string { if o.Port == "" { return "" From 3489673e9d4aad0ea3a790d820562d2aea0d94d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 04:35:14 -0300 Subject: [PATCH 008/124] Locate the authority instead of guessing where it starts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round ten. The URL-parser pass I added last round closed the five shapes it enumerated and left five more, which is what a rule of the form "a run of two or more slashes" earns you — it is a guess at where the authority begins. This one locates it the way the parser does. Every shape below was verified against ada, the parser Chrome ships, to resolve to the canonical origin, and every one was served unrewritten and uncounted: http:www.example.fi/x a scheme differing from the document's needs http:/www.example.fi/x no slashes at all — special-authority-ignore- HTTP:www.example.fi/x slashes skips a run of length zero as happily as one of length three https:\\www.example.fi/x leading C0 and spaces are stripped first, <0x01>https:\\www.example.fi/x which moves the anchor every positional rule depends on https://user@www.example.fi/x userinfo pushes the host off the separator https://www.ex%61mple.fi/x the host is percent-decoded before lookup https://hämeen.fi/x an IDN host behind a named reference The first is not an attacker shape: M0 measured one fleet host appearing 165 times over http and zero over https, and matcher.go builds http:// patterns for every canonical for exactly that reason — so the population the matcher was widened for is the population that reached the browser here. The pass now replaces only the host's byte range. Scheme, separator, userinfo, port, path, query and fragment are copied through as written, so it cannot damage a value it does not need to fix, and a same-scheme reference with fewer than two slashes — a relative path, which never reaches production — is left alone. `xlink:href` joins the attribute list; browsers resolve and dereference it for and . The two catchers also did not compose, though html.go claimed they did: decodeEntityLeak returned the value *as written* whenever the decoded form did not itself rewrite, so the URL pass never saw a decoded byte and `https://www.example .fi/x` — the exact case the comment described — went out untouched. They now share one decode. parseURLRef accepts non-ASCII. An IDN host is built from it, §5.5 calls IDN real for .fi clients, and a UTF-8 letter spliced between attribute quotes is inert — which is the only thing the structural exclusions protect. Named references go through the HTML5 table when they carry a semicolon, which is unambiguous; the legacy no-semicolon form that would turn `©=1` into `©=1` is still declined, and that is what the short hand-written table exists for. Then four in the add-on, three of them mine from the last two commits: * The removal guard probed `git worktree list`'s *recorded* path, which a plain `mv` leaves stale — git marks it prunable and says nothing, while ddev and hostshift both work fine from the new location. It counted the worktree as gone and stripped the shared exclude block while the add-on was still installed there. A stale ignore entry is harmless and an un-ignored .ddev/.env is not, so a path that is not there now counts as still installed. * The "what removal leaves behind" list could never fire: removal_actions run with cwd `/.ddev`, so every path in it was one level too deep. The file documents that exact cwd sixty lines earlier. * `grep -vxF … > tmp && cat` never ran the cat when grep selected no lines — which is precisely when info/exclude holds nothing but this block, the shape the install itself creates. The block survived removal and went on ignoring .ddev/.env in a project without the add-on. * v0.1.0's init wrote `HOSTSHIFT_SLUG=` unconditionally, for branch-derived slugs as much as chosen ones, so reading that name as a deliberate choice pinned the naming on every project that ever ran it — and announced "keeping the slug you chose" for a name nobody chose, on a branch that may no longer exist. The marker is now a name v0.1.0 never wrote. And three more found the same round: * `check` refuses a variant hostname that is not under any domain this DDEV project registers. A production-canonical site with no `base:` derives the variant from the canonical — `local--www.example.com` — which has no DDEV TLD, no SAN and no DNS, and on a client domain with wildcard DNS resolves to the production server. Loopback containment does not help: it pins the canonical names, not this one. * A worktree whose branch predates the parent's committed hostshift.yaml built its map from the parent's ddev.site hostnames — the wrong side, so nothing is rewritten and every link points at the live site — and check called it injective and anchored. Adopting production-canonical is exactly when worktrees exist on older branches. * copy-db's guard missed every quoted compose spelling, and `- "KEY=value"` is the form Compose's own documentation uses. An unreadable sibling .ddev/.env aborted every subcommand under `set -e`, the post-start hook included, with a bare `sed: Permission denied`. `--slug=` was accepted where `--slug ""` was refused, pinning a branch-derived name as though it had been chosen. wp-cli.local.yml is now ignored — its own header says not to commit it, the README calls it gitignored, and nothing ignored it. `--help` lists the flags that exist. --- ddev/commands/host/hostshift | 141 ++++++++++- ddev/install.yaml | 46 +++- internal/rewrite/audit_m3_test.go | 13 +- internal/rewrite/entity.go | 68 ++++-- internal/rewrite/html.go | 42 ++-- internal/rewrite/urlobf.go | 377 +++++++++++++++++++----------- internal/rewrite/urlobf_test.go | 45 ++++ test/addon-command.sh | 51 +++- 8 files changed, 597 insertions(+), 186 deletions(-) diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index 8f9499d..77c7f53 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -35,6 +35,29 @@ set -euo pipefail [ -n "${DDEV_APPROOT:-}" ] && cd "$DDEV_APPROOT" cmd="${1:-init}"; shift || true +# Every flag, because --slug-from-branch is the documented way back from a +# pinned slug and appeared in neither the usage line nor --help — its only +# mention anywhere was one line init prints, which is also the only mention of +# the pinning itself. +usage() { + cat <<'USAGE' +usage: ddev hostshift [flags] + + init write .ddev/.env and restart to pick it up + check is what is deployed still what this checkout resolves to? + copy-db replace this worktree's database with the parent's + loopback pin the canonical hostnames to 127.0.0.1 inside web + env print what init would write + wp-cli write wp-cli.local.yml + + --slug NAME name this worktree's hostnames; recorded, and it sticks + --slug-from-branch forget a recorded slug and follow the branch again + --dry-run, -n say what would happen, write nothing + --force copy-db: overwrite a database that has tables + --quiet, -q say nothing when there is nothing to check +USAGE +} + slug="" slug_explicit="" forget_slug="" @@ -46,7 +69,14 @@ claimed="" while [ $# -gt 0 ]; do case "$1" in --slug) slug="${2:?--slug needs a value}"; slug_explicit=1; shift 2 ;; - --slug=*) slug="${1#*=}"; slug_explicit=1; shift ;; + # Both spellings agree. `--slug ""` was refused and `--slug=` was not, so + # the empty value left slug_explicit set, the branch derivation filled the + # slug in, and a *branch-derived* name was written to .ddev/.env as a chosen + # one — pinning the naming forever, with nothing said and --slug-from-branch + # the only way back. + --slug=*) slug="${1#*=}" + [ -n "$slug" ] || { echo "--slug needs a value" >&2; exit 2; } + slug_explicit=1; shift ;; # The way back. A recorded slug outranks the branch — that is the point of # recording it — so without this the only way to return to branch-derived # naming is to hand-edit .ddev/.env. @@ -57,16 +87,15 @@ while [ $# -gt 0 ]; do # Silently discarding an unknown flag is how `--dry-run` came to write the # files it was passed to prevent. *) echo "unknown argument: $1" >&2 - echo "usage: ddev hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME]" >&2 + usage >&2 exit 2 ;; esac done case "$cmd" in init|check|copy-db|loopback|env|wp-cli) ;; - -h|--help|help) - echo "usage: ddev hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME]"; exit 0 ;; - *) echo "usage: ddev hostshift [init|check|copy-db|loopback|env|wp-cli] [--slug NAME]" >&2; exit 2 ;; + -h|--help|help) usage; exit 0 ;; + *) usage >&2; exit 2 ;; esac # The post-start hook runs `check` on every start. Between `ddev add-on get` and @@ -130,7 +159,15 @@ command -v hostshift >/dev/null || { # the branch and called a correct deployment stale forever, and `init` run as the # advice said reverted the choice. if [ -z "$slug" ] && [ -z "$forget_slug" ] && [ "$cmd" != "copy-db" ]; then - slug="$(sed -n '/^HOSTSHIFT_SLUG=/{s///p;q;}' .ddev/.env 2>/dev/null || true)" + # HOSTSHIFT_SLUG_CHOSEN, not HOSTSHIFT_SLUG. v0.1.0's init wrote + # `HOSTSHIFT_SLUG=$slug` *unconditionally* — for a slug derived from the + # branch exactly as for one that was passed — so reading that name as a + # deliberate choice pinned the naming on every project that ever ran v0.1.0, + # which by this add-on's own history is where the longest-running pilots are. + # It then announced "keeping the slug you chose" for a name nobody chose, on a + # branch that may no longer exist. A name v0.1.0 never wrote cannot be + # misread. + slug="$(sed -n '/^HOSTSHIFT_SLUG_CHOSEN=/{s///p;q;}' .ddev/.env 2>/dev/null || true)" if [ -n "$slug" ]; then slug_explicit=1 # Said out loud on init, because init is where a developer would expect a @@ -280,9 +317,15 @@ fi # reason — a config.yaml edited on Windows leaves \r on the value. ddev_name() { [ -f "$1" ] || return 0 - sed -n 's/^name:[[:space:]]*//p' "$1" | sed -n 1p | tr -d '\r' \ - | sed -e 's/[[:space:]]*#.*$//' -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'\$/\1/" \ - | sed 's/[[:space:]]*$//' + v="$(sed -n 's/^name:[[:space:]]*//p' "$1" | sed -n 1p | tr -d '\r')" + # A quote delimits the value, so a '#' inside one is not a comment. Stripping + # comments first turned `name: "a#b"` into `"a`; stripping them after + # unquoting turned it into `a`. Only the unquoted form has a comment to strip. + case "$v" in + '"'*) printf '%s\n' "$v" | sed 's/^"\([^"]*\)".*$/\1/' ;; + "'"*) printf '%s\n' "$v" | sed "s/^'\([^']*\)'.*\$/\1/" ;; + *) printf '%s\n' "$v" | sed -e 's/[[:space:]]*#.*$//' -e 's/[[:space:]]*$//' ;; + esac } if [ "$cmd" = "copy-db" ]; then @@ -343,7 +386,13 @@ if [ "$cmd" = "copy-db" ]; then # A shell assignment, a dotenv line, a YAML mapping value or a compose list # item — all of them are NAME, a separator, then the value. What none of them # is, is a comment. - assignment="^[[:space:]]*-?[[:space:]]*(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*[[:space:]]*[:=][^#]*ddev-$fromre-db" + # The optional quote is not decoration. Compose's own documentation writes + # environment entries as `- "KEY=value"`, and requiring [A-Za-z_] straight + # after the `- ` meant every quoted spelling walked past the guard: the dump + # then goes into this worktree's idle db container while the application + # keeps reading the parent's, and copy-db prints "copied" and exits 0. + q='["'"'"']?' + assignment="^[[:space:]]*-?[[:space:]]*$q(export[[:space:]]+)?[A-Za-z_][A-Za-z0-9_]*$q[[:space:]]*[:=][^#]*ddev-$fromre-db" if [ -n "$asked" ]; then # The container already answered for its own environment; .env is the one # thing it could not see. @@ -421,6 +470,28 @@ fi # other environments, or production ones the database was never search-replaced # away from. Passing --from would override it. map_args=() +# The parent declares canonical hostnames this worktree cannot see. +# +# `hostshift hosts -C

` reads the parent's *DDEV* config and nothing else, +# so a parent that has adopted a committed hostshift.yaml — the README's +# production-canonical path — is invisible from a worktree whose branch predates +# that commit. The map then rewrote the parent's ddev.site hostnames, which is +# the wrong side entirely: the shared database holds the canonical names, so +# nothing is rewritten and every link on the preview points at the client's live +# site, while `check` reports the map injective and anchored. +# +# Adopting production-canonical is exactly the moment worktrees exist on older +# branches, so this is the common case rather than the odd one. +parent_declares="" +if [ ! -f hostshift.yaml ] && [ -n "$parent" ] && [ -f "$parent/hostshift.yaml" ]; then + parent_declares="$parent/hostshift.yaml" + echo "hostshift: warning: $parent_declares declares the hostnames its database" >&2 + echo " holds, and this worktree's branch predates it — so the map below was" >&2 + echo " built from that checkout's DDEV hostnames instead, and would rewrite" >&2 + echo " nothing. Every link would point at the canonical site." >&2 + echo " Merge the branch that adds hostshift.yaml, or copy it here." >&2 +fi + if [ ! -f hostshift.yaml ] && [ -n "$unreadable_parent" ]; then # A linked worktree whose parent declares nothing readable. Falling through # silently is the failure this whole path exists to prevent: the map gets built @@ -463,7 +534,14 @@ for other in ../*/.ddev/.env; do # exits after one line SIGPIPEs whatever is still writing, failing the whole # pipeline. It is timing-dependent, so it passes on a fast small write and # fails on a slow one — sed that stops at the first match instead. - ovar="$(sed -n '/^HOSTSHIFT_VARIANTS=/{s///p;q;}' "$other")" + # `|| true`, and stderr discarded: this is the one sed in the file reading a + # *foreign* file, and a sibling project whose .ddev/.env the current user + # cannot read — the file this codebase says holds credentials — made a failed + # command substitution abort everything under `set -e`. That took out init, + # check, env, loopback and wp-cli at once, including the check the post-start + # hook runs on every start, with a bare `sed: Permission denied` and no + # hostshift diagnostic. An unreadable sibling is not this project's problem. + ovar="$(sed -n '/^HOSTSHIFT_VARIANTS=/{s///p;q;}' "$other" 2>/dev/null || true)" [ -n "$ovar" ] || continue for v in $(printf '%s' "$variants" | tr ',' ' '); do case ",$ovar," in *",$v,"*) @@ -623,7 +701,7 @@ args="${hsmap:---slug $slug}" # slug when run as printed, which in the case that motivates --slug at all is # back onto the colliding name the collision warning told you to move off. env_out="#ddev-silent-no-warn${slug_explicit:+ -HOSTSHIFT_SLUG=$slug} +HOSTSHIFT_SLUG_CHOSEN=$slug} HOSTSHIFT_ARGS=$args HOSTSHIFT_VARIANTS=$variants HOSTSHIFT_WEB_HOSTS=$web" @@ -740,6 +818,45 @@ if [ "$cmd" = "check" ]; then exit 2 ;; esac + # Is every variant a name this machine will actually answer to? + # + # A production-canonical `hostshift.yaml` with no `base:` derives the variant + # from the *canonical*, so `canonical: https://www.example.com` becomes + # `local--www.example.com` — no DDEV TLD, no mkcert SAN, no DNS. Nothing + # rejected it and `check` printed "hostshift is serving" for a hostname that + # cannot resolve. Worse on a client domain with wildcard DNS, which is not + # rare: the name resolves to the *production server*, so the developer + # "reviewing their worktree" is looking at the live site, and loopback + # containment does not help — it pins the canonical names, not this one. + # + # DDEV registers hostnames under the project's own TLD, so a variant outside + # every one of this project's suffixes is a name DDEV never registered. + outside="" + for v in $(printf '%s' "$variants" | tr ',' ' '); do + ok="" + while IFS= read -r h; do + [ -n "$h" ] || continue + suffix=".${h#*.}" + case "$v" in *"$suffix") ok=1; break ;; esac + done <<<"$hosts" + [ -n "$ok" ] || outside="${outside:+$outside }$v" + done + if [ -n "$outside" ]; then + echo "hostshift: these variant hostnames are not under any domain this DDEV" >&2 + echo " project registers, so nothing local answers to them:" >&2 + printf ' %s\n' $outside >&2 + echo " On a client domain with wildcard DNS they resolve to *production*." >&2 + echo " Declare \`base:\` on the site, pointing at this project's own" >&2 + echo " hostname, so the variant is derived from a name DDEV serves." >&2 + exit 2 + fi + + if [ -n "$parent_declares" ]; then + echo "hostshift: refusing to call this healthy — see the warning above." >&2 + echo " The map does not name the hostnames the database holds." >&2 + exit 2 + fi + if [ -n "$claimed" ]; then echo "hostshift: refusing to call this healthy — $claimed is claimed by" >&2 echo " another project too, and the router serves whichever it prefers." >&2 diff --git a/ddev/install.yaml b/ddev/install.yaml index c331fe4..be641db 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -44,6 +44,11 @@ pre_install_actions: echo '# hostshift (ddev add-on)' echo '.ddev/**/*hostshift*' echo '.ddev/.env' + # `ddev hostshift wp-cli` writes this, its own header says not to commit + # it, and the README calls it gitignored — and nothing ignored it. Under + # production-canonical it carries the *production* URL, so committing it + # points every teammate's `ddev wp` at the live site. + echo 'wp-cli.local.yml' } >> "$ex" project_files: @@ -135,12 +140,28 @@ removal_actions: # # `git worktree list --porcelain` names every checkout, this one included; # the block stays while any *other* one still has the command installed. + # A path that is not there any more counts as still installed. + # + # `git worktree list` reports the *recorded* path, and a plain `mv` of a + # worktree directory leaves that stale — git marks the entry prunable and + # says nothing, while ddev and hostshift both go on working from the new + # location. Probing the stale path found no command, counted the worktree as + # gone, and stripped the block while it was still installed and running: + # .ddev/.env — DDEV's documented home for credentials — became committable + # by the next `git add -A`, which is the exact harm this guard exists to + # prevent. The same for a worktree on an unmounted volume, or one restored + # to a different path. + # + # So the asymmetry decides it: a stale ignore entry is harmless, and an + # un-ignored .ddev/.env is not. When the answer is unknowable, keep it. others=0 here="$(git rev-parse --show-toplevel 2>/dev/null || true)" while read -r k v; do [ "$k" = "worktree" ] || continue [ "$v" = "$here" ] && continue - [ -f "$v/.ddev/commands/host/hostshift" ] && others=$((others + 1)) || true + if [ ! -d "$v" ] || [ -f "$v/.ddev/commands/host/hostshift" ]; then + others=$((others + 1)) + fi done </dev/null || true) EOT @@ -149,9 +170,16 @@ removal_actions: echo "installed, so .git/info/exclude keeps ignoring its files for them." >&2 exit 0 fi + # `|| true`: grep exits 1 when it selects *no* lines, which is exactly the + # case where info/exclude holds nothing but this block — the shape the + # install itself creates when the file did not exist before. `cat` then + # never ran, the block survived removal, and it went on ignoring .ddev/.env + # in a project that no longer has the add-on. `set -e` did not fire either, + # because grep is the non-final command of an && list. tmp="$(mktemp)" - grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' -e '.ddev/.env' \ - "$ex" > "$tmp" && cat "$tmp" > "$ex" + grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' \ + -e '.ddev/.env' -e 'wp-cli.local.yml' "$ex" > "$tmp" || true + cat "$tmp" > "$ex" rm -f "$tmp" - | #ddev-nodisplay @@ -162,13 +190,17 @@ removal_actions: # inside web after the thing that needed it is gone. The HOSTSHIFT_ block in # .ddev/.env is inert once the compose service is gone, but it is still this # add-on's litter in the developer's project file. + # Paths relative to .ddev/, which is the cwd DDEV runs these actions in — + # documented at post_install_actions above, and got wrong here, which made + # this whole list dead on every removal. `..` is the project root. left="" - [ -f .ddev/docker-compose.hostshift-loopback.yaml ] && left="$left + [ -f docker-compose.hostshift-loopback.yaml ] && left="$left .ddev/docker-compose.hostshift-loopback.yaml — pins production hostnames to 127.0.0.1 in web" - grep -qs '^HOSTSHIFT_' .ddev/.env && left="$left + grep -qs '^HOSTSHIFT_' .env && left="$left the HOSTSHIFT_ lines in .ddev/.env" - [ -f wp-cli.local.yml ] && left="$left - wp-cli.local.yml" + [ -f ../wp-cli.local.yml ] && left="$left + wp-cli.local.yml — it carries the canonical URL \`ddev wp\` defaults to" + true if [ -n "$left" ]; then echo "hostshift removed. These are yours to delete:$left" else diff --git a/internal/rewrite/audit_m3_test.go b/internal/rewrite/audit_m3_test.go index 134e49e..4c035cc 100644 --- a/internal/rewrite/audit_m3_test.go +++ b/internal/rewrite/audit_m3_test.go @@ -322,11 +322,20 @@ func TestParseURLRef(t *testing.T) { {"/", "/", 4}, // browsers accept a numeric ref without the semicolon {"/", "/", 5}, {"%", "%", 8}, - {"©", "", 0}, // not URL structure: left alone + // Decoded, and correctly: a browser decodes a semicolon-terminated named + // reference too, and the value is only ever spliced back when an origin + // in it rewrote, so this is faithful rather than gratuitous. Non-ASCII + // named references are accepted because an IDN host is built from them + // — ämeen.fi is hämeen.fi — and §5.5 calls IDN real for .fi + // clients. The legacy *no-semicolon* form is what the short table exists + // to avoid, and it is still declined; see the `©=1` case below. + {"©", "\u00a9", 6}, {"&", "", 0}, // '&' excluded: decoding it could splice a new ref {"�", "", 0}, // out of range {"&#;", "", 0}, - {"&sol", "", 0}, // named refs require the semicolon + {"&sol", "", 0}, // named refs require the semicolon + {"ä", "ä", 6}, // an IDN host's letter + {"¬arealref;", "", 0}, } { c2, n := parseURLRef([]byte(c.in)) got := "" diff --git a/internal/rewrite/entity.go b/internal/rewrite/entity.go index 80a7371..db64938 100644 --- a/internal/rewrite/entity.go +++ b/internal/rewrite/entity.go @@ -1,6 +1,10 @@ package rewrite -import "bytes" +import ( + "bytes" + "html" + "unicode/utf8" +) // urlNamedRefs is every named character reference that decodes to a character // an origin is built from. It is deliberately this short: html.UnescapeString @@ -114,11 +118,13 @@ func decodeURLRefsOnce(v []byte) ([]byte, bool) { } out = append(out, v[prev:i]...) // Decline the whole value rather than complete a reference out of a - // fragment we did not consume — see fusesWithPending. - if fusesWithPending(out, c) { + // fragment we did not consume — see fusesWithPending. Only the first + // byte can fuse, and a multi-byte decode is non-ASCII, which is never a + // reference body. + if fusesWithPending(out, c[0]) { return v, false } - out = append(out, c) + out = append(out, c...) prev = i + n i = prev - 1 } @@ -130,21 +136,39 @@ func decodeURLRefsOnce(v []byte) ([]byte, bool) { // parseURLRef decodes one reference at the start of b, returning the byte and // how much of b it spans. n == 0 means "not a reference this cares about". -func parseURLRef(b []byte) (byte, int) { +func parseURLRef(b []byte) (string, int) { if len(b) < 3 || b[0] != '&' { - return 0, 0 + return "", 0 } if b[1] != '#' { - // Named. Bounded lookahead: the longest name here is 7 bytes. - lim := min(len(b), 12) + // Named. Bounded lookahead: long enough for the accented letters an IDN + // host is built from as well as the punctuation table. + lim := min(len(b), 16) end := bytes.IndexByte(b[1:lim], ';') if end < 0 { - return 0, 0 + return "", 0 } - if c, ok := urlNamedRefs[string(b[1:1+end])]; ok { - return c, end + 2 + name := string(b[1 : 1+end]) + if c, ok := urlNamedRefs[name]; ok { + return string(rune(c)), end + 2 } - return 0, 0 + // A named reference for a non-ASCII letter, which an IDN host is made + // of: ämeen.fi is hämeen.fi to a browser, and §5.5 calls IDN "real + // for .fi client domains". Enumerating them is hopeless — the HTML5 + // table has hundreds — and html.UnescapeString over a whole value is + // what the table above exists to avoid, because it also decodes the + // legacy no-semicolon forms and turns a `©=1` query into `©=1`. + // + // Requiring the semicolon removes that ambiguity entirely, so this asks + // the HTML5 table about exactly one bounded, semicolon-terminated token. + // Non-ASCII only: everything below 0x7f goes through the table above, + // where the structural exclusions live. + if d := html.UnescapeString("&" + name + ";"); d != "&"+name+";" { + if r, size := utf8.DecodeRuneInString(d); size == len(d) && r >= 0x80 && r != utf8.RuneError { + return d, end + 2 + } + } + return "", 0 } j, base := 2, 10 @@ -160,12 +184,12 @@ func parseURLRef(b []byte) (byte, int) { } val = val*base + d if val > 0x10FFFF { - return 0, 0 // out of range; leave it alone rather than guess + return "", 0 // out of range; leave it alone rather than guess } j++ } if j == start { - return 0, 0 + return "", 0 } if j < len(b) && b[j] == ';' { j++ // browsers accept a numeric reference without one @@ -185,13 +209,21 @@ func parseURLRef(b []byte) (byte, int) { // // Nothing legitimate is lost. This exists to catch an origin hidden behind // character references, and none of these can appear in one. + // Non-ASCII is allowed through — an IDN host is built from it, and a UTF-8 + // letter spliced between attribute quotes is inert, which is the only thing + // the exclusions below are protecting. switch { - case val < 0x21 || val > 0x7e: - return 0, 0 + case val >= 0x80: + if !utf8.ValidRune(rune(val)) { + return "", 0 + } + return string(rune(val)), j + case val < 0x21: + return "", 0 case val == '&' || val == '"' || val == '\'' || val == '<' || val == '>' || val == '=' || val == '`': - return 0, 0 + return "", 0 } - return byte(val), j + return string(rune(val)), j } func digitVal(c byte, base int) (int, bool) { diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index 30b621b..1668d5e 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -41,6 +41,7 @@ type HTML struct { maxPend int // high-water mark of the token buffer, for test 13 tail io.Reader attrs []Attr // scratch for scanAttrsInto, reused across tags + hosts *hostReplacer } // mark records that output offset out corresponds to input offset in, from @@ -155,6 +156,7 @@ func NewHTML(r io.Reader, m *origin.Matcher, src io.Closer, opt Options) *HTML { z := html.NewTokenizer(r) z.SetMaxBuf(maxTok) return &HTML{ + hosts: newHostReplacer(m), z: z, m: m, stats: st, @@ -218,7 +220,7 @@ var singleURLAttrNames = [][]byte{ []byte("href"), []byte("src"), []byte("action"), []byte("formaction"), []byte("cite"), []byte("poster"), []byte("data"), []byte("manifest"), []byte("longdesc"), []byte("background"), []byte("codebase"), - []byte("profile"), []byte("itemid"), + []byte("profile"), []byte("itemid"), []byte("xlink:href"), } func singleURLAttr(name []byte) bool { @@ -259,15 +261,7 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b out, events := rw(v, surface, w.stats.Explain()) w.stats.Record(surface, base, events) if surface == SurfaceHTMLAttr { - out = w.decodeEntityLeak(base, out) - // After the entity decode, because the two compose: a host may be - // spelled with references *and* carry a tab, and the reference form of - // the tab is removed here rather than decoded there — decodeURLRefs must - // never emit a control character, which is one of the XSS holes it was - // written to close. Removing one is not emitting one. - if singleURLAttr(name) { - out = w.normaliseURLLeak(base, out) - } + out = w.urlLeaks(base, out, singleURLAttr(name)) } if w.dryRun { return v @@ -295,11 +289,31 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b // // Attribute values only: inside `}, + {"inline style", ``}, + {"comment", ``}, + } { + t.Run(c.name+" in "+s.name, func(t *testing.T) { + in := strings.Replace(s.tmpl, "%s", c.host, 1) + out := rewriteHTML(t, m, in, NewStats(false)) + if !strings.Contains(out, "wt-a--example.ddev.site") { + t.Errorf("a production origin reached the browser:\n%q", out) + } + }) + } + } +} + +// NFD, in its own test because it needs a punycode canonical: the composed and +// decomposed spellings punycode differently, and only the fold makes them equal. +func TestNFDHostIsRewritten(t *testing.T) { + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://www.xn--hmeen-gra.fi"), + Variant: origin.MustParse("https://wt-a--h.ddev.site"), + }}) + if err != nil { + t.Fatal(err) + } + for _, c := range []struct{ name, in string }{ + {"NFD", ""}, + {"NFC", ""}, + {"punycode", ``}, + } { + t.Run(c.name, func(t *testing.T) { + if out := rewriteHTML(t, m, c.in, NewStats(false)); !strings.Contains(out, "wt-a--h.ddev.site") { + t.Errorf("not rewritten:\n%q", out) + } + }) + } +} + +// The fold must not fire on a page that is already correct. It cannot run at all +// without a non-ASCII byte, and an ASCII host is the byte matcher's business. +func TestFoldIsIdentitySafe(t *testing.T) { + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://www.example.fi"), + }}) + if err != nil { + t.Fatal(err) + } + for _, in := range []string{ + "", + "tervetuloa äö https://www.example.fi/x", + "

café // not a host

", + } { + if out := rewriteHTML(t, m, in, NewStats(false)); out != in { + t.Errorf("identity map changed bytes:\n got %q\nwant %q", out, in) + } + } +} diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index df023c5..f2f74bd 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -228,6 +228,11 @@ var urlAttrNames = [][]byte{ // attribute's `url(…)`. []byte("srcset"), []byte("imagesrcset"), []byte("ping"), []byte("content"), []byte("style"), + // srcdoc holds a whole document whose base URL is the parent's, so every + // href inside it is navigable. data-src and data-srcset are what the fleet's + // lazyload and the WooCommerce gallery assign to src, which §5.2 already + // names as a real surface. + []byte("srcdoc"), []byte("data-src"), []byte("data-srcset"), } func singleURLAttr(name []byte) bool { @@ -270,6 +275,14 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b if surface == SurfaceHTMLAttr { out = w.urlLeaks(base, out, singleURLAttr(name)) } + // Every surface, because a host that only folds onto a canonical one — a + // soft hyphen, fullwidth letters, U+3002 for the dots, NFD — shares no bytes + // with its pattern anywhere, not just in a URL attribute. + out = w.foldedHostLeak(surface, base, out) + // Every surface, because a host that only folds onto a canonical one — a + // soft hyphen, fullwidth letters, U+3002 for the dots, NFD — shares no bytes + // with its pattern anywhere, not just in a URL attribute. + if w.dryRun { return v } diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go index ea0c2d3..1fd7370 100644 --- a/internal/rewrite/urlobf.go +++ b/internal/rewrite/urlobf.go @@ -4,8 +4,6 @@ import ( "bytes" "strings" - "golang.org/x/net/idna" - "github.com/generoi/hostshift/internal/origin" ) @@ -75,14 +73,16 @@ func (h *hostReplacer) sameSchemeAsDocument(scheme string) bool { } // key normalises a parsed host to the form the table is keyed on: lowercase, -// no root dot, punycode. +// no root dot, and the browser's domain-to-ASCII. // -// Punycode because §5.5 calls IDN "real for .fi client domains" and the pairs -// are stored normalised, so a U-label in the document — hämeen.fi, or the same -// host spelled with references — has to be folded before it can be looked up. +// origin.HostFold, not a bare punycode: the browser runs UTS46 mapping first, so +// a soft hyphen in the host, fullwidth letters, U+3002 as a label separator or +// an NFD spelling all name the canonical host to a browser while sharing no +// bytes with it. §5.5 calls IDN real for .fi client domains, and NFD is what a +// macOS filesystem or a paste produces without anyone trying. func (h *hostReplacer) key(b []byte) string { s := strings.TrimSuffix(strings.ToLower(string(b)), ".") - if a, err := idna.Punycode.ToASCII(s); err == nil { + if a, err := origin.HostFold(s); err == nil { return a } return s @@ -316,15 +316,25 @@ func urlTokenStarts(v []byte) []int { return out } -// locateHost finds the host the URL parser would read starting at v, and the -// origin it maps to. -func (h *hostReplacer) locateHost(v []byte) (from, until int, to origin.Origin, ok bool) { - n := stripForURL(v) - at := h.authorityStart(n.b) - if at < 0 || at >= len(n.b) { +// locateHostIn finds the host the URL parser would read starting at n.b[at], +// and the origin it maps to. from/until are indices into the *original* value. +// +// It takes an already-stripped buffer rather than stripping one itself. Stripping +// per candidate made the pass quadratic: stripForURL allocates a []byte and a +// []int over the whole remainder, so a long value with many token starts cost +// O(k·n) — measured at 55 seconds for a 320 KB attribute value, which +// extrapolates to hours at the shipped 4 MiB token cap. That is the same bug +// class scan.go documents having already fixed once. +func (h *hostReplacer) locateHostIn(n normalised, at int) (from, until int, to origin.Origin, ok bool) { + rel := h.authorityStart(n.b[at:]) + if rel < 0 { + return 0, 0, to, false + } + start := at + rel + if start >= len(n.b) { return 0, 0, to, false } - hs, he, port := hostRange(n.b, at) + hs, he, port := hostRange(n.b, start) if hs >= he { return 0, 0, to, false } @@ -342,6 +352,76 @@ func (h *hostReplacer) locateHost(v []byte) (from, until int, to origin.Origin, return n.pos[hs], n.pos[he-1] + 1, to, true } +// foldedHostLeak catches a host that only *folds* onto a canonical one. +// +// The byte matcher compares bytes, so a host spelled with a soft hyphen, with +// fullwidth letters, with U+3002 for the dots, or in NFD shares nothing with the +// pattern it names — and unlike the shapes above, that is true on every surface, +// not just in a URL attribute. A production origin in a text node, in an inline +// script, in a stylesheet or in a comment is still a production origin the +// browser will resolve when something reads it. +// +// So this runs over the whole value on every surface, and it is cheap because it +// cannot fire without a non-ASCII byte: a host that is pure ASCII either matches +// the pattern already or is not the canonical host at all. That one test skips +// the entire pass on most documents, and on the rest the work is bounded by the +// number of `//` runs. +func (w *HTML) foldedHostLeak(surface string, base int, v []byte) []byte { + if w.hosts == nil || len(w.hosts.to) == 0 { + return v + } + nonASCII := false + for _, c := range v { + if c >= 0x80 { + nonASCII = true + break + } + } + if !nonASCII { + return v + } + + n := stripForURL(v) + var out []byte + prev := 0 + for i := 0; i+1 < len(n.b); i++ { + if !isSlashish(n.b[i]) || !isSlashish(n.b[i+1]) || n.pos[i] < prev { + continue + } + from, until, to, ok := w.hosts.locateHostIn(n, i) + if !ok { + continue + } + // Nothing to do when the bytes already say the variant, and nothing to + // do when the host is plain ASCII — that is the byte matcher's job, and + // it has already run. + if !bytes.Equal(v[from:until], []byte(to.Host)) && hasNonASCII(v[from:until]) { + out = append(out, v[prev:from]...) + out = append(out, to.Host...) + prev = until + w.stats.Record(surface, base, []origin.Event{{ + Offset: base + from, + Surface: surface, + Action: origin.ActionRewrote, + Text: string(v[from:until]), + }}) + } + } + if out == nil { + return v + } + return append(out, v[prev:]...) +} + +func hasNonASCII(b []byte) bool { + for _, c := range b { + if c >= 0x80 { + return true + } + } + return false +} + // normaliseURLLeak replaces every host in v that the URL parser would read and // this map rewrites, and returns v untouched when there are none. // @@ -353,17 +433,17 @@ func (w *HTML) normaliseURLLeak(base int, v []byte) []byte { if w.hosts == nil || len(w.hosts.to) == 0 { return v } + n := stripForURL(v) var out []byte prev := 0 - for _, off := range urlTokenStarts(v) { - if off < prev { + for _, off := range urlTokenStarts(n.b) { + if off < len(n.pos) && n.pos[off] < prev { continue // inside a host already replaced } - from, until, to, ok := w.hosts.locateHost(v[off:]) + from, until, to, ok := w.hosts.locateHostIn(n, off) if !ok { continue } - from, until = off+from, off+until out = append(out, v[prev:from]...) out = append(out, to.Host...) prev = until diff --git a/test/integration-ddev.sh b/test/integration-ddev.sh index f09872f..792310e 100755 --- a/test/integration-ddev.sh +++ b/test/integration-ddev.sh @@ -142,26 +142,27 @@ out="$(cd "$wt" && ddev hostshift check 2>&1)" && pass "check passes a live work # refusal is what stands between a developer and reviewing the wrong branch's # code at the right URL. # -# The parent is already up, so it is the live rival: give it the worktree's -# variant and it is genuinely contending for that hostname. variant="$(sed -n 's/^HOSTSHIFT_VARIANTS=//p' "$wt/.ddev/.env" | cut -d, -f1)" -cp "$main/.ddev/.env" "$work/main-env.bak" 2>/dev/null || : > "$work/main-env.bak" -printf 'HOSTSHIFT_VARIANTS=%s\n' "$variant" >> "$main/.ddev/.env" -if (cd "$wt" && ddev hostshift check >/dev/null 2>&1); then - fail "check refuses when a running project claims the same hostname" \ - "exit 0 — a live collision was reported as healthy" -else - pass "check refuses when a running project claims the same hostname" -fi -cp "$work/main-env.bak" "$main/.ddev/.env" -# But a directory left behind by a deleted worktree is not a claim. `git worktree +# A directory left behind by a deleted worktree is not a claim. `git worktree # remove` refuses while untracked files are present, so the directory outliving # the project is the common case — and refusing there failed the post-start hook # on every start while routing was entirely correct. dead="$work/${tag}-dead" mkdir -p "$dead/.ddev" printf 'HOSTSHIFT_VARIANTS=%s\n' "$variant" > "$dead/.ddev/.env" +# The parent is up but runs no proxy, so it is the same case: a .ddev/.env that +# claims a variant nothing is serving. The variants live on the *hostshift* +# container, so asking about web read every add-on-removed, crashed or +# never-restarted sibling as a live claimant, and failed the post-start hook on +# every start of a correct deployment. +cp "$main/.ddev/.env" "$work/main-env.bak" 2>/dev/null || : > "$work/main-env.bak" +printf 'HOSTSHIFT_VARIANTS=%s\n' "$variant" >> "$main/.ddev/.env" +out="$(cd "$wt" && ddev hostshift check 2>&1)" \ + && pass "a running project with no proxy of its own claims nothing" \ + || fail "a running project with no proxy of its own claims nothing" "$out" +cp "$work/main-env.bak" "$main/.ddev/.env" + out="$(cd "$wt" && ddev hostshift check 2>&1)" \ && pass "and a stopped project's leftover directory is a warning, not a failure" \ || fail "and a stopped project's leftover directory is a warning, not a failure" "$out" diff --git a/test/integration-proxy-ddev.sh b/test/integration-proxy-ddev.sh index 5daa57a..c2a03a2 100755 --- a/test/integration-proxy-ddev.sh +++ b/test/integration-proxy-ddev.sh @@ -374,6 +374,28 @@ projects+=("$wt2") (cd "$wt2" && ddev hostshift init >/dev/null 2>&1) || fail "init succeeds in the second worktree" "" out="$(cd "$wt2" && ddev start -y 2>&1)" || fail "the second worktree starts" "$out" +# Both proxies are up, so this is a real collision rather than a leftover file: +# point the second worktree's .ddev/.env at the first one's variant and it is +# genuinely contending for that hostname. This is the assertion the liveness gate +# exists for, and it needs two *running proxies* — which is why it lives here and +# not in the suite where the only other project is a parent with no proxy. +v1="$(sed -n 's/^HOSTSHIFT_VARIANTS=//p' "$wt/.ddev/.env" | cut -d, -f1)" +cp "$wt2/.ddev/.env" "$work/wt2-env.bak" +# Replaced, not appended: the scan reads the *first* HOSTSHIFT_VARIANTS line and +# quits, so a second one below it is never seen and the rival claims nothing. +sed -i.bak "s|^HOSTSHIFT_VARIANTS=.*|HOSTSHIFT_VARIANTS=$v1|" "$wt2/.ddev/.env" +rm -f "$wt2/.ddev/.env.bak" +if (cd "$wt" && ddev hostshift check >/dev/null 2>&1); then + fail "check refuses when a running proxy claims the same hostname" \ + "exit 0 — a live collision was reported as healthy" +else + pass "check refuses when a running proxy claims the same hostname" +fi +cp "$work/wt2-env.bak" "$wt2/.ddev/.env" +(cd "$wt" && ddev hostshift check >/dev/null 2>&1) \ + && pass "and passes again once the rival stops claiming it" \ + || fail "and passes again once the rival stops claiming it" "$(cd "$wt" && ddev hostshift check 2>&1)" + contains "the first worktree's variant still serves the first worktree" \ "PROJECT=worktree HOST=$C" "$(get "https://$V/")" contains "the second worktree's variant serves the second worktree" \ From 7be93719125e67f58621428f13288b0347378a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 16:51:43 -0300 Subject: [PATCH 011/124] Make both matchers reach every surface, and stop the narrowing inverting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round twelve. Both dimensions found large problems; four of the breaker's five were one shape. The URL-parser locator ran on attribute values only. `foldedHostLeak` was made universal last round; the locator never was, so every *ASCII* parser shape went out untouched in an inline script, an inline stylesheet, a text node and a comment — `https:\\h`, `https:///h`, `http:h`, a tab in the host, `u@h`, `%2e` for a dot — with the census reporting a clean page, which is the exact property this file's header says was fixed one surface over. §5.2 calls inline script and style Tier 1 and "where the CSS and JS URLs actually are", and `fetch("https://www%2eexample%2efi/a")` is a production request carrying the developer's session. Both catchers now run on every surface, and on the JSON body, which had neither — so a Gutenberg save was writing an unfolded host straight back into the database. Outside the parser model entirely: CSS unescapes before the URL parser runs, so `https\3a\2f\2fwww.example.fi/x` is a spelling the locator cannot reach by construction and the byte matcher cannot see at all. Measured in Chrome, both cssText and getComputedStyle().backgroundImage resolve it to a live production fetch. Style surfaces get the CSS tokenizer's view too. Also in the engine: * `foldedHostLeak` was quadratic — it restarted inside every slash run, where authorityStart walks the run each time. 20 seconds for a 400,000-byte run, extrapolating to ~38 minutes at the 4 MiB token cap. It now steps over the run. The same bug locateHostIn's comment says was already fixed once, live again in its other caller. * `MapForLookup` implies CheckHyphens, and WHATWG's domain-to-ASCII sets it false. x/net errored where the browser succeeds — on any label with `--` in positions 3-4, or a leading or trailing hyphen — and the fallback then compared raw bytes, so one such host in the map switched the whole fold off for that host on every surface. `--` at 3-4 is what this add-on's own default slug produces. * The 16-byte character-reference name cap dropped four of the seven HTML5 names decoding to a character UTS46 deletes inside a host — ​ and its three siblings, all U+200B, all live production links one character away from the ​ spelling that is caught. * The locator tried the bare host before host:port, so an explicit :8080 origin was rewritten to the wrong variant while the byte matcher, which disambiguates by port, got it right — the two halves disagreed. * `@` read as a host delimiter, so `https://www.example.fi@evil.com` — whose host is evil.com — had its bytes changed on a value that never pointed at production. §4.4's delimiter list does not include it. And in the add-on: * An empty HOSTSHIFT_WEB_HOSTS inverted the narrowing into its opposite. A parent that lists the worktree's own hostname in additional_hostnames — teams do commit that — made the subtraction remove everything, and the compose file's `:-` treated empty as unset and handed web the whole list back. The parent's blog was then served by the worktree, permanently, with init and check both reporting success. The project's own hostname is no longer subtracted, `-` replaces `:-`, and `check` now inspects web's VIRTUAL_HOST — it examined the proxy container four ways and never once looked at web, which is why this was invisible by construction. * The exclude-block migration deleted every matching line anywhere in the file, so a developer's own `.ddev/.env` rule written above the block was absorbed into ours and then removed with it — the exposure the markers were added to prevent, one release later. Only the contiguous run after the marker moves now. A block whose end marker had been hand-deleted made removal `sed` from the marker to end of file and empty the whole exclude file; both markers are required, and install repairs a half-written block rather than freezing it. * After an upgrade from v0.1.0 the command is never replaced, so every refusal added since is silently absent — measured, a worktree whose map named the wrong hostnames printed "configured to serve" and exited 0 while both variants 302'd to wp-signup.php. install.yaml warned once, in the middle of a long output, at a moment when nothing was yet broken; the hook now says it on every start until the command is replaced. * `check` and `init` disagreed about which .ddev/.env keys are ours, so an upgraded project's leftover HOSTSHIFT_SLUG= would be counted but never stripped and check would report "out of date" forever. * copy-db derived the parent's project name from config.yaml alone — the same mistake the collision gate two blocks up was fixed for. * The inherited-hostname note is init's: it says those hostnames serve this worktree "until `ddev restart`", so env, wp-cli and loopback printing it told a developer with a healthy worktree it was stealing a hostname it was not. --- ddev/commands/host/hostshift | 51 ++++++- ddev/config.hostshift.yaml | 25 +++- ddev/docker-compose.hostshift.yaml | 8 +- ddev/install.yaml | 45 +++++- internal/origin/matcher.go | 9 ++ internal/origin/origin.go | 10 ++ internal/rewrite/entity.go | 8 +- internal/rewrite/html.go | 28 +++- internal/rewrite/json.go | 12 ++ internal/rewrite/urlobf.go | 213 +++++++++++++++++++++++++++-- internal/rewrite/urlobf_test.go | 65 +++++++++ test/addon-command.sh | 30 +++- 12 files changed, 470 insertions(+), 34 deletions(-) diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index 3864867..d3c3486 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -334,7 +334,16 @@ if [ "$cmd" = "copy-db" ]; then echo " of a different DDEV project. Use \`ddev import-db\` or a fresh pull." >&2 exit 2; } from="$(basename "$parent")" - n="$(ddev_name "$parent/.ddev/config.yaml")" && [ -n "$n" ] && from="$n" + # Every config.*.yaml, in DDEV's merge order — config.yaml alone is the same + # mistake the collision gate two blocks up was fixed for. A parent that names + # itself in a config.*.local.yaml made this grep for the wrong ddev--db + # and let a copy through that writes into an idle container while the + # application goes on reading the parent's. + for pc in "$parent/.ddev/config.yaml" $(LC_ALL=C printf '%s\n' "$parent"/.ddev/config.*.yaml | LC_ALL=C sort); do + [ -f "$pc" ] || continue + n="$(ddev_name "$pc")" + [ -n "$n" ] && from="$n" + done # Sharing the parent's database and copying it are different decisions, and # doing both is worse than pointless: the dump goes into this worktree's own @@ -605,15 +614,32 @@ done # without narrowing it web and hostshift both claim the variants and the router # picks web — WordPress then sees a variant host, fails to match # wp_blogs.domain, and redirects to wp-signup.php. +# This project's own primary hostname is never subtracted. +# +# A parent that lists the worktree's hostname in its own additional_hostnames — +# which teams do commit, for the mkcert SANs or as a leftover — made the +# subtraction remove *everything*, so HOSTSHIFT_WEB_HOSTS came out empty. The +# compose file read it as `${HOSTSHIFT_WEB_HOSTS:-$DDEV_HOSTNAME}`, and `:-` +# treats empty as unset, so web fell back to every hostname the worktree +# registers — the exact inverse of narrowing, permanently, in the configuration +# where the subtraction has the most to do. Both `init` and `check` reported +# success, because check compares .ddev/.env against a recomputation of itself +# and both said the same empty thing. +# +# A project must be reachable at its own name regardless of what the parent +# claims, so that one is kept before the subtraction runs. +own="$(printf '%s' "$hosts" | sed -n 1p)" web="" while IFS= read -r h; do [ -n "$h" ] || continue case ",$variants," in *",$h,"*) continue ;; esac - case " + if [ "$h" != "$own" ]; then + case " $parent_hosts " in *" $h "*) continue ;; esac + fi web="${web:+$web,}$h" done <<<"$hosts" @@ -670,7 +696,14 @@ fi # and the post-start hook printed it on every start, including the restart that # had just handed those hostnames back to the parent. Four lines of "until # `ddev restart`, those serve this worktree" from the hook of that very restart. -if [ -n "$parent_hosts" ] && [ "$cmd" != "check" ]; then +# `init` only, not every subcommand that is not check. +# +# The note says those hostnames serve this worktree "until `ddev restart`", +# which is true when init says it and false ever after — so `env`, `wp-cli` and +# `loopback` printed it on an already-restarted, entirely healthy worktree, +# telling the developer their worktree was stealing a hostname it was not and +# pointing them at remedial action they should not take. +if [ -n "$parent_hosts" ] && [ "$cmd" = "init" ]; then shared="" while IFS= read -r h; do [ -n "$h" ] || continue @@ -749,7 +782,14 @@ if [ "$cmd" = "check" ]; then # in HOSTSHIFT_VARIANTS, and a hostshift.yaml appearing only in HOSTSHIFT_ARGS # — where the deployed flat map goes on beating the file, so its aliases # silently never rewrite. - deployed="$(grep -e '^HOSTSHIFT_' -e '^#ddev-silent-no-warn' .ddev/.env || true)" + # The same four keys `init` writes and strips, not `^HOSTSHIFT_`. The two + # disagreed: init left a v0.1.0 project's leftover HOSTSHIFT_SLUG= and + # HOSTSHIFT_MAP_ARGS= lines alone while check counted them, so check reported + # "out of date" forever on exactly the upgraded projects. It also swept up any + # variable of the developer's own sharing the prefix — and the compose file + # reads HOSTSHIFT_* names, so that is how someone arrives at one. + deployed="$(grep -e '^HOSTSHIFT_ARGS=' -e '^HOSTSHIFT_VARIANTS=' -e '^HOSTSHIFT_WEB_HOSTS=' \ + -e '^HOSTSHIFT_SLUG_CHOSEN=' -e '^#ddev-silent-no-warn' .ddev/.env | tr -d '\r' || true)" if [ "$deployed" != "$env_out" ]; then echo "hostshift: .ddev/.env is out of date. It says" >&2 printf '%s\n' "${deployed:- nothing}" >&2 @@ -1092,7 +1132,8 @@ trap 'rm -f "$tmp"' EXIT # putting project env vars, credentials included, so a developer who chmod 600'd # it meant it. cp -p .ddev/.env "$tmp" -{ grep -v -e '^HOSTSHIFT_' -e '^#ddev-silent-no-warn' .ddev/.env || true +{ grep -v -e '^HOSTSHIFT_ARGS=' -e '^HOSTSHIFT_VARIANTS=' -e '^HOSTSHIFT_WEB_HOSTS=' \ + -e '^HOSTSHIFT_SLUG_CHOSEN=' -e '^#ddev-silent-no-warn' .ddev/.env || true printf '%s\n' "$env_out"; } > "$tmp" mv "$tmp" .ddev/.env diff --git a/ddev/config.hostshift.yaml b/ddev/config.hostshift.yaml index 33e1a3c..c4d0fb0 100644 --- a/ddev/config.hostshift.yaml +++ b/ddev/config.hostshift.yaml @@ -41,6 +41,29 @@ # configured says nothing at all on start. It has nothing to check, and nine # lines of advice plus a `Task failed` on a project with nothing wrong with it is # how an add-on stops being inert until it is configured. +# The version guard is not decoration. +# +# DDEV replaces *this* file on `ddev add-on get` because it carries +# #ddev-generated, and refuses to replace the command because v0.1.0's copy does +# not. So an upgraded project runs the new hook against the old logic — and the +# old logic is missing every refusal added since: the parent-declares check, the +# variant-resolvability check, the collision liveness gate, and the running-map +# comparison. Measured on a real upgrade, a worktree whose map named the wrong +# hostnames entirely printed "hostshift is configured to serve" and exited 0, +# while both variants 302'd to wp-signup.php. +# +# install.yaml warns about this once, in the middle of a long `add-on get` +# output, at a moment when nothing is yet broken. That is not enough, and DDEV +# itself makes it worse: it prints "unexpected #ddev-generated … Remove +# unexpected '#ddev-generated' comments" on every restart, so a developer +# following DDEV's own advice arms the trap on a project that was fine. Nagging +# on every start until the command is replaced is the proportionate answer. hooks: post-start: - - exec-host: HOSTSHIFT_HOOK=1 .ddev/commands/host/hostshift check + - exec-host: | + grep -q '#ddev-generated' .ddev/commands/host/hostshift 2>/dev/null || { + echo "hostshift: .ddev/commands/host/hostshift predates this add-on and was" >&2 + echo " NOT replaced on upgrade, so the checks added since are not running." >&2 + echo " rm .ddev/commands/host/hostshift && ddev add-on get generoi/hostshift" >&2 + } + HOSTSHIFT_HOOK=1 .ddev/commands/host/hostshift check diff --git a/ddev/docker-compose.hostshift.yaml b/ddev/docker-compose.hostshift.yaml index 0133d7d..3207209 100644 --- a/ddev/docker-compose.hostshift.yaml +++ b/ddev/docker-compose.hostshift.yaml @@ -40,7 +40,13 @@ services: # of hostshift yet. An add-on has to be inert until it is configured. web: environment: - VIRTUAL_HOST: ${HOSTSHIFT_WEB_HOSTS:-$DDEV_HOSTNAME} + # `-`, not `:-`. An *empty* HOSTSHIFT_WEB_HOSTS is a real answer — every + # hostname this worktree registers belongs to a variant or to the parent, + # so web should serve none of them — and `:-` treated it as unset and + # handed web the whole list back, inverting the narrowing exactly where it + # mattered most. Unset still means "the add-on has not configured this + # project", which is what the fallback is for. + VIRTUAL_HOST: ${HOSTSHIFT_WEB_HOSTS-$DDEV_HOSTNAME} hostshift: container_name: ddev-${DDEV_SITENAME}-hostshift diff --git a/ddev/install.yaml b/ddev/install.yaml index a3272e9..345218b 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -50,16 +50,37 @@ pre_install_actions: # # The end marker also means a later version can add a pattern to the block # rather than being frozen by the idempotency guard below. + # A complete block is left alone; a half-written one is repaired rather than + # frozen, since the idempotency guard would otherwise arm that trap forever. if grep -qxF '# hostshift (ddev add-on) — begin' "$ex" 2>/dev/null; then - exit 0 + if grep -qxF '# hostshift (ddev add-on) — end' "$ex"; then + exit 0 + fi + tmp="$(mktemp)" + awk ' + $0 == "# hostshift (ddev add-on) — begin" { drop = 1; next } + drop && ($0 == ".ddev/**/*hostshift*" || $0 == ".ddev/.env" || $0 == "wp-cli.local.yml") { next } + { drop = 0; print } + ' "$ex" > "$tmp" && cat "$tmp" > "$ex" + rm -f "$tmp" fi # An older, undelimited block from a previous version: take it out first, so # this does not append a second copy beside it. + # + # Only the *contiguous* run that follows the marker. `grep -vxF` removed + # every matching line anywhere in the file, so a developer's own + # `.ddev/.env` rule written above the block — the shape this file's comments + # say both pilot repos have — was deleted from their own section and + # absorbed into ours, and the next removal then took it away for good. That + # is the exposure the markers were introduced to prevent, reintroduced one + # release later through the migration. if grep -qxF '# hostshift (ddev add-on)' "$ex" 2>/dev/null; then tmp="$(mktemp)" - grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' \ - -e '.ddev/.env' -e 'wp-cli.local.yml' "$ex" > "$tmp" || true - cat "$tmp" > "$ex" + awk ' + $0 == "# hostshift (ddev add-on)" { drop = 1; next } + drop && ($0 == ".ddev/**/*hostshift*" || $0 == ".ddev/.env" || $0 == "wp-cli.local.yml") { next } + { drop = 0; print } + ' "$ex" > "$tmp" && cat "$tmp" > "$ex" rm -f "$tmp" fi { @@ -203,9 +224,23 @@ removal_actions: # marker; there, matching the patterns is all there is to go on. tmp="$(mktemp)" ok="" - if grep -qxF '# hostshift (ddev add-on) — begin' "$ex"; then + # Both markers, or neither. `sed` with an unmatched end address deletes from + # the begin marker to end of file, so a block whose end marker had been + # hand-deleted emptied the developer's whole exclude file — silently, exit 0. + if grep -qxF '# hostshift (ddev add-on) — begin' "$ex" && + grep -qxF '# hostshift (ddev add-on) — end' "$ex"; then sed '/^# hostshift (ddev add-on) — begin$/,/^# hostshift (ddev add-on) — end$/d' \ "$ex" > "$tmp" && ok=1 + elif grep -qxF '# hostshift (ddev add-on) — begin' "$ex"; then + # A begin marker with no end: take the marker and the contiguous run of + # our own patterns, and leave everything else alone. Deleting to end of + # file, which is what sed does with an unmatched end address, emptied the + # developer's whole exclude file. + awk ' + $0 == "# hostshift (ddev add-on) — begin" { drop = 1; next } + drop && ($0 == ".ddev/**/*hostshift*" || $0 == ".ddev/.env" || $0 == "wp-cli.local.yml") { next } + { drop = 0; print } + ' "$ex" > "$tmp" && ok=1 else # grep exits 1 when it selects no lines, which is the shape the install # creates when info/exclude did not exist before. That is a success with an diff --git a/internal/origin/matcher.go b/internal/origin/matcher.go index ffceac0..54cedb5 100644 --- a/internal/origin/matcher.go +++ b/internal/origin/matcher.go @@ -341,6 +341,15 @@ func delimAt(b []byte, i int) bool { if i >= len(b) { return true } + // '@' is not one. It is not a host byte, so it read as a delimiter and + // terminated the match — but in a URL '@' *starts* userinfo, so what + // precedes it is credentials and the real host comes after: + // https://www.example.fi@evil.com names evil.com, not the canonical, and + // rewriting it changed bytes on a value that never pointed at production. + // §4.4's delimiter list does not include it either. + if b[i] == '@' { + return false + } if b[i] == '%' { if c, ok := unhex(b, i+1); ok { return !isHostByte(c) diff --git a/internal/origin/origin.go b/internal/origin/origin.go index 06cadd7..a02dc88 100644 --- a/internal/origin/origin.go +++ b/internal/origin/origin.go @@ -94,9 +94,19 @@ func MustParse(s string) Origin { // resolution, and this is a comparison form, so it must fold rather than judge. // MapForLookup with StrictDomainName off maps without rejecting, and // Transitional(false) is what browsers do. +// +// CheckHyphens(false) because WHATWG's domain-to-ASCII sets it false explicitly, +// and MapForLookup turns it on. With it on, x/net *errors* where the browser +// succeeds — on any label with `--` in positions 3-4, or a leading or trailing +// hyphen — and the fallback then compares the raw string, which shares no bytes +// with anything. One such host in the map silently switched the whole fold off +// for that host on every surface. `--` at 3-4 is not exotic: it is the shape the +// add-on's own default slug produces, `wt--acme.ddev.site`, and a variant is the +// canonical side of the request-direction matcher. var hostFold = idna.New( idna.MapForLookup(), idna.StrictDomainName(false), + idna.CheckHyphens(false), idna.Transitional(false), ) diff --git a/internal/rewrite/entity.go b/internal/rewrite/entity.go index db64938..c127d6f 100644 --- a/internal/rewrite/entity.go +++ b/internal/rewrite/entity.go @@ -143,7 +143,13 @@ func parseURLRef(b []byte) (string, int) { if b[1] != '#' { // Named. Bounded lookahead: long enough for the accented letters an IDN // host is built from as well as the punctuation table. - lim := min(len(b), 16) + // Long enough for the whole family. The cap used to be 16, which silently + // dropped four of the seven HTML5 names decoding to a character UTS46 + // deletes inside a host: ​ ​ + // ​ and ​, all U+200B, all live + // production links one character away from the ​ spelling + // that is caught. The longest name in the HTML5 table is 32 characters. + lim := min(len(b), 34) end := bytes.IndexByte(b[1:lim], ';') if end < 0 { return "", 0 diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index f2f74bd..afa428c 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -272,13 +272,31 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b } out, events := rw(v, surface, w.stats.Explain()) w.stats.Record(surface, base, events) + // `value` is the same distinction RewriteText draws above: in an attribute a + // trailing dot is the host's root label, in prose it is a full stop. + value := surface == SurfaceHTMLAttr if surface == SurfaceHTMLAttr { out = w.urlLeaks(base, out, singleURLAttr(name)) + } else { + // Every other surface gets the locator too. It ran on attributes alone, + // so every *ASCII* URL-parser shape — `https:\h`, `https:///h`, + // `http:h`, a tab in the host, `u@h`, `%2e` for a dot — went out + // untouched in an inline script, an inline stylesheet, a text node and a + // comment, with the census reporting a clean page. §5.2 calls inline + // script and style Tier 1 and "where the CSS and JS URLs actually are", + // and `fetch("https://www%2eexample%2efi/a")` is a production request + // carrying the developer's session. + out = w.normaliseURLLeak(surface, base, out, false) + } + // And a host that only folds onto a canonical one — a soft hyphen, fullwidth + // letters, U+3002 for the dots, NFD — shares no bytes with its pattern on any + // surface either. + out = w.foldedHostLeak(surface, base, out, value) + // CSS unescapes before the URL parser runs, so a style surface needs that + // view too — see stripForCSS. + if surface == SurfaceInlineStyle || (surface == SurfaceHTMLAttr && len(name) == 5 && bytes.EqualFold(name, []byte("style"))) { + out = w.cssEscapeLeak(out) } - // Every surface, because a host that only folds onto a canonical one — a - // soft hyphen, fullwidth letters, U+3002 for the dots, NFD — shares no bytes - // with its pattern anywhere, not just in a URL attribute. - out = w.foldedHostLeak(surface, base, out) // Every surface, because a host that only folds onto a canonical one — a // soft hyphen, fullwidth letters, U+3002 for the dots, NFD — shares no bytes // with its pattern anywhere, not just in a URL attribute. @@ -327,7 +345,7 @@ func (w *HTML) urlLeaks(base int, v []byte, isURL bool) []byte { return v } // On the decoded form, so the two compose. When nothing decoded, dec is v. - if out := w.normaliseURLLeak(base, dec); !bytes.Equal(out, dec) { + if out := w.normaliseURLLeak(SurfaceHTMLObfuscated, base, dec, true); !bytes.Equal(out, dec) { return out } return v diff --git a/internal/rewrite/json.go b/internal/rewrite/json.go index 431c10e..1d984ee 100644 --- a/internal/rewrite/json.go +++ b/internal/rewrite/json.go @@ -204,6 +204,18 @@ func decodeJSONLeak(m *origin.Matcher, v []byte) ([]byte, bool) { dec, _ = decodeURLRefs(dec) out, _ := m.Rewrite(dec, SurfaceJSONEscape, false) + // The same two catchers the HTML surfaces get. Without them the REST body + // was the one surface with neither: `{"u":"https:\\h/x"}` and an NFD host in + // content.rendered both went out untouched while the identical bytes in the + // page were rewritten — which is the hazard this function's own header + // describes, "the page rewrites; the REST API does not, so Gutenberg and + // every JS fetch get production URLs". And because this is also the + // request-body path, a Gutenberg save wrote the unfolded host back into the + // database. + // + // value=true: a JSON string holding a URL is a value, so a trailing dot is + // the host's root label rather than a sentence's full stop. + out = hostsFor(m).rewriteAll(out, true) if bytes.Equal(out, dec) { return nil, false } diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go index 1fd7370..b3d4c66 100644 --- a/internal/rewrite/urlobf.go +++ b/internal/rewrite/urlobf.go @@ -3,6 +3,7 @@ package rewrite import ( "bytes" "strings" + "sync" "github.com/generoi/hostshift/internal/origin" ) @@ -96,7 +97,8 @@ func isSlashish(c byte) bool { return c == '/' || c == '\\' } // back to where each surviving byte came from. type normalised struct { b []byte - pos []int // pos[i] is the index in the original of b[i] + pos []int // pos[i] is where b[i] came from in the original + end []int // end[i] is one past the *end* of what b[i] came from } // stripForURL removes what the parser removes before it parses: leading and @@ -114,7 +116,7 @@ func stripForURL(v []byte) normalised { for hi > lo && v[hi-1] <= 0x20 { hi-- } - n := normalised{b: make([]byte, 0, hi-lo), pos: make([]int, 0, hi-lo)} + n := normalised{b: make([]byte, 0, hi-lo), pos: make([]int, 0, hi-lo), end: make([]int, 0, hi-lo)} for i := lo; i < hi; { if isURLStripped(v[i]) { i++ @@ -128,11 +130,83 @@ func stripForURL(v []byte) normalised { } n.b = append(n.b, v[i]) n.pos = append(n.pos, i) + n.end = append(n.end, i+1) i++ } return n } +// stripForCSS is stripForURL with CSS escapes decoded first. +// +// `https\3a\2f\2fwww.example.fi/x` is a CSS-level spelling of an absolute URL: +// the CSS tokenizer unescapes it *before* anything sees a URL, so the locator — +// which models the URL parser and nothing else — cannot reach it by +// construction, and the byte matcher sees no `://` at all. Measured in Chrome, +// both `cssText` and `getComputedStyle().backgroundImage` come back as +// `url("https://www.example.fi/…")`, a live production fetch. +// +// One escape is a backslash, one to six hex digits, and an optional single +// trailing whitespace which is part of the escape rather than of the value. +func stripForCSS(v []byte) normalised { + if bytes.IndexByte(v, '\\') < 0 { + return stripForURL(v) + } + dec := make([]byte, 0, len(v)) + pos := make([]int, 0, len(v)) + end := make([]int, 0, len(v)) + for i := 0; i < len(v); { + if v[i] != '\\' || i+1 >= len(v) { + dec = append(dec, v[i]) + pos = append(pos, i) + end = append(end, i+1) + i++ + continue + } + j, val, digits := i+1, 0, 0 + for j < len(v) && digits < 6 { + d, ok := digitVal(v[j], 16) + if !ok { + break + } + val = val*16 + d + j++ + digits++ + } + if digits == 0 { + // An escaped literal: the next character stands for itself. + dec = append(dec, v[i+1]) + pos = append(pos, i) + end = append(end, i+2) + i += 2 + continue + } + if j < len(v) && (v[j] == ' ' || isURLStripped(v[j])) { + j++ // the one whitespace that terminates an escape + } + if val == 0 || val > 0x10FFFF { + val = 0xFFFD + } + for _, c := range []byte(string(rune(val))) { + dec = append(dec, c) + pos = append(pos, i) + end = append(end, j) + } + i = j + } + // Now the URL parser's own removals, over the decoded bytes, carrying the + // map through. + n := normalised{b: make([]byte, 0, len(dec)), pos: make([]int, 0, len(dec)), end: make([]int, 0, len(dec))} + for i := 0; i < len(dec); i++ { + if isURLStripped(dec[i]) { + continue + } + n.b = append(n.b, dec[i]) + n.pos = append(n.pos, pos[i]) + n.end = append(n.end, end[i]) + } + return n +} + // removableRef reports the length of a character reference at b that spells a // character the URL parser removes, or 0. func removableRef(b []byte) int { @@ -325,7 +399,7 @@ func urlTokenStarts(v []byte) []int { // O(k·n) — measured at 55 seconds for a 320 KB attribute value, which // extrapolates to hours at the shipped 4 MiB token cap. That is the same bug // class scan.go documents having already fixed once. -func (h *hostReplacer) locateHostIn(n normalised, at int) (from, until int, to origin.Origin, ok bool) { +func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, until int, to origin.Origin, ok bool) { rel := h.authorityStart(n.b[at:]) if rel < 0 { return 0, 0, to, false @@ -335,21 +409,35 @@ func (h *hostReplacer) locateHostIn(n normalised, at int) (from, until int, to o return 0, 0, to, false } hs, he, port := hostRange(n.b, start) + // A trailing dot is the host's root label inside a URL and a full stop in + // prose, and only the caller knows which surface it is on — the same + // distinction Matcher.RewriteText exists for. Absorbing it in a text node + // would eat the sentence's punctuation. + if !value && he > hs && n.b[he-1] == '.' { + he-- + } if hs >= he { return 0, 0, to, false } host := h.key(percentDecode(n.b[hs:he])) - to, ok = h.to[host] - if !ok && port != "" { + // host:port first. Backwards, the bare-host pair won and an explicit + // :8080 origin was rewritten to the wrong variant — while the byte matcher, + // which disambiguates by port, got the same input right, so the two halves + // of the engine disagreed. §5.4 says matching is on exact origin equality, + // and :8080 is a different origin. + if port != "" { to, ok = h.to[host+":"+port] } + if !ok { + to, ok = h.to[host] + } if !ok { return 0, 0, to, false } // Whatever the original spelled the host with — a tab, a reference, a // percent escape — the replaced range covers all of it, because pos maps // every surviving byte back and the removed ones lie between them. - return n.pos[hs], n.pos[he-1] + 1, to, true + return n.pos[hs], n.end[he-1], to, true } // foldedHostLeak catches a host that only *folds* onto a canonical one. @@ -366,7 +454,7 @@ func (h *hostReplacer) locateHostIn(n normalised, at int) (from, until int, to o // the pattern already or is not the canonical host at all. That one test skips // the entire pass on most documents, and on the rest the work is bounded by the // number of `//` runs. -func (w *HTML) foldedHostLeak(surface string, base int, v []byte) []byte { +func (w *HTML) foldedHostLeak(surface string, base int, v []byte, value bool) []byte { if w.hosts == nil || len(w.hosts.to) == 0 { return v } @@ -385,11 +473,27 @@ func (w *HTML) foldedHostLeak(surface string, base int, v []byte) []byte { var out []byte prev := 0 for i := 0; i+1 < len(n.b); i++ { - if !isSlashish(n.b[i]) || !isSlashish(n.b[i+1]) || n.pos[i] < prev { + if !isSlashish(n.b[i]) { continue } - from, until, to, ok := w.hosts.locateHostIn(n, i) + // Jump to the end of the run rather than trying every offset inside it. + // authorityStart walks the run from wherever it is asked, so starting at + // each of its L bytes was L²/2 work — 20 seconds for a 400,000-byte run, + // extrapolating to about 38 minutes at the 4 MiB token cap. That is the + // bug locateHostIn's own comment says was already fixed once, live again + // in its other caller. One non-ASCII byte anywhere in the value is the + // only other trigger. + run := i + for run < len(n.b) && isSlashish(n.b[run]) { + run++ + } + if run-i < 2 || n.pos[i] < prev { + i = run - 1 + continue + } + from, until, to, ok := w.hosts.locateHostIn(n, i, value) if !ok { + i = run - 1 continue } // Nothing to do when the bytes already say the variant, and nothing to @@ -429,7 +533,7 @@ func hasNonASCII(b []byte) bool { // the separator however it was spelled, userinfo, port, path, query, fragment, // and every byte between the entries of a list — is copied through, so this // cannot damage a value it does not need to fix. -func (w *HTML) normaliseURLLeak(base int, v []byte) []byte { +func (w *HTML) normaliseURLLeak(surface string, base int, v []byte, value bool) []byte { if w.hosts == nil || len(w.hosts.to) == 0 { return v } @@ -440,16 +544,16 @@ func (w *HTML) normaliseURLLeak(base int, v []byte) []byte { if off < len(n.pos) && n.pos[off] < prev { continue // inside a host already replaced } - from, until, to, ok := w.hosts.locateHostIn(n, off) + from, until, to, ok := w.hosts.locateHostIn(n, off, value) if !ok { continue } out = append(out, v[prev:from]...) out = append(out, to.Host...) prev = until - w.stats.Record(SurfaceHTMLObfuscated, base, []origin.Event{{ + w.stats.Record(surface, base, []origin.Event{{ Offset: base + from, - Surface: SurfaceHTMLObfuscated, + Surface: surface, Action: origin.ActionRewrote, Text: string(v[from:until]), }}) @@ -459,3 +563,86 @@ func (w *HTML) normaliseURLLeak(base int, v []byte) []byte { } return append(out, v[prev:]...) } + +// hostsFor gives a matcher its host table, built once and cached on the matcher +// so the JSON path does not rebuild it per string. +var hostsCache sync.Map // *origin.Matcher -> *hostReplacer + +func hostsFor(m *origin.Matcher) *hostReplacer { + if h, ok := hostsCache.Load(m); ok { + return h.(*hostReplacer) + } + h := newHostReplacer(m) + hostsCache.Store(m, h) + return h +} + +// rewriteAll applies both catchers to a standalone buffer — the JSON path, which +// has no HTML tokenizer around it. Counters are the caller's business; the +// events this would emit duplicate the ones RewriteJSON already records. +func (h *hostReplacer) rewriteAll(v []byte, value bool) []byte { + if h == nil || len(h.to) == 0 { + return v + } + v = h.spliceHosts(v, urlTokenStarts, value) + if hasNonASCII(v) { + v = h.spliceHosts(v, slashRunStarts, value) + } + return v +} + +// slashRunStarts yields the first byte of each run of two or more slashes, which +// is where a scheme-relative authority can begin. +func slashRunStarts(b []byte) []int { + var out []int + for i := 0; i < len(b); i++ { + if !isSlashish(b[i]) { + continue + } + run := i + for run < len(b) && isSlashish(b[run]) { + run++ + } + if run-i >= 2 { + out = append(out, i) + } + i = run - 1 + } + return out +} + +func (h *hostReplacer) spliceHosts(v []byte, starts func([]byte) []int, value bool) []byte { + return h.spliceHostsIn(stripForURL(v), v, starts, value) +} + +func (h *hostReplacer) spliceHostsIn(n normalised, v []byte, starts func([]byte) []int, value bool) []byte { + var out []byte + prev := 0 + for _, off := range starts(n.b) { + if off < len(n.pos) && n.pos[off] < prev { + continue + } + from, until, to, ok := h.locateHostIn(n, off, value) + if !ok { + continue + } + if from < prev { + continue + } + out = append(out, v[prev:from]...) + out = append(out, to.Host...) + prev = until + } + if out == nil { + return v + } + return append(out, v[prev:]...) +} + +// cssEscapeLeak is the CSS-tokenizer view of a style surface. +func (w *HTML) cssEscapeLeak(v []byte) []byte { + if w.hosts == nil || len(w.hosts.to) == 0 || bytes.IndexByte(v, '\\') < 0 { + return v + } + return w.hosts.spliceHostsIn(stripForCSS(v), v, urlTokenStarts, true) +} diff --git a/internal/rewrite/urlobf_test.go b/internal/rewrite/urlobf_test.go index 8a768df..43a3fb7 100644 --- a/internal/rewrite/urlobf_test.go +++ b/internal/rewrite/urlobf_test.go @@ -114,6 +114,71 @@ func TestObfuscatedOriginsAreRewritten(t *testing.T) { } } +// The locator ran on attribute values only, so every *ASCII* URL-parser shape +// went out untouched in an inline script, an inline stylesheet, a text node and +// a comment — with the census reporting a clean page, which is the exact +// property this file's header says was fixed one surface over. +// +// §5.2 calls inline script and style Tier 1 and "where the CSS and JS URLs +// actually are", and `fetch("https://www%2eexample%2efi/a")` is a production +// request carrying the developer's session. +func TestObfuscatedOriginsOnEverySurface(t *testing.T) { + m := obfMatcher(t) + for _, shape := range []string{ + `https:\\www.example.fi/x`, + `https:///www.example.fi/x`, + `http:www.example.fi/x`, + `https://u@www.example.fi/x`, + `https://www%2eexample%2efi/x`, + "https://www.example .fi/x", + } { + for _, s := range []struct{ name, tmpl string }{ + {"inline script", ``}, + {"inline style", ``}, + {"text", `

%s

`}, + {"comment", ``}, + } { + t.Run(s.name+" "+shape, func(t *testing.T) { + in := strings.Replace(s.tmpl, "%s", shape, 1) + out := rewriteHTML(t, m, in, NewStats(false)) + if strings.Contains(out, "www.example.fi") { + t.Errorf("a production origin reached the browser:\n%s", out) + } + }) + } + } +} + +// CSS unescapes before the URL parser runs, so `aff` is a spelling of +// `://` that the locator cannot reach by construction and the byte matcher +// cannot see at all. Measured in Chrome, both cssText and +// getComputedStyle().backgroundImage resolve it to a live production fetch. +func TestCSSEscapesAreRewritten(t *testing.T) { + m := obfMatcher(t) + for _, c := range []struct{ name, in string }{ + {"style element", ``}, + {"style attribute", `
`}, + {"escape with a terminating space", ``}, + } { + t.Run(c.name, func(t *testing.T) { + out := rewriteHTML(t, m, c.in, NewStats(false)) + if strings.Contains(out, "www.example.fi") { + t.Errorf("a production origin reached the browser:\n%s", out) + } + }) + } +} + +// A trailing dot is the host's root label in a URL and a full stop in prose. +// Absorbing it on a text surface ate the sentence's punctuation. +func TestRootDotIsPunctuationInProse(t *testing.T) { + out := rewriteHTML(t, obfMatcher(t), + `

Visit https://www.example.fi. Then leave.

`, NewStats(false)) + if !strings.Contains(out, "https://wt-a--example.ddev.site. Then") { + t.Errorf("the full stop did not survive:\n%s", out) + } +} + // The census has to see them too. A leak the counters call zero is a leak // nobody goes looking for, and --json reporting a clean page is what made this // survive three audit rounds. diff --git a/test/addon-command.sh b/test/addon-command.sh index 34183ee..e37d2e4 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -153,9 +153,19 @@ rm "$wt/hostshift.yaml" # which has no cross-project uniqueness check, resolves the tie by rule length, # which a worktree's longer directory name always wins. `ddev start` says # nothing about it. -out="$(cd "$wt" && "$cmd" env --slug wt-a 2>&1 || true)" +# `init --dry-run`, because the note is init's: it says those hostnames serve +# this worktree "until `ddev restart`", which is true when init says it and +# false ever after — so env, wp-cli and loopback printing it told a developer +# with a healthy worktree that it was stealing a hostname it was not. +out="$(cd "$wt" && "$cmd" init --dry-run --slug wt-a 2>&1 || true)" contains "an inherited hostname the parent also serves is called out" \ "also registered nat.acme.ddev.site" "$out" +# ...and not by the read-only subcommands, where it is false. +out="$(cd "$wt" && "$cmd" env --slug wt-a 2>&1 || true)" +case "$out" in + *"also registered"*) fail "and env does not repeat it" "$out" ;; + *) pass "and env does not repeat it" ;; +esac # The map the proxy cannot work out for itself: the compose service mounts only # this worktree, so the parent's hostnames are unknowable inside the container — @@ -242,7 +252,14 @@ check "init is idempotent" "$first" "$(cat "$wt/.ddev/.env")" # # Run against the real released command out of the tag, not a stub of it, and # against every tag there is — a stub only proves what its author remembered. -hook="$(sed -n 's/^ *- *exec-host: *//p' "$repo/ddev/config.hostshift.yaml")" +# The hook is a YAML block scalar now, so take the whole indented body rather +# than one line after the key. +hook="$(awk ' + /^ *- *exec-host: *\|/ { inblock = 1; next } + inblock && /^ / { sub(/^ /, ""); print; next } + inblock { exit } +' "$repo/ddev/config.hostshift.yaml")" +[ -n "$hook" ] || hook="$(sed -n 's/^ *- *exec-host: *//p' "$repo/ddev/config.hostshift.yaml")" [ -n "$hook" ] || fail "the post-start hook line could not be read" "" for tag in $(cd "$repo" && git tag -l 'v*'); do old_cmd="$work/hostshift-$tag" @@ -257,10 +274,17 @@ for tag in $(cd "$repo" && git tag -l 'v*'); do out="$(cd "$wt" && bash -c "$hook" 2>&1 || true)" rm -f "$wt/.ddev/commands/host/hostshift" case "$out" in - *"unknown argument"*|*"usage:"*) + *"unknown argument"*|*"usage: ddev hostshift "*) fail "the post-start hook parses under $tag's command" "$out" ;; *) pass "the post-start hook parses under $tag's command" ;; esac + # ...and it warns, on every start, that the old command is still in place — + # one line in an `add-on get` output is not enough, and DDEV itself tells + # developers to strip the marker that keeps the command replaceable. + case "$out" in + *"predates this add-on"*) pass "and says the command was not replaced" ;; + *) fail "and says the command was not replaced" "$out" ;; + esac done # Mid-rebase, a parent's config.yaml has conflict markers and is not valid YAML. From 6a2d242bc4cb789ba57ce31515423645166fddc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 17:59:31 -0300 Subject: [PATCH 012/124] Bound the host scan, reach the request direction, and apply the web check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round thirteen, and one correction: 7be9371's message claims `check` now inspects web's VIRTUAL_HOST. It does not. The edit script that was supposed to add it ended without writing the file, and I did not verify before describing it as done. The daily-work agent found the claim false by grepping for it. It is in this commit, actually applied and actually tested. The engine: * The URL locator was quadratic, and reachable from a request body. Nothing bounded the authority scan, so where a region held no delimiter it ran to the end of the buffer — and urlTokenStarts offers a candidate at every `http:`, which authorityStart accepts with zero slashes because the scheme differs from the document's. A body of `"http: "` repeated measured 7.3 seconds at 192 KB, extrapolating to about four hours of pinned CPU for one 8 MiB JSON POST, with no timeout on that path. A host is at most 253 octets and percent-encoding inflates that threefold, so nothing longer can be one. 1.5 MB of the same shape now takes 0.28s. Third instance of the bug class scan.go documents; the other two callers were fixed and this one was not. * The locator was allow-listed on attributes while the fold and the CSS pass were not, so `data-large_image="https:\\www.example.fi/a.jpg"` came out byte-identical while the same attribute's folded spelling was rewritten. PLAN §5.2 names that attribute in the same sentence as data-src, which *was* in the list. The list existed because the pass used to delete whitespace; it stopped doing that, so it is gone entirely and every attribute gets the locator. * The entity pass shadowed the locator: urlLeaks returned as soon as decodeEntityLeak fired, so a second origin in the same value went out untouched — `srcset="https://a/1 1x, https:\\a/2 2x"` rewrote the first entry and left the second dereferencing production, while --json reported a successful rewrite on a value that still leaked. * The request direction could not reverse what the response manufactures. The splice replaces only the matched host, so the browser gets `https:\\wt-a--x.ddev.site/a` — an obfuscated *variant*. The byte matcher's prefilter needs `//`, `\/` or `%2F` and that has none, so a form post carrying it back went upstream unreversed and wrote a worktree-local hostname into the database §4.3 says stays byte-identical to production and is shared by canonical, every worktree and CI. That is worse than a leak: it damages the artifact the design exists to protect. The request line, query, headers, form and multipart bodies now get both catchers — and so do Location, Link, Refresh and CSP on the way back, where every obfuscated and folded spelling had been passing straight through. The add-on: * `check` inspects web's VIRTUAL_HOST — the change 7be9371 claimed. It looked at the proxy container four ways and never at web, which is why a failed narrowing was invisible by construction. * A parent that registers this project's *own* hostname is a collision nothing looked for. Keeping that name out of the subtraction stops the list emptying but does not make the project reachable there: both put it on web, the rules are the same length, and traefik breaks the tie by an order neither controls — measured, the parent won, so `ddev describe` in the worktree named a URL serving someone else's code and database. * Installed-but-not-configured is inert, except in a worktree of a project that declares additional_hostnames. There, `ddev add-on get`'s own advice to run `ddev restart` registers the parent's blog hostnames on this project's web and the router prefers this one — so the add-on created the hazard at the one moment it could have mentioned it. * An `info/exclude` with no trailing newline had the marker welded onto the developer's last rule, which stopped ignoring that rule, defeated the idempotency guard so every later install appended another block, and made removal impossible, since none of its paths can find a marker that is not on a line of its own. * The legacy branch of the removal action still used `grep -vxF` as a bare statement, which exits 1 when it selects nothing and aborts the action under `set -eu -o pipefail` — so the block survived, the temp file leaked, and the farewell action never ran. The comment directly above it names that exact case. It uses the same contiguous-run awk as the migration now. --- ddev/commands/host/hostshift | 70 ++++++++++++++++++++++++ ddev/install.yaml | 28 +++++++--- internal/proxy/multipart.go | 1 + internal/proxy/proxy.go | 9 ++++ internal/proxy/roundtrip_test.go | 93 ++++++++++++++++++++++++++++++++ internal/rewrite/html.go | 68 +++++++++-------------- internal/rewrite/urlobf.go | 37 ++++++++++++- test/addon-command.sh | 20 +++++++ 8 files changed, 275 insertions(+), 51 deletions(-) create mode 100644 internal/proxy/roundtrip_test.go diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index d3c3486..dd13309 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -129,6 +129,26 @@ esac # start — the one file the whole check is about, silently unread. if [ "$cmd" = "check" ] && { [ -n "$quiet" ] || [ -n "${HOSTSHIFT_HOOK:-}" ]; }; then if [ ! -e .ddev/.env ]; then + # Inert, but not silent in the one case where installing has already done + # harm. `ddev add-on get` tells you to run `ddev restart`, and in a linked + # worktree that restart registers every hostname inherited from the parent's + # *tracked* config.yaml — a multisite's blog hostnames included — on this + # project's web, where the router prefers it over the parent. The parent's + # blog is then served by an empty database, `ddev describe` on the parent + # still lists the URL, and the moment the add-on could have said so is the + # moment it created the hazard. + # + # Cheap: the .git file names the parent, and the question is only whether it + # declares extra hostnames. No map, no binary. + if grep -q '/worktrees/' .git 2>/dev/null; then + pdir="$(sed -n 's/^gitdir: //p' .git 2>/dev/null | sed 's|/\.git/worktrees/.*||')" + if [ -n "$pdir" ] && grep -qs '^additional_hostnames:' "$pdir/.ddev/config.yaml"; then + echo "hostshift: installed here but not configured, and this is a worktree of" >&2 + echo " a project that declares additional_hostnames. Until you run" >&2 + echo " \`ddev hostshift init\`, this worktree also answers to those, and the" >&2 + echo " router prefers it over $(basename "$pdir") for any they share." >&2 + fi + fi exit 0 elif [ ! -r .ddev/.env ]; then echo "hostshift: .ddev/.env exists but cannot be read, so nothing here can be" >&2 @@ -696,6 +716,31 @@ fi # and the post-start hook printed it on every start, including the restart that # had just handed those hostnames back to the parent. Four lines of "until # `ddev restart`, those serve this worktree" from the hook of that very restart. +# The parent claiming *this* project's own hostname is a collision too, and +# nothing looked for it. +# +# Keeping the own hostname out of the subtraction stops HOSTSHIFT_WEB_HOSTS +# emptying, but it does not make the project reachable there: both projects then +# put that name on web's VIRTUAL_HOST, the rules are the same length, and traefik +# breaks the tie by an ordering neither project controls. Measured, the parent +# won — so `ddev launch`, `ddev describe` and every `ddev restart` line in the +# worktree named a URL serving someone else's code and database. The collision +# scan only ever looked at siblings' *variants*. +if [ -n "$parent_hosts" ] && [ -n "$own" ]; then + case " +$parent_hosts +" in *" +$own +"*) + echo "hostshift: warning: the parent checkout also registers $own," >&2 + echo " which is this project's own hostname. Both put it on web, the router" >&2 + echo " breaks the tie by an order neither controls, and \`ddev describe\` here" >&2 + echo " will name a URL that may serve the parent. Drop it from the parent's" >&2 + echo " additional_hostnames, or rename this worktree." >&2 + ;; + esac +fi + # `init` only, not every subcommand that is not check. # # The note says those hostnames serve this worktree "until `ddev restart`", @@ -931,6 +976,31 @@ if [ "$cmd" = "check" ]; then exit 2 fi + # web's VIRTUAL_HOST, not only the proxy's. + # + # `check` inspected the hostshift container four different ways and never once + # looked at web — so when the narrowing failed and web came up serving every + # hostname the worktree registers, including the parent's blog, check could not + # see it by construction: it compares .ddev/.env against a recomputation of + # .ddev/.env, and both said the same wrong thing. + # + # Only when the file asks for something. An empty HOSTSHIFT_WEB_HOSTS is a real + # answer but an unconfigured project has no answer at all, and a container that + # is not running tells us nothing either. + webenv="$(docker inspect "ddev-$name-web" \ + --format '{{range .Config.Env}}{{println .}}{{end}}' 2>/dev/null || true)" + webrunning="$(printf '%s' "$webenv" | sed -n '/^VIRTUAL_HOST=/{s///p;q;}')" + if [ -n "$webenv" ] && [ "$webrunning" != "$web" ]; then + echo "hostshift: web is serving a different set of hostnames than .ddev/.env" >&2 + echo " asks for. It has:" >&2 + echo " ${webrunning:-(nothing)}" >&2 + echo " and this checkout resolves to:" >&2 + echo " ${web:-(nothing)}" >&2 + echo " Anything there that also belongs to the parent is served by this" >&2 + echo " worktree instead, and the router decides which. Run \`ddev restart\`." >&2 + exit 2 + fi + if [ -n "$parent_declares" ]; then echo "hostshift: refusing to call this healthy — see the warning above." >&2 echo " The map does not name the hostnames the database holds." >&2 diff --git a/ddev/install.yaml b/ddev/install.yaml index 345218b..365fce5 100644 --- a/ddev/install.yaml +++ b/ddev/install.yaml @@ -83,6 +83,16 @@ pre_install_actions: ' "$ex" > "$tmp" && cat "$tmp" > "$ex" rm -f "$tmp" fi + # A file that does not end in a newline gets one first. Appending to + # `…node_modules/` with no trailing newline welded the marker onto the + # developer's last rule — `node_modules/# hostshift (ddev add-on) — begin` — + # which stopped ignoring node_modules, defeated the idempotency guard (the + # welded line is not an exact match, so every later install appended another + # whole block), and made removal impossible, since none of its three paths + # can find a marker that is not on a line of its own. + if [ -s "$ex" ] && [ -n "$(tail -c 1 "$ex")" ]; then + echo "" >> "$ex" + fi { echo '# hostshift (ddev add-on) — begin' echo '.ddev/**/*hostshift*' @@ -242,12 +252,18 @@ removal_actions: { drop = 0; print } ' "$ex" > "$tmp" && ok=1 else - # grep exits 1 when it selects no lines, which is the shape the install - # creates when info/exclude did not exist before. That is a success with an - # empty result, not a failure. - grep -vxF -e '# hostshift (ddev add-on)' -e '.ddev/**/*hostshift*' \ - -e '.ddev/.env' -e 'wp-cli.local.yml' "$ex" > "$tmp" - [ $? -le 1 ] && ok=1 + # The same contiguous-run rule as the migration, for a block written by a + # version that had no markers at all. Not `grep -vxF`: that removes every + # matching line anywhere in the file, which takes the developer's own + # `.ddev/.env` rule with it — and, as a bare statement returning 1 when it + # selects nothing, it aborted this whole action under `set -eu -o + # pipefail`, so the block survived, the temp file leaked, and the farewell + # action never ran. awk always exits 0 here. + awk ' + $0 == "# hostshift (ddev add-on)" { drop = 1; next } + drop && ($0 == ".ddev/**/*hostshift*" || $0 == ".ddev/.env" || $0 == "wp-cli.local.yml") { next } + { drop = 0; print } + ' "$ex" > "$tmp" && ok=1 fi # The *status*, not the size. An empty result is correct when the block was # the whole file, so testing `-s "$tmp"` left the block behind in exactly diff --git a/internal/proxy/multipart.go b/internal/proxy/multipart.go index 58354af..f841d39 100644 --- a/internal/proxy/multipart.go +++ b/internal/proxy/multipart.go @@ -90,6 +90,7 @@ func rewriteMultipart(body []byte, ct string, m *origin.Matcher, st *rewrite.Sta } nv, ev := m.Rewrite(body[bodyStart:end], rewrite.SurfaceRequestBody, explain) + nv = rewrite.HostLeaks(m, nv, true) st.Record(rewrite.SurfaceRequestBody, bodyStart, ev) if bytes.Equal(nv, body[bodyStart:end]) { continue diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 6e1658b..6783566 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -224,6 +224,7 @@ func (p *Proxy) rewriteRequest(r *httputil.ProxyRequest) { // which is what makes redirect_to=https%3A%2F%2F… work. if q := r.Out.URL.RawQuery; q != "" { out, ev := rev.Rewrite([]byte(q), rewrite.SurfaceRequestLine, explain) + out = rewrite.HostLeaks(rev, out, true) p.Stats.Record(rewrite.SurfaceRequestLine, 0, ev) if !p.DryRun { r.Out.URL.RawQuery = string(out) @@ -235,6 +236,7 @@ func (p *Proxy) rewriteRequest(r *httputil.ProxyRequest) { // — URL.String() percent-encodes, which would break test 24's spirit. if esc := r.Out.URL.EscapedPath(); esc != "" { out, ev := rev.Rewrite([]byte(esc), rewrite.SurfaceRequestLine, explain) + out = rewrite.HostLeaks(rev, out, true) p.Stats.Record(rewrite.SurfaceRequestLine, 0, ev) if !p.DryRun && string(out) != esc { if dec, err := url.PathUnescape(string(out)); err == nil { @@ -248,6 +250,7 @@ func (p *Proxy) rewriteRequest(r *httputil.ProxyRequest) { vs := r.Out.Header.Values(h) for i, v := range vs { out, ev := rev.Rewrite([]byte(v), rewrite.SurfaceHeader, explain) + out = rewrite.HostLeaks(rev, out, true) p.Stats.Record(rewrite.SurfaceHeader, 0, ev) if !p.DryRun { vs[i] = string(out) @@ -299,6 +302,7 @@ func (p *Proxy) modifyResponse(resp *http.Response) error { if st != nil && isRedirect(resp.StatusCode) && safeMethod(resp.Request) { if loc := resp.Header.Get("Location"); loc != "" { rewritten, _ := fwd.Rewrite([]byte(loc), rewrite.SurfaceHeader, false) + rewritten = rewrite.HostLeaks(fwd, rewritten, true) if sameURL(string(rewritten), st.url) { if p.StrictOrigins { p.log().Warn("self-redirect suppressed by --strict-origins", "url", st.url) @@ -324,6 +328,10 @@ func (p *Proxy) modifyResponse(resp *http.Response) error { vs := resp.Header.Values(h) for i, v := range vs { out, ev := fwd.Rewrite([]byte(v), rewrite.SurfaceHeader, explain) + // Location, Link, Refresh and CSP had the byte matcher alone, so + // every obfuscated and folded spelling passed straight through — and + // a Location is followed by the browser through the URL parser. + out = rewrite.HostLeaks(fwd, out, true) p.Stats.Record(rewrite.SurfaceHeader, 0, ev) if !p.DryRun && string(out) != v { vs[i] = string(out) @@ -530,6 +538,7 @@ func (p *Proxy) rewriteRequestBody(r *http.Request, st *state) { default: var ev []origin.Event out, ev = rev.Rewrite(buf, rewrite.SurfaceRequestBody, explain) + out = rewrite.HostLeaks(rev, out, true) p.Stats.Record(rewrite.SurfaceRequestBody, 0, ev) } if p.DryRun { diff --git a/internal/proxy/roundtrip_test.go b/internal/proxy/roundtrip_test.go new file mode 100644 index 0000000..dc45c17 --- /dev/null +++ b/internal/proxy/roundtrip_test.go @@ -0,0 +1,93 @@ +package proxy + +import ( + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/generoi/hostshift/internal/origin" + "github.com/generoi/hostshift/internal/rewrite" +) + +// The response manufactures obfuscated *variants*, and the request direction has +// to be able to reverse them. +// +// Only the matched host's byte range is spliced, so `https:\\www.example.fi/a` +// reaches the browser as `https:\\wt-a--example.ddev.site/a`. The byte matcher's +// prefilter needs `//`, `\/` or `%2F` and that string has none, so a form post +// carrying it back went upstream unreversed — writing a worktree-local hostname +// into the database §4.3 says stays byte-identical to production and is shared +// by canonical, every worktree and CI. +func TestObfuscatedVariantsAreReversed(t *testing.T) { + mp, err := origin.NewMap([]origin.Site{{ + Name: "main", + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://wt-a--example.ddev.site"), + }}) + if err != nil { + t.Fatal(err) + } + rev := mp.Reverse() + + for _, c := range []struct{ name, in string }{ + {"backslashes", `https:\\wt-a--example.ddev.site/a`}, + {"three slashes", `https:///wt-a--example.ddev.site/a`}, + {"a differing scheme", `http:wt-a--example.ddev.site/a`}, + {"userinfo", `https://u@wt-a--example.ddev.site/a`}, + {"scheme relative backslashes", `\\wt-a--example.ddev.site/a`}, + } { + t.Run(c.name, func(t *testing.T) { + out := rewrite.HostLeaks(rev, []byte(c.in), true) + if strings.Contains(string(out), "wt-a--example.ddev.site") { + t.Errorf("a variant hostname would be written upstream:\n%s", out) + } + if !strings.Contains(string(out), "www.example.fi") { + t.Errorf("not reversed to the canonical:\n%s", out) + } + }) + } +} + +// A Location is followed by the browser through the URL parser, so an +// obfuscated or folded host in one is a navigation to production. +func TestObfuscatedLocationIsRewritten(t *testing.T) { + mp, err := origin.NewMap([]origin.Site{{ + Name: "main", + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://wt-a--example.ddev.site"), + }}) + if err != nil { + t.Fatal(err) + } + up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", `https:\\www.example.fi/next`) + w.WriteHeader(http.StatusFound) + })) + defer up.Close() + + upURL, err := url.Parse(up.URL) + if err != nil { + t.Fatal(err) + } + p := &Proxy{Map: mp, Upstream: upURL, Stats: rewrite.NewStats(false)} + srv := httptest.NewServer(p.Handler()) + defer srv.Close() + + req, _ := http.NewRequest("GET", srv.URL+"/x", nil) + req.Host = "wt-a--example.ddev.site" + cl := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + }} + resp, err := cl.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + io.Copy(io.Discard, resp.Body) + if loc := resp.Header.Get("Location"); strings.Contains(loc, "www.example.fi") { + t.Errorf("a production origin reached the browser in Location: %s", loc) + } +} diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index afa428c..cc6489a 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -206,43 +206,16 @@ var structuredAttrNames = [][]byte{ []byte("srcset"), []byte("imagesrcset"), []byte("ping"), []byte("srcdoc"), []byte("content"), } -// urlAttrNames are the attributes whose value carries a URL the browser resolves -// through the URL parser — one URL, a list of them, or one embedded in a small -// grammar. +// Every attribute gets the locator, with no allow-list. // -// An allow-list, not everything: the pass rewrites what the *parser* would read -// as a host, and in a title or an alt an origin-shaped run of bytes is prose the -// browser never dereferences. Leaving those alone is correct, not cautious. -// -// The list ones — srcset, imagesrcset, ping — used to be excluded because the -// pass deleted whitespace, which is their separator. It no longer does: it -// replaces the host's byte range and copies everything else, so a list is safe -// as long as the locator is offered each entry. See urlTokenStarts. -var urlAttrNames = [][]byte{ - []byte("href"), []byte("src"), []byte("action"), []byte("formaction"), - []byte("cite"), []byte("poster"), []byte("data"), []byte("manifest"), - []byte("longdesc"), []byte("background"), []byte("codebase"), - []byte("profile"), []byte("itemid"), []byte("xlink:href"), - // Lists, and small grammars with a URL inside: srcset's `URL descriptor, - // …`, ping's space-separated list, a meta refresh's `0;url=…`, and a style - // attribute's `url(…)`. - []byte("srcset"), []byte("imagesrcset"), []byte("ping"), - []byte("content"), []byte("style"), - // srcdoc holds a whole document whose base URL is the parent's, so every - // href inside it is navigable. data-src and data-srcset are what the fleet's - // lazyload and the WooCommerce gallery assign to src, which §5.2 already - // names as a real surface. - []byte("srcdoc"), []byte("data-src"), []byte("data-srcset"), -} - -func singleURLAttr(name []byte) bool { - for _, s := range urlAttrNames { - if len(name) == len(s) && bytes.EqualFold(name, s) { - return true - } - } - return false -} +// The list existed because the pass used to normalise whole values, deleting +// whitespace — which is content in a title and a separator in a srcset. It +// stopped doing that: it replaces the matched host's byte range and copies +// everything else. Keeping the list only produced a second class of missed +// attribute, on exactly the surface §5.2 already names — +// `data-large_image="https:\\www.example.fi/a.jpg"` came out byte-identical +// while `foldedHostLeak` and `cssEscapeLeak`, which never had a list, rewrote +// the same attribute. The WooCommerce gallery assigns that value to an img.src. // structuredAttr matches on the raw name bytes, case-insensitively. Lowercasing // every attribute name to a string first cost one allocation per attribute — @@ -276,7 +249,7 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b // trailing dot is the host's root label, in prose it is a full stop. value := surface == SurfaceHTMLAttr if surface == SurfaceHTMLAttr { - out = w.urlLeaks(base, out, singleURLAttr(name)) + out = w.urlLeaks(base, out) } else { // Every other surface gets the locator too. It ran on attributes alone, // so every *ASCII* URL-parser shape — `https:\h`, `https:///h`, @@ -334,20 +307,27 @@ func (w *HTML) rewriteValue(surface string, name []byte, base int, v []byte) []b // only ever saw the undecoded text, and every shape needing decode-then-parse // went out untouched: `https://www.example .fi/x` resolves to // production and was the exact case the comment here claimed was covered. -func (w *HTML) urlLeaks(base int, v []byte, isURL bool) []byte { +func (w *HTML) urlLeaks(base int, v []byte) []byte { dec, decoded := decodeURLRefs(v) + // cur is what the locator sees: the decoded form, or the decoded *and + // entity-rewritten* form when that pass fired. Returning early on the + // entity pass's success skipped the locator for the whole value, so a second + // origin in the same value went out untouched — + // `srcset="https://a/1 1x, https:\a/2 2x"` rewrote the first entry + // and left the second dereferencing production, while --json reported a + // successful rewrite on a value that still leaked. + cur := dec if decoded { if out := w.decodeEntityLeak(base, v, dec); out != nil { - return out + cur = out } } - if !isURL { - return v - } - // On the decoded form, so the two compose. When nothing decoded, dec is v. - if out := w.normaliseURLLeak(SurfaceHTMLObfuscated, base, dec, true); !bytes.Equal(out, dec) { + if out := w.normaliseURLLeak(SurfaceHTMLObfuscated, base, cur, true); !bytes.Equal(out, cur) { return out } + if !bytes.Equal(cur, dec) { + return cur + } return v } diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go index b3d4c66..dc4563f 100644 --- a/internal/rewrite/urlobf.go +++ b/internal/rewrite/urlobf.go @@ -315,9 +315,26 @@ func (h *hostReplacer) authorityStart(b []byte) int { // ends belongs to the credentials, so `https://user@host` names host and not // user. The authority ends at the first '/', '\', '?' or '#'; the host itself // also ends at ':', which begins the port. +// maxHost bounds the authority scan. A DNS name is at most 253 octets, and +// percent-encoding can only inflate that threefold, so nothing longer is a host +// this map could ever contain. +// +// Without the bound the scan ran to the end of the buffer whenever the region +// held no delimiter — and `urlTokenStarts` offers a candidate at every `http:`, +// which `authorityStart` accepts with zero slashes because the scheme differs +// from the document's. A body of `"http: "` repeated was therefore k candidates +// times O(n) each: 7 seconds at 192 KB, extrapolating to about four hours of +// pinned CPU for one 8 MiB JSON request body, with no timeout on that path. +// Third instance of the bug class scan.go documents; the other two callers were +// fixed and this one was not. +const maxHost = 253 * 3 + func hostRange(b []byte, at int) (start, end int, port string) { end = len(b) - for i := at; i < len(b); i++ { + if lim := at + maxHost; lim < end { + end = lim + } + for i := at; i < end; i++ { if b[i] == '/' || b[i] == '\\' || b[i] == '?' || b[i] == '#' { end = i break @@ -646,3 +663,21 @@ func (w *HTML) cssEscapeLeak(v []byte) []byte { } return w.hosts.spliceHostsIn(stripForCSS(v), v, urlTokenStarts, true) } + +// HostLeaks applies the URL-parser locator and the IDNA fold to a standalone +// buffer, for the proxy's request line, query, headers and non-HTML bodies. +// +// Those surfaces had the byte matcher alone, and the response side manufactures +// exactly what the byte matcher cannot see: it splices only the matched host, so +// `https:\\www.example.fi/a` goes to the browser as `https:\\wt-a--x.ddev.site/a` +// — an obfuscated *variant*. The byte matcher's prefilter needs `//`, `\/` or +// `%2F`, and that string has none, so a form post carrying it back went upstream +// unreversed and the variant hostname was written into the shared database. That +// is worse than a leak: §4.3's case for the whole design is that the database +// stays byte-identical to production, shared by canonical, every worktree and CI. +func HostLeaks(m *origin.Matcher, b []byte, value bool) []byte { + if m == nil || len(b) == 0 { + return b + } + return hostsFor(m).rewriteAll(b, value) +} diff --git a/test/addon-command.sh b/test/addon-command.sh index e37d2e4..084cc21 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -201,6 +201,26 @@ git -C "$main" worktree remove --force "$twin" # and put $wt back as the dry-run test below expects to find it rm -f "$wt/.ddev/.env" +# Installed but not configured is inert — except in a worktree of a project that +# declares additional_hostnames, where `ddev add-on get`'s own advice to run +# `ddev restart` registers the parent's blog hostnames on this project's web and +# the router prefers this one. The moment the add-on could say so is the moment +# it created the hazard, and it said nothing. +mv "$wt/.ddev/.env" "$work/env.hold" 2>/dev/null || true +out="$(cd "$wt" && HOSTSHIFT_HOOK=1 "$cmd" check 2>&1 || true)" +contains "an unconfigured worktree of a multi-hostname parent is warned" \ + "not configured" "$out" +# ...and a project whose parent declares nothing extra stays silent. +cp "$main/.ddev/config.yaml" "$work/pcfg.hold" +printf 'name: acme\n' > "$main/.ddev/config.yaml" +out="$(cd "$wt" && HOSTSHIFT_HOOK=1 "$cmd" check 2>&1 || true)" +case "$out" in + "") pass "and one whose parent declares nothing extra stays silent" ;; + *) fail "and one whose parent declares nothing extra stays silent" "$out" ;; +esac +cp "$work/pcfg.hold" "$main/.ddev/config.yaml" +mv "$work/env.hold" "$wt/.ddev/.env" 2>/dev/null || true + # A parent that has adopted a committed hostshift.yaml, and a worktree whose # branch predates it. `hostshift hosts -C
` reads only DDEV config, so the # declaration is invisible from here — and the map then names the parent's From ae741b85797df81b7ef642098734b7bce93b0276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 18:33:43 -0300 Subject: [PATCH 013/124] Give the browser model an oracle, and fix the two leaks it found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five audit rounds in a row found a test-28 leak, and every one was the same mistake in a different place: the byte model saying one thing and the WHATWG URL parser saying another. Millions of fuzz executions found none of them, because the engine's invariants held — what was wrong was the *model*, and a property test cannot discover a model it shares with the code under test. Each leak was found by a person reading the spec, which does not scale and has not converged. So the model gets an oracle. test/gen-url-corpus.js enumerates 137,500 URL spellings — five scheme forms, ten slash runs, five userinfo shapes, twenty-three host spellings including every fold family and six near misses, five ports, five tails — and records what Node's URL resolves each to. That is ada, the parser Chrome ships. The corpus is checked in gzipped (389 KB) so the test needs no node, and the contract is test 28 in both directions: the browser resolves it to the canonical ⇒ hostshift must rewrite it the browser resolves it anywhere else ⇒ hostshift must not touch it The second half is what keeps a fix honest: every round that widened the matcher risked rewriting something already correct, and nothing was checking. The whole corpus runs on href; a deterministic slice runs on all seven surfaces, because a missed *surface* is how two of the last three rounds' leaks survived. A third test round-trips every canonical-resolving shape through the response and back through the request direction. It found two things on its first run: * `hostRange` stopped only at `/ \ ? #`, so an embedded URL swallowed whatever followed it — `fetch("https://u@www.example.fi")` took `")` into the host, the fold then failed, and the shape leaked on inline script, inline style and comments while working correctly in an attribute. In an attribute the value *is* the URL, which is why every directed test passed. The byte matcher never had this problem: delimAt knows a quote ends a host. * The bare-host lookup ran whatever the port, so `https://www.example.fi:80` — a different origin under §5.4's exact-origin rule — was rewritten to the variant. The byte matcher, which disambiguates by port, got the same input right. The bare host is now a fallback only when the port is the scheme's default, and the scheme is found by looking backwards as well as forwards, since foldedHostLeak enters at the slash run and saw none. Both are verified load-bearing: removing the authority-byte stop reintroduces 20 corpus failures. One honest note on scope. The oracle models raw URLs, so the two style surfaces skip candidates containing a backslash — CSS unescapes before the URL parser runs, and a raw-URL expectation does not describe what a browser sees there. TestCSSEscapesAreRewritten covers that path against a measurement from Chrome. --- internal/rewrite/audit_m3_test.go | 2 +- internal/rewrite/oracle_test.go | 224 ++++++++++++++++++++++++++++++ internal/rewrite/stats.go | 2 +- internal/rewrite/urlobf.go | 65 ++++++++- test/gen-url-corpus.js | 80 +++++++++++ testdata/url-shapes.tsv.gz | Bin 0 -> 388955 bytes 6 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 internal/rewrite/oracle_test.go create mode 100644 test/gen-url-corpus.js create mode 100644 testdata/url-shapes.tsv.gz diff --git a/internal/rewrite/audit_m3_test.go b/internal/rewrite/audit_m3_test.go index 4c035cc..6d1906c 100644 --- a/internal/rewrite/audit_m3_test.go +++ b/internal/rewrite/audit_m3_test.go @@ -333,7 +333,7 @@ func TestParseURLRef(t *testing.T) { {"&", "", 0}, // '&' excluded: decoding it could splice a new ref {"�", "", 0}, // out of range {"&#;", "", 0}, - {"&sol", "", 0}, // named refs require the semicolon + {"&sol", "", 0}, // named refs require the semicolon {"ä", "ä", 6}, // an IDN host's letter {"¬arealref;", "", 0}, } { diff --git a/internal/rewrite/oracle_test.go b/internal/rewrite/oracle_test.go new file mode 100644 index 0000000..7135ec9 --- /dev/null +++ b/internal/rewrite/oracle_test.go @@ -0,0 +1,224 @@ +package rewrite + +import ( + "bufio" + "compress/gzip" + "encoding/json" + "os" + "strings" + "testing" + + "github.com/generoi/hostshift/internal/origin" +) + +// A differential test against the URL parser browsers actually run. +// +// Five audit rounds in a row found a test-28 leak, and every one was the same +// mistake in a different place: the byte model saying one thing and the WHATWG +// URL parser saying another. Fuzzing the engine's invariants found none of them, +// because the invariants held — what was wrong was the *model*, and a property +// test cannot discover a model it shares with the code under test. +// +// So the model gets an oracle. testdata/url-shapes.tsv is generated by +// test/gen-url-corpus.js from Node's URL, which is ada, the parser Chrome ships. +// Each row is a URL spelling and the host a browser resolves it to against the +// variant origin as base. The contract is then exactly test 28 in both +// directions: +// +// - the browser resolves it to the canonical ⇒ hostshift must rewrite it +// - the browser resolves it anywhere else ⇒ hostshift must not touch it +// +// The second half is the one that keeps a fix honest: every round so far that +// widened the matcher risked rewriting something that was already correct. + +const ( + oracleCanonical = "www.example.fi" + oracleVariant = "wt-a--example.ddev.site" +) + +type urlShape struct { + candidate string + resolved string +} + +func loadShapes(t *testing.T) []urlShape { + t.Helper() + // Gzipped: 137,500 rows is 7 MB of text and 200 KB compressed, and the + // coverage is the point — a smaller corpus would mean choosing in advance + // which shapes matter, which is the judgement that has been wrong five + // rounds running. + f, err := os.Open("../../testdata/url-shapes.tsv.gz") + if err != nil { + t.Fatalf("%v (regenerate: node test/gen-url-corpus.js | gzip -9 > testdata/url-shapes.tsv.gz)", err) + } + defer f.Close() + zr, err := gzip.NewReader(f) + if err != nil { + t.Fatal(err) + } + defer zr.Close() + + var out []urlShape + sc := bufio.NewScanner(zr) + sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) + for sc.Scan() { + line := sc.Text() + tab := strings.LastIndexByte(line, '\t') + if tab < 0 { + continue + } + var cand string + if err := json.Unmarshal([]byte(line[:tab]), &cand); err != nil { + t.Fatalf("corpus line %q: %v", line, err) + } + out = append(out, urlShape{candidate: cand, resolved: line[tab+1:]}) + } + if err := sc.Err(); err != nil { + t.Fatal(err) + } + if len(out) == 0 { + t.Fatal("empty corpus") + } + return out +} + +func oracleMatcher(t *testing.T) *origin.Matcher { + t.Helper() + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://" + oracleCanonical), + Variant: origin.MustParse("https://" + oracleVariant), + }}) + if err != nil { + t.Fatal(err) + } + return m +} + +// surfaces wraps a URL the way each of the rewriter's surfaces would carry it. +// A leak is a leak wherever the origin sits: §5.2 calls inline script and style +// Tier 1, and a comment or a text node is still read by scripts. +var surfaces = []struct { + name string + cssEscapes bool + wrap func(string) string +}{ + {"href", false, func(u string) string { return `x` }}, + {"src", false, func(u string) string { return `` }}, + {"data-attr", false, func(u string) string { return `
x
` }}, + {"text", false, func(u string) string { return `

` + u + `

` }}, + {"inline script", false, func(u string) string { return `` }}, + // cssEscapes: on a style surface the CSS tokenizer runs first, so `\a` is a + // newline escape rather than two literal bytes and the raw-URL oracle no + // longer describes what a browser sees. Those candidates are skipped here; + // TestCSSEscapesAreRewritten covers that path against a real browser. + {"inline style", true, func(u string) string { return `` }}, + {"comment", false, func(u string) string { return `` }}, +} + +func TestURLShapesAgainstBrowserOracle(t *testing.T) { + shapes := loadShapes(t) + m := oracleMatcher(t) + + // The whole corpus on the surface most origins live on, then a deterministic + // slice of it on every surface — a missed *surface* is how two of the last + // three rounds' leaks survived, so the matrix matters as much as the shapes. + run := func(t *testing.T, list []urlShape, only []int) { + t.Helper() + var leaked, falsePos int + for _, sh := range list { + for _, si := range only { + s := surfaces[si] + if s.cssEscapes && strings.ContainsRune(sh.candidate, '\\') { + continue + } + in := s.wrap(sh.candidate) + out := rewriteHTML(t, m, in, NewStats(false)) + + // A trailing root dot names the same host in DNS, and PLAN §4.4 + // specifies absorbing it. The parser keeps it in `host`, so the + // oracle has to be told the two are one origin. + resolved := strings.TrimSuffix(sh.resolved, ".") + if resolved == oracleCanonical { + if !strings.Contains(out, oracleVariant) { + leaked++ + if leaked <= 10 { + t.Errorf("[%s] a browser resolves this to %s and it was not rewritten:\n in %q\n out %q", + s.name, oracleCanonical, sh.candidate, out) + } + } + continue + } + // Resolves somewhere else, or the parser rejects it outright. + // Either way nothing here points at production, so nothing may + // change — this is the half that catches a widened matcher + // rewriting what was already correct. + if out != in { + falsePos++ + if falsePos <= 10 { + t.Errorf("[%s] a browser resolves this to %q, so it must not change:\n in %q\n out %q", + s.name, resolved, in, out) + } + } + } + } + if leaked > 10 || falsePos > 10 { + t.Errorf("%d leaks and %d false positives in total", leaked, falsePos) + } + } + + t.Run("every shape in an href", func(t *testing.T) { + if testing.Short() { + t.Skip("corpus sweep") + } + run(t, shapes, []int{0}) + }) + + t.Run("a slice of shapes on every surface", func(t *testing.T) { + var slice []urlShape + for i := 0; i < len(shapes); i += 37 { + slice = append(slice, shapes[i]) + } + all := make([]int, len(surfaces)) + for i := range surfaces { + all[i] = i + } + run(t, slice, all) + }) +} + +// The response manufactures obfuscated variants — it splices only the host, so +// the wrapper survives — and the request direction has to reverse every one of +// them, or a form post writes a worktree-local hostname into the database §4.3 +// says stays byte-identical to production. +func TestOracleShapesRoundTrip(t *testing.T) { + shapes := loadShapes(t) + fwd := oracleMatcher(t) + rev, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://" + oracleVariant), + Variant: origin.MustParse("https://" + oracleCanonical), + }}) + if err != nil { + t.Fatal(err) + } + + var bad int + for i, sh := range shapes { + if i%37 != 0 || strings.TrimSuffix(sh.resolved, ".") != oracleCanonical { + continue + } + served := rewriteHTML(t, fwd, `x`, NewStats(false)) + // What the browser would send back: the rewritten value, through the + // request direction. + back := string(HostLeaks(rev, []byte(served), true)) + if strings.Contains(back, oracleVariant) { + bad++ + if bad <= 10 { + t.Errorf("a variant hostname survives the request direction:\n from %q\n sent %q\n back %q", + sh.candidate, served, back) + } + } + } + if bad > 10 { + t.Errorf("%d shapes did not round-trip", bad) + } +} diff --git a/internal/rewrite/stats.go b/internal/rewrite/stats.go index 395cc9a..2b79618 100644 --- a/internal/rewrite/stats.go +++ b/internal/rewrite/stats.go @@ -42,7 +42,7 @@ const ( // storing origins in a form the byte model does not cover. SurfaceHTMLObfuscated = "html-obfuscated" SurfaceHeader = "header" - SurfaceJSONString = "json-string" + SurfaceJSONString = "json-string" // SurfaceJSONEscape is a JSON string whose origin was only visible after // the string was unquoted — a \uXXXX-escaped IDN host, an HTML character // reference inside content.rendered, or double-escaped JSON-in-JSON. Like diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go index dc4563f..ad6f6be 100644 --- a/internal/rewrite/urlobf.go +++ b/internal/rewrite/urlobf.go @@ -93,6 +93,25 @@ func isURLStripped(c byte) bool { return c == '\t' || c == '\n' || c == '\r' } func isSlashish(c byte) bool { return c == '/' || c == '\\' } +// isAuthorityByte reports whether c can appear inside an authority — the host, +// its userinfo, or its port. Deliberately generous: anything non-ASCII is a +// possible IDN label, and `%` a possible escape. What it excludes is what ends +// an authority in every context the rewriter sees one: a quote, a bracket, +// whitespace, a comma, a semicolon. +func isAuthorityByte(c byte) bool { + switch { + case c >= 0x80: + return true + case c >= 'a' && c <= 'z', c >= 'A' && c <= 'Z', c >= '0' && c <= '9': + return true + } + switch c { + case '-', '.', '_', '~', '%', '+', '@', ':': + return true + } + return false +} + // normalised is v with the bytes the URL parser removes taken out, and a map // back to where each surviving byte came from. type normalised struct { @@ -288,6 +307,26 @@ func hasFoldPrefixASCII(b []byte, want string) bool { // document's scheme the parser goes to special-relative-or-authority, which // needs two, and `https:www.example.fi/x` is then a path. // - No scheme, and a run of two or more '/' and '\'. +// +// schemeAt names the scheme governing the authority at b[at], looking forwards +// for one written there and backwards for one the caller entered past. With +// neither, the reference is scheme-relative and resolves against the document, +// which is served at a variant. +func (h *hostReplacer) schemeAt(b []byte, at int) string { + if _, s := schemeLen(b[at:]); s != "" { + return s + } + for _, s := range []string{"https:", "http:"} { + if at >= len(s) && hasFoldPrefixASCII(b[at-len(s):at], s) { + return strings.TrimSuffix(s, ":") + } + } + for s := range h.schemes { + return s + } + return "https" +} + func (h *hostReplacer) authorityStart(b []byte) int { if n, scheme := schemeLen(b); n > 0 { i := n @@ -334,8 +373,16 @@ func hostRange(b []byte, at int) (start, end int, port string) { if lim := at + maxHost; lim < end { end = lim } + // Stop at anything that cannot be in an authority, not just at `/ \ ? #`. + // + // In an attribute the value *is* the URL, so the end of the buffer is the end + // of the authority. Everywhere else the URL is embedded and something follows + // it — `fetch("…")`, `url(…)`, prose — and taking those bytes into the host + // made the fold fail and the whole shape leak on exactly the surfaces §5.2 + // calls Tier 1. The byte matcher never had this problem: delimAt knows a + // quote ends a host. for i := at; i < end; i++ { - if b[i] == '/' || b[i] == '\\' || b[i] == '?' || b[i] == '#' { + if !isAuthorityByte(b[i]) { end = i break } @@ -417,6 +464,12 @@ func urlTokenStarts(v []byte) []int { // extrapolates to hours at the shipped 4 MiB token cap. That is the same bug // class scan.go documents having already fixed once. func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, until int, to origin.Origin, ok bool) { + // The scheme decides which port is the default, so it has to be found + // wherever the caller entered. foldedHostLeak enters at the slash *run*, so + // looking only forwards saw no scheme and fell back to https — and + // `http://h:443`, whose 443 is not http's default and so is a different + // origin, was rewritten. + scheme := h.schemeAt(n.b, at) rel := h.authorityStart(n.b[at:]) if rel < 0 { return 0, 0, to, false @@ -442,10 +495,16 @@ func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, unt // which disambiguates by port, got the same input right, so the two halves // of the engine disagreed. §5.4 says matching is on exact origin equality, // and :8080 is a different origin. + // host:port first, and the bare host only when the port is the scheme's + // default. §5.4 matches on exact origin equality, so `https://h:80` is a + // different origin from `https://h` and rewriting it was a false positive — + // one the byte matcher, which disambiguates by port, never made. if port != "" { to, ok = h.to[host+":"+port] - } - if !ok { + if !ok && origin.NormalisePort(scheme, port) == "" { + to, ok = h.to[host] + } + } else { to, ok = h.to[host] } if !ok { diff --git a/test/gen-url-corpus.js b/test/gen-url-corpus.js new file mode 100644 index 0000000..3b4de98 --- /dev/null +++ b/test/gen-url-corpus.js @@ -0,0 +1,80 @@ +// Generate testdata/url-shapes.tsv.gz: every URL spelling we can think of, with +// the host a browser actually resolves it to. +// +// Five audit rounds in a row found a test-28 leak, and every one of them was the +// same mistake in a different place — the byte model saying one thing and the +// WHATWG URL parser saying another. Fuzzing the engine's invariants never found +// any of them, because the invariants held; what was wrong was the model. So the +// model gets an oracle. +// +// Node's URL is ada, the parser Chrome ships. The output is checked in so the Go +// test needs no node; regenerate with: +// +// node test/gen-url-corpus.js | gzip -9 > testdata/url-shapes.tsv.gz +// +// Columns: candidate, resolved host ("" when the parser rejects it). The +// candidate is escaped with JSON.stringify so control characters survive a TSV. + +const BASE = "https://wt-a--example.ddev.site/dir/page"; +const CANON = "www.example.fi"; + +const schemes = ["https:", "http:", "HTTPS:", "HtTp:", ""]; +const slashes = ["", "/", "//", "///", "////", "\\\\", "/\\", "\\/", "//\\", "\\\\/"]; +const userinfos = ["", "u@", "u:p@", "@", "u%40b@"]; +const ports = ["", ":443", ":80", ":8080", ":"]; +const tails = ["/x", "/x?q=1", "/x#f", "", "/"]; + +// Host spellings. Everything here is a way of writing CANON that a browser may +// or may not fold onto it — plus near misses that must NOT be rewritten. +const hosts = [ + CANON, + CANON.toUpperCase(), + CANON + ".", + "www.exa­mple.fi", // soft hyphen + "www.example.fi", // fullwidth w + "www。example。fi", // ideographic full stop + "www.example.fi", // fullwidth full stop + "www。example。fi", // halfwidth ideographic full stop + "​www.example.fi", // zero-width space + "www.example​.fi", + "www.ex%61mple.fi", // percent-encoded letter + "%77ww.example.fi", + "www%2Eexample%2Efi", // percent-encoded dots + "www.example\t.fi", // tab + "www.example\n.fi", + "www.example\r.fi", + // Near misses: none of these is the canonical host. + "www.example.fi.evil.com", + "awww.example.fi", + "www.example.fi.", + "wwwXexample.fi", + "example.fi", + "cdn.other.example", + "wt-a--example.ddev.site", // the variant itself +]; + +const seen = new Set(); +const out = []; +for (const scheme of schemes) { + for (const slash of slashes) { + for (const user of userinfos) { + for (const host of hosts) { + for (const port of ports) { + for (const tail of tails) { + const candidate = scheme + slash + user + host + port + tail; + if (seen.has(candidate)) continue; + seen.add(candidate); + let resolved = ""; + try { + resolved = new URL(candidate, BASE).host; + } catch { + resolved = ""; + } + out.push(JSON.stringify(candidate) + "\t" + resolved); + } + } + } + } + } +} +process.stdout.write(out.join("\n") + "\n"); diff --git a/testdata/url-shapes.tsv.gz b/testdata/url-shapes.tsv.gz new file mode 100644 index 0000000000000000000000000000000000000000..548d5992a4a6c9475a3d119fa451e93fe057bbcf GIT binary patch literal 388955 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%0D~N?v1~V71 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
  • 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

    uE9aBnX-{tMmx({p1h{_O!p1h}qGE0fZJz&U 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 z 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%0A~IRNwW< zyfmf%`%-f1MoYm4Zs23uWR}9{N?u8Z$^y=`R!EPv9giVy(HKDEK)YHmC*yWRD&lwI zbr2x%+|k~s2cq14xGiP2BPa8 z&R}qy!(vq3lk1KA#eOq)<-muVo0ZN%(!AUB^0{|{$FOeK)qZQn3QjkXrxg9VF8*|O z=qGKSa??B|I|1Lf`Tnvj$|a4OCEAM97B_O5&+Em}w&$|$RJ5AgyJZmsO^{OCjA?$F z)k_NJROKvgFIwE(n0qqk=U0XiDKAVu@8MBaf_ISvV~|cVdB1X`tyC5T#6*jD34o(* zgzg;|bO_Yx*uq}AH*E<_3G8EiG5f`u>=oR1Ul26uVH;P1cYpn(AmxPRi)tHe>^3iq zSVETGQ$k>OLEM3Sn|8+Dcp3Bl?a(ft%3;@MxWgO)y9R6ae9^#U;~M=}d?((DrUmqX zHRe}bm9y~YoU5Et{B-ndlHV&bSIirIpTE_S>c_rUJLCa-8E;@XYH|cIr_BrhRU>VK z`8CQ>^mpgP{lFucuXv8U?6Z7#6520&+Aw2T_la39MQ~*=6*o9n2p2cCqI-^O<;y5~I=!;s6Dv#md^XN>bccUN=$Y;7U|bMz zL0CH5%d#HO+dEIUh#nA)1IK^w60*E|9%(I*KIUo~7=}vT^p3C=NQ&EiAe@5@e(fr# zO9uJJL`ZJ&?%SyYqD0~k$cWWNW}^GCM4<-1sfoLu{v-qk#__0O2~Z}0zR)C6k6Nli zWJ}G0$g9o?KVvQ5UHn~*0!z&EmeN0MiP^+4@ila4tlA+x+8)-C;!XZJQPnR zdU%HcW~HnI0lt9F61LrdlvxbIdM;#MdBrv-FNqi8)Da;wud<{}%PZaX=3t8+4TAs#pSQ?zlb zWBx;+9ydaq(6pV zDCvK2rJDZe0@;ssRhb(ISt)xCzV7@6n|$*e9GCk6vcvZrd=d5ybS6F!1*O3%@h$?&t(F5-5|!GSS)zFPZE5ob!YBHty`B$2D*`{ zSnsflexKp6nU?DW*+m`IhMb}3PeAsOxmY4cNTLLgeT4h>kYo5RBL`hL#aRba54b!J zn(w*D9t_<9Yc@LE)WkN39n}oftE|@$)mu!98PwtQ*TmPdwTO+?yn)w{)DRTx8EzK& z?WwQrsqUFBJ5%q;FBI+U>d#6dD}3#0_$pLPYTU|=v}($JzmcmsD5Dgq5hxSm5PMsS z^R%YCL_>FHu4Rv5XQ+|$WE=CW`f)MacC}8C!~BI}g(P>ON^iAi0b5gbMZQXT^}$cd zylR1;8XjA7*?Y2EL*FH}erQ~6@_APaVNcgsf-UH2wyZLD>7bG zCn;iIG$iD_T9C$f?pZLzk#zq+5VSCK-&t>A3{x`Zy)S0vFjVCXP6q8Sm?vI^pZGgW z!ukAtd*PmbluhtbU$%1isgFt?oZE*o4esR~B|C@t8Ls=z2ntVltI;!SI=!1Zt39#n z1e_{x);jFOI6F14dptAO>(n;W-c=Ys(~fYmn(1rp6_{yma=JG|d|6@OBDP-<@2s+X zDg`eznO6S2%RX&bB5~+Iurf7N;8Z@cZOjt(`mIb{zKXZ{{n0;D|>C6&*|Qcnx7 z29;@cdj<_DCEVo+f(C~kJLwH}5f?HJ4n5*H9%#pNY8@EE-i;ra!Q!wS7`rPmDCLXw zn^;QavP>6DP}^^KQ4rp*epX=8Pd|9nc*suLKek`6(l@okUf$Qd=@`_9`SnP1Rs2xc zA*@VDnBb(BtMV&iX)UJQJI65{m9k#CmY3)K-Ht{iCHKRc&`!@Afuo*o@pxX1@E#rN zqn@@MxgJcMGEHuko6=Y=izD7~PL=gTsmcPUZsqbF@ox3f9lUP+V#k>-Lc zGZwMrQhpZ7h*HmIVgbBxW*kr6GbS;+QXVE96J9SyF|ATKgOOY*lAcYZl$YLtqtt^A z=V@ulQx(#~#=ru`j;{AR$B1Ts_BKS9Pj~`i=53E9V#Y&N0MY6eeit$0yff0??x338 zK4BZ~+1_QvF55ovT9vfD*^GU)t;;yPylv(sZg5+(ez;y+hfWVeTbE|RN$ZdrduwaE zazR2XJqRf!&#u<0FXKqvN{mqgClEXNb&T)3dC(&)K8R;D{_8QGSobdjT%(3xF*s~x z2U2X}9@tSq%~MP}dd)IX2RKBFCC}ew5u{fI+AJ+p3B3D>n|}U5p>!dS+%>13G`kw3a?VYHGf$N^4-{3fv-Ke^;>y6L5{VVpD ze04Jb6%M}saLHG>bdAnl7f2a3SFfO74*gVh2j!O(j|!iPjODX)mhCmckYJj~Z^Hg2>ON2=@W2YxvZMC^BK!zmkf3E$jte_%Aqz5%Dw&itmeZ;r zK%=32o$5NpG`n>4w4SeB=)MT`s%ui&^S+P5)C)h}UT?NAjtF!~Iz&WPfABX@8*;%l z)tXIwq8j1vqS?L`Yn|9UWiqRu6dq}eO_|_{siAPzez3Ou70;Ewx$`_H^J|3DfRI8J zMveUBuW+22KSM;_-AC-H;O4YT62)`YWW6l1bMrEK#VZ{j{JE*Oj&I9lK$}u_>n@`x<%FN*aE4mD;FDC) zl%sjvtIc#2Gv-;{__IqY{@5zBL z$$=3T`V^x<0iywd(eM$Y0Slvn8Kc3oabK%(|9PVe`GL`WG&uF_#-ZyHdcJov^6Tb$ z_U2DQNR8`<{m`+bDECthpX)k6BC~kWd~=j=^9+O|0jAMamzSPZ)K8;^oiZ`&L?j`tsT76Br;3HUm%2-d`B0zM5Zf^{AlgRkw1 zKxga#U+ng_tf3j#(1l^l*m&sgdgZK>6S~5E)vS{=pr)U7GD=ri{MgQ40U%0cog@G? zQagVvpr)C1LLK(I-tZ4YG07j|ZO;V7ZMG#t+468Z(m~;LZ~pEVG_s|U7Nl*QAtrY( z&&vlLUK_|2MNBJ=TqJoCvMrCw=11s}<%z4y#a|!BAX^Kisj|k9*Bl8cI@9%3nL5Zf=E33{QvX#T~|S9s21qt zMgxR|s)NpNR6!R|6`-yJx`HZ#P&e`bT@G|~BLg~uN`o$Mq(Em-FzEV50-%e7&Td3O zH&79PE(Ej-0(|_SYbYN;=K<>6po<$$V0;c3l(jbw1Fd8Uy@BZ-uHVGny74frh4IRC z5FZHv0k+S&Lou`3W7b-@Zh^?uc5@mZI(sH-*4K3I6!t{5IK2}DH$CT-{ozrlp7R6F zBv*qJ5Ht@}GM)l@Q-abm_zD6}N*)~l3i5735gMd|-gKg3##3QUa_b6ao{$kM;P8jH zkZWFx5_-4gNB*x@pr5Ooo?N$o<3UGYTAa@Vfl`D|en4HFsf4v}o&)K@We5*7RPpcM z@0h<0R`_qb;B0bSw#9p1V?QW7jVecV<*P-HSv>+~iAh7TTrXd!c_xOmYoE&~CZ#oeN>QCZZu)n1af z$Gk1GwGsbNxLZU5yR*zdqsdFZdeDGW(AeHbJ3blyie z00JbGG~gUAyUDe#F@YEtWzH+OLzJ?0~q6p{ele zn63+jsV!K*kL$t63x&;1kWCKscl* z!(U&Qc%cwstDwDjXps7K2@}eOE(Tf)zINL>S1?=#v4lhK7o96OEP<>-p{Z2YgQn*; zSwYbIg69gK7C_tYq4C#OgKK9B%JZN_UugK&72%gN1?)M{su%Rv*wv!^nL^hLEC6ve zh<&CoISpcVhNhNXA*)XjDN`VNJ7_%Ys>J@(=Cu{HHu}nq^Hjlp0%TzhE%3W?n>+c) zHV%4a1P!viiitc?*c=5p>O(7auVN%mY`$qjf6HBI-8n&24TEykptU?#C8fuRcSE3T zMQ8!_RdVPcXkQkZihmWubc|3O0BwVzsTb(Qu_J{C{h(D*=iDD@#I+7dO}vaz-a|}O zg4mWIsezY^7rP2caM06PNS4c`*3VrmqY4n`1SHGolH9f&6Jg)#2({@3QQvl1TxgfXAhlQ0~kazurk%^D?ztFl0 z-x=vwRlUMZ9qHFpxC&PtiTa`mo&#UGo*XY13`Y^FgGU6e%zKZQafhQ4Rls9xS5LB! zGaZMbh?KxnPp=gHkLh~`qhjR2Lu6ODx`R;_vS8&$SHeQa^uGt9_@%+h_pi(ej*l{F zO3z;G;-3EMN{3y7tO1~EL-wIh$4W)iD<$4P_*z7vLzc4bwCZ)_e86*BcjNt zzxKHQF``nMu+fRYnFjG)9M~w)IOHpcQqH7L09Chs0n*HMAldl0K+4 z%8d|gDueF9ZS9wpM8DWS$V_PI?-4}z$hGv3@Ss(XoBKQ1(cxLm{S(Y+T)pQ0Mmn_W z-RAxQYIN;4JV<-{f(-)>#5#S!=Gy~Eh!;@15BW`ePQLN}<^+bmak{cDMWWYYOVCPy z8-`Ngbx)Ps${>W!3LIdi<f7s)-~ zBKol1v=_pZSO$VhT)L6{26@X}#w7lRH3{r0n2kV2^mY*@V~}IdT^o;Am*mEikd^FR zgy}To+8y(-r6f?R?w=t3HfC)6yFWic-vEx1u6Nn2+~jH&8%c7OmM|Y=ws(BSd7=Jb z6<7BOsH!O_gpWc^slQWQS>2BNsRZ>nM@GR!CEf7kyDH?Zf|itfqU%1RcYvu9=le~R zbAcZgQoWqhXm0;f*?ZHi^KWhocwnv|Y*bHxhHSQseN?X zfqg$d_fYn!$)K~a9ji*rF@`Fi4Q(8!xkv`PQ>ykw2OblD>Y%N%kGhxk|7T-*^#y0} zI2jYBuPo^QX9Bj@0z9hMc9R-M%XBnATCV;ZC&P?IHl$`C)`y;dn}P>W*09kLW@R+V z-Q$r45y0%Vg|I8gyS)cwH-C&CND3LqMygNtEA`QJ8mVo5)Qq`w3wcCN*19D#V=jME zbb92rd|tM;_ol8XZ$hHts@0!jrmkt=AA;Jdm5#TXwl_-Glrtewb#+z8yI1>`FsW-Q znvke+O8|;Y35m+9h1%ZRx^uh1&^Iq*sUUeMQ}pyeVPqu@`s>PTti550}Fm4YLK8C-JxIwX$|BBAMzv@Yc~32-&xBi;WjIHk8;1)W58-&j|p7Z z%h7#LkaC-4zDKd%>lp`n5Ca#WhdRbl0?PJ=_Is9ha zEL3jmyaIb#Ks*#A1xUHx+K~(kjGWBz?YVjZunvAjCeedW6&79Ya>`Bpe{ldIGg<%xmnj2N zBi)62CTZ7YQt@3!#NGoZ3)7QKoo8my?wU7#Y>ELy(I@g_vK7eQ8eqk{l&8_#CAxlQXvmnW_nM+Vrj)4S9)uPJ-@5Gq~$zfv_*1eNt5OaCoL z3&pL1&R(7%VS1p;mnUwsB&)?q;4+o9{*@Gz1y>6A;wLJT>nk{Od#!(=8AV^53O*e| z@l|>OuvIz$Tdhac`vI`kdcT>MZX6GauoLpYqU(tMqk>HNI+ph7o1_O$xnM4DF{ae% z7at(fZVW7hKEc~Bo5a719`lK$jJ`8$@y26Xv&s1%K+B@tPRXZpO@|_M2*+n&JdH}Agt8m**V!4=Nq@zNSrv!!P z@TjIrA=Q1#Vi~-Dn5M0*3|bmA41WHM!xjE@Jr7S|WEaMiFPI4@UJ(*Q43bCoyS(#? z>qIv2gy^}%ZkXvPT(C$MX!PJ098t<33 z==d(qv@;8d)^u*-sXM>3&m=z5S%wBReDdoZWo_<0#XoHd@gm;w&4dOr3p7$ld9Jbi2UcD6H_y zS6EQ<0u=RHJwX}%(Kp|QriBqjUzV}DZhVd1;5U@8nT;K$Tp{7A#k>gh9*y2ueBXJj zBc0NPOHO;{E~-s5IeO0JTR$t8UvGm;nYUZSgLA=s0`^@toF<{c$$;J{M<3fb3rea~ zz=9f}hOg4!3PWRvqa}Yx&Ig5_n~bQ(i=Z}Dp0}M*Zj@=mnk>!Hg87o>9fP-{sQNWN z8bqpjc$pv3f2EmPnYfR&PT=Ha9@jF&r}XH<;WU*5ZFrYUtb6)rq+(o4BlTzc{gHSNAP zJVVR$k@daP7G1tG-;Zl-=hTRk>OXInDH|yr9$&6(6tw3Gh!;17q4K6|3D<}Ay<9d9owgu0}Z}Lw|JtA=%$>@uYk+{b7K@Un?|Db^yM}Y%rpdk|1;eROSG*G`N za2XAhN#c5f2I>(7j-!F*NL=^$ppk#LN+hmT|CB3ld2a#+ZpOKbZj7hF@MviH?mlHt zPSj8!m>0GFcv*+)YOu4xnfHd(T5O}C^c;bIbVP-2Yiroyxu(dWtaVFuq23Tv5CxAQ zOVp!|BbHM^tFDm8H(m`-PIjRjThrItBXCs050?`ht@-QmjCz{}+!G0u-Vc|{&zkcW zeM!J$n;PZMcN~45xh1Hlwq>Zx|HDyZG__K|+X7-fwkhKH0cGdd zyqWALyDOVaKRXqKwceWIMF~@_n5=a@o$L&Y*_zD8dgK4sPo)KG^D5;WD})mt;<`~0 z3VlCOoU8`Q9-OAkCxo*FyLJHkzM1U8zH%K7z%`)fYpZga2AHhBlyCv+s-eQnSfZU(uPsjGP zh;z3gdfTp`-pl*cmmy9DiCDsyr;ZSL?8~qx+)6c5-_Y9dtYHQCjR3z^Dw>%7oQC@Z zxedp|n_MUFF1jUP*X{&A8qmaBT$iC2-Qtp66AJ?%OKO3%1 z(jp|uyqT=E0YKa3#*DSKZ;9)<`{SF?WFj7&lK@oNdzDt8e%)-PH^n7iU?M2jF z)VHrW+AB)Fl%u&V=@mk+z@v;uh%S>M7_oN&x(8UIghg^TpZ7*~fxbv``s&7694auZ zTk$-dA$aHOG|e_Kmv@Re1lgF3;`Zg3^lhJ`JmqVId>=QeHmx}!{taOr>#y{28727U zlRu&n>a#yAmTKB{9GeYFI;O}nps35-z{0yh0oQtuQ3my zI*Gjl(ZrV3mh732(pGuB@an?mzX;@1T@6MImXTV4X-}!MPM9MfkWOI`k7r z7)cZcmqx$;2%0*ee`1<{kRFssgZ?LQoT&EoLK?>YKktegj4oy?_p$xYmM+76VK4R{ z`;2$hFwH5{U;gtpv9!i&7AxoSkq5|bFU9>VvPO0W>qE`jbi(Z3f>(BhL#c_+?i~jq z?HcPT1^LT=Y!gLmvL_3>C7(~$b?gay*_HT)`se)8c;K~N?BF-sxt(qTU#rF#PpByR z;kjF&VrlYVJH%kT;^Kim=*L#9EEEIJbY}IP9zQpa(~e80W0f z=3`|c?p~`!Se8;7alg>38aoQLL|Qrln3q+dT@Q~KR*idcV2u`n6A-rl=oJ$ zrLWFI(!8(GO%kK8&Uw%F&1MSQi1FvOSU$2ZRRKNauO;bvupm{f-GxA|gu}3xdrj`S z2`WEl#8&yT{OID1(v7EA@R`n7s~9^Y6pFar8Dktmj44*`&}%}D^jYJDo5Lpu%^AC;bRu|U<@44A+M;t;r;jSa#y=KAaK}y0 zAK-u#7h@m7zotew|$2Wq9@YfB@YiA_r zbi4WV3mV~hos~4o( zkgxgp`t1F7`it57TNTQ1`s%8F4p(QkHySUTUEIZ@jqGP)muQ4%C>Hhaz?b3WbrK)o zSX&hg@IrXuFDDKs6>Fm>)AuJHS65_}C*((HIdR&nsQa4RIVc*jOhpcOK`Ibsg{e*x zj#k!2+`iwfd|9VOCW$%)r7KoT(jt0aIcB_;Au@V5@uWqbNYLU}Z~zB}W44*aMM}*$ zRh`#T`C?I?XGDfWrm@k|6l#nL{N9B4GK%_IrO5EwgR*`0_^j32yC}9R3xPH$`yA-?CrD&XzdF%**;96hKc=rST*`5 z>|E3d8aMLvl$K{Ups7XNIU+zLx$%QWSCN2f`OQEbaTl#H`k<=cWq{j4sGfo4LT zgD)dA9Nw7*5+B**^gDRy2fisKHo3Vez3ID3L5rPr-vB@0fWJ;1RPm+-s(A7ZitEPw z+Tp+w_}N}NoI74S9J^ktfYWXcqt+Yb`_~)V2{sx+G&UOUM{P7P^=~v>&w{3Q8ks(3 z+ceYOsj0~lW>9>)e=Au3Ofg73vjZfs@0)phvq5s6Das{i9_m4bmo;Gu43p4)_DSVHD3+&vDM@zzt|Zs#*Q z!AHxFGmm8Yd$GmAS%~$su7~xmAuT$oY0De9h;9`?5d(KESuT7^7PLj=y`07O4wNv74;QdI&9A= z)hn%HHp(cxFRJ|i(DfEjaXe4_U`TMcUiRS23!d7`Mt!F)y@Cm)ntAh;rf282X$K46^Xn??*D7lRoRLSxhIN^~~gYY@hEA zuyc1#p~*$qxI5PT_%!-4Gj`bR5mf&8Q7r3`;<)fI@70oa;0Vk{*1P+o#;uN~w<$)+ zZ6Q8|cp+b{WYiNM4{tL&vW>TA&4LUv}QhdTwd>(dnYj$;GcXjI@60Kjw+22^qUri>TBQ0MC*#5Qw&l8monSxjRvAaGL zV{bC4Z^^80+o^B3U*A|PUQO1XCu$!uC9n8%cYQps_-p=lTHN)qyn6cvz2L)rp7{y! zHE@HUeac7j{7g%x!(ZR~8b;|201UeN{Mt3!VSUxF#=QFD+(qzhaXUN~|0R5~nXJwVs+iQpM;unhH3HE$DOh@@Gy^qF;$9ajlZj8_qo`R`viqMl1=5k{0WIHtG z@_J@)>}EFW zJ9J#G*09p8p4_MB+6#5WN4yhOdG?J~hyFzBU>^|>IGtzt7~L(eeM>KSQgtrd*O;jy zqO0aso2<@;vaPvZQzAf1R@yL|F0{Prnt;_lX|I_WhV8kpIdmN(!X~yQS zNItY3*@7h!bS7Ba(t7SR+TgmUx6ZxXfP5Fe?X~H<7yLBd)oxd`U9s6NX!VM5O86$- zap@yZt^M*QN2>YhESceXpaVmX)y>Yw?A4Wbnfv>5$Li&k4^R1e+qBm1Rb2*#txtWc z+th;0Rd+?xNmxf-?`JHC_4V`APhKW_Ew{99j$87K$9uz9M~_X9*VV7uV5Y6C)7BuK zcVds)k+%`px}VHyUY8|ONAsik&_VooO!KTLK!2woT3k{O$;Q(sZ-?BoW6y=!7H_Zi zYTe+vmPBXGw}<7&IlXnygZ4D*qot8!_`@{cr+}T7PfthZix(yIe>}{u{xIV51VA?@ z`p7$p+sdCtZYC(#n8JG9ZQFR=d&xVuSG>SX6NtW<=ePW4!fxKTp%n~}FulNF3DRzGTdBF_;f*Yg-Brpi=e+7(FAI8+pp~|!)(q{=0WLy^ z`oG%tk7jg*C-(e+aOXs$+kh8pTZ^F?PM#QE1cEBpc-ah?Pk3vi+f1(Py?vbx{wLnr zZKQy%!y}7TmXizjT6T@kwQHeT!PK`o6=e%Vhi*h>Z2|ES4tMh_fA$@bBMr-33@5&^ znYBT4vR}C(t|8emotNpgWUASGv9El5)oj>sZ6!F7#iA^8@$CWMjmOam=Q$5*-S-Ob zZS6^S?)B91&FIP-Ip2n1v9gA}<}n_E02g6xL{Hkq)0acul5|GCj_HVTxy0D?u4S$D zbT~^ap^7_gXL?KgiVoG)sfvOs=_ytv)--<%!4VxQbBs1;Z3Ou+Gmbun4;^sVcv)?^ zk*Uv6A>-HsglA75{Qff1JSSd7+8q_zzv<7Me~C4XScsiC_Lds`n&4x9SFy_sVs=d0 z%B`{_G6duHMi$riHO(N=ZynMdX>zThkH2W!)#{RjpJUsWb&*{+WlpmPJr~ifShCQ| zpy%MSa_Y9=i;Sq(tul%+izyL9O#}?qP~|WmlOA8#&q){l*ZJV}JHtKi6ReZ)g$!N& z4_7}+y4oM6wSw{ebmE8^ZvguEsi_Pd&Wn-*LB-O+C-~ZI0nq-)+`GF_)IXA zF%w}}GE6aHjx=e$dx6vXY0F?ktIIjkTjhl^53eHJr}vdM0k0hTX;-={)jV^bdN?W( zuOs&s#wdk>@~qG+B>zi%N9rwhYTQ^B#I5QwG~q|dOR;6bS*lD?;r$jupQD~Lk7|c{ zXFp`E${wu4%8xFK!`zl#$XYo)Sf`aiT$WYHT8%vkhn3`9U}`P6na^!G1n!V|q+rTiTP+3O@Lf`d{8?2d&={-VNHk)(WH%jNVM`R)lvx zA1^L`YnMSl*XWA7I$4NeN&<)I$gRWkS@v ze6^mny3VKlr?*j~ZoTD|L4*Z6<9_4owkSSG)h? z#BRnMi!AE;XD6%P^lXfXBbZBdq^T-Aw;%b%RhPr+NPboBU05Ydw5p_vrN&9>)Z)hz zggvDsSZDSeeYTMo3tv_@oYNi{8c4@nxCkwpCuns?9Al@f2z75Zc{62@t9_o*--hBR z>h|7p-kM&o39o(r4r-gsrKJiJ#;e!lbdbJ9Nzmo5sgczNpLF&)LjThE6G2AxY-ol4|xpe@){_baA7{X6jfca}&h;Ba! zM*@&pb{E;4PQL6$oET3E%<8KWTEqwj+xc1Y|10R$vvGnYRhu_DGg#uI-uvH+E3Yc0r_Kv7D|Wm`4ju=m%Nc9qcY`!Z0C)yG8Llj z=g0oh{hG34H4JthV*uV2lXFD8#)j;h2{}n`)COZrcufp=ylH9E;Nv>#@SpP&4pl|0 zCKSs|gtdiEQ*sVEDgauy;@R*mWd>fvxZ0P;4PA7y{FA?C+PxmrKr=I!k>9o3(0Z9I zhS!LOyG)isK4nt=$T(Tyv987hhtbUmR z;?86wHv_Z(FPnWyiZUxw4S5_*D6S!ao0-l4jB?|*Eo z8E^GU{fJEk?xR)eyA}MhcJ@4bz%q{KPJ?@1$_fKoTrTEn^F|$B)#1DhdU6pqOuZH% zvZmsL^@&Jyu38@e!&`FWbu-xQba};@^M1pKq}}ShrAsZI*Cz9# z4#tOagqVr%Cdk?>l7{=imBr8t!As&*4rt}+RQA_@l03Q@;Q(*#JXf2}B{hkucy|8@ zTr+U=*OAh(s0lqzm|dy>z)vOI!e0Nwt~~%GZQn;0Z|&6Zkw{$o)qjj@;had^N?CiG zYQeBqxwuDc-`zY*vv#5Z?V2z=7hVXu* zd??xligd!7KUjQU%U_|+qUIQ>OT8YnwCMk_WAbW@v+f8DYyM>o0!Q@I;HFYpgUAv6 zqI+2V8hTlS;Sv2Dn7RdSmI)rwuewXut=X3~s2|ahXZwQv%BTd`H>(_GslF#Qo43BsN<~mlGEV!dsv;{Y;32U7Z zm)%9p_av+l<7+AgOVnwB2jh`trnqJ>Dh}9VEG`hi2`ZE2pKFi3a&$Hy-G-pN%;8s+Fse zB@Pv54kt@5kJ>)|9p3-xM*DSdUXNs#Hk(ASkF3^XliEn*y)xT(v0?~P2(Vu}C_cJ+ z%U;qvTK&J(qRZyl1Z$h&?Ot;P^V88Ez09YW*qgg1hNsJkwe^YkP4BMi+{e4B%l%ic zQocbS{`r270tCj_b>kb+cL1^o=naq#bZO>zWcb|y)pOQiSZIyJ3egZ+a#1I%6ICG# zJ(1-n0As1Vi%LRVIaI8{5y#=D!w~(x<#-hyK)J(%J<{9BmzwP7#i#h8=AgYA-5p<9E3VH%7=1(L3a(7r9nkW z1T>FJKQF?ozN7*Ca|R2y*ei-z~BrJw;59Dx3J)d-8a1#_`%$qYGguQMlEZ5`DDGVjSURA|54(t1W%z>yRV=P zJ{CbCAA#hfvevJGQqCBUL_Ec+Fwt}_g^%(FSz7U>u1RJA`V+`reOJhe>*hZ$I97hn zmCdNht+BHlEU$`aTcS^O2+8OGjKRY&Q#ZWbRpK%rB*P%Ps}Ut%zIt5$xMpdFt`29# zjUj=5#y$EXPCnBjeAqSVC}->YkaWH^W~RL?YHpRJNJS=R_zE~O)33p+xQGJ6ps~`b ze7ei7)kp^{n$#HQyZ_*x=({w&`MFJ<$yQ?b8RZ}Ab_$op#TI&eL%L*_3UcI#tW=44 z2x6Xzs*R1txA`#HOR5Q;EEh{BJuLT{a7Foj2zJmjcnp0r!ZLl)Eg__Nm#Ksp1)i zpNujyP-KU!Q=~2;r+#Q$q81Vsz%(23eRT@y7>yK~McD zwYTp$=6H2%wBOP=8P&%k7CzZ4rM)VdHu*#bqnBM==aZeuexT=zKW~$WJOdyW;Ra~% zx49H6eFN;~ZE}@on2VLZ1IGNN&VZYEH$c2=Fu_uuVL5i>8o0&Vq%O~}7>jZ|Bz_GH z@VFG@eW=Xv6p`nvrX-w;ML8V`z60jEUrzBp4E`k-P!g`jP83mQ3jXb~5{q&&G;j^< z@wn6ne-p^CN1f?zU&za>P`<9u;e$01G`>P`l}jeI=az`w9>u4=&z zuoqvk6D}LI?6wNml)kX)3vgRHs;f8%5AT<~kdkekM4PD~8YLZaXn3W#KVdfMK4Ss9 zS!V?%iJOePcD#4gOfX{aS8F7p+D&3x;WJmj_(Sy75(Vi&jV#Q(N$kJgJHwIROOu9I z6$E=IDf0b)X6fT3*entGfrSYV;GOi^W$-_BV8?L$YnGw~TeJ-VoR?aVoBtnXX`yC)EZyT~4?4(L4)dvY3p^;yjmS z(%kDu{GV4Q7XXU36DEGCt=J3~x6LCQYlyh%5r$6cwqs1``~k1;51MO>kZ$=4=~3$L zt4K-tiEZDXQq~qt0hkrDy$JYBFYx|68`9Z2zc!}T>BokY_T%A8LEj9aHWH(TmWr37 zF;&`+QHoOu%}XJT$13)f2p5uqRXU7h_WYVC4#5pSst6lFV)eB^%)tbcBv%Nhua&A= z6qoMTH$~B3@bZ`jIV$MieiyNckQ32dD+mXpv&ZFR%OH+5Dd+`}P+`($N5MkzQFRGc zA^uTU(DxTYjWf^ke;gwa@d-FhX~Y+L5$Y?S=V7pn z(VX$_hAfA2mnVLQnn=FLRjFI~J{|o`#h%ibyJ?BKYusGM{a3tyNv|roKTWa{b%@k~GEEx#NL*UJ z3PnCNb-oximDIL^d7wR+0`k42nY^ZJ%%9kwX}~qI9*XR*LrId>GD>|AwnS}a``nMr|1Aw>ML!E`1K{8YS5H7It#N>hp^9iC<&9548%7$MwJToBVq zqLM)T6S2vF3a-abx9CEM9Gj~?DmmygU7jX!Hhi>lNR$vArcegzVjv_%FHJTHT#z^{ zlMoFW{7(hEKzO3wN1{TQd~OZ_m^zN4MnMxKX45FzfH)kocA{>mXu1#$K~wnm3Q?y1 zN$4sOSptyRL=q1I`Y=))3dMd&h`{ezz3*X^vW(xyqwV_&6nwD9i)!|Tgc2dy2>I|? z%U2lIHtU@nY-iQm9sKK^|1-PcTfy!+!p6Gv@;dG8`r<<>sShKZ596hG#gcbLr}tTr zH_Nom>4Pj&M<|fp;5lkD7=pj(4x%}-(F^B9*&uV{F>6jdn=kuXGDrTw6K8LmIn(d~ znj#bWA+CRqif{MaE-(__GjLt_mcaKet2ubEZEAhC1IPo5yxY5x~Y+!F#v;j2r4fOa2S;? zn9&(;&ryHEkhMm2Nh9$nqbHOYi%{CKjuwNd2Ro%u`2% z&!T0GJR{GUc(kJ&-BIf)FUGdx>dcTu+*VsCMr!#h}VQiV&NKX11;INw|iaCesGp%vN)Q zmH)7dBgxa%8_vhe*bI^9RW|h7Jiqg0!`ay1y)BaN&1zm^@>F zj;&K7i62yE1rA(FYFs3eg<*T!*b0p|C-FCq99)7bCf2Ch8L0P~)Jd+=tKlYGhS$T} z`9O)~|KqR5P>_sNNc)D0;oUgCRGFrM-VfGIe@q zgiU1vgOFTDYchAE(Opyi$H!h;LE=14YTPf@-wRYhm0fB_&*CcG(j&R7RumAdx7fW# z*b2;LV?*C`E063S;|YMP@CZb*3hiF;BusR%w1=oM38wZ83Xk{jbtW#^mdgglpl=}m5OUsHr(eo2ao;U*+=J0eZ2- z`XdryKFPSE!zv$x5X#729Xj7WhxyNvi+!V7mOtFEY27Z8eitUQNi?g+{!YZd zWWeIscB@A!(JHJ+Yqc>}iWz$8=ZnpNjQ-pL%KMPIVLPl|q_K*}I5Twt%$9Ka#{Uh-+)0l!(^qh+39h3=*gI3l^HhDl5~huK&%?>f|?_`t}=%_ zQqc}i5=z;uS~>`dZfr=IJaP#d4r9mo4%jlI5QHKv zBk@*{-s+r8bgAI7wR<&u`-MxKkA)CEd&C9|Etqd(Fz>%-bYI^QGCy8^QQRG66jFe@ zYK3@n3IX)~%WH@PH+6~pl2ig>%nLMPAe8D6=Xl5;1c9=jKBdaS-q6jlp=eB2H?=HH zI54SK{oLf)RqSGpy88N0T#-OzY?3Ge8j9R^;A~d4F>B5q^$-e)nJTO&TXv-e{*Bw9 z*qGGR1Zb@EjU6IQ{@}Az&GpOJZ7yn5yHqOv)K>_pgV;IAhw*ykzds3jdCn!CpJ_Oz z!1B4r9C4gtOW+@(F}Y5X%QVjBa8EG{04A$`$OB1ePg5}Z-^c6M@VQ*mEYa5{8`AoK zFTiB-op%jey^K5vOFU0s4rH?udiK)vO(Uqj)&b!^CwomvZedu5g#$c30c_3+^8W-} z5(b;bI)5}0P3NIfou>Xat>?*8okERu3N#We=UGkX^HZIQjdjX37P2%JDm500G!m`n zcT$}$z_diOdC^p-O=BV#$F;dd<7bWOQjQ88icE* zk>>LQtVb-S^CpX>6GeY1HuJ&kN82ekOk4mFtw%%0PTatatYF&njH5ZSIBX^S$ zY54UZ_$_CGlE&a9p_|wLqY>i~7E|ehdV4II>$k0YLjk)v&MD zOM@zi6Z9GiElJigE?sWxtgcGYCWW zi>57FDJcJAo!UCtEDKfz0?WK)AFS`9+EZGrR42X)dOFP3p}G8&Yb@(nY0W%xx4(9< z8i;t+#yw4>=gw|>s(X;;x@v0yrBiJW6t7mx`C{5vdb@5`OThpl_HKiucIi5W6QoZA zXaGWPo#|blAhf&8;L9ITAp#1@%S}>S7c>6*VMIQPlP(eGbK(+{KYBuNnbo0`p2O>k zn_vx?o6O`nZZdfeTCC<1-^rJ>v+YYcWQM%S-{v`C;DWq*8{wb>l4a<8zBi(CeB;pU+^E!JrKRM6)+Z`fZ87Hx|)pLw{sAg~QGbM4;(5dbe5aY|F{k z*odS39)V#45}V8);h^(lPiK5;rzpop4!SK9WTX9C!@CzPl^6OCuUHMEr-P0id8m7s+) zOm7Or+OP{7Y!p=lFcVnY!8auNYoT`1|5#5jvKa|&=&M>mAGhM%s&BQ{!m6kLv7E4F zIU-%pDr{-7wUCf+Aze}z)U~XnY6VwNpQWIMSxCP-jYVk|W?m~wv@8T|W#?U=#-jzD zPJcLtRbV_qRb%A0WM6M-r&=FKrG*t+7D}p>5k+4(kP!F-A*G7Eaj{#;l9;SM2}SGo zPx|t1EGYdj^~xZe#jW?2oX>T?pETn<=}X%Z{JxLiRqVJfY*kyp&DNFfX<`i5wQgx5 z+tQmgU>Ryjhm-|X%?rs{Fy+)Gjca~3rZ=y~+Eho7EbT*^w|_Oqb+0RG*2K}K|527; zq&fmu(q}h^UT&_TR7a6w?j`{$wbG1{r>81R5Ku<&C=M!`HIOnVCaS9p*NhUSC(6O< zQb6D?%7>jre>4-kuPt)Z^yj9hPEQb$LwG9aGnL!Go4^Y-Cvugr-{f&f0}?r zE*)l?ACxsMBxa_cQJW>B`Hq}^D;ldnLYg{{{N1$wt*N4OZ6Jpx`X~CG-&ho4VMjUS z`crsGIr*GZN-?IKBDG0`nrX=Ne|};?2qS1@izZFx2$?c{uC>RmeFvwh@`3(?9~Qen z*hMDP&cxQf3Flc2;*EyN10AAgLJjXoO@@)^1ap!J^*{~kriS_%9jpu17w$0IbkV%= z90?Pq%o_WY8sA|JOd#D4J1i59Ft{|Ri?OX!V@~^;-}M@p%XF5O3EQkABPm9TW6T-G z)KWE3IT|>VbfzX)dMsff$$gchTXM#VBsC!s8nFFz)&>dNUq&R7MA1gOUyX1dt4rNA zkXz}jbrSR#M&J`!yXZ$+5_Tj;Vp5C*2de}1HIgdm$keceX~SIOMT>_Gq>Oe1j3830 zsiZZ;^5`NIu^gxn(Bt}UhV0J`aUH6u*frEs=z?Vu?#M@`V~kXWl(GzorK`V?YA8g} z`HExNk%mP@i`ESqC>ZXD8A1?L7a?ju2hp7hV&xJc&_wk?4BFrRz`d<1x>v{Xpxfg~ zpd=VMj4;w0P%8LAJYH1^R7bU=+hE7a{uEXeF51;^p!#D+<_AP}71g+UzY(1rGu8|a zg3#|iq<;Hn16;Q%s%G^?Ejn@f1U0OY_hCkseM+SU#GvGTf%>E(od6Zq6o$on%K2HD zQrtxK?^2}@G3v^MRcl#j7Ud-Kh$5xgLFx|BRcmOdM@Y|=1*iJqLp|=|+@*I}J?I%+Ppzf~>gv?B4`;Oxhqp_)wM!Yc|59j|;%z6v)ZKyC zHF~ETbo(96xdP;?Ag+`EmS_czzhxbLgFX6YcsLh7Q__;BhB-yMz4x*3>Q}KFR97pr z>URx_inJ)T&jYlZTk)h9!xpYa#I4NG->F~ATRqiSuFEObbTkgjYwgu=8flGI;`L78 ze>saXwWM%3vyva17c9gFADQdT&&8FLu;(?WkUFtm9hghZ%xUMBxCk`6_vDmN3#Zh{P!)yy=Fip&IyU*dkvqrkSR8M@X7fpWQwA4Hc?ghDcYI?I;VL@{Ljq>LzTW?{d z2+`7su%A%_%6HZlol#JBbFlWvKiYgD=);0FVI78O#P^R5^7rI^PvRpid)5s+UZt{S zEfe4*>9|jk(XI}5Kn2eW$Zb4hgFD}*pF2y@dnUDJ*f^mKMSK)o;Bd9Vd9ECxYqic9 zoGLLmh3g=*ecQID+jfdRLcr!);{!hfg;>oYZqYt7ZQ2=iH(8qEalb?t*mYW!WVaWQ zh$%_OrfgZTBK_KNvh-`3v{cDd^vU??lm$xyvS15H=4kKM(W4U%B5E{iMuc42paq~n%??aetl zkG1S+f&46e=%m9q0nR9h6YbrTqf(+Agylau*1Sw!c$v-<_e<*OL8$+5@u$z6=MZ3A z9Qa`;8ND&AGcul(x>7m^B{5i24}|ug7milpE*5T@jQM?99RHtt2s@KIMnx zIZLOrc**w9z#KiWuuBI39|2^S_PzCb7&0Yj_x?|yX*2GM)B#o;HlkEW&i@Py|7_lST)mduIYE4yfXc8gHSaQE8Z>KkNqnjBO1QNn)UZGhli0bAs#U{dMzku^8 z&)56pJ;FSm{$Eq{|ILED*gac+I!%aez(imF?|)kQ6xHOg*tbDTdB!KUyh#DE?m@Pt zk0El#V|=*j9W%JmfM4vj_;WKgqRD4b7L(Kc+bHj5BIGF{qi!iOc14fqk!>`Wb`iL_ zb7-5)rCp1RUEX7KXuHIvT@I#_|82%{X}2RMmh_Mx*>-Vh=YabJ9oklLd8*^{wIUO0 z2BQ@Bz|3v-9G%-a-6wH*n&R^{A`>bHN0#*r{3W9~-7j)^QaIh;b9rjx^Hn1gss~3F z^}sA_1{|IXIt}*q3G>YLAZr@3u|rrjG(-T4NI;*|uWQW6o%BKcaQl&opCgokF-$!Wo~~8Z-sab+}oj4&quq z!B(zL9k$Fbikz3o_4ZqZ$m@r8S^AYTKO@Ya?Y*rq$U9sPCENdWV#!$}O6LL|PGk#gy1@K;{(u$Gn%DXV`-vUK zma;7g6Je@)4joZn=wDJXWBJ+{Ao11yH>u$aobJtw#-TW_Ugne%>{Lykv z8s1MK{S@}Ef&JpXClRXa{GD@PDe!gHAze_@p?UgR5W<5lP`>kb)`fwluVHxkmLP}` zZX9-Z!8G0~aw`J&zcJkf>Y_Kk8F60Fv5y2`kIIZhg0*+V;pAe1EMog*7IL=jL-OF^ zBz5AGo>H(%Ge8bKxfZ$2WhtnJ>+7x&%m0}0n@^Xq*vKIM`5A6V2R9tB(ysTTH4`Hzielhh55NS~Qu zytLJX7freLfyeD;rG3=}n2AEuhl8tHLG31~r+<0Ck@UV=l57%xd2{##ejCcQw|xn6 zw!U@`2jT7W#jBNL2tL_rHmN{afhKMjYuf~5unp2K+-C*`Yl_goUnSY`pFhNBU^_ z;^e((V}HDp`cC7Ndml#?{VQ)p;Q7!Y-tn)fCyP}mtw2faEg>ke0|w#4IPQF%uZ+qD z@gO<>VlBpye&RS#{F6|x{4GY8%q48}V>b%}gmIGfhc4;Dhyx06B3#jrYoZouiv<=Q zv!tL!Mm0;7?IcIi&wjNwxj@OxPww{&U&hBG2f3Ah?EGE7=%E=%$}hf`2meX=>mSJ3 zzfzWw!k-06nIRs_93*Ags^Dd?lmodG6v0w1mJ;LrN6JI%6tHQtR=#aJXd1<-u<7p> zX4sYdl9!<`a4TIDSvZ{%yUwyncp(ZOb)tM+bZhs#OI@k*?;R0Un))#) zXN6(!8$dPdftw>(`}R=Hu6_)PStB3!FQJ+(!BpwLO=GBLioXP%EClR*C8%Z*KL+)z zFsyxHgli_K$Mg4I>VABc0))z0k@S-TANP9@uF;?#8{d0*`tjBOCDTj}VDGcgPMRoz zyNF}&2OwN4LOq7O_tFD@Gic3ynFtG7J~gmcucPmp7H8LQ+kFKzvqL1|$SswAo0am%mFf08S zX!USU{|K+)9hw3Ah>a$Y!hV;8A6#1ZXx+z#_WTEf(HZofsFNuCxXbPgUxlf#KV~qd z2$ca2ICn`zopk#Vw?r<7l89IQYNC9O>3m@v!BD;(sE+k{_+f^k_geL3g+Ae#h8|H{1{}izCPh{Xx9ASCX1s5L3?aTXFq4$8>I9q`Cbyp*DD^_EgIML zan(%kvITwuU@E=>|NH5wTR}I9Y{?bT6Oq^v!)3OVrCKYVDKoGFj4f952q~00(GMFf zwgOG^STJ*edqJvQkRNLfHnu)73s_X;u|nVa^@251p_c^Xe_{om#X()aXSH@ORp5a- za$AiwWyz?ulW}5~>(ZX0=B)qc#EEuy`FjIt^@&lYJ<(^tApg<4q@c)Faje#n78rrt4e0)B3Yu&^+H%M^IL0P_}6v|1B&_vvgJ{ z2kX8TvXGpa-pDLjGBu!9AU7Yg%+8ckXarJoaKheWzN)|iuXem zqDCUIua?fy2!cjhM5BMmUOGXjS|nODknZjWK7j>LeYoF%W!|?+HOxtc&cy%<-6Cia z4&}&79i~U>BUVghcR(KwU5p;?hY>_|?+RO+o|Th|1M%MLlV2v^W8Wv#P@NN);b}l*yUj$ds^ZB2E5r zRF$rg(^ieF=8FDiUy2dt1Yb3dsVZNI{QS+YLdxx1Rmr0E&IZk#DpK8W`nR8@Li%&( zf96%4DrwZdK@}hJe2XmBl$`tWXISCJ_fSSYeX@97cFvRrOZj6WO{ofOz7lyndU3z_ z4hK!FB5|0xj0!>y7gD@(QMaHI>>q3edLQ#+WvEQ|8%MDWxmM+{bn^pDmcn68F8nr3VA=jmAWil?P`zi~N8QA!kllwy_t zWu*ia3m=aelQ&Bmgi{S#16oP^bsN-0&hhgUgie?3& zkj%9e!u=yeCLv&G=oJwBl%Qr&vjA^~_mpw`xKBf4^6=j1Cm4#EfCtCHLNq?=vrsGf z4<4FxGtn7Z$A;RJHC&vtlJRmvjwG3}+i7yU zU+E}WIhh8{zRF44aRtYcrU`A-KJWIfU7r^yvy(T$waNNf8JJ-(rH(GCjhcF%A2H7?K2$N)b=;23mY=vso!ARfCFYy?E6>jxyxzG*Aj=PQ!4r2Rc&0 zN>b72_Ho@gy{P0$RJeje&yo?KRheQcV+YfK8+kJnNUW0SlD}o}^rtUl#uI>*c?#t8 z)SMS&yc7pyl0yvL=*>6zV6Ugm>nmdIIGvQE-z ze9n`7ZSepur71Lrh<^Lps9fdGoSRh7VqxrZK(Prnt{O%Ea0LrY6;fwBwCTY>v2GTn z)ab6?O~bGfVuwt4zoVsp|JDzLCWXOCZ#Uf+J!CL&oC*!g(5ju!@xzL*4ha!GrWMjg z8~hdcO-7~+ zv~ZlJ5T;)^zd|5_lhi{#3pv8)5Th3Rgj@(y61v|`rUXOts4QpaBU6rNizBDoQn~en zT5$v+nf%G7M1sl4?nKNtEW#1C#If(REx;Z>=q}dc5JV6+*i57K9H8<1K$Auj^dsjS zDf65@?HoVZb{xH354{}H_uJ=hpKkwgWXd5n2u?ptNsGVV`diVb?+5>}3T2BW1U`;9bVWup>zA};u7;lw zM-^xl2sb}-@e5RW1eK6q|I`H{E;eF2R)qpu>nNPr7V3%lpeRrAwuuYmNbGP|vQqn% zA!Zg~K9p3U=yvskm-o>IvS)OoZMiNJMSXs{H>@5_u`oVMYSA;0`#jV;z#v4hr2J$@ z2=X=k+aNl529;IPpxr(Z?GmSAdeZs|MF)Rz=O&^geF|j`UdR06548Jm-~Xwb#n_(~o+6=WCrP++;qiR(@m5rU7kRtu3)XONDBeyJ-?`(tSnr`uX zLjjiGmxS9dnZ z`^y+M#68Z^adLXIBKvwyh3WAkaf~R&=X5b6BkEW1@HsgBa>Cjmj)5c~@YcM+GUVoW^ zzEaAF%HPAEmU4DIu=`&Y9Q^^Hy^&gv`#5wY!DR)`rJt|wyn8%gvWN(LRTBT1?aXvh zf*&Hvn}5OR8Ypn651Ku{e%<-~&<*C|i9kS(cf4NT>F7E>_HIlDo`tx4S+tY0ck~d9 zxj~xiOH^cl0t*4CJ#>p$1E}A!^TJ89jFTE5NsG6r>6Z;4sb2}z-+cWi&czd~w!jWW zhQO55`k?+bV zZnK2Q2t-o%$S9vVMSuKcT9KI|y4K#9TCWq>P9D2o|Mza5kv6qkc|j=Rq)Pzly9UY7 zJAb$!57=!Ne-_M!Oe%*%D?M`{0XwSA`#sImf9?qR7v1-!k#DEW@hzr14BFu4Wqga} z4uf)&^taRM_!f%z7SA09wI*rR(-f7{6wvu^59s(7ksSsNaHsD07S|oVR(nFVrpRJI zuj>e$TEP5Qd49FVBp&+DeYad$|(zoq6(&%C^SVD0mM~L zb7}6n;-5B#cjC^C!c@9iEn_8<{-Ou2!2lfOdbb_JP&Ut1kk@V8j?fu=_6fr8zFlJ! zb-f-8-k1^Qp;M+3r*JHx-uK>}tUV8ZUp@-gbSqI_qmBdBjd~&gaoD3j=`Z(=|Na~P z^x|{y_0CF?&Dwo(oN=XzihR=RB)r=!@dJR|*5oh=pfZPjwEQPX4tv7Pf1;lz@pn0` zDB5XrUi+qHryxvQ+h;ER2?PN6)d2cN$x@nXMy!sl85^cPbpjwk0Du(FuJBjEWR#1R zQNYiCwAgP8BGfc~Yy#Bfi9ULfCkM0UAC0FH+-KMU3r>O=E+a$Z2g@}NtvcR6GaI5~ zUt8h87}O`|RS=Nlh{0VmxdIx1=nw2)pR6g?hc7Y)f|4Swf4Cgfg|F2v{MEr*QiQpW zj?DUxsJEb9E#4Q(Z%U-|S)oqOczH6rMIn(NUO^L!d##Z%7t4d^u?wz!pBj3(PE;P3 zHXvrgjs2|uMTWzx4s@%V_AnXUAPN>=jOFe=Uic<)pql- ztro?gGIlH~DBTiP;P}be#@mSTnZE2cn~j`?qo+Cv>&pw46Q z53~-Vug%(19W-jk6+_=%)qwENH;wNNoCHgoqm*@+6m^>9bWjTz^p&u8ix4Dd`%n+N zUmdC4n*+IYP}3O9WfKhwq!DJw?GDh(9dVVKLnw4GV;GDj)W*fJqjC|lr-It{{Z$+x z$eUqNbrydz^a&-RW+NC(isJ91Lpn;nG+{n#FM2YJ^Cre+SorcJ-lj`Wjqj-J8Du%w zjWkhhYm3-21hQk>ry|6TLDlWPS8zbfZTd2yEoIE$$BccQ6s9yvj=3B2&Vkya=}U{Y zwl+g3{X(Y(gDhR*bo@xyaG%W1R+_z{WD{(T_Mkk23}vEHthDbCR2eWw#-5tE>1%}c zv?zlhN#azLw9p{ymniA0fqdY${k|Pi>o%U!0IO1j^oM~RtL?2)JI<*_L}Tq)CI%TC z?Ab8s&c2|rEg@|?rs76OCGBZS22l)b)zGkj-h8^PX+%3ozeY$t?S6a)S>y$YPvsC_ zwWo0yMi8*$gGREt`*=4E;%o&)8;OXtyO9}2p|Q~e!t}ey(>7v6Y^m`ZzaVM@AQ}1~ z5)*u-Njk}&*O{+v%x)UQHf)ee*JDO)G&UQkPPO`P=;a=fc*o+nJXNcXg`GP_uGo+QLMkC3#8daW|o zTHAeVz$wz2oTl&J$HI0D+iDU80E7&!?dTgm$7%Hs(hqNACD@H*H5%~(>@ltFpc){3 zwdUK);5@Xxx6%i%COBFnpf`{^u5?#g5zo})m}tpV(?={~-B^T8)&!_wQg-qz- zY7&?=5FAST$me5VEs&n)rmxMZo$5X}YL05skC$S7R1Mqs6Qn)opJxsyTlXbTb5H>U ziYG)VOXn2lx6XE}nA?%pVMb{#i_s6|B%mse=oRJT&*GsKu};cMcNZFs&Ex>ga1UxZ z>@`Q(=_OLJlw=VC3ZUp`-v2g3>#F_oU2~Y8UN8Y`Rw_&=pZxc9438Oga_tu>O#msq zWc0iMF+EaLLbdovRc>GJ)YhV@;{Qd~S%*dSMg5*eKtd3ZmX?riWJpE2ySuw%P(%cz zMOs2ShVJf8=>}<}JH>luy!XoQeV>Ovw%ar3nX}iKwbx#2e?Qxz@)5iCTf&z}p#(gy zF`P4yX?{=zTN(*hJ|)u@!+iPZ6G4@j4BL0wz)@vBOC!QcJ~VA*#3Fbk?K!xYUGL&z zghRilbv2D-OjtbGsK7bW>b+nd^d@*JgkhNanslW7qXk1#1=51n=pJ*wTU?ZY3{^_D z?{GVpg;Q(=O|KSWXa!e?mh=j9kOP4WZ)i;N>#8BDQFBU@iZ_ks;bud`8A;qnTsC4> zQ<^^@8gN72ZNm`CWGxl;3jJ1&U0V7IeM$}+h{+UbHzZzm&^x*VGEFn+job$DxEo0N z5zno`YJ;vn)maaD;-dAomN`s^!1X0YRNQN>f%YgfhK_P111-g3=FjSJM~pJcvDshy z+eOTrlFMmiv=F1qx!!2?e`l6dBp9a;?TC3j)JL^oN@-ON$DuVE&nzLM4v|*Jk|wC6 zkzxB1h}2ilr_l~Gok!e zRyv>=Wy+k+PT>EHiTxSV1|d@n;cf(r`pQd!90D1JP}=uhAF7R&)XTo+X%ee2@4Se6 z{&ZkFg!X4AaY9ITU8l0LF+WWi3dk7Ks*^ZBI9s&yguvJdrR>{#O%!3~U*rUI*fv>Y zQ_ln--<>oOmrCi%jlz{Wyt_CdgfBXX89#9&Ki)-dDAhN{*u^g^?bb9JN@@2Fc>y7G zYxl;agJ{UMdk0ZN5+yWUK9=U(8_I3BCFKXayOq1w9o_mhzx5o1w-qUR2Ou@*LJt9D z8vQngJm^UkkPUu;<4a|oj0XH;NKg1ll~f4-D_k&DLEr1NVC!x*cp0LkBZ#`H=iGONQ5x@E0Xa3L4>*fFg!Ij8}uKuPqSq2;_S zm7%}+2`YS0-Zb46$8@Rz`4LCF?gSJdX)jTQDa47&27bg-{Q3IrHaPjwwGI@&g@`{>bl67MFMD-HHpjFwNSy&gWd1W{pv)ECkT3 zDdA%SnzaKqqVwDrT}mrCOs*15Go=mZd$|yDRrR(SZeVm|h*O~)5SWeR=9qHIWncAd*D=rb z>;&zEu*}M2Q+pzenS=-6#XMt(f$~5XiB!sMn z`so18ng`P*uxEU)FGPPfCo|RcgBkAwedAnVZ^Z(A#yd)&egdnffOHt=%N3x~M7Dg# zI?QZ^MCCf$irE;tI2LF{Mc4tWaX=gd-c;Q*doaGV_dh^5Y#zHmbe=|kxK4!r3XbID z(~aDLt>-@kza@s$g^XVEMu*PQ%r+!{4~IV0U6CBKX|>f&H=QC!!-FmC*Eh&M&LOF~ zwe*C)`ntTK&2swqUwwf-&c~^`1@!R;eYCI(a-dD_Uwz!$(2m(u-AelSoxUy~=dx7Y zQhJJ+n8@uu)XVh%Z>abzIMvrw0aQRwu@HlO*e7|up6PQE4eORw0M*l{DJg&gV3${Efwp-IsK=7RU+Todtm^p(YM-w zP_Hmb`0Z_a!{bzc1b@`}Atb789uIC&UIz9cNh+eszSlgKF_^BScFMyKzzO;(mfznM z3s?ocE8eT?Xekc8kS0i3936?>!Kj=4E<6ntt+9UC?wJdvyeYfS3%=Kciq`aB0-KNv z>FWD*U;T*upQ5!c@DDpWN4T0gCZt-JCzi^z8zz&?3+IvISLoz$sgyDQWf?EO@B?_J z$~IMs{qcO^xx*;5EImK22in=B#E`5YU*;XMi$@Gi?m<8Av`A;N;)OOPLwv{9SXh0@ zI$j1VdoKm@WpAZ8=bu$VDHLIG zx%^}LX~+Ec`8aj*;6?{E1ypxI?z;qmS^>Odn~dvXNORpd3ySq!vQ2Azw`3a+)m_{+ zYQ+zN9Hz2kMw0jcgDqYa4xj`59o0!HJjoVkZR;j-V_aD?Yb;L9wc8vU} z8)ZDu*RZ^stQOK!Dflf5oH9Iz+(AH&R+Q^I(XDJH1fXbY6MNKmy&_A$qw4QEHAfBz z^4CV)@YGw0E^{_j^?m&FL8;9R7*B-~pLvvxz71h$ip4UJg31dz4ieU~9S17!jJlyHgA_%rKUM z1@?_u+NwG2qFj$V1BCXYJjytYupekQzC6Y?PL}n?RSa3o0Vc-222HE7AYfvw49zEM zi1cTPv$su)Xm~bXwl7N}q4x^MR6+1Pi~8q1qkmbev6tdf__{>qhD|Yiccrd!So&%IWTx-y-2&CzL*>eCI>J?s_ z5Xd~;wP>q{wYY%?;p*Wyhe^%An1_{$MNQ(C@#ahI;{6M+dz6G#<^lvnEXTm(^3p3bWNn+a^jz5_*o#yy&$Bp? zGUB}(xoteD3gZ<$I5eZ6`J;b z4Pw)KwN611e9!n=rnH8M@aw<+;;I$uFU*k+Vg5qdF(wqULPuiR_Sat!EtrdQ*A7YA zJO0OC)X_yzH)_2*7w1jHcz*LYgnh994PjM({taOXmU40RJkuI)+y92J91FCZ7PT_; zV@Ll9VHc((PmUtpB%hr3%pr!{^z=Kn*v@u6UxEx7j7?HP=M2pFC9c(xN#4jSPA7tp zzhSpWJn#?$oedL1boYSMqrrKHn(1J0)RcVSsZ9@A1v8e3UG@nQ)DPrq(S6PD0*{f) zG7@@d38^59x6}@O`rwot`FM>ED!V8%_3iD6J2r|`!L) zvcyO9_fEe*z?m8syTS2-WkzPk2r9m(B6gZn;=tQoIm^0 zGa*PYQ}#6i-z6;E1dCK=(Nl!PW?J)mw>+2mH};w^22 zYeFiGMe)UR6R5hxUc|jBD-U6p9aJ-x!o_}N9R7qUWQM(S@cAYj`(zNBP}qV|FcyZ> zGnbBDV)M~b#ZR>C5MQG(3>PiUvi7;Bap|R_A2DemWIvx!y~6oOOJTcjh>l7E9B$t> zi1A62?du0*ys&UXBqUBU&C%8|Wc3$p1pfS}5N1P6co}3(oYw9?lUIjt@5xGA#_pKJ}OrAKwU4s zaL2FS&02)75ae=~Y%QL06Dk_e(+m1VtQ7BDKQq=9Rjlla{2k-B?&mE=evOK{u|a&p z;vdemv4W1y++PYm>h`Q**)C9q2`W2rhwKXJM*I{&n`Wz>XzhYynR37fGcD)@FDW0Z zJJrn9$Bq^$+G9E~f#>1v3(a)8<^;yw;pVWo=bxArlIcXwIJZq5S9KR>JHAx6VbHQ^ z)Q*_s$S2^a>dvbYDH^xpmt)e^4jS{0o^r12&QQZC(y+8Z+V4!Ox`M=6unQIrYQYbS z*~KYWOeAvmG838;v0~V1Ar5G(O+}Y4#Na1DVoZgoZ7{VEdl`kM#>+T)-9L0|v#Yz5 z&3sl+?$uq&ki+HJsux2Q^QoI351^fh}pUT8S@Hrv90#pLx3>QZS~(9nPqy>YqUjoVJxmjo}9OT^e~3H*Z3i`-&l6fyf7aNaax~`uld91MxpsesNsf;_J)q?#zej^V*WRbSk6>uOO^Y51K{ruB7E#^=Y9b3?ZkB^ zF|c$IMZAo3J!f!v)Q8$o|1Bv{yp6tbPWOnigvaOiH;tIDr@G8Ht<#hw;LY7AY|zDI z4(bERQ%+6fzr%PWO1~EKwQq z!vmj<-urEZ2LT-O+(@P9{rv~$H<5y)m(S7xf$D>N%!uRHv#$g4okv+<0B~MbnJJ{@ zJph~rbEA!x6J)tCk2i2uEdL-Bl0OV^Tis zK3s&#I_>kH^8g-e=$huys~{%U^Kb4!c*5Z-peoES^v-Dcnmcv=C^ZRGjx_+<Z7|}50(a6pEXOtjKX+1?e5U^#akZ|h6~`E#MJP9 zG?{;H?I77pewyCIK0YyVTbfI8pOMt|9li#Qdjnyfzv; zJkA&ix_!I83G$9#y5(b=)^_osOggt!xt^LcV&y`zlTxEo)H4^2uNNl2)C$S}tA~qG zoXR|1;Q1*0Ce^DJy3v;Cc!069SLoAZ-O92yTHgYTg`Y>@&$?W`KbUb}e?aeaS;(wA zDdb?}_dW2c{^4$0s~5DM4UhT5NY@sjsW+o@S^p)KDN?i@^ObWR_L)K51EDM>R=kdy zG^>ExuVgzu3-Ib3U0{U=iY}(+&q%=-`14a6HOl0w3NZJP%ClN!-4Ct&y+#b-x^`ft#*+%sD-Bq^Ni^;})?J4_kzWtL z$(Q5=uNETzz96o5HT+pIw~0L4OV-_APMlCr-*B?UKKw?S<{>Ql<)QJvRs$P6kD<-! z^w9W&)$DrDIu`x<(CIu_$KI-0gJ*cXXLy4rCh%A9DOc~A$f92z8o#^R)!-Sz;wM2U zTpmg>*@b02dEGj3-40n!6_AGUoU9ilW( z=izS$TL~ec+(xiXL# z?7IV=hX@8|1)AZ@t$sY2ySn;ZAl?qYlqF1sKl%-jSnZ!)*+WU!zR_(pyR5IScwD&J zVXQPi+K%MGNlXW4!dJe)id_@Q_g#mB{aH%Np|^ft^f3?NOpD#cuRlNYLae}k+_PJGq&F>xal6|!F!<11u+dJR zy{xQ)JB{rPxJ9F-x`xLm+f2VOA^v6~ykxz;2T1PxF}TS^_OMY%lbtI%6plKpgHmmU z-d&XGjOcIfS47+*El@DG_~w*J>4FAkvLe!qU|hCZKlL&>J>kq5I@UBfTj&45P0iPD z*75NPtkb-Q-LA>wS!`befK9H!Ld3c@_Zl@?Aa_un#-d)j@@muPKZK9r8H6_u_@3@{V64IN+Tj8=M~kEBN%*p_>B^-i?NV z6!0M!y0@lbb8+h+!XvE-Z3#Hq;$O}#6@n!J5T=i=xs+>x#4A9|2C%84)wh4Ksfk5R zm;g4FN}*v0z@~osr<$^TQc~q=DbjN2$hbIsluBPJI(4o5~f9u{gr<{me@R*t6|=F&MJ3HU2LP_Hp25 z#^qoNwS@TDFkq&%myzDMIzzdD#!FNIH?c0nREU9VQ=DT*iiQoE*4~%ec}X#@rC@Aw z&GjIyT@8wjX*lmI9IjdiLv!O6LQ-n$DzJQ6$kV8Rw2}AmC&;ttO&)p; zF!lU;ZTBdxU6cu(_loVGsN~(1QED>E0y8Q_Vtljb+uQDn0h?4jJ$HI@TPl6Z3^cvPIIFGYzLe_EkF{P7n9%tK> z4eM=$Hn}aEvUMEtr*w+X32d6WVAo`w6IeH)RxJhS9QqeszMt}WyDLUy;Z;Iey&tCPlxqw(HuHd%~y$bADUMR6K8=m@6%eeM7>RO z6Zg7`&vnqq!I_usc0sRYuY&io9H4GI1gEO>IM-aP5AIK0wk~X0mpU|`F{`-)XA3m$ zeEsVhHNVQOW+?LHXA0K=EN3pqNxJ!o$wb>2LZfnkUHZVErA;k$_NLhgI2~-&3vpj= z!MLeQ9~X)Sn(tx|uj@D#ST)@hDR`N%m)6YPn1uWw&?$&quV?>P=60UHXCKhMq3z5q<|I15^*Ynu|zKMfu~Xjo=T+*#u9n-IoH;h9$<-Zm;IL|f~zkI zSU_mER=m~xuLZVAVHU6@`F|GZJF1qneg;_Jto{F4pzx?!q4+6a0dV{Ow!oi#-fI`p zv_glpfGgQ=!YIcsFf+UZLh5u@zH5mIV7-wpaukwhM|UML!Q=5;VuB93b~p>kqa%b? zqEUc)HQmO8mv(DXg7Hol{H{or>8|eYK?4>O?O@nJ1ELK_)HqeCrx-PL>F0SywaFdS zM!Q~prG0OTEVw!zzWQ0z4%I)+e@}AFo3iB10w+0)#G&>Z#Q+$@FIYeng8Byc&@oNA zvjco$Pk!KR4M=QHjN%9PI#^P+gKqx|PQ~W#T!eyC37!Z;y*TNe?_;x0psMJ1r{a7- zZBMmMZ)Quw(|4C^wnB4Yk%)7fsAUymmvp!AxU19t0d(3+=n}7C@b@H%xDzyRq}fq$ zU`PcL!UuW<5?yozZoeedUS$Q?yOkRYkE3*?rrdn)A6sG*;CRU0bUl z@dsVh%_$y=EA{nLyD^5AkA=FF1Dr^t(@e8FfckN+mMyzGuqJ$$LlztsnTFFj!Gvp&bv8rI~ z`A;ER0cX-Ftyf<-h74B79y8}|hZeG0cbI1{K?~U;72fB^QIW}$ zy^#;aVlD^*QS+_8CrvP5<>Q+ym29+GKw7nc5jzmoAgh|I9kiDrnutzW4hXA^Vj{lv zg2>PFDybgos*WYIarvD|L?}Xr&U)pbqNAUv8zEHC+oUTD2F0zjhzs+Myy|1=2MMJm zeHr44U8Ts}DmSG7z^U~0rVr$W)M!~#>-GRR)y3fiM$SC_m33em4={4~<KUmV)^c2@$kWE3|kq5l7xd7Qyx_pl@hJr>66O+CwsVQc_q>&w(m{S7+Y z^DJ(rWYf`@g`x#u&M&2>_clw8@E${pTYNC(+muj^IHv{Q2fokQfUVfCZ1ZggKmhv5 zyFIIYgXb&AOnRey$Xp>wvmRKpYu-2VAF*IxpCzg!#y=e2E6H1t)`cQT2PFygO;}C5 zgO}g`6SS}{dFW_Pw@D|qyez?D9%FiF|A}W4c&g(Xp0cRfoE?&6k-k0ie?0H?UBbOS zT5so{sk(XeguBpYAGG*PcmORh=cnq{)5pUuNQ5@0^XRA3p&e#E&OcIhbLa^Vp_S{l zR9`~{PzJ2#{HrhMe4W}GT64}z^>tJLH9`rhkq3Q}7tn5Yq3i8b-#0?nk71Y0r21Ma zfB>56QVjMk@X0Jke(dX+7{R?KADs8GI<71Da0FO=1|xA9QhK1s?N2&5g{O@eKLkbu zH}toxpcElq9wX~5JNixcC3kA)EidTeg_nnzw~LAUSWFVw6UsJ1GXfNES8DonAGN@0 zme{f*bR7oX>)!S%##muA^;@9WtJK?dy{iT23cvr`2uq?=nw~%z=u%7QL8w)pI+F%? zM4lCk)iA{Szq@kKw?P0omC7F)PNE}`Z}Z>JyM70W#gj^3LDl>+dVg#R5vED^9!mZ zcy~*Zw&`4sC4_Mu|M+@c386QOt{o2B*7)IHzOG4Zg`JaaS~ zubvDH{$NSW&(srel7v1JiVC<=A@|<0MVv0m{eO^C>AsMklx2C$P-mUZLCL93hB5jV z2^6#^Qn01Vv~pSHzj)wNn_TqVVgT0- z_j^Jx?q zKvfg>YN#}07XU=?$b(!t-aUD>azjz{+0AIb|W;7%5PPsCnS1z}#5h(FJ-PGhdv zOqvzkWJySfd2L+dp4=M=H&c}i&E00%mKi~7w(D{-H1m3o&LN|=4a}BWG;=(sx{uW% zu6DPt7&UF@|7+$Ki}rYZyFGKTI=09ZM7hZh$k~1|-?-RkdCF6G;m}|^0cmfw1N{F2 zgc_}8Bxs0{d%^wREH6FUHP~&(@w0_xq6b#r4@omfiSg-8qDD(IE{ki3+)yZ2AKV|` z8E4jTnFr{MsaEvv2UbCMbyL@M4@Y<9Wst=9BHfD4i^M!wN&NjXgtpv1830!<=VO8w z^@3Xz2ErCuWR|n#QWniqLlbK(>#FCsu1P8-7VI8uT#M>#ht-&WKrncq^<<#jya+8m z;||r!RTQ=NSmL z9kkkfv&Mwq{l zYLjCr|B!5(j_d&Q7v_%1n3F3!6m@O?@fRCOA`wSG2#dXF%JP2uZwQ;=2n}JC1OJAw zI-Y6Lu0O#i2N;L25Vqeje(>8*mXx<0{|R9obtw8egr~?}Pf*xk)BK`f>iAu5Mi}5( z!ShZ^Bj^e`XXtv52Ln4lU#*UGYSG-o!tQ0gRlQXfyhCN)QFY`Om7wHc*w^?!lZfXv}rIZg_zV3qAxh($U->W<%=-qZ8j<94P2^QJ?<(QH} zr#nbUY^Zk8h;O-PBZXA=Etq7n@;UcJxPsvN+@@9h*ZIA_?7v_a-+6%jXEt^0^_Ttk z_EqyLIuhTPdBWr^xyqp?fb|kkA7tl=;CEzpTZ6ZQ=$Ql(3;sxO>R#Z;iDFj@ zdtwxJXBoemvMRWpLHl7bpa0XosMG<8PHNUrNB5jhEmcQplV#YTV zg(LZ+vDQL;?a_KKeLDT2MfH|@g4M&1=maCspzT(AUZbs9^L!(#%>H#16Ma$+;KV@PL_q+lg_EhJT~jTHsSwH&6Wb zLS+5ELv)|%)&}K8vZ#i+d9|717_y+Ci^GX&EH?jg73BCRuV;Ms6dg&kd zA6d>^7*Y$@b%!>kIW^wby%~sAJ!tKld|Qm`Ocp6v>J-Fw@V0A`rufp4|I;pwZpVr; zA|Ya}W{cWck)s2pw@jB#*P`{Vx>$`^9d~EZnH_3vQJfvV&93Y`yrtlTt>o7tcUvLj z0c4$?Y2t>s_A1SyY*XOuEPnL?V3Yp@apn|4m3;07SCOL?ne^^!?SUV}q3WWQ`rlKY zRN}pxTCLDd?b?HISRnIZRB1^MDO*fEuh2~}Yh)<%VuRp@Gk$CcJnyZxi}qZQJ;h4>Z9OodA2&J;{BWny?tQ>Bck zg*c^Rw~T5fjE4nr#nNj9qeaBi1y74um*irR zDX2`>MrJ%uBfe(fP2<>Sa7`8anO~Y>JjrnKjiWE0?VEH{zH_p18ADAHV|M;UqI6wjl*!gi$(unz^k;IpI1d+v%OpJ}ZvvBR%7u?~*LoYnSINDxRhO$g%Pp)j8qV8$2K3x-%6b;i?0bdQjl@xF)D- z)n(&Sw^t~8cHCMhe|-FrP~A{W{;Tgjcv7#*+VFk}`ZvS`3zk)>FbD>g;PngS=c`x> zWM`_N36!P86!K@syHIXSyphjdb7hgITU{rRFIb_um!nw@8%!>9=XAA4X&>sWCMx8K zw!nMKBi>e3X9EOgA>V#Z#k(=az@Rh zHF9vxjAiIFOr?c4#Uk>D5~M?Sz9nb}*OeQws3wdBSkWdhA#Q5Ft8+DA>5M1Y%~sOa zUe1oua~{tg)ny@zr+CCN?LH7Y=e$*QCt`ga{o@2LWn<2)j!?_bxa>~W=sLRP1kr1Q zaI=4;GB!7cnCDNaEtq?tZ8#7tUY4gP(m|5$)^eI|b->@NgMT3 zQj{-~^E@OVdG)nL$d-WWUaT%-m69*|(5lps7F(u&$qEX_aty8uA1n>_)V=8+_Wq3Y5(fhK2X;3SD}Xie$(0S9L$ z;@dzB%ZHFgD`I2WlO*#Y^~E~w4}?qw{8IVK@3S$;&k2eww4;O#6jOgxBZ9l(!ZR0b zF$#;undkx4ZW0j-?SAdv=VwAzKusM>hI5WdhJHyuhq)xStx|MtVX0O&2+PP%u+U1_ z2Qh$oSz5k_#VN;UamV#>GK#l}#%}!#P)EAqMIchLVZ5Klu2*6L7Dza?*c#0fsqTdI z%_O-u6Sy=U>&jVkC)axX>ILpDLj4Crgr6qAqt*#)6i@m!_iV_s5GXrjCKD*V?a@{_ z=Jq$9`m9p%7->wWPupis?;BrJenS}@En^N_YOTe#QonS_cKI6%mfqoAKhZhkqMZYe|Q_i0}Z_1agPTo~Y zSXz21cqX!A$6WE-H*z3FmpN{b)cv;#&*7 z$(uXPKt5+3O+CW9!dS&b9jDD2CfU^apLc}J`EP0x3#2Yp2<#{KbrL8laa!yJ^~UYd zexuBtt5hhzP&-n8T=%Rl>=M{$?9|#DY=3Zz&nF~(L4bAR(X502T31rtdKG3}m7JVaz`6rtS_>!FWSrH6 z9#HEh6LPEp*7Z==UVC8OPvn6}n`@ujB0Z^4PoUq0id+DZ)fxKArtw)Mts`)bBuBH= zeZL;xR7}wwsZ95o2$So#Lp@C=;)`vVB~aJN)sR0(PV7veF2Cw!1~GmqHoot{Az|7#1e`rcH%de7{n|VS@ z237~4A0%ks`7XKln(XGI{Y3QuYEPL1yzGQ%nga`~vvOn#NFWt^4(IjxLWpO z``7s_J9sjU!Wy?Rxjf|;YtYXWH0TVTP%&PZ}^G$ z&O?fI^Z&c6ep#B-!{vT?nL8c5!jM0ZaG#vOCb&C2IeB|!|CV1$hqaz;0m8eeLRT?X zWIwSHE-W@OIT{V$IC!>Uo*dEBZn7cyHvbGF0hgZ^gK6W#;&oTe zIdCKM)Vr6&n_VL#F|IL_lh4Hev6*ZB4W$o*#96J#m=ZQF&O>P=e1hWlCm zv_;RE8a17aLOr{Q_gJ*$_?BN9`MkyuNGfrJYBSUKdi8BL`McZcma#wOE18u(tUF5j zE#YS>-#o&ZZfnRxon1eT3q~lscN-}TA|zUFJ-S~du{O~0rF2DrP%O(q(vM?1(Mgvi zd`--8O?!J%{HA76$dDL4JVt=G;FrLU3j3ozqSRHj>R|bq zkqY;n{QGk$RelRk=h`Bpl^)e>Wgyp(yyQ zi3u)scl*eX+7%%`&2b+xLg*?C;5zUsLK`~G(L6%rFZK~!$Jx5#{2dVV5GMp* zU;+D^R`=d;o|p^w19wUG10&h*;fie~gF^R7KTq4ym6{Nf{hIXXB70hDlM8uiAT&nxM6 zuu>4B8#0rvK1;VdWriFa^V6g}t4}F-+x*r(bP#}|g6FzZw=#V1YY-Ar(%^O@3StCb z+d9~@d2XKmrh3BN60)7Dl$U73R_h?avFZH9u)RTYNAF-|8w)o%E;-~wW})%LM^?1G z<@lb}^$cH-QM$Ar0z|MteidgoisW(XHEB?%?;TwR-_znX;>W8vv*80$AJEnmKZgnO zeHMqX*wdj$6n~z@Yo;mvqnMf$9D1_y(dF_8&u~%6?P<^i>w@9YU;rNy<4@k`F3Nb+ zX+zu~2|`_YTv_6v;l~SYTBOQ^j+{!4u4;p+N3{OGlbzEqz#7=Op{C6x-V@1BBSl|) zY~t>c_kJt7xd}#aL183u=#xWx^t)k_G=k%6g(Hlm|ZwnMM zBd(2^m@(=naCBtbBvPGqQFJn`x}W^E;m1_5CAy*;j)Rr26kK?;@H3KxhY8N7<#11WQ)$5qx&+)kP400Mx*El|3a-o)q~drjN9!!k>NY+ z^jrcAGtR+k=~-plO5(_+bEm@%UB)fH5m!;$N-7q=EYv)Qds)Sm0htYg%rETz+2`Fw zmB&#yDD<>sFVpBuNnRj8`(%ZsGB^%LVEHb8k@+!rGSkAnjH;lFGEcBXyj{R0g!RHHDXf-nJ`(us`%nSD9_JkkjvwynF)#{Pk z#|O>_{&e@M)g!c@hV?qZnnCkbunX|*2?OW5e!3gg>frz%BTsx_a-n3uJB zx2ysLR5td)r!uzgFXFao6qrSL|Jl4@RrSNwi+xhF$dfs_3z)z1=`d1?7*$nu)7J=WJQ@nsq6 zD4_lf>O&fT;XtjLx`Jc)djcM+*5%bPxGM{*Id+JR*}Ia}sgp|t;-+N>{vFaOI*Nuf z&Uo<32heM@@v|oXHsCl#?a)b#?|6p4w^s@&o$meJO4)euDcEmlSt%VxysW{^Eq8*S>MM z-mt8&vuT{=a-1fNcAXr9y*UnU0b@=tf;UT)1W^&%yzk}?IqwXXJ1H& zJ;(*2@tWoM7`xPp3NG59)0I1pN7N(8waa<_TKebWNoal=7WsTtKq*8WV}kg>O_8aM z+}9(tS)}morQ)Qu2e+DDenN;}Bq6nPX9c!d%C1)yw)R?UwE0gTgwOr+s}!*osSO%l z-a5(k2Y&FtUA7FY6HklVR}ym#>`YD<1Ka<)OM2oY*#J;Wb8^iYynL|vPKf`QkOHkW`-FdAWy}5%+ zM4c7T_q`S98d=>wHQ|OI$mN@rB<18?VNEt%5Wi}a;cjm7E}*)ms(Z=7^wB~VuI#YM ze|;j+|1fYAtrUczXtG);>WldWE)djAkB5@yauRF#G<{+~19*c-)0c*)SB%~hiyH{3 zMf*lC5F`SW&^K^bH%lU}zj|jcp|3v$N}l88YM|AyqQ<^1UTn7>yR8-zSTm=E0pU50rY4lEwPvxQ-$FfXLNLm%YajtE*!EBa}E!%wS&0qVO3a z0m?k=mmf+_j=q`1tgyD$Xa3@&{H|0A*f{7o!3UW_f;?j8rRIApE`*j9pD%`QSr`Wl z(YEp*$p!q!z|kH@YM@j$SL}PJkH=b7u0-+X1f$ABd-znzu7bQj3%d@H|mb0)i#h7sHUIKreu^6OaTMi_nXR<5R`b7`r{fy zhO6Eo3#-FADPy%k(t*SF=m=|U=J9U+=k?I_>Rt#=YP26&+Ty1Y=aaXT$0V>XV~+jY zWyC13jgwx1f_naE!21#MkmFLgA2B_H-dCwK&+cZT>#=2?fm{cF2R(SE$N}uoj*hjX zQNC9+SlFMT&04-!bXeHo9RX`c34E`3up(g>1Vfu6bXbv*9c!ycy?n3Oup)z@*QE2k zV#Z<(?HE`)8slrE!D9UkZPxQO(qXZNcZ9ATCGj=#V3oiw_zZ0x(qWZEcI>Vm_475d zVU+|!ugTzRe1TQxRMw{Aqd87B9u$k|KCa<IP&#P{Gwdj$X6ui5u|?cSL6%D}k=($ltQs-ok5o zs0z&!V<4ru8Qb~-N=j-3hcy!P!EgshSKekePARtNwlW0jAQNO%f*xLL`u`s?y)(dq zM6`4TpdAoesyPDOkl}RqGr?wf&tm_zW8lY?<;Nogik>~c>ZF@rLj-1=Xg&%I<}wh$ z_8!>xgZ;l5xu(k>^`zu=`20ahVD`enyLj4^4@pyxu63TJb{GcURdDSmOq|>Irj0FO zU8^izQ~r4|+|V5xLwlkG@}i=6>+tywUh}e6;8t-nt@mv0**RlqDgQ~x;8YDgkJU2F z+jdfF8UK8YZ?ADRoD~ufCb?8S7#trkFwIef6U2agd`S)4n9`3c*bR z*}=r9Sex=T6$kBcD!kmk!)~7f8+LYu|2yn=FzR zEo<*+%lTa626z)XDY^AP^k{V9;)e~86=tmP)AEEDeD#dx3RhoR?_UVfBW_|md+EVy zIl2J+$Pggdx(WC+!@?&<&Fq{Z93l7>t+=e_{=?>jz#PTEcxn=7Y2M3x+!?La>i5s5 zeiYdr^hLOqbKM*Ag{}nX4XVBiS0Ej8_qI505eyLqG`pI_myhUy;bHRblSlvjChM z0WY?YhK#F^wD|}6o1$CoUTBZF<;^hFsagY2?7N}jPSzW`ncJpBlQU?lGQ9=qdGQb6E+2l=Jm-o4 zQQmdHnUlTLx;lM7@k)EhCE+Rs?lO|+)qNG5Q`D3T8RLaCOYGbW5G|E5^$uR}2&m=@ z2uA>ZDnK^deGIpA`Z<>B$_p?%jeK2hrPwJOZSk0KQikRi0TZk67}D&iG3S4;$zwQ| z1d#vAM1IeiKXmFvSKQ)!2~52o;JVbu31g7+xRcc_anl#?hvDXgct9zIyt8 z7AoM5ph!iCw|?--!Z}*LC&x<C?#D8}Qz*-|G{mPor*E z!Futb%|+zxMI>m4+k1V|bPeQfe0Xo8_xjJ$+n46JgE0g2ViegmYi7vXzMzs%=l7vb zf;SbDvLc{kgxyl|tiZ~pOX zQ%EGGU8`>itD&nvbGqoSD}YE^3Sw(fkK+5&dws(_*cSmBimK$z>~AM0S&YS3uW4nNAnTVZ*b8T$&c|o(RBC5q-Bup}w0z^i|*lrcXxGzBQJC9Bn2%6+Pf8E8vN3C6ciZ#8YLU zJ6%e|GX1E(i6>8vd)s#@Vf2)o71H^cjWj|FRAcNuJ!6H|Ry7K;_);}v+G|ubWYo)E zHSpHrUe%~>$XMlo)@EiUMBT!%vPUIEq;f(L4X3h2P6}GlCM{K2(I=6BX6_Rjoo5yr z7%gQM=YzXpTI&G`G7WW#eqmZ_1L-ztG>3ppLJd_Pn1t)#<{PJ~;ffmvDMYUu)k;MN z8wH8tJ~yi6hjbf;az%p-OJ73n8x}G^at&%}RK*Pn$(+*zkF3>c0-{vabNq1l)aiXi zsnxT5j322pzRNsTOY;()Q)2`>b*QC#WEQE>yR%2D<-0n0sWH0Xn5gAD&A!rU92%R{ z&g{W)*Yh(8spXs{i-slQ5f&EWPGHkYHAe4 z4G9f#qBEG)P;Y$iw4jHxuVSvNGNrPmz5GoDq{W`6vZG-fp<<@a?yj<_>SUl|t%4(| zvZ{2%s$!wYj<2#Te}piW#OstUTREr9Ak*HVoGFD!5vWWnNusNBNzeXG(U|TiZ#XI2k>Yt}fMOv{yR{+} zHIAxc4rL~vA}s|DwPFF8=p#jHQk-LjTw;|u1$v_84uwpDu_A@%_&CuD=}*VJene?I zzQv8wRM^DM6jCt6T7IFh^28rg!R#^4x%}26(M5R^betafbu^VSc}vvgc=_e~WAEk7 zQE))=OGsmiLs3s0QxT&W<>?SIaplwREMLh{U;C}dW&MT@$i29vtdh$(hbGI>oUsMS z<(;%y%TXLxsLEv@Liyy#4l1YzLpw%zUMICEP4T4GDY^2LR$x8ji7Se%<*v<%)Z`9I z!#d-tO&p2i3XP8B<|_R>GV!V~c*N~ht)J4dGN!X%dSuq7vCCvEr$pjq zRwgpu%NUMBK{6|2eu^@NqZQmTtHV%onYTY2QDt@qEB2)g2b8Czcl%}@yyUEs&hHsb zmZtAk4voR8AIj_W;}yG1e1p$d?;d)KCUOigFd${;wgQ2Q$#&oQq8bBU2YlX#f#8_ z;rJIxSrI%hfMxTho^ygEcQYLoCCxMZxFr|T+Q=o1QjJh0S5lz+5};(tDG6vI^qYif z0$ZNMMqFEjgkfxjyTocV)Ij2GRE1=BsN8TbWs-y-99H%`^=nMfx zbzN5|ojq0Xj23ybf{52b;7kwMxJegN#b-S(vgRj3J64}ba!8x^1-zd7Tn^kh3%&Y9 zak8X%dFpdySLmsIM(k21X5tQw7u#^9d@p9?0tJb!I{)Pv!6)d$EAj^J0&@D+gBJQ9VhM?luOUTLB%RV?n0?3NM1@o&Taq|{h0Y7^$E)m88xE_ zqmU!6NV-7Al`uWO&1XuLUZ_N?S+n;Jj(S zHD%G#MwAhP#CirnEpRE~g^fqcLrYsnqr@9NQmtj#y%=JH=>G&2EGSHG@0}uvaCIJ%B^L;C$|$_Wws^Me*Gq@pUS_An zVT3=3Cbc|78e+bjzeU3|@wzPLyr)zGe^%3oi>UQ|HC^4LTyvtm?mp99I~|Dp`7S}Z zt}3k8zbu-HW%f|li8-9Z4`z{iG^58OY7)|UGNBwjDKkb{H*&nwmx@#~&4JtRRhKo3 zd5&JOnQcP#_=y!n-kmGfz8shUY=x3pu_f{r6iML^5A|r+lV&8Pl(#Q^&dM6F&ZAZk z_pnh#*Gfs8C#B3F&a= zF8xlEU35F=woWrE8@&sUw`538Ez8B@0Qh_8u%~7r`(^@{>jPk z?0m(^h?{|yC6MB>ny|%ds%#}TYoTi4oz(kwCy;W++dxN_llM2wK7x5-EHiIJ4 zC-aR8tq`Ld;W5ukpOj^BuaW0YOJD#@`1>4ycQw*u|0`*=XTsKb^9xFOOz6gn}D)_hY zCQT%;;ZfE`KWBS|`4jhxE3UM14f@BFd!~vr?rG3bL;Z~|D}oP!Ewv2Qw@&9iP=FN{ zGGj=7GStQU3f5erIGO0;rM3?Xo-w32u_Qk^?c!y$5BoS%9yC)PI#UkcgP#24Ll-YA zY$soelfACS*Y?FBGZy40!(EN9V9ia6lc}yoYWw2g84HRNYx0xxu0}@t;*T@+K{NHC zGxhL27|Bn9yBb+xJB3i3EOsrwSV$EGwGvJlQTJ7KU(rH{>^p6TRwC)7EWaDVCiLf# zx&l|HvcKm3Ob4tkrkx?O^Rp{thW$RY z3z+==^SgeDK-mLK>lZYoLMqSU)B00U7UGHCKQ)SA39sC_w$0?kK1dc}1(-v@jyjDc zU<+sEJhe1o1Qx&hGdD`g{OTH)19NJAQBmaLWT-tap|F^;c#yDxvPN$N!|nOh<4^Ec*LDlz3qMh7vPN z!zpp$FpLr#M0nj$VjD*oCD!@zKPa*3JAe{%K=6jl)Nd&9t7JGOcDkX&^aDU&l7Lpg z$$3MGvw^@0I|RvE!vcGo5^ML~P-43OQexuee<*Q5H=Gi)Qo$*41;sy<7|$L~iLYyJ z;XfIca(oyi9yQYbhY~*v`G*px)W9gQ>>`X3dwjT|#D+w0N^D;9Z%Tai6HbX=`TmCz zZz2CfiCON#De;6ioDz4;|3isSwqcZ*2mEhJoHYld#QLi@Aae1smg_&1xb^}@iIZnx zl(^REh7!j+!YHxj;4KOXP-1R7I3>o|`41&_nZBXK&eJeT%sB-CC~=#C)(s`rn1)ee zIjetBV*M02CH`3cA4=R;_75cvhf`t~OBf{vm)=m~?GhLzX8!g+DDi&74JEGL{4XV5 z(ENuI8*aiWaWn)@iA^B?P~z`Xa7x^$`7cV$k_V^6^m+fG#Fn}LP~y;YI3<>cQ(_+g zsRERE##IwWiF?ieO^J1GD6!;!D6xmc4JGapf>YvHF&HH_A-JK$o47ZWxZ>7r0#M@U zAs8jrNB%D*zWX0ad~yb(#0#edFiPyk`wt~|4E6f zH-C0T0hE~XA4=?dLy0Z^p~Oz3043%DDDlDYKa^MtMv1)vN*urXmlFS_#D6LAUrPL! z691*dcm7gh*qb+hDe+%Q{Ff5{rNnE^4btMOgk%2_ml;`8(NlzGq)C)2jx^MZ{@b6KV*O%EnWzF_R4 z;kNCi=?hlc+~)%pXGB=)eMiOKgrpwvebXB$+eG0$`23d`iPw(AY7qy!YBMPNWgqWY z^VnA#U2fB-PKs&|xyGJO%87~_N~aP-^qeLwBHlo)=~OCOxIlp;rb6{ zlXxF4tR_1S927;q;m_Cd-HbGwDGY-Zt72HMMSP`sjh~8!JSfnT@vbPg`%2rc@$?L4 z9Eg({WapGhws79(QOii|WT_!AVA)eG*^zMiY2~FBc#<7nc%~BO9=Uj$K+F7c9Gho- z@4_t0zzgBbMUhgUvG&lzJV&Q!#n#W0r#*Q&puag4q28W1%+Qvz&ERLD{%k!cOSULz zJu#QL7Ov$mUxRk>b~Cko`8W}vEhH$UiDKXTNus;Ia~6VR(K{mbIO^^`<*n zprMDI3dyAuLSnlOtp5B*Bh@jM3nB0CpN}H%2cc2c_hREO21xI)g?rW{8-G3YQkn|T z;;vt9`?O&Fr>yGXQK>*p9>xH^neY6)t-+r3#J1p{pVG5QppxAPztnBNlA3F-T`Bk@Y4<2M`*h9d`k9; zh!p76B7G}s|I9}C4VMIV!x24VJJ)(*Jp<7SCHuDXpPH=Hd=&iIG*50*%0V~{O~z9E zU=Ksp*kOGiFR0(!D)Y_Y6Q)1Wv;|mbkiCM14bLlaQL_45sM`CKb6?0gAcAu%cjjG8g3^PG0BpI(C=K?>Plqj(iuzLKI-Ql5cJ->kj z!{@C>@-+$sthHz&Z${mteTF>*a|Nu{x4a&Q$koRowkHn7x=7!@9XwZ`%4x|m!v?QZ-xDQ8A$RQyAF^B*`3;>f?s(aA{<$uK8#)oNrb@1h=!QoTl43w)5|u9i9Hl3!PAtt@F_|+SB@% zfyA4J>g?jiaY|vMkz7aJ*m2+(j8BI1+pa+aHdpX7QPhTV6jVvxRpjI(%KZ@?*q*}2xf+#{}EHerw zCEjR++y9Aq^;oXazRGmmg6>fz)=oA4VZW3|jFHNqtOKj~P1ft_djHfi9BC|=JOz*y z`uupPp_q2;VlM0vl4e{*iCrdbsUyb__gdYae! zs5$c0m1@B6_hlVZr#Y5>pwITubG3N3487Z@N!QBy*OZ=m-}^qO zH=LXIHn08t)7QpGWB5JE@IRb0A-_U?kGMTTshpWD1k1wOnHsW{AypvGHTX7&q|^|L z-Jo+&wJlz^?@wus&tBE{=ue{>R)GTXV|)3N1h2l}en zWxEmi4DA9grw}coO}v0Qf2&n{L7m7Ac6@!o(Mx|ydLQzy*@*DQ{S3{a;cR+x^tFxflZcH7j`rajIYiFyq?kX?j(hvsS4gv4Mj%B(-Xz!&!cy;JD>(ey{(#|c0A z<*v#EAQ5CHmp*G+wR^Y|p#HD{r+}xstJ~@{ha|}a!{l@P@5ewg!=MjD`Yy-IO2h}q ztjU9R_hhI#0JSd5MEN!|DU@#*pT8zMo}vr5&aQ3(**f$k9s4W7Hlm~*)*a$OOrR{OV#XiM}dVAUkVm{vLWa3HgWcZ zm3Ivru?KX*%Dbdg*W5gX?8>;u4Z8!nCBb1pd6$pT5u`I9EOIQCWV7}Wjt5nYq!o6` zFxC*_*}awWPR1@2A)1zRN~KJ!GN6bl7BMP;Oivm+d+L!!s$eXtun`OJrfx*h>u`4B z87j2QP#2Ed)UUPA*({EM_Dau;=lLtp14kA@%dq!!#tGf?nU|j zYwlc^G9bj+wGXx;G45qhbIRiyCC0T0odE&HjvWxy7$#-R4J(T`7ii!DS+xxeJpl8T zD>ZGbmS+X@=)t;h30ZC>;b5ATc7PJW7 z*@v^SyBf%gVH~Ao%=QIWtBpHc9wk+7!DGE@f&+h8TD8y`6ig)VcsX~^mVW0&5^y;8ZN^(UyyJ&$_N zcnF_z2NGXM1<@y2!8(P>_dpRXV;f}Br3aj(lOp$NIfS}zip$AhZXlO{#pSq>k0*-> zzc6w!GWL{|qDt)XejoHKhXmaV!$kh{COaf1!?MG^`%QM3J&>XD8sy|6;Je8V2O8mV z>*Z-!BL;Du~P1x{2OJM#U??T(Xb9mC@8=}E_Dwr~}b`LBM zc#z0nnzf2r54}g<`YH#k!}4F_NfURe|5asPInh;R8HDI+w=PFbanwWDn{EUvZYI?m z(!rblt(Ej#iRYDw_l8!;8VP*-Tqi5`jr2Hqu&X84d6n>0jCb`w+_VV~T}&2RI>AR}U6 zd&rRIxiHK{&?{jq!0^ibTtZe6V}l8ECBrebshgIj6yu1iSHMn|u^sp0HYMBmiA1cC8W`V_7z?kGjQx`lpNGe*q};1H8Q9Ll01k5J93sn*Pu0zI?{w#pc0SVwu=i@ySTNQ6 zLXbmc>?-rQ%v_wB1u$~SJ|46GBj=O$vn`EQwkP@$R{0Z5o7T$!&0}5DBKKMlS-c=YqIr*p_q= zx&@9j1r$4f<+usC%zv3zbHCeRwO!!>k)9fCFl4|%c(vCF_X z{MK0vx5;O35l9pvp7b=40+k;cs9MdIp+GsaNJ&W_UoP{zM)NAa^i%e#Nl+Seach># zGy(bTzUHnauSS_k|JJ8kBrJ!P5VLUqZj6x1boQ5CaGgW@YTr_3#V~H~RlWjEANR)M zdm4Q+H9&kfg?`uE#P@pRbuB!;cp+A`Kzt)BJ7$3Rn$)sa!{RGfZ?_7>_d;`Z@Ya>Z zbcKGNa7?-9=@;?Li3Au*-n}e|j&%aQ&i&I2j>F9+(yPR1E@!%EzDyb3df*GiiB-&0 zX&qeNWnkHf{!?A;{}_d4Q=!{kq>MPWAdni?bSxu|D-fgHWa)M{DoYkEBTgz1quR9V zcE{~@kM4F?h3}D45J&^tCS67xUjQ7Uz1v--OgFp$k9so`-b^Y`qu#{nc9$vBjVo{o zFHob|ROxotDVq~5n=>w(19na-7^m4>hHcZ0EQo>qKM-NFPT&mJq?1Huq!UcIM(}ug zcXT- zCj952TXF+kGgKVU8gq0iyrfYt=e5@GP3gh?+*Ev>Sa=ddN zUKktu`kpEzqztc-UF9N!zYll6xzyr4``{fkCw^(lyCD;7%JeHFw!e)DWRGp#`U^n@eYktX|1`jP4SUwRgCv&!D^Yb)EJ!7tQ`A#WcfED5*S8|=F zokHAZ&F(>WX+Z+%(yt*DuCPGe7WWyL+^)$SakkTt$&8=)g6#U&(DwJG^Q0M zo)M%M=^waBaafN@g&(jolVbg5aVDYrRa!zUyv%Y$gf>~qMnu=o;!Nn!T3SKCG}AV7CD^EF2_*^-EW-9A$s3sOOMy$AP?YRSN|7CmnGcYJHJI6u&}5jM?@2>|HTjDp5A0tS5A7S!6l3nA5-=(MQp6~Z*aIOJkM5$L6g}CM+}shW%?H;@R&7ex zqxJ6$m)2u$f$EFYHtmaxA~*2U8OPQQVi{HYPel|)+@A&)g^Kg2y9{6E6qgEz zXF=nI*t4j;g$gs0&D%oO`TbRr7MqDxXsO%7CiRMwps>Q2347ndk#T$%2ApvN3kDSk zz8*u&7^@P)=qSEeA*;|gAOyN4Dyb>aIIly$m0Z4D6A zhj(Zi7C_n!)Cw@$QKjgW+Z6ffzkEl1Ne}r({#QQzS3dn$KK)ld{Z~HyS3dn$KK)ld z{Z~Hyf09pMpI={{R3A1ooYL>i$+#mN@kqeyGLLNpq5+@idQvAsDwg=~J$FO@hr8^m z>K8&w1TdLvQ@y(3l2iygcSE7N&%fMyx#O1_uaixzUwVH541FB;IYEp}WR6XSHFs6c z@&TPFBW-aB*UX3LhS>K(^fQTp4R0cC(~p1Ms)h8W+!|fn-G{7SyunY{MFZ@}NBWP8 zpLjyjIUC-eV!x42+rx|}T%elRY()cXqJ3M4HZ2-Rb1jib4I@E#VGQU2s0j8)|2hab?UT$d}pg4u1Ch&O8eC z8j03^6H306s!7Nmv44u)m4rG!ZDNXFCEYdI1g+Ba1OcB%MWwtvUTWQAA`@ymJa^dG_24+McK?XfVL zPuRV1?k(tunq=SkV}!&Z@+=aKQbNX9#=48^Whz}QK)vaiD+y~eBWtiFp|_wn;1ZQ- zTFOgq5arhT1jpW!9?;w$HA`@pD5AfePs@0ZgPol);1b2su>;6A;1XS-u?AeCZXl|i zKeO*b@+q`|A%IKt<&PVScH(D|7ch(Vch`pu-)zV+#8w^^wX)$W8QOA5FjL~ylGpei zC{r5zH2EVotX{4?s-`88i>>j37km4@g6hsA`Nl06!7Rv%}I`f*d zndAJ2hT48M(RPS6}s!J9gSx>$iR9SiMtt}=wW1cBr5t)j4|3s&HG=Yo@+t1+SO(?GR0*ggt`y4Zp8 z-=>zr8;fn8xL(vD*RRuYI~EQwK-5?`3S{9b(a$ebI;nGIoGiZtDF?#5aVWg@4j^E8 zi7e!PMZJxQjeLspuY(-;OUn)^Rj+@e+M7Q@~G`d9$CQ$ZRGJDYC zkn}no3Fc20q}L?#RjT-=nY0E^cjv+umdb&UHGTBL+wSV=#ct$tCFS*d-l+5Bt^7R^ zvVt-UzddmSR)=!=NHWbkGm7jynj{GdSMwX#+^=v~b(VTm7tR=Mwv%i!p13sKf$$a$ z@O?R>Vg3E;O=Ax^HMHP!tG3$P!tx#TU(aKoHYN~d6^ZCi1L~VlrE=Z!yQbwPh;oYk zn3Y&mG3snuub!Ayp_{mv_}WlQK?>q#S*rC72K-!UOv1!iE1N{WDs19!WfiZd(J(-32_Z6pP}dfjN+ai7QglF{Ly}nohvc3GUn_SE;XC~U_VY1ufz){;XVIwN|Xz<;@KFCc*?<4qT7k_RDeoUsn~@5Phv(EQ0JfnRH8i< zXMn1(Riw3!+1eS<`UrM$@0zq4zpb~o;c&&Q8*bK+8#BUhu|PAOCP!QJ4=PV6GOzaY zLfk&j)o{pr$+Sw)eHEH!J%9(ZRyQs=eHy?A6rT0F?O6jfIP({pTBZ*IPnu^YG-SRI z)}YO~@+2VtN-A-1fkaYSK>BuKwU}8<-^6wwgc{-bOug2F8A;jflg7&5w2yU>w|=r>=<2c;jjetw0MJdDu)9lKU?egwNcLK$C5 zDiKOs6|MRwH5=af`uz~;UiK&6tY%ohnSLjVZv++SH}A_^vxAv6-hs-^eW>rg*vi@k z7N0)rzpNFFQ)lZp25=1~DMQOp?7t(*4$CYfl*+!R|Ey01*6GK+c@OeRW0Je~^3 zL?dgpr*CAUtaZm$pGPWfkP3=)ZnTy|+uW7!oyT*D_Un)Tm)4TwUDG%UGY=8T5T(g&(HP7FJ?^DF#ezzY@mK(nqS*5+r;a zbW3c{|3yr8vN^i)s<%0s1EKF)C)Oz>54nEJ#@v%5{q6DKVuJ-h;sLEF&BzwUP`$}D zElrZ(wq+vtPPxPJT^YzEiJ$D9qjj-pPu&9yi;3drSh!O(Ut-Bpeij6H!umN@5~~M2 z77^U$Xrr=h+GbgWLjW6F2v0gjzsQaMNL0yAtOeS{P0g+XmE1}>FDniq9O(Ad1|yVF zbkQ)05j3dP*=)b{$Ow133h;WDZTo0ffeDPtx3%u3SdFhGq@QJ=RZPhiejRrP1@={e z%XdF*M9N^A{6I{|e!I>2TpU@~Wr2Hg|ByS`dT(1L z(lLh_!Wd9~j0sToe(-gv`rWYie97ub5L|ecm$_T*0~4MV)*hYo`&?L9T^0U1 zCDxko?#+!Cahak+u^mU4v68~Defh^Gi+Nwu2aUFoaN^-*8(e3{fZmD@*V)ZrU9Jd- zTd3KFc9Jo8wk`wGw9mR4zV*1I8JS!)aeq3kCvC64Az|x1+Q9TO0B%D8ah_~j#QaUn zKsFAvW-iQO2&49ro4CVwTP~GI`V7wGg(9Q}=BxPDS^w3pLGHgj*h>gv3@E{9hNyMYhG%mM$!e7c3hsU=VF<`I36_g)`+b8U1PHoMd5niy}cJyaCIr80pV}ObY z&o{_|+nE4-e*c;QD%9Vu{2Rin|2LLKe+GYNT_i$)|VnJPNV@#C_o>KkjczR6az$=^nLj5sc7jPj)5gq>R-JLo$VssKej55 zm+J0H=~@dg()<~IeUK;F#{z_QF68{M8|5ujlhZy3CbTPJrfkk(vu~uTeP7Y&-u&@=S*|FM&khcS__?qj8f842y~!-V*)B=;$eL6V!H!>?nkLSVEI!pu{A#ouWJ8;T{~}9#!EU&fy-O&}304@snDyH?W;-p~S?s zF=9JT;T|$jUHn=+coPiOC8!-2*-;7iu!7D3ACT0Ji|#~)dvJz(REK-GgnNLYbD~h) zC$(yCU_04CbqQ;G?#))-!5uD=DIhCM*a|b4y~~xF&5@$5vF5)+0Cr%zVC;Lu%VyxU zmimlEq`I()J1y*7UhZ(>V|-d?Kpzfo#l?OX-^P(>foIh7`FlMmo#EAAch>MQUG?-P zXuW>>pJGdk;I5-8sgbiuG{eT-adLHcUZy+VrIB5y0ntb@vP;F-ZbUN@7&)&a%-_1e}Ofr=51)i@`T(eA{66e`zDhL#@d2*; z)mV`Xz5YMfeX<08-ObwmbKOIL>u$1@`pnMoKi6IOc;X{>8F1Yr{(r7}rT#Zd>)xz9 z%f;?D5ti0n&hC~=9a#pJmhBvpmYd(5SuL%<;o@6vw#?lxY5YBvTby}wavWpU_&7&A@YfO66~7(sMt&bXkWueev{G%3d`=J`n+1LjnT z$5rb|Aor!Xst9wd*baB|-2RttF~4no`+Aa7}Y8{}xQ|cH;5L0VCSsNW{7N;3qYA|^g)u=rg9~G*q!4*{~?`EiU{K9xuAsTZzRM0J7 zZmu@6@s-!8zK` zjn@<1VVP&H>AZ8?VJ_bcZFqEZKKZYzOMZ%4c)A%{JN+~yG-^zpHZ*lGw&!D1x4Om0 z%DC0tq%B5es?!BGro{FJCB;pc1?5IQMGGnoRnPt4 zANW-AL#~hddSIo8dT^k>(*$i`rj08Z0VL~f<}>F{3;z19=6Jff{*`JIQvS6H6YGAV zQnA5)rJ}Jf{6hI-dwt`$o`QTsUrwORj*q?>wFI&OMKD_YOUbA@Gjd!K?EMT^rZ*N?MG$0Q=93$AKFmnFK>rg}d zRdv!wEBSP2MwY2{Qhzu-(qS5^Jl0MhP?^(a>Ysh)z*(og*)x`=ZP~37ti90bZ?6sN z$kfzcYFif4Hu>)VLVKyT5>wl>W%*odp~-1c%ciliM{B8GrA*7FZr0qI^OF``&6u-R zL6wTG7FnhL8?C(ZOhzq=vSnPYtP=k#O^V{m70v9z15Jy}F-gs>bQM<3w`uX0*cQ4EIx{aHed%e=4eh)mqD3m{zO4w3+F**3U%pz!u26+2E}9ImBCrKfsqEw4?GhpM&4v?q=pt5_(^(keIdk98|$ za*bIixeSbkGLKCuR=#jrm6(-MtPU6rC3aeVqhg?JB^o8EydccZI$HSP2n~H>&ZiFR zE8}*yUXEPzezF$@?<|2F|4@?G68CRe$MdM=L$vmF8n2OEXx4nZO!H{G*D~90X|uYj zIIlF;-z|o9Ut+D%v#!sIy)S=z$95cse%$@sP`bbn0e?hwMJ((XjruSroG$?MgxzSs z`pFHEs^1PQ%#mPSp$C7VPr2G?JaV@dh^pz9-SzH!!aN(whWkTV=fI4;bX$1-!+hY~ zy*E#5m21v3_Pe4s#xP|m@TAo9gCZ!x>ut+8(D%DkoFVRIM*Z=VL=I@wD?h&wBNZXB z@jOGb-Dl-v0cMnfCM)kQvv(5~15$bN2|(r%;_x#%{x%l&pkv`#w07-)6ehMOgD4m zvBO>@hE`emkf_NsS>0NB0YN;+f`_?*UeZ7dwuaB*p6&;$Y~*uIf5DyC)#wQ*=RK}G z72>`+bv764bn$+dn*Kah-QT^)+LJyRjI}cDS$lBi=bM^byEEHZhb~`pDEeB%x?^&e zoq@dRrG3=plU?;Pt%x<^xee!O0z5N z7Vc(`=nTm%vGzL!9XkpC82^wEG}ueOglnoMGs8QJe&&ob9XPS3wbg_!p7GEQmzlmJ z+Mr)aS2ergfi*Z})d#$cF7~CL-3eydj#Ukyl8n!dJr}We-@8 z&R`i;nSa11c=;0^|BoQ!-hS<{p81XYC-(EgzYS0{VyDlDV|Z%S#T!F`Wn9|RR#1i1 z?$IX91CH;fvD4~HGp!qSUx%nwSKp_HGh7a!4T1v;Vtk23*mYLa-`#KW45vp>zn(fS>ERU#=f)FA6sqQt|) z{I?H5KN*)+*Rk(iol{T;4^2IYrew#?&41s+Vym1{!qMBRA7X`Q8`e)W4hzn11!vD@ z>Vvvz=l#vw(U~xeY75g5ybM<`>G3iIo~``Vv-NYg2EPN^F94;C$=GC3Tb*Cm|T_^gmo61B<|#rdY$BfroZvU?Xu7l3`Ok z_=bSPA7o%>GVn1OxQYzSK?YVO11pP@5Dkk396lifON;Aa4UY#LI+20h#Py!QnzMd~ zM`U0falL25{;&;|WMC)pc^&b2u=u={_`Hnxyo$IU&ahhGVH6oy6!stANb#|C z%hU>|k48^d0n6X=tqFQA3#Nw^vAm24gALZ1&Pl`1W>6>lR-FaR`PeUZa`-myp|48t zLw>`kX%6ijob9_TE3;&7%AqM0Kkts${ld_uETB;tqX3HnA@o~T%d=ZUhM zakC7?;$JP8MDQQnUDWo=`;~BfB7YrqgF*xyRBBdvf#1ZG`2%#BCgy z^t z)Yjbl|NIdK`ms)kxS<~BgpA&g(y=C+ zC;8hK9I5k77G?awDk#k@_1m)rMagkKnZCqlAM&YdFvHNXAz?M zNyGUO>l>YC*UQ1<>UmQ&rkWFt*Xjtf1YuOl%}eTluQ^BV`U?~z?)uqxFHsHBNbe|i z8cc~LZ!}=_ov~P|-1}YvQ}l7|yWo?Cy6%XfEnT%h?*?uu6AcfQSH=%G%3|bGs9G9y zWq`S|l!!~R1AfB^rr^YH5m)pDgl&V1)y}61@y-u7gRA{}y`enc&+av1k21h`&uT)t z48y5~>h5ebtVm-D)M=5S5<0G)j#mua3Qx{bVt(6or*Q0WVIQCGx-58VahNyh!5+;R ze+OXa)Qi!cMUUpyWdm!_T@|s_*VLb%VsLvIf{vz~%ufr0UR4m#pRatWE!za1&8{1^ z!lbS$>;}`Ak2S~J`*ZJlI{~J0-zk=;*hp@KX-7$-(Qe8fHVJ>q5iH-UJtt7++**^T zB-8Ui;vv5Fl6!S20nd+Fev%-ZKM`o$q?PvTS1~7Tj*LI^CsmM;S?*)@JuXXD$;*S%d^%9@SyzS3XOuem#su^XZ+f>ZG z?c?lDZw}ND%<%0l$Fhn(I_x-u`O0WBSUS z{WGPa#txudien8}ab;%)!rXWq$l=9)!>5;zhy0Wo?X7$+<+Ny&p(a+KY)@7M$fwPb zpdi%*u7`KM%}!0XR`pOkB!t(SA`#NkGaQ_-ZZOJ_34KoCakkP*tN zX;6gC(V*T_YwSIm+*70PW1bt9n{~DqD0&8@-)g3u_9I*L?zfdMLORR&Y}-#Eq3e-!v|>3jDI#24GNeZ`=WVjxMSNGjko8D2 z^3CDrU_Z37*7HNbfzE#&=2LFlpnBACYP{Rid038Nt;GnQ4Zh1Ui-yG5?uJTn&XcAb zJpsGWb9hKs--}+=a4trC$0ghi`_NaCaPQeZO}4k~@hUQS)yApPbx$W2jJSQzw|Kb1 zP+g3e7=5_qi6jY8R78t8LhfKP!4Yl??3~_Ugi4m`>7Wun%RFvE4k3BkAKi5OtX|T- zHa{nf;G>aCiOd{)FtR#y$VCl)B$x7ic(vt_ixm7wI>mQ*wQb#D|M1LPquvmT8ZmSK zdvd&>nmqdAt%n(!0C2=}@-m5V7g7cEojhPIz%n z=m>sP!`Ios*V)K7-pCiDowBKyvZ<3|p_?LAJSbB%xIDfJ{EvmB+Mnmzu3*MGzVRl$ z@n*gly_5u!HbCX}9{exgc(lwc&J)@@X%eFYbnlB1esuOzqoDqr2HYuu*VGhr;m zR8|n}#2r`FaiILPaQU_5>?aeQyN7=MD_DrV zx=JB2>z$7v$zD!D0@wX+zKW2%KEBq*${hW2^~*6&sCZiY2=Af0+Q;h*lBdJ@CR;2x z%1uAYOISmb+ig166gHgMHnO}A2l!9VNG?8mU#3`@=Fk?6g_6q)5)8r?{*G21YW~gt z=;iyBp9H4Q47w@fpFd2fBIY~NJTr$CbyL7*zDi$O?<_`c)htduUMU~!P(UP z>(N6S`v&3d1j1riAQCvak}H3jAKTc!&J)yYc65(S{~&E+k6n5t^U3ssoZy4&UuA(( z#D%lv^lZj)^WToO%ADewGp)tS2Wt3ySPvedkE2Mu9DI<`)X3MD;0Bq-sHo?w;y$2F zDwd-h#4cSw7XYvD{t2nvfiydAe|iN4Bk^*j-7lprmr6R*sH_jtj51F)jpKno`8H@E z+f6N<=Jiwd8)d(u3=V!_{EJs+?7Ql$4@|l!vM~~Qt%qFblaAumau3Lco>+Q?{4R3N zymlanw9Jb=DNo3s#8y)OcJ}{~_0G|8e%~8t+=gv3u^KeCZ8Wx>iH*i+8r!yQHnwdW z4cnLv?o7Yyu1|mWu66#&tTQ|BocCz&=Xv&qpJcZQgK--koN|b=zNT&RNy=}P#bNcy zx(-aYL>rI!BKL{Yb3MmSGps$mto?;N*Zmp=l)GWI*{IfgPAu3H^MPZ6HOtaYJ8zqn zsu^h`(;rg~C*^#jx6h()ZX;YK!xk8mUmi;fr;oVY#gknXb4{NW)mgpY4${dgoO)+T zH)L>g`V<)f<+wski{xFVC#YDCdOl>Id+`-h{@~v;@sk6v@gFLR6=wx0TO&W9O=Adl zv;mjq-Ld4iiq22|XGgcWf2q{}I?!nFuNlO5g_+F$gwOUnUUncW^g5=$Srzws6q!V+rk3kX;}=w4`%Lnq0BS?0xz0iTC)deV0i>U`p$LqDAqKyNZA3H!9B^j1mEpNuOT}aXw{S zjJut0CyxTE0lc=?V#}aHjq3SJhOsaz6g|?lMQ|#UdBistm-)dTvYZGlk@1dHExmk_ zKHO-7C(bVbTxJfnDFNde0(DVMfJ28DQ;Ed)VBT!{F=P+SwqV3HA(Lqy?d}g{Z+gSQp zmMXG-I9J2R&}PwiH;q!!KGi5~>cQ*fdzp6pXx#>Pu?01cYFn4Ty_(grK()dCc3HhYS-lVAl9HX@5leeG3s2bb zvdp*O!?$Jm{dkGz!cQph65hNT@87yG9C-ZIZ~GAs-pvNygS- z&sLfnc_;}gprAic<)1Hgd%OUbR;QAzKd~8Lqi<;mL{wHLol7}c^SVr=^|V%VY>R1g z))^U1>7*t=-!AtN|j|`}aig%8o zT)wHS0c=*EuJWMh7WsXi$e_^?mg$*DrKW1%sjgB-{m(*PEw21psW(99e~rM3GHD0q zu_5o2Aw&+UV&Er~yV9nLvQ#>&&i57byQ@!u(+ip1mPJhbR>jHOFDFTn?W1af^MXG@ z$k`OHB{t*>?}*Mw3c3Yz59mNgb(x0A^h@Y$Qf-+e>)wmb@gEIUI6k-2Vrl6Sk7WOB z#@YncFTb*xeUnt>{xbwBzf(Wjeep|4pgd$6zGk!4t0jLSXb3SyLu;e|Glx!}#Qce) zo6yskgl;AGuFh~|<-*XRbn2tluX)OmB{JsE|*P z@60U{_*Cl%ja>klNq^r_<$)_d!4X@g3%240@ZC^_9Z$!8($!sV4PCylB!}Z_#T@}& zG=6^xc2vKi9*wkHR`19H)792Em}WZCN}K_y7pn4mRD0yYhvjlTCMkA#)!wR*_vEI# z>R5NSB&uC$p+Zqr;&s?wbOvul_}B*LCp&9mD9eA#=Pa@z@zrp1Z38XqEJa~j_A68a;eRzT~)EpB-Fnkw~eF1+$Y*5luSGb1s1 zo20%6I>)s)pHTr1Xz{PM4A|*vnfukcza_e34up9;2(u^;q|tg*t|Shexbwnck!ZYY zN2>Uv@r%=F3V6zMTT%O${Ma*-|5F2pRX*!HN;Temu+>%YQ#tcd56{pf%kbvGYv~?} zCV}$@g4*Fx3(kSX?+lQ-kn?R+cP4damlrl@nP-11-<&?LB$$uZV z46|9~O`36=r!UO8S-u*739RRaO*VY?dY4g8ZSZXnv9g}t+IzBZQUYiFu};nwq={l*hgn%B>$2qJ936PL9_IRn844_MsWR-g zl4SBy)m&&!c9gT7ewcx?qSRli4VoTQM#xQSqmCEtpld^S!Z^VyoL4P7ZxI#}P~1{h z^Oa@;yUKOB5bec`nYY6kUGvzXOLY-o z0n^gutj17W;=HdnQ2F^Dfi-CzwC!0haz_t)3vTW)J)x70Cwabu<31s<{8geJ0IFP; z#H{`f*{ljBdR7NRmV&&cC!L0+$7TGdPAtODixAQ&HpQVVtKE8j^`Prg#kgweCfbp* zZ5(f+rK8rGKnBp5dzbzl^X@{>m@{w84>uhDzhkbz0UmQ|=l_lQj}y?Ct8p|rcC`L~ z#{7)xY}UEI@_L3_zJjHN+j4^%@`U_9mI$IiKZJh=Qy6|UxNSffQTZPmJ2WL8iPx2 z*yNjz-;g2~O1(o9PH;D8&OoY4#%cef1U_K*{fEOHD)#iu*Xvu34YThyl4)pDH=4-cajZh+ARk%Bn2OM+N7g-(;O#9p4~P@9=hYDV z(OoY&deJ2=p}Ej8FEKV_T3A6g!ez3BRTTEF2C0wPG$N@Xs_?Wdk+LY~paPAdJRWtrr($2>BPVMo+qiAW=!iEuDYHAxaNEd?fh zG>z2{x?rYF7HUFDF@d2a)xs$pO;P?yY9dG}?N&EWK4!pN6$kqn=Vllx0!i99gxCm9 z6E5c9Nkb}{?6*ch6tM%+SEFfM=xJ=lK?o)|Z>E6omAaIvFxVoE_hDjb*hT%#q=O%s z=tC6wBbE){qlC}bq#z-7)IcCg7clk)D3V4%8g`<))q~P0+%bK5Aj+)S*$(h}AI@hhJ;>M#36a2oCgq#=NS*4oZ~N;POwz z)qwwwajYKF+4~zqwEDZ7SToI5D{vY^u}Wum^sbWAHgQA^-bzfC21$!}0zp}Ax)v$~ z1GNIJle)2-xjC#{6;cznojOF7xOCLC%qxHNU0I5pm^t-QVVxp1y^;yODo{}|1c62+ zB&YWky}$T7uh?>Fe}+^~sZ9Fryvnu=q%i`5a!7KW4b`WlD^)6SDOCB74FtV87 zB^Ameg_D=?UMfMp*Ch^_f|w0HMzSawR2(`!VV_a5K6V~j(m!f{R-#|*O7XM2C^OgR zK4E6!&(cD;@C7RZSC^#ZpHT0H*`lp4%;%$MPloNI;P%bYqOk4yZAcODP#50Eil>?- z7>Sn}Coqb~8U{e-OE4fDksM(vHc(=Ov+F3@Vwfl>mY@seQC@^H$^{sc45A&7&!Bu> zH5a84L+26o3^>B`Jwp6uZuOyZCpVG1fjl^Aa~cY7RYF9cQA8g{WD7#XQ}+m3%X|lc zPBAx;68)=i3)xNe+e7u+vpyEO2NuWM%M$_R8{%Dgh7(-lUOA<+xf@;~N)tnY3dq-) zV6`hsxVka@~6 z7O>oGMZ*#ZXqXrEv?)b0opUSeOFn_w4DdB9110o8j-Xb?!nY`*=Y5cI{?~2!F={qc zO6@AJ&7C*L1F~Xy$3m~1)mrlQwd$0QZO(1<#bW7ab{e`4+_jnB7k_H#qeHzD;YP!Y z`f3JIXMjJxZlp2OgI4#e4VQ31>+nOQh+G-UB^9UP=4eVv+V0>4QcbC$WfK0m8BiX1 z#K59R8-c2feSc#fR*H??-qxyLSvJ1Ux=#2Q%H$LRQxgZ}I=cQX`BB=&D zl>$Ydt@v%3=Dd`(4=i^c`vu+MXXp-kaHb|2|Jfh?7%Wrl|N;#Z@3=&A@qaR#bfIau5!|0MCl_dlsXC)d~kbD#pbL zMBv_W|E303z7^AkY;W`e{JCY(N6l;(TXKq#)l|uk43Z5bJ$}>hzfUFowbT~lf5?96 zs*nIBvC8NI6@jm3AJgOe;Z$#{_mT8G$8%W|WCv1D{L`;`ZeM-LF9=%MK@3Idgjzf- z@VjK8YM(Vea`%*Q3^$amKWEYL{vrUmx@_F>A5~8C?qVA(8clKKQ?X`~7s2oPoLB2Y z3x{Y36<}?%ED#A{bo!5jvgWvERX#dohdpd_4tAC%(y=XUb?2Kyr=6{eF6Z3&a^GFhzCI)Zc%W-@1Ntxpn zUeHK|=R1&2Xu^l;n8!so;YDf|^;en&uh_Rw7Lcy82=veoCj0vviIC6b5+qxVh7aKq zQ{Dgmu3uMuw!1qVm(%HP!?L_bZFiAvcLO;tXVcy0(%m>XE`M@dnsQtQaa{JMyZNWPUCOo}k?T1Da6WSO z6Odb(GbwA@_;1{(t|miAveSPuZ@5D00hRXEpXdx1L6{_1Ot;+iQ;xn}s4)<1V8Rr8wnKhK>5%nZWJZ67AU9+Z|P+1p4z%R%?<`mhz z(n*#IfExgArDk*+hm}z{Sl50YrR_qHaB;@zUq6?_qTkg4y0!zFZ14^oi~D6xzL*Os zSK(ior7;jE$vS5cX2R!n!dI^G*}zAQpDJ9|IuA45{G2YS$NIlWoNf5K6q~#=!lD3YKgo8*YB;iEnzEUwAPOGOsm6Ezd4- z3^smQp4UEpnD91MtFC*W8)H&XygjNh)Pn&E7*XKU)kRJk8{=iK50huePsH(&t`z1) z;s}j!V@*g22QjP!BOBM*F-pbjda-bglhY*7D>U)|>xLbrMW$T08a~KtaO}3ntaX9X#i=7Yla-xMVf;t+ zx*H6bu3b%IkIFhfw;a(P@Z{>=T;eObRz9EGvBAGalEeC>OFq~!!@tmx*LL-MezRi; zeVHJO?P~mdZ$~@wx+c}@qbxbIrKEJhkpT1N5_hyE6Zhvwoan1~+;dkX$O_5Gv$g~? zffS^lQWHNFVpf8g9D-v!LV={AvC?oddOq1fc^ojOAcTx6IW~b#0%0#UY+VA@xF83U z3PEg?jYP~%;*q(47hS379aXv?x)KpAghc9HzRsPZhL=hJAGJLpZ0Hxo4Mi9ibT)zk z&M%K~`4GD$-F7Mx9Mnp9fj#05<%;}sl;o^o+Yyes;wzu?ts@oR1JN0=O|HTn55x?e z6k~sseAhx(!4&fqL)BJHP(=q|92kW`Mdd*WE9PdEyq7~~2Mn}`J}Tv5$5HN*juxVt zl!pit<<$gI4ih1~4tN^TAHrfyQfHT0tQ-2Qs zqj)yN6a~>We4dvuVYYRcd}nVl#I~~eITi3eV1UoTT7G;5KyfGP%!jHWKRmD8e@vBh zgHGas@XSl*logUjf$%d_)BP%y7m`?>KZ@e#ZfJ~~BOC9^$E;@G;)pI~lC97@*WS&{ zkIobo>+n#nP)(Wdt%^k(l*3l2zMl2N^T2v!Lg-MKFTy)G*A;RZ4hF+WTHV>x)4g`oXImpO#l9G#ugdQ0>?l7*HrdZRoByY{X0p{A3e_N0l=3nlK*#5?-=7#4Ul8J< zl*0=TC7;(6ud^sA>J%5vHS#sX7)=iMh8)K}Uc6qCZmB@eGA@)A{z0bBf?Sh2o;OCa ze&BPoVH_!=MQ<`|t90E68L^EdgjSpd?Z`nQ>xh)20@*}XoQIU8f8J*pO87NNKkK;f zDWCnws2tfSoN{EzG^H|FS^CY6Aw9m;c>K*E`?a0oYM@MWS?-`~vF z>i5?~qqX*EP52dK@utw&!ZyEZ!0?(sq|AuiwY9#h0E81Z(s^8!*v-dhg8>@0vpf=( z6EQK{i}0*c1C)q{u$i_DzEeqN4-iE_h0jx5)e1Oz8dsD8BQlhhrM@SpNtMj^0}$F^P%mB zQ%ZI6VTD0aVhazN?i-3GU}(o z)-xt!V`|0x1z7-8N@YaZ5tp@`+Y=h!FR(R5>z78Wl&B$c686-v`Esnw_euk)A@vtR zNzaBLRW6mo&4$V?SDEb1y{so%NF4pB?RyL$W=sn6I9E12qGi)Oj3;hN`X|KgsckbR zeSUK33HvnrH{$;DI2^r*+aiOXk_jAf=k=a9q6S6Wi{{>`6yRQFv-v4G6N#Ik_KvR! zit{#ky;Be7jVtHA@D=}KL{A-h<5+Xur&z+x100xMXy19N`s z!LPy=YI=s{B}-N|n)B|w^O#3l@!avQli@n=gwp+Vi7o%1u)Qa>3rwkc<+ZFw*t{Lv z(P{G2k4Q@;)yX#NPD<5rU(~}2NcFOim?4{WR{3NPwKc3rL}ioNb~Dk`GSiul9uE&Q zeedMCml`{{Zp(gkiR{X~sDS|O(yznJK4qh_!4bGB1Nhj-A8?XNz;osoF)86f{VJz% zsiZ3TkjS8JnI$H2ZgBJ`KCg>{ubwy7g(tdvSyn`<9pJS8*5A0p`^&g3yoCKNRk;v@ z2d6CNtNy+vc`Lyh>XQz6>s;W1ai+YDMqSzACALn80l@`x&F@9|Du1invPPd>LYs0> zy^|$|z%Fy>LDfX)F4Ovqav(~?a+lF{Lpg~s;g3IvO*8EtYW4cn#P?+~ar#xMcdGx) z;+1l~?HB^LVH9rTA%5c_0b>eIV+vkl3U1?fpX&NPtQZELU5L0G5%cWF5I7CXftuH_ zoZGOR{ji+lFbbD(FkxLE%F5-370BQ-KM|K5;2 zlL_j^KCBc5pT&u|%n-MM+9h!tsCy4_+X!*nevGciFg=g)k^s0VtXbA!a?b4|I~2(< z{QRl7ZHvrj?QV5sw5knCq}>^1f+)Rxve%~Tgi%%1WBWw?OIt@7OMfqoyM<7F>oC6B zWX-rS<8(oX{-49p-lN!K z>ol>4MIlGIGizYK1!ZZ)@k+WkY{$b@@OI;3bS%8Kb z{R$wCHo$@|!@F3hD<)2Q>mRiJTifg!r z(NE&=e?TczJCyc%QlbYuD8DAV!YcP=-9n?^j5jDvlY%nCJC1#z`5Hu|X8J{>LL%te zZ<#1m6}1ykPksjujO?TUoo5vXPHxV^R9D>o*@?8Xd5mNi^IX!}4Sywo$wmnMwYkAI zQOMph&K19|dX{Rf4Ofi3y?%sO2`sYxbxZ=SPj-2Xl_}IoPXBmaHCm@vBz^5j8yi@c z$*Knd$el8GB{pxKZ~2{%Q!fI4=_Kuo%Ep6B9%1VjT2r7w@r}J}%yQ-OT%5Dws|Ne> zhf5x1^UqrHi5YsQ?uU7rtO11l-)=5?;wN=0ZBqFxi_bs0nf^6(LQpr%M&o!*?BBHF zi4q|b9D!hV4qAbI#L$x~<*VGrxz1{2ks?7I^}zpDV7mb{X@?AaC5Qm{BMe4p5mbRkKMAl$>j9R=-E*|#)$oYJLPW`9=+B2 z)#rkfRExjmeeyralxC`t|N127El`;jDDpG9L{PC@*c!I|lM0{oFJ1X>8V6ZgnYTU$ zWJOwB(L>w9FsI*>eVxzCa%FA-^JNc8=$x90_Z-WZ{?@|U zFK5n>si_SBvhdphODm&h8dC(E(W)z@nzMP8>no)_v-w-r7C|IzH_1E=W;S$`X!FE0PvIaA}<%(I(`rY?`MUN(+@)Sp5-3p*BCb#g~BmNuqA$XuoS^5`WPD zB9Nx3k);bM1EO;(cWT``t z8T~nVZ`6X9O?S9Ennu|kC+K<@y@kYpU`QvtZZMh zC-_znxium_sY4)UXPUmK!xdTFO|x3Xsa~|r{BUlG_ge=w$Ie>BvkJcczRAX?D(gRT zMfMn71(S;37=hOMK(fPb@{;3kfp*7`5>JED^qfz_Rlk6Fo?nwDFTY};+yrj z&*BCySA<~d%wQMeXFa`C(JuO%@pem06yGh3G)FxQAkFq$p8ehR=IzZ=!1#1|{aJEA zRsO%fYvaUUZmXyGynD{I`6Oi2r$n1>I?Cg`<+9^Jto^u^O<~h32b$X)b_r}WVM3mbiD?S%5J|8ST4=O%~ zD?TSLKA)X)jh}OUJZ?L?(RF*Wb$j}~d$aLyvvG8@0s6${iC(}vIjd6|{MG1DZ{1aq z(pHoxDb+*udJaS2gRHLCfZfSH_cu_E?am};fc!NcuJ!#K=F2LW>zewmYHRu3@HZod zwL3g+@0;7RHCn7Ye`j5WQ`^==xW%pVRy7E#U7yo=eDOPV^ za{m?v;r*Hq`49mnfy6+JWf(#xttdj%#_jw^Xy~YHa_;_OgBZbgf@gU4zq5#BmuDFVE?p4v`CRG|v z^FncL)em$)U_FBlh>6)*>}R^`EZIY(SGt>4P1O$ehqB)tJ?eRYcRrvJ5I>eZH16`5 zKPAkSmia>M@Guzy&sC~JJHWAeH%!A!C)QqWKF7&LS|W4rdB~qB0gH z-htOmpALezN&FZa&`rw9B&<%-k7Q{45e|ZQ{v8#c#JW%z3bVuXA)GH8wxEA5Gd8OK zGS+@`4g(^yT1<)`J*Byo-*>ctC?a7P8=>h#XlNeB0BBOSkMX|ih{hU3$xu`Pi3mX$ z7;$a`8R#H-gYQt}Bt$z9rLe<9g8Gm!Fhmz`M6bd70_iW;6I~F`h>cw#Pkoc0zPzSf z47m$f{y|;-Q2Gt^7|Zh;@lPy?Z}59xkOUAOm^!A4|9oG2H`6J2)~EBS?g@Jyi`pA8 zkKyi-B!eOTk#j6L)eC<33md=83Kf6-%NHNfSwYoedC0z zcMvsSoH{}l!*x48E+C3_1kB|p=|LSkh()7%Oiv+Rwg+Zl)w{>0Ci}X}jP0oqI$yny z3l8Az-!sJf(Ca2RK>As4e`f$&k8%eVNe{TK2*EIO&gnh0&J(g3xVz!k7GBx*zI7~t zM||C3D{9w8v`M6bfcY1r`8zfq!4=dYdf_u*5B>frR2#kTX^B>HjPrWAkp@A+iExpv z;rxK0Zo!-hE5X-ePN^?q+`Tiz;ZcH4D!E25ndeiGUi7htN-p#)hn$>P=3isd5Hq+U z#)%E1E*(?plKUJ>sgq+IV=00exoAg(#8I#vVPZ%<@C)}+@DB4G1S6!Io9DyEayYB{ zVMXz8cA)T4#_U4^jmUN2dJk}QqKw1az#-rcbrQ z4r;$|7i+~U0%eIGRN$a*Rh*2}uzl|sj{YOX9`U6K!cKahy|+oRKGN5wa}lnDeY-)Z z02$tfed_0$HRL#EnKkN25FdL~EmLSDi}kz#DFoi^5{U<$%aX7Y-Nz*~+ei&7bSC72eLOof=-mXLW#?=2JLrO#MVikaAu<1BKMo4RL#RCK^u zX3|JV3!N|dthf#{AH(28S%-6p5hsGCyPe68@r0xt5>)L8mn(R zlWAbZwo=XwM9_aag5pk={YspSn4?oPeBh|FycmSMGGCR;U_ds*To4RyPf_GGj3faN<_X4c3B)$C}?KnKx*$CLH@| zlgF+qDsV=b6KDmEg+3wfXf#C})N9m-9{6fR1+gh=O$C6MQpJgQL7v; z!>$-@Ec;MW7M)QhA5+$-(0Ewt!oEOQv|6Tu7iW2(=u@b5lyk z*Z8&A(aDln?&VbA^;F<($+UQLVZ%quhvMX|`4!BlyL|pBlbNQMRT}^DheeehbaCAX zXEg!ce|MbINWfX zqgOOQ)VwtLPX%@0u*K!gqobOlT~_D#v}N7maekT_-+x9Zg6p7vggqr4Q86RWUH`qg z#x$p`4?D1}^z2i|@3a#wqUkyZoc9MfgH<9FrfucYli$^ov(T%#@uvQ}w_cne|0*y0 zA%La*dx;0^0zK?8H*~ksKE$r5^`yBAjlcG+eCbWY)GbByA8Z|e-cWX`kc0i|*3Xy6CCYw~X_OXY+_%oi{b^Bx5-)G$DdJ0#pNY*V zZxddt^_c;|1`<=cY4MJE>aHpR7F8-Mq5T?HIbg9X?7N$q$psQFM7A+0Y^8j9wrOtg z;$(#1qM6ocexpz)^(2LMP9Yo4+nmi&5wiO|fF5)HsYR1jBg5V%uW(0sFd(vG0si#Q zyLBy)e`yYpu@qu(I``8(5L=sv1wrdDan;%;raJ7a9^}HJ@$)rqo{h*RUv-Mz=|8?f zDxm5@jiUX0>&IppsDKLeemnWAcDBnDP5kz1s^Tk}RaS-6ZPnFzeA zJNH5t9RZGTxNty~Yy+#8R5nDr4o*s!C(Hx}I00K*FkAoOYRy+QCXpps8OLkc18Uvm zK160hm4NvL)a#5v9onB@l#;NwOoc$;zU#mjOQ8zO?r~-3k`Q6I1 zhFxv2JGIYo+(2a6V7)pH$#(i+tbleK0YrD_fUd7o747_TTkWn3?JkxH`=@bq(sk5s zK>&M$j)I?s-Zh#el4mlh6Rv_ROq{{acIY5cm33j|b ze7l5i=%YvU`DM`(Jkd@R^zHDNDMU7+3idJZu!JND{`FLDpR~#1X-rO8>g=C;{lo1$ z!Z85;n1A$v(9l(lk$bO^JLb?;|In4_(3SDf)rFCJosoN-k^50C?}J7bs4dj;hX3t0 z`}f0+n+V`Txc11l44@$=P~O3;_d!% zLzj`2{hD@B5El?5WRpoY|$CsQsY**o64DsM(Q`mv!r<^E~MeY^$>%1*W3HTmA zr9!@={Yv*yqri`g7nqu7v-n+X?YShJ9Tb&eUP7Lx1)p zC+;81=M`32pwzVb1Fin@Ioz3E?-q`K0!zMDPj!Ne(Ir2`W4s#p{?PA9N&b^uC;BR! z^xPFecFq7Cd6w45^MRBe(eFfi3s(pF97|m==)gal(DZbflieg&7{9^NJo>aqJu|3L z{gGCcdKFGQ(l0`L4OgjqLra|2&p~?_Q)YhKO<3<@B~z!t@Rd5iE4*@3N@Xsklq%~d z#VD2-Kvxp_uF_420b)h>E3Pb;c$ukWV5=5|Q~QdEfl4mQsWg424hTVb$YnlOXt6Bu zVpJJNz%Ud(K*+6gP;kIf!2zKQ&=`S&fyfHPugV&)=%NC!?eeH6%Ar@K=npEUo?-P_ z2%_?k82Ps>IraU6Mkm2&L&<6+s1r(|F;Ff)uXOh;$WtS!yxz z#D};rb4g-VCF}(BhR?9rlBhyTFwyAFqyxhek6Hx~=%xK3Dx)IQ@x%j)60n2?M6jiT zzUXj7foETs?i5oWO7J~ZPI#$_2{DeNglqC^jwy$^5G0~VLh?hlD5W^UN+Yj'QS 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

    uwG0n>*S8bI#p(Dx#0P3-9F_W>UhLXjEInu9U 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 literal 0 HcmV?d00001 From a15502887d083ff31edef97aa9e8266d31108fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 18:36:15 -0300 Subject: [PATCH 014/124] Measure scaling, since none of the four quadratics changed a byte of output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four have been found here by audit, each in a pass whose sibling had already been fixed for the same thing: scan.go's separator search, locateHostIn's per-candidate strip, foldedHostLeak restarting inside a slash run, and normaliseURLLeak's unbounded authority scan. Every one was reachable from page or request content and the worst extrapolated to hours of pinned CPU for a single response, with no timeout on the path. None of them changed a byte of output, so no correctness test could have seen any of them — which is why all four waited for someone to notice by hand. These measure instead, on the four shapes that actually bit, in both the response pipeline and the request path: quadrupling the input must not raise the time by more than eight, where linear is four and quadratic is sixteen. Honest about what this does and does not prove. It runs in a quarter of a second and the code is currently comfortably linear — 4x the input costs 2.7x the time on the slash-run shape. But I could not make one specific reintroduction fail it, because two independent protections now cover that shape, so treat it as a guard for the class rather than a proof that any one line is load-bearing. The correctness half of that argument is in the oracle test, where removing the authority-byte stop does reintroduce 20 failures. --- internal/rewrite/scaling_test.go | 134 +++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 internal/rewrite/scaling_test.go diff --git a/internal/rewrite/scaling_test.go b/internal/rewrite/scaling_test.go new file mode 100644 index 0000000..dff443b --- /dev/null +++ b/internal/rewrite/scaling_test.go @@ -0,0 +1,134 @@ +package rewrite + +import ( + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/generoi/hostshift/internal/origin" +) + +// Three separate quadratics have been found in this package by audit, each one +// in a pass whose sibling had already been fixed for the same thing: +// scan.go's separator search, then locateHostIn's per-candidate strip, then +// foldedHostLeak restarting inside a slash run, then normaliseURLLeak's +// unbounded authority scan. Every one was reachable from page or request +// content, and the worst extrapolated to hours of pinned CPU for a single +// response, with no timeout anywhere on the path. +// +// None of them changed a byte of output, so no correctness test could see them. +// This one measures instead: quadrupling the input must not raise the time by +// much more than four, and the shapes are the ones that actually bit. +func TestPassesStayLinear(t *testing.T) { + if testing.Short() { + t.Skip("timing") + } + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://www.example.fi"), + Variant: origin.MustParse("https://wt-a--example.ddev.site"), + }}) + if err != nil { + t.Fatal(err) + } + + for _, c := range []struct { + name string + // build returns a document whose interesting region is n units long. + build func(n int) string + }{ + { + // The unbounded authority scan: a candidate at every `http:`, each + // scanning for a delimiter that is not there. Every byte here is a + // legal authority byte, so only maxHost stops the scan running to + // the end of the buffer — a space would end it after one byte and + // prove nothing. + "scheme candidates with no delimiter", + func(n int) string { return "

    " + strings.Repeat("http:", n) + "

    " }, + }, + { + // foldedHostLeak restarting inside a slash run. One non-ASCII byte + // anywhere in the value is the whole gate. + "a long slash run beside a non-ASCII byte", + func(n int) string { return "

    café " + strings.Repeat("/", n*6) + "

    " }, + }, + { + // The same, in an attribute, which is where the per-candidate strip + // was quadratic. + "a long token list in a URL attribute", + func(n int) string { return `x` }, + }, + { + // CSS escapes, the newest decoder. + "css escapes", + func(n int) string { + return "" + }, + }, + } { + t.Run(c.name, func(t *testing.T) { + const small, factor = 20000, 4 + run := func(n int) time.Duration { + in := c.build(n) + start := time.Now() + r := NewResponseBody(io.NopCloser(strings.NewReader(in)), m, nil, + Options{Stats: NewStats(false)}) + if _, err := io.Copy(io.Discard, r); err != nil { + t.Fatal(err) + } + return time.Since(start) + } + // Warm, then measure, so the first allocation of the run does not + // land on the small case and flatter the ratio. + run(small / 4) + a, b := run(small), run(small*factor) + + // A generous bound: quadratic would be 16x, and the floor keeps a + // sub-millisecond small case from turning noise into a failure. + const floor = 20 * time.Millisecond + if a < floor { + a = floor + } + if ratio := float64(b) / float64(a); ratio > 8 { + t.Errorf("%s: %dx the input took %.1fx the time (%v → %v); "+ + "linear is %d, quadratic is %d", + c.name, factor, ratio, a, b, factor, factor*factor) + } + }) + } +} + +// The same shapes through the request direction, which is a different code path +// with the same passes behind it — HostLeaks, not the HTML pipeline — and where +// the 8 MiB body cap makes the worst case bigger than any response. +func TestHostLeaksStaysLinear(t *testing.T) { + if testing.Short() { + t.Skip("timing") + } + m, err := origin.NewMatcher([]origin.Pair{{ + Canonical: origin.MustParse("https://wt-a--example.ddev.site"), + Variant: origin.MustParse("https://www.example.fi"), + }}) + if err != nil { + t.Fatal(err) + } + build := func(n int) []byte { + return []byte(`{"u":"` + strings.Repeat("http:", n) + `"}`) + } + run := func(n int) time.Duration { + b := build(n) + start := time.Now() + HostLeaks(m, b, true) + return time.Since(start) + } + run(5000) + a, b := run(20000), run(80000) + const floor = 20 * time.Millisecond + if a < floor { + a = floor + } + if ratio := float64(b) / float64(a); ratio > 8 { + t.Error(fmt.Sprintf("4x the request body took %.1fx the time (%v → %v)", ratio, a, b)) + } +} From a4cb8a8905eb4537ddb38628718cb29a895833ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 19:11:33 -0300 Subject: [PATCH 015/124] Carry scheme and port through the splice, and stop guessing either MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round fourteen. The map dimension turned out to be almost entirely untested: everything in this package used one shape — portless, single-scheme, canonical https to variant https — which is the ddev case and the only thing the new oracle varies against. Five findings hid behind that. * The locator spliced the host alone, dropping the variant's scheme and port. With `https://www.example.fi=http://localhost:8080`, the plain spelling came out right and *every* obfuscated spelling came out on the wrong port and the wrong scheme — on a configuration `check` calls injective and anchored. The round trip could then not reverse it: the reverse table is keyed host:port, the request parsed a portless host, the lookup missed, and the *variant* hostname went upstream into the database §4.3 says stays byte-identical to production. It now splices the whole origin when the scheme differs and host:port when a port is involved, which costs the obfuscated separator — a fair trade, since what replaces it resolves identically. * An IPv6 origin rendered without its brackets. url.Hostname() strips them, so `http://[::1]:8080` was stored as host `::1` and rendered `http://::1:8080`, which ada rejects outright — every rewritten link on the page unparseable, with `check` reporting the map healthy. Both renderers bracket it now, and the locator parses a bracketed literal instead of splitting the address at its first colon. * `maxHost` capped the whole authority, and the userinfo search ran inside that window — so pushing the `@` past 759 bytes made the host vanish and the origin leak, silently, since the byte matcher needs `//` immediately before a host and could not see it either. The cap belongs on the host, after userinfo is out of the way. Scaling is unaffected: the scan is bounded by isAuthorityByte and a candidate only starts after a non-authority byte. * `locateHostIn` picked a scheme with `for s := range h.schemes`, so the same input in the same process produced two different outputs — 176 one way and 24 the other over 200 runs. A rewriter whose output depends on hash seeding undermines every byte-identity and corpus-diff claim in the project. * `foldedHostLeak` had no left anchor, so it walked every `//` in the buffer and rewrote a *path segment*: `https://cdn.other/p//www.example.fi/q` was rewritten while the plain ASCII spelling of the same URL was correctly left alone. The two spellings disagreeing is the model error. And one configuration the engine cannot represent is now refused rather than silently half-honoured: the scan ignores the scheme when choosing a pair and the host table discards it, so one canonical host with two schemes and two variants went to whichever pair came first in one half and whichever was written last in the other. Aliases that share a host across schemes and point at the *same* variant — the real fleet shape — still validate. TestMapShapes is the coverage that was missing: six map shapes across five URL spellings, asserting the canonical is gone, the variant's whole origin is present, and the request direction can reverse it. --- internal/origin/matcher.go | 36 ++++++- internal/origin/origin.go | 13 ++- internal/rewrite/maps_test.go | 106 +++++++++++++++++++++ internal/rewrite/urlobf.go | 174 ++++++++++++++++++++++++++-------- 4 files changed, 287 insertions(+), 42 deletions(-) create mode 100644 internal/rewrite/maps_test.go diff --git a/internal/origin/matcher.go b/internal/origin/matcher.go index 54cedb5..6c4a34f 100644 --- a/internal/origin/matcher.go +++ b/internal/origin/matcher.go @@ -55,13 +55,19 @@ func (e encoding) relSep() string { // hostPort renders host[:port] for this encoding. Only the port colon needs // encoding; hosts are ASCII after punycode. func (e encoding) hostPort(o Origin) string { + // Brackets for an IPv6 literal — url.Hostname() strips them on the way in, + // and without them the replacement is a URL ada refuses to parse at all. + h := o.Host + if strings.ContainsRune(h, ':') { + h = "[" + h + "]" + } if o.Port == "" { - return o.Host + return h } if e == encPercent { - return o.Host + "%3A" + o.Port + return h + "%3A" + o.Port } - return o.Host + ":" + o.Port + return h + ":" + o.Port } // pattern is one automaton entry: a left-anchored origin prefix plus a host. @@ -235,9 +241,15 @@ func (m *Matcher) Identity() bool { return m.ident } // substring ban: it is that running this automaton over each *variant* origin // yields zero matches. That is what permits a variant host to contain a // canonical host, which the leftmost-label prefix scheme requires. +type hostTarget struct { + to Origin + name string +} + func (m *Matcher) Validate() error { seen := map[Origin]string{} rev := map[Origin]string{} + byHost := map[string]hostTarget{} for _, p := range m.pairs { if prev, dup := seen[p.Canonical]; dup { return fmt.Errorf("origin: canonical %s declared by both %q and %q", p.Canonical, prev, p.Name) @@ -252,6 +264,24 @@ func (m *Matcher) Validate() error { return fmt.Errorf("origin: variant %s produced by both %q and %q — map is not injective", p.Variant, prev, p.Name) } rev[p.Variant] = p.Name + + // One canonical *host* cannot map two ways. + // + // The scan deliberately ignores the scheme when choosing a pair, and the + // rewriter's host table is keyed on host:port with the scheme discarded, + // so a map declaring http://h → one and https://h → two is a + // configuration the engine cannot represent. It accepted it and reported + // "injective and anchored": the byte matcher then sent both spellings to + // whichever pair came first, and the locator sent them to whichever was + // written last — two blogs' content served from each other's worktree, + // with the two halves of the engine disagreeing about which. + if prev, dup := byHost[p.Canonical.HostPort()]; dup && prev.to != p.Variant { + return fmt.Errorf( + "origin: canonical host %s maps to both %s (%q) and %s (%q) — "+ + "the scan does not distinguish schemes, so only one can win (PLAN §5.4)", + p.Canonical.HostPort(), prev.to, prev.name, p.Variant, p.Name) + } + byHost[p.Canonical.HostPort()] = hostTarget{to: p.Variant, name: p.Name} } if m.ident { return nil // an identity map is trivially anchored against itself diff --git a/internal/origin/origin.go b/internal/origin/origin.go index a02dc88..8eb77fe 100644 --- a/internal/origin/origin.go +++ b/internal/origin/origin.go @@ -145,11 +145,20 @@ func NormalisePort(scheme, port string) string { } // HostPort renders host[:port], omitting a default port. +// +// An IPv6 literal gets its brackets back. url.Hostname() strips them, so an +// origin parsed from `http://[::1]:8080` stored the host as `::1` and rendered +// `http://::1:8080` — which ada rejects outright, so every rewritten link on the +// page became unparseable while `check` called the map injective and anchored. func (o Origin) HostPort() string { + h := o.Host + if strings.ContainsRune(h, ':') { + h = "[" + h + "]" + } if o.Port == "" { - return o.Host + return h } - return o.Host + ":" + o.Port + return h + ":" + o.Port } // String renders scheme://host[:port]. diff --git a/internal/rewrite/maps_test.go b/internal/rewrite/maps_test.go new file mode 100644 index 0000000..ad93d9e --- /dev/null +++ b/internal/rewrite/maps_test.go @@ -0,0 +1,106 @@ +package rewrite + +import ( + "strings" + "testing" + + "github.com/generoi/hostshift/internal/origin" +) + +// Everything else in this package tests one map: portless, single-scheme, +// canonical https to variant https. That is the ddev case and it is the only +// shape the corpus varies against, so the whole *map* dimension went +// unexercised — and the locator turned out to splice the host alone, dropping +// the variant's scheme and port. The plain spelling was right and every +// obfuscated spelling was wrong, on a configuration `check` calls injective and +// anchored. +// +// Worse than wrong output: the reverse table is keyed host:port, so the request +// direction parsed a portless host, missed, and sent the *variant* hostname +// upstream into the database §4.3 says stays byte-identical to production. +func TestMapShapes(t *testing.T) { + shapes := []string{ + `https://%s/x`, + `https:\\%s/x`, + `https:///%s/x`, + `https://u@%s/x`, + `//%s/x`, + } + + for _, c := range []struct { + name, canonical, variant string + }{ + {"variant with a port", "https://www.example.fi", "http://localhost:8080"}, + {"variant on the other scheme", "https://www.example.fi", "http://v.ddev.site"}, + {"canonical with a port", "https://www.example.fi:8443", "https://v.ddev.site"}, + {"both with ports", "https://www.example.fi:8443", "http://localhost:8080"}, + {"an IPv6 variant", "https://www.example.fi", "http://[::1]:8080"}, + {"the ordinary ddev shape", "https://www.example.fi", "https://wt-a--example.ddev.site"}, + } { + t.Run(c.name, func(t *testing.T) { + canon := origin.MustParse(c.canonical) + variant := origin.MustParse(c.variant) + fwd, err := origin.NewMatcher([]origin.Pair{{Canonical: canon, Variant: variant}}) + if err != nil { + t.Fatal(err) + } + rev, err := origin.NewMatcher([]origin.Pair{{Canonical: variant, Variant: canon}}) + if err != nil { + t.Fatal(err) + } + + for _, sh := range shapes { + host := canon.HostPort() + in := `x` + out := rewriteHTML(t, fwd, in, NewStats(false)) + + // Nothing may still name the canonical host. + if strings.Contains(out, canon.Host) { + t.Errorf("%s: the canonical host survives:\n in %s\n out %s", sh, in, out) + continue + } + // What is served must name the variant's *whole* origin — a + // variant on another port or another scheme is a different + // server, and pointing the browser at the bare host sends it + // somewhere nothing is listening. + if !strings.Contains(out, variant.HostPort()) { + t.Errorf("%s: the variant's host:port is not in the output:\n in %s\n out %s\n want %s", + sh, in, out, variant.HostPort()) + continue + } + // And the request direction has to be able to reverse it, or the + // variant hostname is what reaches the database. + back := string(HostLeaks(rev, []byte(out), true)) + if strings.Contains(back, variant.Host) && variant.Host != canon.Host { + t.Errorf("%s: a variant hostname survives the request direction:\n out %s\n back %s", + sh, out, back) + } + } + }) + } +} + +// A map the engine cannot represent must be refused rather than silently +// half-honoured. The scan ignores the scheme when choosing a pair and the host +// table discards it, so one canonical host with two schemes and two variants +// resolved to whichever pair came first in one half and whichever was written +// last in the other — two blogs served from each other's worktree. +func TestOneHostCannotMapTwoWays(t *testing.T) { + _, err := origin.NewMatcher([]origin.Pair{ + {Name: "one", Canonical: origin.MustParse("http://a.example.fi"), Variant: origin.MustParse("http://one.ddev.site")}, + {Name: "two", Canonical: origin.MustParse("https://a.example.fi"), Variant: origin.MustParse("https://two.ddev.site")}, + }) + if err == nil { + // NewMatcher may build it; Validate is where the contract lives. + m, _ := origin.NewMatcher([]origin.Pair{ + {Name: "one", Canonical: origin.MustParse("http://a.example.fi"), Variant: origin.MustParse("http://one.ddev.site")}, + {Name: "two", Canonical: origin.MustParse("https://a.example.fi"), Variant: origin.MustParse("https://two.ddev.site")}, + }) + if err = m.Validate(); err == nil { + t.Fatal("a canonical host mapping two ways was accepted") + } + } + if !strings.Contains(err.Error(), "maps to both") { + t.Errorf("the error does not say what is wrong: %v", err) + } +} diff --git a/internal/rewrite/urlobf.go b/internal/rewrite/urlobf.go index ad6f6be..bff264b 100644 --- a/internal/rewrite/urlobf.go +++ b/internal/rewrite/urlobf.go @@ -2,6 +2,7 @@ package rewrite import ( "bytes" + "sort" "strings" "sync" @@ -48,6 +49,19 @@ type hostReplacer struct { // own scheme decides whether a reference with a scheme and no slashes is an // authority or a path, and the document is served at a variant origin. schemes map[string]bool + // schemeList is schemes in a fixed order, so nothing here depends on Go's + // randomised map iteration. + schemeList []string +} + +// tableKey is how a parsed host is looked up: unbracketed, because that is what +// hostReplacer.key produces and what origins store — HostPort() brackets an IPv6 +// literal for *rendering*, which is the opposite of what a lookup wants. +func tableKey(o origin.Origin) string { + if o.Port == "" { + return o.Host + } + return o.Host + ":" + o.Port } func newHostReplacer(m *origin.Matcher) *hostReplacer { @@ -56,9 +70,13 @@ func newHostReplacer(m *origin.Matcher) *hostReplacer { if p.Identity() { continue } - h.to[p.Canonical.HostPort()] = p.Variant + h.to[tableKey(p.Canonical)] = p.Variant h.schemes[p.Variant.Scheme] = true } + for s := range h.schemes { + h.schemeList = append(h.schemeList, s) + } + sort.Strings(h.schemeList) return h } @@ -83,6 +101,11 @@ func (h *hostReplacer) sameSchemeAsDocument(scheme string) bool { // macOS filesystem or a paste produces without anyone trying. func (h *hostReplacer) key(b []byte) string { s := strings.TrimSuffix(strings.ToLower(string(b)), ".") + // Origins store an IPv6 literal unbracketed — url.Hostname() strips them — + // so the lookup key has to match that. + if len(s) > 1 && s[0] == '[' && s[len(s)-1] == ']' { + return s[1 : len(s)-1] + } if a, err := origin.HostFold(s); err == nil { return a } @@ -106,7 +129,7 @@ func isAuthorityByte(c byte) bool { return true } switch c { - case '-', '.', '_', '~', '%', '+', '@', ':': + case '-', '.', '_', '~', '%', '+', '@', ':', '[', ']': return true } return false @@ -321,8 +344,13 @@ func (h *hostReplacer) schemeAt(b []byte, at int) string { return strings.TrimSuffix(s, ":") } } - for s := range h.schemes { - return s + // Sorted, not a map range: `for s := range h.schemes` picks by Go's + // randomised iteration order, so the same input in the same process produced + // two different outputs — 176 one way and 24 the other over 200 runs. A + // rewriter whose output depends on hash seeding undermines every + // byte-identity and corpus-diff claim in the project. + if len(h.schemeList) > 0 { + return h.schemeList[0] } return "https" } @@ -354,7 +382,7 @@ func (h *hostReplacer) authorityStart(b []byte) int { // ends belongs to the credentials, so `https://user@host` names host and not // user. The authority ends at the first '/', '\', '?' or '#'; the host itself // also ends at ':', which begins the port. -// maxHost bounds the authority scan. A DNS name is at most 253 octets, and +// maxHost bounds the host. A DNS name is at most 253 octets, and // percent-encoding can only inflate that threefold, so nothing longer is a host // this map could ever contain. // @@ -368,11 +396,8 @@ func (h *hostReplacer) authorityStart(b []byte) int { // fixed and this one was not. const maxHost = 253 * 3 -func hostRange(b []byte, at int) (start, end int, port string) { +func hostRange(b []byte, at int) (start, hostEnd, end int, port string) { end = len(b) - if lim := at + maxHost; lim < end { - end = lim - } // Stop at anything that cannot be in an authority, not just at `/ \ ? #`. // // In an attribute the value *is* the URL, so the end of the buffer is the end @@ -391,11 +416,35 @@ func hostRange(b []byte, at int) (start, end int, port string) { if k := bytes.LastIndexByte(b[at:end], '@'); k >= 0 { start = at + k + 1 } + // The cap belongs on the *host*, after userinfo is out of the way. Capping + // the whole authority put the userinfo search inside the window, so pushing + // the `@` past 759 bytes made the host vanish and the origin leak — silently, + // since the byte matcher needs `//` immediately before a host and could not + // see it either. A DNS name is 253 octets and percent-encoding inflates that + // threefold; nothing longer is a host this map contains. The scan itself is + // bounded by isAuthorityByte, and a candidate only starts after a + // non-authority byte, so the sum over candidates stays linear without it. + if end-start > maxHost { + return start, start, start, "" + } + hostEnd = end + // A bracketed IPv6 literal keeps its colons. Splitting at the first one put + // half the address in the "host" and the rest in the "port", so an IPv6 + // variant could not be reversed at all. + if start < end && b[start] == '[' { + if k := bytes.IndexByte(b[start:end], ']'); k >= 0 { + hostEnd = start + k + 1 + if hostEnd < end && b[hostEnd] == ':' { + port = string(b[hostEnd+1 : end]) + } + return start, hostEnd, end, port + } + } if k := bytes.IndexByte(b[start:end], ':'); k >= 0 { port = string(b[start+k+1 : end]) - end = start + k + hostEnd = start + k } - return start, end, port + return start, hostEnd, end, port } // percentDecode decodes %XX in a host. The parser percent-decodes before @@ -433,15 +482,24 @@ func percentDecode(b []byte) []byte { // // Only token boundaries, so a `//` inside a path or a query cannot be mistaken // for an authority. +// tokenBoundary reports whether a URL could begin at v[i] — the start of the +// value, or just after a byte that separates one token from the next. +func tokenBoundary(v []byte, i int) bool { + if i == 0 { + return true + } + switch c := v[i-1]; { + case c <= 0x20, c == ',', c == '(', c == '=', c == '"', c == '\'', c == ';': + return true + } + return false +} + func urlTokenStarts(v []byte) []int { var out []int for i := 0; i < len(v); i++ { - if i > 0 { - switch c := v[i-1]; { - case c <= 0x20, c == ',', c == '(', c == '=', c == '"', c == '\'', c == ';': - default: - continue - } + if !tokenBoundary(v, i) { + continue } if n, _ := schemeLen(v[i:]); n > 0 { out = append(out, i) @@ -463,7 +521,7 @@ func urlTokenStarts(v []byte) []int { // O(k·n) — measured at 55 seconds for a 320 KB attribute value, which // extrapolates to hours at the shipped 4 MiB token cap. That is the same bug // class scan.go documents having already fixed once. -func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, until int, to origin.Origin, ok bool) { +func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, until int, repl string, ok bool) { // The scheme decides which port is the default, so it has to be found // wherever the caller entered. foldedHostLeak enters at the slash *run*, so // looking only forwards saw no scheme and fell back to https — and @@ -472,13 +530,13 @@ func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, unt scheme := h.schemeAt(n.b, at) rel := h.authorityStart(n.b[at:]) if rel < 0 { - return 0, 0, to, false + return 0, 0, "", false } start := at + rel if start >= len(n.b) { - return 0, 0, to, false + return 0, 0, "", false } - hs, he, port := hostRange(n.b, start) + hs, he, end, port := hostRange(n.b, start) // A trailing dot is the host's root label inside a URL and a full stop in // prose, and only the caller knows which surface it is on — the same // distinction Matcher.RewriteText exists for. Absorbing it in a text node @@ -487,18 +545,14 @@ func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, unt he-- } if hs >= he { - return 0, 0, to, false + return 0, 0, "", false } host := h.key(percentDecode(n.b[hs:he])) - // host:port first. Backwards, the bare-host pair won and an explicit - // :8080 origin was rewritten to the wrong variant — while the byte matcher, - // which disambiguates by port, got the same input right, so the two halves - // of the engine disagreed. §5.4 says matching is on exact origin equality, - // and :8080 is a different origin. // host:port first, and the bare host only when the port is the scheme's // default. §5.4 matches on exact origin equality, so `https://h:80` is a // different origin from `https://h` and rewriting it was a false positive — // one the byte matcher, which disambiguates by port, never made. + var to origin.Origin if port != "" { to, ok = h.to[host+":"+port] if !ok && origin.NormalisePort(scheme, port) == "" { @@ -508,12 +562,52 @@ func (h *hostReplacer) locateHostIn(n normalised, at int, value bool) (from, unt to, ok = h.to[host] } if !ok { - return 0, 0, to, false + return 0, 0, "", false } // Whatever the original spelled the host with — a tab, a reference, a // percent escape — the replaced range covers all of it, because pos maps // every surviving byte back and the removed ones lie between them. - return n.pos[hs], n.end[he-1], to, true + from, until = n.pos[hs], n.end[he-1] + + // The port, and the scheme, when the variant's differ from what is written. + // + // The splice used to emit to.Host alone, which is right only when the + // variant shares the canonical's scheme and has no port — the ddev case, and + // the only one anything tested. Anywhere else every obfuscated spelling came + // out on the wrong port and the wrong scheme while the plain spelling was + // correct, and the round trip then could not reverse it: the reverse table is + // keyed host:port, the request parsed a host with no port, and the *variant* + // hostname went upstream into the shared database. + // + // Widening the range to cover the scheme drops the obfuscated separator with + // it. That is a fair trade: what replaces it resolves to the same origin, and + // one fewer obfuscated URL on the page is not a loss. + needScheme := to.Scheme != scheme + hasPort := to.Port != "" || portOf(n.b, he, end) != "" + switch { + case needScheme: + // From the scheme through the port: the whole origin, written plainly. + return n.pos[at], authorityEnd(n, he, end), to.String(), true + case hasPort: + return from, authorityEnd(n, he, end), to.HostPort(), true + } + return from, until, to.Host, true +} + +// authorityEnd is one past the port, or one past the host when there is none. +func authorityEnd(n normalised, he, end int) int { + if end > he { + return n.end[end-1] + } + return n.end[he-1] +} + +// portOf reports the port text between the host end and the authority end. +func portOf(b []byte, he, end int) string { + if end > he && b[he] == ':' { + return string(b[he+1 : end]) + } + return "" } // foldedHostLeak catches a host that only *folds* onto a canonical one. @@ -563,11 +657,17 @@ func (w *HTML) foldedHostLeak(surface string, base int, v []byte, value bool) [] for run < len(n.b) && isSlashish(n.b[run]) { run++ } - if run-i < 2 || n.pos[i] < prev { + // The same left anchor urlTokenStarts applies. Without it this walked + // every `//` in the buffer, so `https://cdn.other/p//www.example.fi/q` + // — where the run is a path separator, not an authority — had its path + // segment rewritten, while the plain ASCII spelling of the same URL was + // correctly left alone. The two spellings disagreeing is the model error; + // the oracle's second half calls it a false positive. + if run-i < 2 || n.pos[i] < prev || !tokenBoundary(n.b, i) { i = run - 1 continue } - from, until, to, ok := w.hosts.locateHostIn(n, i, value) + from, until, repl, ok := w.hosts.locateHostIn(n, i, value) if !ok { i = run - 1 continue @@ -575,9 +675,9 @@ func (w *HTML) foldedHostLeak(surface string, base int, v []byte, value bool) [] // Nothing to do when the bytes already say the variant, and nothing to // do when the host is plain ASCII — that is the byte matcher's job, and // it has already run. - if !bytes.Equal(v[from:until], []byte(to.Host)) && hasNonASCII(v[from:until]) { + if !bytes.Equal(v[from:until], []byte(repl)) && hasNonASCII(v[from:until]) { out = append(out, v[prev:from]...) - out = append(out, to.Host...) + out = append(out, repl...) prev = until w.stats.Record(surface, base, []origin.Event{{ Offset: base + from, @@ -620,12 +720,12 @@ func (w *HTML) normaliseURLLeak(surface string, base int, v []byte, value bool) if off < len(n.pos) && n.pos[off] < prev { continue // inside a host already replaced } - from, until, to, ok := w.hosts.locateHostIn(n, off, value) + from, until, repl, ok := w.hosts.locateHostIn(n, off, value) if !ok { continue } out = append(out, v[prev:from]...) - out = append(out, to.Host...) + out = append(out, repl...) prev = until w.stats.Record(surface, base, []origin.Event{{ Offset: base + from, @@ -698,7 +798,7 @@ func (h *hostReplacer) spliceHostsIn(n normalised, v []byte, starts func([]byte) if off < len(n.pos) && n.pos[off] < prev { continue } - from, until, to, ok := h.locateHostIn(n, off, value) + from, until, repl, ok := h.locateHostIn(n, off, value) if !ok { continue } @@ -706,7 +806,7 @@ func (h *hostReplacer) spliceHostsIn(n normalised, v []byte, starts func([]byte) continue } out = append(out, v[prev:from]...) - out = append(out, to.Host...) + out = append(out, repl...) prev = until } if out == nil { From 97d3d079701c83d99364d50e5f06c207b56e358a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 19:15:34 -0300 Subject: [PATCH 016/124] Drive `check` past the container gate, which nothing had ever done MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The no-Docker suite made every `docker inspect` return empty, so `check` always bailed at "no ddev-…-hostshift container" — and everything after that had never executed: the map comparison, the stale-VIRTUAL_HOST test, the crashed-proxy test, the running-map diff against `docker logs`, and the web VIRTUAL_HOST check added last round. Four separate audit rounds have found bugs in exactly those blocks, every one of them by hand. A fake `docker` that emits the shapes the real one does is enough to drive it to the end, and the cases are the ones that have actually shipped broken: a proxy started with a different map than .ddev/.env asks for (a fleet-wide 421, twice), a proxy answering on stale hostnames, a proxy that has exited (`restart: "no"`, so it stays down), web serving more than the narrowed list (the inversion that handed a worktree the parent's blog), and a hostshift.yaml edited without a restart. Verified load-bearing: stubbing out the web check or the running-map comparison fails its assertion. --- test/addon-command.sh | 108 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/addon-command.sh b/test/addon-command.sh index 084cc21..f9dfbf4 100755 --- a/test/addon-command.sh +++ b/test/addon-command.sh @@ -588,6 +588,114 @@ case "$out" in *) pass "and does not refuse when nothing is shared" ;; esac +echo "== check, past the container gate" + +# Everything in `check` after the first `docker inspect` had never run. +# +# This suite has no Docker, so every inspect returned empty and check always +# bailed at "no ddev-…-hostshift container" — which meant the map comparison, the +# variant-resolvability test, the collision gate, the running-map diff against +# `docker logs` and the web VIRTUAL_HOST check were all unexecuted, in a file +# where four separate audit rounds have found bugs in exactly those blocks. A +# fake `docker` that emits the shapes the real one does is enough to drive it to +# the end. +fakebin="$work/fakebin" +mkdir -p "$fakebin" +cat > "$fakebin/docker" <<'FAKE' +#!/usr/bin/env bash +# Reads its answers from files the test writes, so each case is explicit. +case "$1" in + inspect) + name="$2" + case "$name" in + *-hostshift) cat "${HS_FAKE_DIR}/hostshift-state" 2>/dev/null || true ;; + *-web) cat "${HS_FAKE_DIR}/web-env" 2>/dev/null || true ;; + *) exit 1 ;; + esac ;; + logs) cat "${HS_FAKE_DIR}/logs" 2>/dev/null || true ;; +esac +exit 0 +FAKE +chmod +x "$fakebin/docker" +export HS_FAKE_DIR="$work/fake" +mkdir -p "$HS_FAKE_DIR" + +# A healthy worktree: the proxy is up, answering on the variants, started with +# the map .ddev/.env asks for, and web holds exactly the narrowed list. +(cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || fail "init exited non-zero" "" +env_args="$(sed -n 's/^HOSTSHIFT_ARGS=//p' "$wt/.ddev/.env")" +env_variants="$(sed -n 's/^HOSTSHIFT_VARIANTS=//p' "$wt/.ddev/.env")" +env_web="$(sed -n 's/^HOSTSHIFT_WEB_HOSTS=//p' "$wt/.ddev/.env")" +writefake() { + printf 'true +proxy %s +VIRTUAL_HOST=%s +' "$env_args" "$env_variants" > "$HS_FAKE_DIR/hostshift-state" + printf 'VIRTUAL_HOST=%s +' "$env_web" > "$HS_FAKE_DIR/web-env" + : > "$HS_FAKE_DIR/logs" +} +writefake +out="$(cd "$wt" && PATH="$fakebin:$PATH" "$cmd" check --slug wt-a 2>&1)" \ + && pass "check reaches the end and reports what is served" \ + || fail "check reaches the end and reports what is served" "$out" +contains "and names the variants" "hostshift is serving" "$out" + +# A proxy started with a different map than the file now asks for — the shape +# that shipped twice as a fleet-wide 421. +writefake +printf 'true +proxy --slug something-else +VIRTUAL_HOST=%s +' "$env_variants" > "$HS_FAKE_DIR/hostshift-state" +out="$(cd "$wt" && PATH="$fakebin:$PATH" "$cmd" check --slug wt-a 2>&1 || true)" +contains "check catches a proxy running a different map" "different map" "$out" + +# A proxy answering on hostnames the file no longer asks for. +writefake +printf 'true +proxy %s +VIRTUAL_HOST=stale--acme.ddev.site +' "$env_args" > "$HS_FAKE_DIR/hostshift-state" +out="$(cd "$wt" && PATH="$fakebin:$PATH" "$cmd" check --slug wt-a 2>&1 || true)" +contains "check catches a proxy answering on stale hostnames" "the running proxy answers on" "$out" + +# A crashed proxy: the container exists, the env is still there, it is not up. +writefake +printf 'false +proxy %s +VIRTUAL_HOST=%s +' "$env_args" "$env_variants" > "$HS_FAKE_DIR/hostshift-state" +out="$(cd "$wt" && PATH="$fakebin:$PATH" "$cmd" check --slug wt-a 2>&1 || true)" +contains "check catches a proxy that has exited" "has exited" "$out" + +# web serving more than the narrowed list — the inversion that made a worktree +# steal the parent's blog, and which check could not see until it looked at web. +writefake +printf 'VIRTUAL_HOST=%s,b.acme.ddev.site +' "$env_web" > "$HS_FAKE_DIR/web-env" +out="$(cd "$wt" && PATH="$fakebin:$PATH" "$cmd" check --slug wt-a 2>&1 || true)" +contains "check catches web serving hostnames it should not" "web is serving a different set" "$out" + +# The running map differing from what hostshift.yaml now resolves to. Only +# checked when a hostshift.yaml is present, since otherwise the command line is +# the map and the comparison above already covered it. +printf 'version: 1\nsites:\n - {name: main, canonical: https://www.acme.example, base: https://acme.ddev.site}\n' \ + > "$wt/hostshift.yaml" +(cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || true +env_args="$(sed -n 's/^HOSTSHIFT_ARGS=//p' "$wt/.ddev/.env")" +env_variants="$(sed -n 's/^HOSTSHIFT_VARIANTS=//p' "$wt/.ddev/.env")" +env_web="$(sed -n 's/^HOSTSHIFT_WEB_HOSTS=//p' "$wt/.ddev/.env")" +writefake +printf 'hostshift: map from /project/hostshift.yaml\nmain https://old.example -> https://wt-a--acme.ddev.site\nhostshift v1: listening on :80, upstream http://web\n' \ + > "$HS_FAKE_DIR/logs" +out="$(cd "$wt" && PATH="$fakebin:$PATH" "$cmd" check --slug wt-a 2>&1 || true)" +contains "check catches a hostshift.yaml edited without a restart" \ + "running a different map" "$out" +rm -f "$wt/hostshift.yaml" +unset HS_FAKE_DIR +(cd "$wt" && "$cmd" init --slug wt-a >/dev/null 2>&1) || true + # Every command this prints must be one DDEV actually accepts. `ddev start -p X` # is not — it fails with "unknown shorthand flag". if grep -n 'ddev start -p' "$repo/ddev/commands/host/hostshift"; then From 816bd0533bbc9a5848155b7e1762b38734198287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 19:31:44 -0300 Subject: [PATCH 017/124] Rewrite text/plain and XML, scan every project, and make check ask MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round fourteen's daily-work agent, four findings. * `text/plain` and the XML family were never rewritten, and one of them is the most common admin action there is: wp-admin/async-upload.php sets `text/plain` before wp_send_json can set application/json, so every media upload handed the browser a canonical `url` and `link` while the listing endpoint beside it — application/json — was rewritten correctly. Feeds and sitemaps are the same shape. Under production-canonical that URL is the client's live site and the admin session fetches from it, and loopback containment cannot help: it pins hostnames inside `web`, not in the browser. Silent by every available means — --explain printed nothing at all for those responses, not even a skip. CSS and JavaScript stay outside the set, which is §5.2's measured decision, and there is now a test pinning both halves. * The collision scan read `../*/.ddev/.env` and nothing else, so two worktrees of one parent in different directories — `git worktree add ~/worktrees/foo`, or two developers with worktrees under their own homes — collided in total silence, both projects up, both printing "hostshift is serving". Meanwhile a *dead* fake sibling with no containers warned immediately. It reads ~/.ddev/project_list.yaml now, which is where DDEV keeps every approot. * `check` never made a request. It inspects the proxy five ways and web besides, then reports what is *configured* as though it were what is served — and two states measured green with the URL returning 502: a directory rename that left DDEV's old traefik routers pointing at a dead container, and two live projects claiming one hostname. One request closes both. Only a hard routing failure is fatal; a 404 or a 302 means an application answered, which is the question. * Adopting production-canonical was a pincer that configuration alone cannot see. A worktree whose branch predates the parent's hostshift.yaml has a map built from DDEV hostnames — correct while the database still holds them, both variants serving 200 — and check refused it on every start with a warning whose every statement was false. Merge the file, as that warning says, and check goes green while both URLs 302 to wp-signup, because the database has not moved. The developer's only escape from a permanent red was the action that silently breaks the site. `select option_value from wp_options where option_name='home'` separates the two states exactly, and fixes both halves; the add-on already talks to this database in copy-db. Best-effort: no container, no WordPress, an unusual prefix or a stopped db yield nothing, and nothing is not evidence. --- ddev/commands/host/hostshift | 102 ++++++++++++++++++++++++++++++- internal/proxy/proxy.go | 61 ++++++++++++++++++ internal/proxy/roundtrip_test.go | 70 +++++++++++++++++++++ test/addon-command.sh | 54 ++++++++++++++++ 4 files changed, 285 insertions(+), 2 deletions(-) diff --git a/ddev/commands/host/hostshift b/ddev/commands/host/hostshift index dd13309..94da4be 100755 --- a/ddev/commands/host/hostshift +++ b/ddev/commands/host/hostshift @@ -555,9 +555,33 @@ variants="$(hostshift map --slug "$slug" "${map_args[@]+"${map_args[@]}"}" --var # prefix. Checking is cheap — every configured project leaves its variants in # .ddev/.env — so check. here="$(pwd -P)" -for other in ../*/.ddev/.env; do + +# Every project DDEV knows about, not just the directories next door. +# +# `../*/.ddev/.env` sees a sibling and nothing else, so two worktrees of one +# parent in different directories — `git worktree add ~/worktrees/foo`, or two +# developers with worktrees under their own home directories — collided in total +# silence: both `.ddev/.env` files carried the same HOSTSHIFT_VARIANTS, both +# projects were up, and `check` in each printed "hostshift is serving" and exited +# 0. Meanwhile a *dead* fake sibling — a bare directory with a .ddev/.env and no +# containers — warned immediately. This is the one check whose job is to stop a +# developer reviewing someone else's code at their own preview URL, and it was +# blind to the arrangement where that most easily happens. +# +# ~/.ddev/project_list.yaml holds every project's approot. Siblings stay in the +# list because a project that has never been started is not in the registry yet. +scan_dirs="$( + { + for d in ../*/; do printf '%s\n' "${d%/}"; done + reg="${DDEV_GLOBAL_CONFIG:-$HOME/.ddev}/project_list.yaml" + [ -f "$reg" ] && sed -n 's/^[[:space:]]*approot:[[:space:]]*//p' "$reg" + } 2>/dev/null | LC_ALL=C sort -u +)" + +for other_dir in $scan_dirs; do + other="$other_dir/.ddev/.env" [ -f "$other" ] || continue - odir="$(cd "$(dirname "$(dirname "$other")")" && pwd -P)" + odir="$(cd "$other_dir" 2>/dev/null && pwd -P)" || continue [ "$odir" = "$here" ] && continue # No `| head -1`: this script runs under `set -o pipefail`, and a head that # exits after one line SIGPIPEs whatever is still writing, failing the whole @@ -1001,6 +1025,52 @@ if [ "$cmd" = "check" ]; then exit 2 fi + # Ask the database which hostnames it actually holds. + # + # Everything above compares configuration against configuration, and adopting + # production-canonical is a pincer between two states configuration cannot + # tell apart. A worktree whose branch predates the parent's hostshift.yaml has + # a map built from DDEV hostnames — which is *correct* while the database still + # holds them, and both variants return 200 — and check refused it on every + # start, every statement in the warning false. Merge the file, as that warning + # says, and check reports "hostshift is serving" and exits 0 while both URLs + # 302 to wp-signup, because the database has not moved yet. The developer's + # only escape from a permanent red is the action that silently breaks the site. + # + # One query separates them, and the add-on already talks to this database in + # copy-db. Best-effort throughout: no container, no WordPress, an unusual table + # prefix or a stopped db all yield nothing, and nothing is not evidence. + dbhome="$(ddev exec -s web bash -c \ + "mysql -h db -udb -pdb -N -B -e \"select option_value from wp_options where option_name='home' limit 1\" db" \ + 2>/dev/null | tail -1 | tr -d '\r' || true)" + dbhost="" + case "$dbhome" in + http://*|https://*) dbhost="$(printf '%s' "$dbhome" | sed -e 's|^https\{0,1\}://||' -e 's|[/:].*$||')" ;; + esac + + if [ -n "$dbhost" ]; then + canon_hosts="$(hostshift map --slug "$slug" "${map_args[@]+"${map_args[@]}"}" \ + --canonical-hosts 2>/dev/null | sort -u || true)" + known="" + while IFS= read -r h; do + [ "$h" = "$dbhost" ] && { known=1; break; } + done <<<"$canon_hosts" + if [ -z "$known" ]; then + echo "hostshift: the database says its home is $dbhome, and this map does not" >&2 + echo " name that hostname — so nothing on the page would be rewritten and" >&2 + echo " every link would point at it. The map covers:" >&2 + printf ' %s\n' $canon_hosts >&2 + echo " Either the database has not been moved to the hostnames" >&2 + echo " hostshift.yaml declares, or the map is built from the wrong" >&2 + echo " checkout. \`ddev hostshift wp-cli\` and a search-replace move it." >&2 + exit 2 + fi + # The database agrees with the deployed map, so the warning above — that this + # worktree's branch predates a hostshift.yaml — describes a state that has + # not arrived yet. Refusing here failed the hook on a site serving 200s. + parent_declares="" + fi + if [ -n "$parent_declares" ]; then echo "hostshift: refusing to call this healthy — see the warning above." >&2 echo " The map does not name the hostnames the database holds." >&2 @@ -1070,6 +1140,34 @@ if [ "$cmd" = "check" ]; then fi fi + # One request, because everything above this line is inference. + # + # `check` inspects the container five ways — it exists, it is running, its + # VIRTUAL_HOST, its command line, the map it logged — and web's VIRTUAL_HOST + # besides, and then reports what is *configured* as though it were what is + # served. Two states measured green with the URL returning 502: a directory + # rename that left DDEV's old traefik routers behind pointing at a dead + # container, and two live projects claiming one hostname. A single request + # closes both. + # + # Only a hard routing failure is fatal. A 404, a 302 to wp-signup, a 401 — all + # mean the request reached an application, which is what this is asking. curl + # is not guaranteed present, so its absence is not an error either. + if command -v curl >/dev/null; then + probe="$(printf '%s' "$variants" | cut -d, -f1)" + code="$(curl -sk -o /dev/null -w '%{http_code}' --max-time 10 "https://$probe/" 2>/dev/null || true)" + case "$code" in + 000|502|503|504) + echo "hostshift: everything is configured correctly, but https://$probe/" >&2 + echo " answers ${code:-nothing} — so something between the router and the" >&2 + echo " proxy is broken, not the configuration." >&2 + echo " A stale router from a renamed project does this, and so does" >&2 + echo " another project claiming the same hostname. \`ddev restart\`, and" >&2 + echo " if that does not clear it, \`ddev poweroff\` retires the routers." >&2 + exit 2 ;; + esac + fi + printf '\n hostshift is serving:\n' >&2 printf ' https://%s\n' $(printf '%s' "$variants" | tr ',' ' ') >&2 printf '\n' >&2 diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 6783566..2f2a639 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -451,6 +451,48 @@ func (p *Proxy) finishBody(resp *http.Response, st *state, changed bool) error { return nil } + case rewritableText(ct): + // Buffered and swept like JSON, without a grammar. + // + // These were streaming through untouched, and one of them is the single + // most common admin action there is: wp-admin/async-upload.php sets + // `text/plain` before wp_send_json can set application/json, so every + // media upload handed the browser a canonical `url` and `link` while the + // listing endpoint beside it — application/json — was rewritten + // correctly. Feeds and sitemaps are the same shape. Under + // production-canonical that URL is the client's live site, and the + // developer's admin session fetches from it. + // + // Silent, too: --explain printed nothing at all for those responses, not + // even a skip. + body, over, err := readCapped(resp.Body, p.maxBody()) + if err != nil { + return err + } + if over != nil { + p.log().Warn("body exceeds the size cap, passing through untouched", + "cap", p.maxBody(), "content-type", ct) + p.Stats.Record(rewrite.SurfaceText, 0, []origin.Event{{ + Surface: rewrite.SurfaceText, Action: origin.ActionSkipped, + Reason: origin.ReasonSizeCap, + }}) + resp.Body = readCloser{io.MultiReader(bytes.NewReader(body), over), resp.Body} + return nil + } + out, ev := p.Map.Forward().RewriteText(body, rewrite.SurfaceText, p.Stats.Explain()) + p.Stats.Record(rewrite.SurfaceText, 0, ev) + out = rewrite.HostLeaks(p.Map.Forward(), out, false) + if !p.NoSweep { + out = rewrite.SweepBytes(out, p.Map.Forward(), p.Stats, p.log()) + } + if p.DryRun { + out = body + } + resp.Body = readCloser{bytes.NewReader(out), resp.Body} + if bytes.Equal(out, body) && !changed { + return nil + } + default: return nil } @@ -596,6 +638,25 @@ func rewritableHTML(ct string) bool { return strings.EqualFold(mediaType(ct), "text/html") } +// rewritableText is the set that carries origins in no grammar the rewriter +// models: plain text, and the XML family. +// +// text/plain because wp-admin/async-upload.php sends JSON under it — the one +// endpoint every media upload goes through. The XML family because a feed and a +// sitemap are full of absolute URLs, a browser renders both, and a reader +// dereferences them. Excluded, deliberately and as §5.2 already decides: CSS and +// JavaScript, where the fleet's themes have zero absolute URLs, and every binary +// type, where the Content-Type answers the question for free. +func rewritableText(ct string) bool { + mt := strings.ToLower(mediaType(ct)) + switch mt { + case "text/plain", "text/xml", "application/xml", + "application/rss+xml", "application/atom+xml", "image/svg+xml": + return true + } + return strings.HasSuffix(mt, "+xml") +} + // rewritableJSON covers the REST API and everything modelled on it. JSON-LD // arrives inside a `, true}, + {"an XHTML style", "application/xhtml+xml", + ``, true}, + // An HTML parser does not decode inside script or style, so a reference + // there is not a URL and must not be touched. + {"an HTML script", "text/html", + ``, false}, + // Nothing parses references in plain text. + {"plain text", "text/plain", `see https://acme.ddev.site/p`, false}, + } { + t.Run(c.name, func(t *testing.T) { + up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", c.ct) + w.Write([]byte(c.body)) + })) + defer up.Close() + upURL, err := url.Parse(up.URL) + if err != nil { + t.Fatal(err) + } + p := &Proxy{Map: mp, Upstream: upURL, Stats: rewrite.NewStats(false)} + srv := httptest.NewServer(p.Handler()) + defer srv.Close() + + req, _ := http.NewRequest("GET", srv.URL+"/x", nil) + req.Host = "wt-a--acme.ddev.site" + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + got, _ := io.ReadAll(resp.Body) + if c.rewrite && !strings.Contains(string(got), "wt-a--acme.ddev.site") { - t.Errorf("not rewritten:\n%s", got) + t.Errorf("a production origin the parser dereferences was left:\n%s", got) } - if !c.rewrite && string(got) != body { - t.Errorf("a download was rewritten, so it names a hostname that exists on one machine:\n%s", got) + if !c.rewrite && string(got) != c.body { + t.Errorf("a reference no parser decodes here was rewritten:\n%s", got) + } + // Whatever happened, the escapes themselves survive: the splice + // replaces the host's byte range and never re-serialises the value. + if !strings.Contains(string(got), "//") { + t.Errorf("the references were re-serialised:\n%s", got) } }) } diff --git a/internal/rewrite/cssrt_test.go b/internal/rewrite/cssrt_test.go new file mode 100644 index 0000000..f80c5b0 --- /dev/null +++ b/internal/rewrite/cssrt_test.go @@ -0,0 +1,54 @@ +package rewrite + +import ( + "strings" + "testing" + + "github.com/generoi/hostshift/internal/origin" +) + +// Every spelling the forward direction can emit, the reverse direction must be +// able to read. +// +// cssEscapeLeak splices the host into the *escaped* spelling, so a style +// attribute reaches the browser as `url(https\3a \2f \2f /x)` — and the +// editor posts that back. Nothing on the way in could read it: the byte +// matcher's prefilter needs `//`, `\/` or `%2F` and that string has none, and +// stripForCSS was reachable only from the forward pass. The variant hostname +// went upstream and into the database §4.3 says stays byte-identical to +// production, which the file's own comment calls worse than a leak. +func TestForwardEmissionsAreReadableInReverse(t *testing.T) { + canon := origin.MustParse("https://www.example.fi") + variant := origin.MustParse("https://wt-a--example.ddev.site") + fwd, err := origin.NewMatcher([]origin.Pair{{Canonical: canon, Variant: variant}}) + if err != nil { + t.Fatal(err) + } + rev, err := origin.NewMatcher([]origin.Pair{{Canonical: variant, Variant: canon}}) + if err != nil { + t.Fatal(err) + } + + // Shapes a page can hold, each of which the forward pass rewrites in place. + for _, in := range []string{ + `
    x
    `, + `
    x
    `, + ``, + `y`, + `y`, + `y`, + } { + t.Run(in, func(t *testing.T) { + served := rewriteHTML(t, fwd, in, NewStats(false)) + if strings.Contains(served, canon.Host) { + t.Fatalf("the forward pass left a canonical origin:\n%s", served) + } + back := string(HostLeaks(rev, []byte(served), true)) + if strings.Contains(back, variant.Host) { + t.Errorf("a variant hostname survives the request direction, so it "+ + "would be written into the shared database:\n served %s\n back %s", + served, back) + } + }) + } +} diff --git a/internal/rewrite/html.go b/internal/rewrite/html.go index cc6489a..a0117e3 100644 --- a/internal/rewrite/html.go +++ b/internal/rewrite/html.go @@ -42,6 +42,7 @@ type HTML struct { tail io.Reader attrs []Attr // scratch for scanAttrsInto, reused across tags hosts *hostReplacer + xmlEnt bool } // mark records that output offset out corresponds to input offset in, from @@ -106,6 +107,12 @@ type Options struct { Log *slog.Logger // MaxToken caps the tokenizer's buffer. Zero means DefaultMaxToken. MaxToken int + // XMLEntities says the document is parsed by an XML parser, which decodes + // character references inside ` }}, + {"text", func(u string) string { return `

    ` + u + `

    ` }}, + {"srcset", func(u string) string { return `` }}, + // CSS-escaped input, which is what makes the forward pass *emit* the + // escaped spelling — the one round sixteen found the request path could + // not read. Wrapping a plain URL in a style attribute is not enough: the + // ordinary locator handles that and emits a plain variant. + {"css-escaped style attribute", func(u string) string { + return `
    x
    ` + }}, + {"css-escaped style element", func(u string) string { + return `` + }}, + } + var bad int for i, sh := range shapes { if i%37 != 0 || sh.base != "https" || strings.TrimSuffix(sh.resolved, ".") != oracleCanonical { continue } - served := rewriteHTML(t, fwd, `x`, NewStats(false)) - // What the browser would send back: the rewritten value, through the - // request direction. - back := string(HostLeaks(rev, []byte(served), true)) - if strings.Contains(back, oracleVariant) { - bad++ - if bad <= 10 { - t.Errorf("a variant hostname survives the request direction:\n from %q\n sent %q\n back %q", - sh.candidate, served, back) + for _, w := range wrappers { + served := rewriteHTML(t, fwd, w.wrap(sh.candidate), NewStats(false)) + // What the browser would send back: the rewritten value, through the + // request direction. + back := string(HostLeaks(rev, []byte(served), true)) + if strings.Contains(back, oracleVariant) { + bad++ + if bad <= 10 { + t.Errorf("[%s] a variant hostname survives the request direction:\n from %q\n sent %q\n back %q", + w.name, sh.candidate, served, back) + } } } } if bad > 10 { - t.Errorf("%d shapes did not round-trip", bad) + t.Errorf("%d shape/surface combinations did not round-trip", bad) } } From 8ff7e0950c6f8b00ee3ee50229eefc74828be152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Sch=C3=B6ldstr=C3=B6m?= Date: Sun, 30 Aug 2026 22:18:08 -0300 Subject: [PATCH 028/124] Make the instruments measure what they report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round seventeen's breaker, four large findings. Two of them are instruments reporting health they did not measure, which is the failure mode that lets everything else survive. * **`diff` ran only the byte matcher**, justified by "the matcher is by definition exactly the set of origins the proxy claims to rewrite". That stopped being true the moment urlobf.go existed: the proxy also runs the URL-parser view, the IDNA fold, the CSS view and the reference views, and the leak check ran none of them. So every class found since round ten was invisible by construction to the one test §7 calls the only one that validates against reality — and it printed GREEN on a page whose `` a real browser resolved to production. It pushes the served bytes back through the same pipeline now. That leak is real and is fixed too: decodeURLRefs declines a whole value when any fragment in it would fuse into a new reference, and the fragment can be anywhere — one in a query string left an ordinary `https://canonical/` in the same attribute undecoded and live. The guard is right about splicing a decoded value back; it has nothing to say about locating a host and replacing its byte range, where the decoded bytes never leave the view. The reference view does that and needs no guard. * **The database gate asked a question the application cannot answer.** `wp option get home` runs through `add_filter('option_home', …)`, which WordPress registers unconditionally and which returns WP_HOME whenever it is defined — and Bedrock always defines it. Under `ddev exec` that made the answer the worktree's own hostname, refusing a healthy site on every start; with a wp-cli.local.yml it made the answer always equal a canonical host, so the check cleared the parent-declares warning by echoing generated config back at itself. PLAN §4.1 says exactly this: the application self-reports and cannot be interrogated. `wp db query` reads the stored row instead — wp-cli resolves the connection from wp-config.php, so Bedrock's dotenv and the README's DATABASE_URL are both handled, and the query bypasses every filter. * **Inline `` is foreign content**, where the HTML tokenizer never enters the raw-text states — so a browser decodes references in `