Repository navigation
Exact-target control gate (DNS TXT / HTTPS file / recorded authority review) - #12
Merged
Merged
Conversation
… authority review Mailbox confirmation no longer queues a job whose terms version requires proof of control of the exact hostname (config target_control_versions, frozen per job). The customer publishes a one-time challenge (DNS TXT at _ct-scan-challenge.<host> or /.well-known/ct-scan-challenge.txt) and asks for a check: at most three, a minute apart, each one TXT lookup or one HTTPS request (fixed path, no redirect, 4 KiB, 10 s). The worker takes a scheduled check, is admitted once through the admission guard (kind control, attempt consumed in the admission's compare-and-set), runs it in the unprivileged child and relays what it saw; WordPress compares it with the stored token hash. Three failures block the job; a blocked job is never scanned and gets no detailed report. A recorded manual authority review (reviewer, evidence, target, time, expiry) can stand in. Schema 5 adds the control_* columns. Fixtures: a loopback DNS server, a local HTTP server, a real child process, WordPress/MariaDB, and an HTTP round trip. Refs CompleteTech-LLC/complete.tech#17 (gap 1). Co-Authored-By: Claude Code <noreply@anthropic.com>
…lity; close review gaps Independent review found that the published challenge doubled as the control page's bearer token, so anyone able to read the DNS record or file could spend the customer's three checks and block the job. The page now has its own capability (never published); the challenge shown on it is derived from it with a keyed hash. Also: a job whose every check is spent is blocked even if the last result was lost; scans and control checks cannot be claimed at the same time; profiles broader than r2 always require control; the DNS wait has one overall deadline; the control gate gets the scan deadline; blocked jobs are included in the retention scrub; the schema readback names every column; authority-review evidence is shown in the admin queue; and tests now defend each central invariant directly (forced unverified queued rows, a fourth admission, unconfirmed rows, revoked pages, unknown methods, serialization in both directions). Co-Authored-By: Claude Code <noreply@anthropic.com>
Merge of the evidence and admission fixes. control_required is now frozen from the terms policy: every version is strict (requires control of the exact target) unless an r2-or-earlier version is explicitly grandfathered as legacy. Fixtures and the HTTP test run the strict policy; the legacy flow is covered by the default-policy suites. Co-Authored-By: Claude Code <noreply@anthropic.com>
This was referenced Oct 22, 2026
… control_required - WordPress records the admission on the lease (control_admitted, set in the admission compare-and-set, reset at claim). A second admit kind=control under the same lease is refused 409 already_admitted, even after the one-minute interval has passed within the two-minute lease; the result route also requires the flag. The worker treats already_admitted as a final refusal. - ct_scan_control_required(): a missing control_required value counts as required (liveness, confirmation and detailed-report gates). - Authority review is refused while a check is in flight (control_state checking), and its compare-and-set includes the state it read. - HTTPS file match strips a leading UTF-8 BOM. - Docs and customer page: CNAME at the challenge name is not followed, DNS negative caching can consume a check made before propagation, and jobs created before schema 5 have control_required = 0. - Tests: same-lease re-admission after the interval (attempts unchanged), review refused mid-check, BOM accepted/near-misses refused, missing control_required, worker gate does not retry already_admitted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CompleteDotTech
changed the base branch from
feat/bound-acceptance-evidence
to
main
October 23, 2026 19:40
Brings in the #10 and #11 review fixes (probe busy/inconclusive handling, bound-evidence pair comparisons, duplicate evidence refusal, accepted-copy live check). Resolutions: - admission.py probe(): keep main's bounded queue_busy retry and use this branch's per-kind route ("probe" for http, "control_probe" for control). Test added for the control route. - ct_scan_send_accepted_copy(): main's live-job check allowed only queued/leased/running, but confirmation now leaves control-required jobs in awaiting_control and sends the copy then, so the default path would never send it. awaiting_control is accepted as live; every other check (authorization live, recipient, terms hash, compare-and-set) is kept. evidence-strict.php covers confirmation into awaiting_control; docs updated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s_policy activation - ct_scan_admin_send_report() compared the raw `_ct_terms_sha256` meta with the job's lowercased terms_hash, so an upper-case recorded hash refused a reviewed report. Normalise it as the accepted-copy path does; test added in integration.php. - docs/WORDPRESS.md deployment step 5 and the README activation note now say an explicit `terms_policy` entry for the served terms version is required to enable the integration, with an upgrade note: without it the worker API answers 503, new acceptances are not queued and in-flight jobs stall. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The awaiting-control confirmation test checked accepted_copy_status for 'sent' (the function's return value); the stored state is 'accepted'. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
CompleteDotTech
marked this pull request as ready for review
October 23, 2026 19:55
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
CompleteDotTech
added a commit
that referenced
this pull request
Oct 23, 2026
Brings in the merged #10/#11/#12 review fixes (probe busy-retry, list-of-pairs evidence comparison, control_admitted and 409 already_admitted, fail-closed ct_scan_control_required(), terms_policy docs) under the retention branch. Conflict: the schema's required jobs columns now list both control_admitted and the retention/hold/identity columns. The schema version moves to 7 so an install that recorded 6 before control_admitted existed re-runs the upgrade. Retention coherence with the control gate: awaiting_control (pending or checking) is not terminal and is never closed; a restored row recognised by a tombstone is no longer deleted while it holds a lease (leased/running scan or a control check in progress), so the one-at-a-time gate is never freed while its traffic may be in flight. Tests cover both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs CompleteTech-LLC/complete.tech#17 (gap 1, exact-target control). Targets
main(#10 and #11 merged; main merged in). Scanning stays disabled by default. When enabled, the gate is on by default: every terms version isstrictunder configterms_policy(required, explicit per served version), and only an r2-or-earlier version listed aslegacyis exempt.What changes
Mailbox confirmation shows control of an email address, not of the website, and a referral code shows neither. For every job whose terms version is
strictunderterms_policy(the default for any version; frozen into the job at creation ascontrol_required; any profile broader than r2 always requires it), confirmation no longer queues it: it waits asawaiting_controland cannot be claimed, started, admitted or given a detailed report until control of the exact hostname is shown:_ct-scan-challenge.<host>=ct-scan-verify=<value>, or HTTPS filehttps://<host>/.well-known/ct-scan-challenge.txtcontaining the value. The customer asks for a check on a private page (GET only displays; nonce-protected POST acts). At most 3 checks, >= 1 minute apart; each is one TXT lookup or one HTTPS request, fixed path, no redirects, 4 KiB, 10 s; no probes. The worker takes a scheduled check (control/claim), is admitted exactly once per lease through the same guard as scans (admitkindcontrol: cancellation, expiry, lease, budget, pacing, attempt consumed in the admission compare-and-set; the admission is recorded on the lease (control_admitted), so a secondadmitunder the same lease is refused409 already_admittedfor the whole two-minute lease, and the worker ends the check without retrying), runs it in the unprivileged child, and relays what it saw (control/result, length-bounded, untrusted); WordPress compares with a stored hash. A result counts only if an admission happened under that lease. Three failures (or all checks spent) =>blocked(target_control_failed): never scanned, no detailed report, no fallback.control_state = checking).control_required = 0; a row with a missing value is treated as requiring control.Fail-closed behavior
Any anomaly (unknown method/profile, unconfirmed recipient, lapsed lease, no admission, wrong lease/scope/method, oversized or malformed result, DB fault, lost compare-and-set, cancelled/expired/revoked) refuses and changes nothing; a result is never accepted without an admission; a job that required control (or whose
control_requiredvalue is missing) and has no verified control is cancelled by every liveness check; a lease is admitted at most once; control state is cleared by Stop, revoke and expiry.Tests (local only; no third-party host or resolver)
control.php(WordPress/MariaDB, re-runs itself with the strictterms_policy): confirmation behavior, limits (3 attempts, 1/min, pacing, budget, one admission per lease even after the interval passes), authority review refused mid-check, BOM handling, missingcontrol_requiredfails closed, forged/malformed/near-miss values, spent tasks, lapsed leases, Stop/expiry mid-check, DB faults, serialization, blocked => no scan/report, manual review (authorization, evidence, expiry), and cases that defend each central invariant directly. Mutation-checked.test_control.py(34): DNS wire parsing (compression loops, truncation, id/question/source spoofing, caps), a loopback UDP DNS server, a local HTTP server (redirects not followed, 4096-byte edge, non-UTF8, slow server, refused admission reaches nothing), the real scanner entry point, a real child process under the supervisor (kind enforcement, output validation), and the service loop.deployment/test-http-control.py: the customer pages and the signed worker API over real HTTP (the CI step takes ~61 s for the one-minute interval).Reviewer: please attack
Any path from a
control_requiredjob to scan traffic without verified control; the challenge/capability separation; therequested -> checking -> pending/blockedstate machine and attempt accounting; match logic; the DNS client; admin authority review.Not done / needs the owner
legacy(exempt) interms_policy; every other version requires control; whether a customer may get a fresh challenge afterblocked; per-contact/domain/IP quotas for fresh challenges (spec section 4.5) are not implemented because there is no reissue.TODO(owner)); form/CTA/email/caller-script copy is out of scope here.Fail-closed review round 2 (coordinator findings)
Fixed in this stack, each with tests that fail without the fix (mutation-checked):
egress_enforced: true(loader refuses otherwise); docs/README/example config state it; unverified nftables egress sketch added (not a claim of enforcement). In-flight bound is exactly 10 s from the grant (no grace). Authorization read from the DB, not the object cache. A completed result needs >= 1 admitted request. Failed schema upgrade: oneerror_log, 5-minute back-off, plugin 0.2.0. Special-purpose IP ranges listed explicitly (CGNAT, documentation, benchmarking,3fff::/20, 6to4, NAT64, ...), tested independent of the interpreter'sipaddress.terms_policy, strict by default;legacyonly for r2-or-earlier); the loader refuses to enable without an explicit entry for the served version.ct_scan_target_summary()is the one canonical summary and now includes limits and period.closed_at >= created_atand a sane clock; retention runs outside the queue lock (own lock, throttled, one short queue lock per deletion) so Stop never queues behind a batch.