fix: unblock fresh installs, sign the trial stamp, one honest guard count (v0.4.6) - #41
Open
FvdHMBAI wants to merge 5 commits into
Open
fix: unblock fresh installs, sign the trial stamp, one honest guard count (v0.4.6)#41FvdHMBAI wants to merge 5 commits into
FvdHMBAI wants to merge 5 commits into
Conversation
added 5 commits
August 17, 2026 00:20
pre-edit.sh loads edit_path_guard.sh and edit_secret_guard.sh as required, fail-closed guards, but CORE_GUARDS never copied them. After a fresh npx guardrail-agent init every Write/Edit was denied with "GUARDRAIL INTEGRITY ERROR: required guard edit_path_guard.sh is missing." Three changes so this class of bug cannot ship again: - CORE_GUARDS covers all 13 guards the dispatchers source; the count check derives from the list instead of a hardcoded 11. - install.sh probes the pre-edit dispatcher before swapping the installation, not just pre-bash. Custom guards are excluded from that probe so a user's own guard cannot fail an upgrade. - installer-adversarial.sh asserts a fresh install allows a harmless edit, still denies a dangerous one, and that every guard a dispatcher sources exists in the installed tree. Verified: reintroducing the old CORE_GUARDS list makes the new test fail.
.trial-started held a bare unix timestamp, so `date +%s > .trial-started` renewed the 14-day Pro trial indefinitely. The disable token in the same product is HMAC-signed; the trial was not. - The stamp is now "<ts> <hmac32>", signed against ~/.guardrail/disable.key with the same construction as the disable token. Unsigned, forged, replayed and foreign-key stamps all resolve to "no trial" instead of a fresh one. - _guardrail_trial_days_left existed twice, in bin/guardrail.sh and in lib/guardrail-license.sh. The CLI now sources the library so the CLI and the dispatchers cannot disagree about whether a trial is valid. - tests/trial-stamp.sh covers the five tamper paths. - tests/pre-edit.sh and tests/new-guards.sh existed but neither npm test nor CI ran them. Wired in, plus the exec bit new-guards.sh was missing. Same trust model as .disabled: this stops the agent and accidental resets, not the human who owns the machine. Suite: 230 assertions, 0 failures.
The README advertised 172 guards, a 96% enforcement rate and 1,048 autonomous tasks. Those are numbers from the private system the patterns came from, not from what a user installs. Five different guard counts were in circulation: 11 (install.sh), 18/19 (README), 20 (npm), 22 (package.json), 172 (README). The shipped product is 13 free guards plus 48 Pro guards, 61 total. That is now the only number, in README, package.json, install.sh, the CLI, the landing page and the launch copy. It is also what `guardrail status` prints, so a reader can check it. The guard table listed nine guards that no dispatcher loads (force_push_guard, deploy_branch_guard, large_diff_guard, credential_leak_guard, wandering_detector, self_correction_loop, tool_call_budget_guard, context_window_guard, uncommitted_code_guard). They ship and have tests but do not run after an install, so they are named separately instead of counted. Version drift resolved: package.json was 0.4.3, bin/guardrail.sh 0.3.3, install.sh printed 0.3.1, npm live is 0.4.5. All four now say 0.4.6.
…dispatcher The table stopped at 10 and never mentioned self_bypass_guard, edit_path_guard or edit_secret_guard, and the dispatcher mapping had no pre-edit row at all. That was the sixth guard count in circulation.
edit_path_guard used \`realpath -m -s\` to normalize . / .. / duplicate
slashes before matching. Those flags are GNU-only. On macOS the call fails,
the guard silently kept the raw path, and the self-bypass protection for the
hook registry could be stepped around with a leading "./":
pre-edit: 27 passed, 2 failed
FAIL: dotslash settings expected=deny actual=allow
FAIL: doubleslash settings expected=deny actual=allow
package.json declares darwin as supported, so this was a fail-open on a
supported platform in the guard that protects .claude/settings.json.
Replaced with _guardrail_canonicalize_path in lib/guardrail-common.sh, written
for bash 3.2 (what macOS ships). Verified against realpath -m -s across 17
paths: every path-structure case is identical. The only divergence is that
realpath also resolves symlinks in the working directory, which is what -s was
meant to prevent, so the shell version is closer to the stated intent.
Surfaced by wiring tests/pre-edit.sh into npm test in the previous commit;
it had never run in CI.
This was referenced Sep 8, 2026
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.
Fixes the three GuardRail items from the pricing audit
(
/opt/obsidian-vault/03-Produkte/2026-08-16_angebots-pricing-audit.md, section 3).Base is
mainbecauseorigin/developwas deleted after PR #40 and featurebranches in this repo PR straight to
main(see #33-#36).1. Fresh installs blocked every file edit (the expensive one)
pre-edit.shloadsedit_path_guard.shandedit_secret_guard.shas required,fail-closed guards.
CORE_GUARDSininstall.shnever copied them. Afternpx guardrail-agent init, every Write/Edit was denied withGUARDRAIL INTEGRITY ERROR: required guard edit_path_guard.sh is missing.Reproduced before the fix, on
main. It hit 100% of new installs, so the wholetop of the funnel.
Three changes so it cannot recur silently:
CORE_GUARDScovers all 13 guards the dispatchers source, and the countcheck derives from the list instead of a hardcoded
-ne 11.install.shnow probes the pre-edit dispatcher before swapping theinstallation, not only pre-bash. The Bash-only probe reported success while
every file write was broken. Custom guards are excluded from the probe so a
user's own guard cannot fail their upgrade.
installer-adversarial.shasserts that a fresh install allows a harmlessedit, still denies a dangerous one, and that every guard any dispatcher
sources exists in the installed tree.
Verified: putting the old
CORE_GUARDSlist back makes the new test fail.2. Trial
The
/api/guards/trialroute the audit reported as missing already exists onthe live server and works. It was added on 15/16 August, after the copy the
audit was written against. Verified against
license.guardrail.promptandbuild.de: the route serves the tarball andrate-limits one download per IP per 24h.
What was still open, and is fixed here on the client side:
.trial-startedheld a bare unix timestamp, sodate +%s > .trial-startedrenewed the 14-day trial forever. The disable token in the same product is
HMAC-signed; the trial was not. The stamp is now
<ts> <hmac32>, signed against~/.guardrail/disable.keywith the same construction as the disable token.Unsigned, forged, replayed and foreign-key stamps all resolve to "no trial"
instead of a fresh one. Same trust model as
.disabled: it stops the agent andaccidental resets, not the machine owner.
_guardrail_trial_days_leftalso existed twice, inbin/guardrail.shand inlib/guardrail-license.sh. The CLI now sources the library, so the CLI and thedispatchers cannot disagree about whether a trial is valid.
3. One honest guard count
The README advertised 172 guards, a 96% enforcement rate and 1,048 autonomous
tasks. Those are numbers from the private system the patterns came from, not
from what a buyer installs. Six different counts were in circulation: 10
(
docs/guard-classification.md), 11 (install.sh), 18/19 (README), 20 (npm),22 (
package.json), 172 (README prose).The shipped product is 13 free guards + 48 Pro guards = 61. That is now the
only number, in README,
package.json,install.sh, the CLI, the landing page,the launch copy and
docs/guard-classification.md. It is also whatguardrail statusprints, so a reader can check it.Note this is 13, not the 11 the audit assumed: the installer fix moves the two
edit guards into the free set.
The README guard table also listed nine guards that no dispatcher loads
(
force_push_guard,deploy_branch_guard,large_diff_guard,credential_leak_guard,wandering_detector,self_correction_loop,tool_call_budget_guard,context_window_guard,uncommitted_code_guard).They ship in the npm tarball and have tests, but they never run after an
install. They are now named separately rather than counted. Decision needed:
wire them into the dispatchers, or drop them.
4. Version drift
package.json0.4.3,bin/guardrail.sh0.3.3,install.shbanner 0.3.1, npmlive 0.4.5. All four now say 0.4.6.
Tests
npm testwent from 147 to 230 assertions, 0 failures.tests/pre-edit.sh(29) andtests/new-guards.sh(43) existed but neithernpm testnor CI ran them. Both are wired in now, plus the exec bitnew-guards.shwas missing. New:tests/trial-stamp.sh(8) covers the tamperpaths on the trial stamp.
End-to-end, against a sandboxed instance of the patched license API:
Not in this PR
npm publish. There is no documented or automated publish flow in the repo
(CI only runs tests). Frederik has to publish 0.4.6 manually after merge.
License API.
guardrail-license-apiis not under version control on eitherserver, so it cannot go through a PR. The container was not redeployed. Patches
are staged inert on the Forma server at
/home/developer/guardrail-license-api/.proposed/with aCHANGES.diff, andcover: trial marking on the response, webhook branching by Stripe
price/product with an
amount_totalcheck instead ofsession.modealone, anda
campaigncolumn missing frominitDb. Cutover steps are in the report.