Commit Graph
52 Commits
Author SHA1 Message Date
jw 9a533a4d5a Send X-Request-ID with the case Dynadot requires
Every Dynadot request this provider has ever made was rejected with "the
X-Signature provided is not valid". The signature was correct. The header name
was not.

urllib capitalises header names when they go through add_header, which the
Request constructor does for everything passed to it, so X-Request-ID left as
X-request-id. Header names are case-insensitive under RFC 9110 and Dynadot's
gateway is not: it matches X-Request-ID exactly, did not find the rewritten
form, and computed the signature with an empty request id while we computed ours
with the UUID. A correct signature over a different string, reported as a bad
signature.

That message sends you to the algorithm and the credentials, which is where I
spent the evening. The header we were plainly sending is the last thing anyone
would suspect.

Assigning the mapping after construction bypasses add_header and preserves the
name. Confirmed against the live API: exact X-Request-ID is accepted, and
X-Request-Id one letter different is rejected.

Two tests, both of which fail against the previous code: the wire name keeps its
case, and the id in the signature is the id in the header. The second is the
invariant that actually broke, and it can break again without the first.
2026-08-24 10:41:28 -04:00
a4b8a8729b Surface Dynadot's refusal instead of only its status code (#2)
A 400 from Dynadot arrived as "HTTP 400" and nothing else. Dynadot explains the
refusal in the response body and we were discarding it, so the operator was left
with a number that does not distinguish "the domain is not in this account" from
"the signature is wrong" from "the caller address is not allowlisted". All three
are 400 and all three have different fixes.

The body is bounded to 400 characters and quoted as remote text. No credential
can appear in it: the key travels in an Authorization header and the signature
is computed rather than echoed.

Reading the body is best-effort. A stream that cannot be read must not cost the
status code as well, so the failure to read is swallowed and the code is still
raised, which is what the second test pins.

Co-authored-by: jw <jw@jwmacbookm5.local>
2026-08-24 09:01:51 -04:00
John Winter MurphyandGitHub 644652b186 Fix Namecheap non-MX preference normalization
Ignore Namecheap MXPref placeholders outside MX records, add round-trip regression coverage, and keep the declared pytest gate collectable.
2026-08-01 14:31:05 -04:00
jwinterm 3fe9c472ef feat(dns): allow exact protected-name record exceptions 2026-07-31 16:06:46 -04:00
jwinterm 0c6bbace07 feat: add guarded Dynadot DNS reconciliation 2026-07-31 15:07:41 -04:00
jwinterm 502e5bd587 feat: reconcile preserve-unmanaged DNS manifests 2026-07-30 07:43:17 -04:00
jwinterm 4767b7965a fix: emit real micron, and validate values interpolated into daemon configs
render_micron was a one-line alias to the Gemtext renderer, so every .mu file
AMPG produced was Gemtext. A Nomad Network browser renders those markers as
raw punctuation. Micron is a different markup: headings are > by depth, links
are inline as `[label`url] rather than standalone lines, emphasis is a toggle
pair, and literal blocks are fenced with `=.

Content is now escaped as well. Micron's control character is the backtick, and
>, <, #, and - are structural at column zero, so a vendor named ">Free Beer"
would have become a section heading on the published page. The line-start check
applies to the emitted line rather than each text chunk, since whether a
character is structural depends on it landing at column zero.

Separately, torrc and tunnels.conf are line-oriented with no quoting or escape
mechanism, so a newline in an interpolated value injects an arbitrary directive:
a tunnel_name of "web\nhost = attacker.example" silently repoints the tunnel.
tunnel_name and keys_file are now validated at the point of interpolation.
_validate_site_id covers the init path only, so a config loaded from disk never
passed through it. There is nothing to escape with here, so these reject rather
than sanitize.

Generated systemd units had no User= and no hardening, so an operator following
the install steps as root got a root-run daemon. Units now carry User=/Group=
when a service account is configured, a visible warning when not, and a baseline
of hardening directives chosen to not break a daemon whose state directory may
live anywhere.

Two tests asserted Gemtext markers inside a .mu file and so had encoded the bug.
2026-07-28 16:03:40 -04:00
jwinterm bb63585098 Add signed service manifest contract 2026-07-11 11:05:48 -04:00
jwinterm 1651514c8e Version browser context fixture contract 2026-07-11 10:20:54 -04:00
jwinterm 84d72cb2ed Declare browser profile isolation 2026-07-11 09:34:28 -04:00
jwinterm f0d26933e6 Model transport provider sources 2026-07-08 10:12:59 -04:00
jwinterm e272ec1afb Recognize distro i2pd daemon process 2026-07-08 09:14:13 -04:00
jwinterm c38eab9c28 Fix I2P web tunnel deployment shape 2026-07-08 09:11:32 -04:00
jwinterm 234a510798 Add DNS provider flow and demo site split 2026-07-07 12:41:52 -04:00
jwinterm 7f3631a624 Add Micron renderer and AMPGateway site target 2026-07-07 11:34:54 -04:00
jwinterm a143cd9342 Add package deploy apply stage 2026-07-07 09:37:36 -04:00
jwinterm 8b58bfb588 Add health deploy apply stage 2026-07-07 08:01:20 -04:00
jwinterm 8f4724089b Add address capture deploy apply stage 2026-07-07 05:56:47 -04:00
jwinterm c3d44a5e5b Add start stage deploy apply 2026-07-07 05:29:34 -04:00
jwinterm f276eb470b Add supervisor stage deploy apply 2026-07-06 21:47:30 -04:00
jwinterm b2bf06e050 Add free domain DNS onboarding hints 2026-07-06 18:52:57 -04:00
jwinterm 1dd601b0e4 Add state stage deploy apply 2026-07-06 18:49:34 -04:00
jwinterm a7500dacbc Add clearnet DNS reachability planning 2026-07-06 18:16:33 -04:00
jwinterm f4a1355212 Add guided deploy plan 2026-07-06 18:06:42 -04:00
jwinterm db5d9737c0 Add site onboarding config generator 2026-07-06 17:55:19 -04:00
jwinterm 0426ca673c Require approved artifact digests for apply 2026-07-06 16:30:40 -04:00
jwinterm fbfddbf2bf Add apply preflight gate 2026-07-06 12:29:08 -04:00
jwinterm a921f158c3 Plan managed supervisor actions 2026-07-06 12:17:05 -04:00
jwinterm 3063e61676 Plan managed state artifact copies 2026-07-06 11:49:36 -04:00
jwinterm b268e93634 Add managed state contract 2026-07-06 11:06:11 -04:00
jwinterm 12983ca134 Add address capture to activation plan 2026-07-06 10:30:42 -04:00
jwinterm bf2a171ec6 Capture transport addresses in state 2026-07-06 07:00:04 -04:00
jwinterm c52e700b6f Add fixture-based health plans 2026-07-06 06:02:58 -04:00
jwinterm 1641f2009d Write managed install artifacts 2026-07-06 05:50:12 -04:00
jwinterm 035ab4ac81 Add managed daemon install plans 2026-07-06 05:42:41 -04:00
jwinterm a3ad5c900b Add deployment profiles 2026-07-06 05:28:35 -04:00
jwinterm 034b463611 Scope operations by selected protocol 2026-07-06 05:01:45 -04:00
jwinterm 83847b205b Add dry-run apply activation plan 2026-07-06 04:54:03 -04:00
jwinterm 27f3805c19 Add transport status and doctor preflight 2026-07-06 04:47:48 -04:00
jwinterm 2e9307ba26 Add local preview fixture manifests 2026-07-06 04:24:30 -04:00
jwinterm 38d86adfbf Add route catalog manifest generator 2026-07-06 04:16:09 -04:00
jwinterm 407d4d106a Add route manifest contract validation 2026-07-06 04:07:10 -04:00
jwinterm c1d8337365 Explain route exposure decisions 2026-07-06 03:58:47 -04:00
jwinterm bd782ed7ed Gate route fixtures by protocol tier 2026-07-06 03:51:14 -04:00
jwinterm 2ca73ca431 Import app route manifests 2026-07-06 03:49:34 -04:00
jwinterm cf0b89b572 Expand fixture manifests with route policies 2026-07-06 03:41:04 -04:00
jwinterm 5bfdd65dbc Add interaction policy to fixture manifests 2026-07-06 03:27:48 -04:00
jwinterm 49054f729b Add interaction capability policy docs 2026-07-06 03:20:05 -04:00
jwinterm 717c8fb9c5 Emit AMPB fixture manifests 2026-07-05 21:16:06 -04:00
jwinterm 5c924cae60 Add Gemini renderer and semantic audit 2026-07-05 19:59:23 -04:00