feat: tripwire challenge/log, UTC timestamps, and a safe release script - #68
Merged
Conversation
…at can't repeat my mistakes (#53 #55) #53 — tripwire_response accepts challenge and log. block_action has always accepted both; the deterministic path, the one backed by proof rather than a score, had only the harsher options. `log` records and returns — note this is NOT the tripwire_dry_run setting, which collapses to ALLOW inside the rule (TripwireRule.php:139) before RuleEngine can record it, so dry run is silent where this is observable. `challenge` serves the existing proof-of-work interstitial. Documented rather than papered over: the challenge binds startChallenge to a click and needs JavaScript, so no automated client can ever complete it. On a tripwire that is the intent — the path exists nowhere and is reachable only from a hidden element or a robots disallow — but it is still a denial for any non-JS caller, which is why block stays the default. Deliberately did NOT gate it on GoodBotList::verifyBotIP() or isGoodBot(): both return true on User-Agent alone for bots they have no verification rule for, so either would be a trivially spoofable bypass. #55 — all eight created_at writers now store UTC, and the one display site converts with get_date_from_gmt(). They were current_time('mysql') (site-local) while readers built bounds with gmdate() (UTC), so on any non-UTC site the Today/7d/30d filters covered the wrong span and the checkout velocity window was the wrong width — narrower west of UTC, wider east. Existing rows are NOT migrated: shifting by the current offset is wrong for any site that has changed timezone, and a bad shift is silent and unrecoverable, whereas the mixed-epoch skew is bounded and ages out (one hour for checkout attempts, the reporting window for detections). The reasoning is recorded at the canonical writer. Release tooling. bin/deploy-wporg.sh now refuses to continue if trunk carries anything WordPress.org forbids, and asserts the staged version is the one requested. This is not hypothetical: syncing from a build/ tree left behind by release.sh stages includes/class-webdecoy-updater.php — a self-updater fetching from our own CDN, an outright guideline violation — and rsync had already carried a local .claude directory into trunk. Both were caught by eye before commit; now they are caught by the script. New bin/release-all.sh does both channels in the only safe order, because the two build variants overwrite each other and the ZIP is not byte-reproducible (mtimes land in the archive). update-info.json must therefore ship from the same pass that produced the zip it names, or the self-hosted updater rejects the download for every Pro install on a hash mismatch. CDN first (build, verify hash, upload zip before manifest, verify what the CDN serves), then wp.org, which rebuilds --org itself. Preflight refuses a dirty tree, a non-main branch, a branch out of sync with origin, or a version not declared consistently across webdecoy.php, readme.txt and changelog.txt. 80/80 tests, phpcs baseline unchanged, phpstan unchanged.
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.
Closes #53, closes #55.
#53 — tripwires can Challenge or Log
block_actionhas always acceptedchallengeandlog;tripwire_response— the path backed by proof rather than a score — had only the harsher options. Both added, with the UI copy.logrecords and returns. Worth knowing it is not the same astripwire_dry_run, which collapses to ALLOW inside the rule (TripwireRule.php:139) beforeRuleEnginecan record it — dry run is silent, this is observable.challengeserves the existing PoW interstitial. Documented rather than papered over: it binds to a click and needs JS, so nothing automated can complete it. On a tripwire that is the intent, but it is still a denial for any non-JS caller, which is whyblockstays default.Deliberately not gated on
GoodBotList::verifyBotIP()orisGoodBot()— the earlier review found both returntrueon User-Agent alone for bots they have no verification rule for, so either would be a spoofable bypass.#55 — UTC storage
All eight
created_atwriters store UTC; the one display site converts viaget_date_from_gmt(). They were site-local while readers built bounds withgmdate(), so on any non-UTC site the Today/7d/30d filters covered the wrong span and the checkout window was the wrong width.Existing rows are not migrated. Shifting by the current offset is wrong for any site that has ever changed timezone, and a bad shift is silent and unrecoverable — whereas the mixed-epoch skew is bounded and ages out (1h for checkout attempts, the reporting window for detections). Reasoning recorded at the canonical writer.
Release tooling — the actual point of this PR
bin/deploy-wporg.shnow refuses to commit if trunk carries anything wp.org forbids, and asserts the staged version matches. Not hypothetical: syncing from abuild/tree left byrelease.shstagesincludes/class-webdecoy-updater.php(a self-updater pulling from our CDN — grounds for removal from the directory), and rsync had already carried a local.claudedirectory in. Both were caught by eye; now the script catches them.New
bin/release-all.shdoes both channels in the only safe order. The two variants overwrite each other's output and the ZIP is not byte-reproducible — mtimes land in the archive, so a rebuild changes the sha256.update-info.jsonmust ship from the same pass that produced the zip it names or the self-hosted updater rejects the download on a hash mismatch. CDN first (verify hash → upload zip → upload manifest → verify what the CDN serves), then wp.org. Preflight refuses a dirty tree, a non-main branch, drift from origin, or a version not declared consistently in all three files.80/80 tests, phpcs baseline unchanged (14, all pre-existing in
tests/bootstrap.php), phpstan unchanged.