feat(license): lease-model client — offline-verifiable short tokens w/ auto-refresh - #361
Open
runyourempire wants to merge 1 commit into
Open
feat(license): lease-model client — offline-verifiable short tokens w/ auto-refresh#361runyourempire wants to merge 1 commit into
runyourempire wants to merge 1 commit into
Conversation
…hort tokens Consumes the deployed lease server (/api/license/refresh). The user activates once with a stable `4DA-LIC-...` refresh credential; the app exchanges it for a short-lived, OFFLINE-verifiable `4DA-...` entitlement token and re-runs the exchange on startup + every 6h, so revocation (a cancel/refund reflected LIVE in Stripe) reaches online users within the interval while offline users keep working until the token's embedded expiry. - settings/license/lease.rs (new): is_refresh_credential, refresh_entitlement, maybe_refresh_lease, RefreshOutcome. Pure classify_response() is unit-tested. INVARIANT: network/5xx/malformed NEVER downgrade a paying user (KeepCurrent); only an authenticated "not entitled" revokes. Durable credential stored in the keychain (survives a settings.json license-block wipe) with settings rehydration. - settings/types.rs: LicenseConfig gains `refresh_key: Option<String>` (#[serde(default)], zeroized on drop, redacted in Debug). All existing struct literals updated. - settings_commands_license.rs: activate_license matches `4DA-LIC-` BEFORE the `4DA-` signed-token branch; stores credential + first token, slides activated_at. - app_setup.rs: spawns the 6-hourly refresh task after startup validation. - Verification path (verify.rs), reconcile, gating: UNCHANGED — the token is the same signed 4DA- format they already handle. Legacy 4DA-<sig> + Keygen keys still activate. Additive, no migration. Verified: cargo test settings::license 27/27 pass (6 new lease tests incl. "5xx never revokes"; all reconcile/gating/serde-roundtrip tests still green); cargo fmt clean; clippy introduces zero new warnings in changed files. GATE: ships behind a 7-day founder dogfood on a RELEASE build (doctrine) before any customer. Delivery switch (success page/recovery returning the 4DA-LIC credential instead of the legacy long token) is a follow-up, sequenced AFTER this client is in users' hands. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01PNQqR59PomAowHWXJoAzha
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
runyourempire
added a commit
that referenced
this pull request
Aug 28, 2026
Findings from a repo-wide audit run today across six domains
(release/distribution, security/privacy, scoring/ACE, frontend, data
layer, CI/repo hygiene). This PR carries the subset that is small,
verifiable, and closes something that is actually false today. The
larger items are listed at the bottom rather than silently dropped.
The through-line: **the code is careful, and several promises about the
code had drifted away from it.** Every fix here ships with a test or a
gate, because in each case the defect survived precisely because nothing
tested the claim.
---
## Privacy — two things NETWORK.md promised that were not true
**Git commit messages were being sent to a cloud LLM.**
`build_rerank_context_summary` put the five most recent commit messages
into `JudgeRequest.context_summary`, which goes verbatim to whichever
provider the user has configured. `RerankConfig::enabled` defaults to
`true`, so one saved API key switched it on for every analysis cycle,
and the `titles_only` privacy setting did not cover it (that applies to
article content). NETWORK.md said: *"Project files, source code, file
contents, and git history never leave your machine."*
Removed rather than truncated further — a commit subject is prose the
developer wrote, and no length makes it not-git-history. The signal it
carried is already supplied by dependencies, git-derived work *topics*,
and declared interests: nouns, not the developer's own sentences.
New gate `scripts/check-privacy-egress.cjs` confines raw-content columns
to the modules that mine and store them, with six tests covering the
exact regression, a module nobody has thought of yet, and the escape
hatch in both directions. Wired into `validate` and CI repo-guards. The
real defect was that nothing tested the promise.
**The disclosure list was wrong.** NETWORK.md said the only
locally-derived data sent anywhere was OSV package names. In fact npm,
PyPI, crates.io, `proxy.golang.org` and the GitHub advisory API all
receive your package names, GitHub search receives your languages,
Reddit your subreddits, Stack Overflow your tags. Replaced with an
exhaustive table, and the sharpest edge is called out explicitly: a
private Go module path such as `github.com/yourcompany/internal-service`
discloses your organisation and repository name to Google's module
proxy.
## Security
**Any non-empty string in `settings.json` granted Signal, permanently.**
`key_is_usable` returned `true` for any key not starting with `4DA-`,
and it is the FAST PATH — so it short-circuited before the validation
cache it claimed to rely on was ever consulted.
`{"tier":"signal","license_key":"x"}` was a permanent paid tier: no
signature, no network call, no expiry. The comment directly above it
asserted that this hole was closed; the fix had only ever covered the
`4DA-` branch, and there was no test.
Now a Keygen-format key is usable only if the validation cache vouches
for *that* key — matching SHA-256 hash, paid tier, inside the freshness
window — via the `is_cache_valid` helper that already existed. The same
hole in the backup-file recovery layer ("Keygen format — trusted") is
closed the same way. Six assertions including a positive control,
because a fix that always returned `false` would strand every real
customer.
**The SSRF guard was switched off by an attacker-controllable string.**
Three sites read `if provider != "ollama" { validate_not_internal(url)
}`. `provider` is a value the frontend sets over IPC, so `provider:
"ollama"` disabled validation for *any* `base_url`, cloud metadata
endpoints included. Replaced with a host-based `validate_llm_endpoint`,
which fixes the INV-032 false negative in the same change: a local LM
Studio, llama.cpp or Jan server is exactly as safe as Ollama and was
blocked purely for not being called "ollama". Private LAN and metadata
addresses stay blocked — loopback is the user's own machine,
`169.254.169.254` is not.
**The secret scanner missed every key format the app accepts.**
`/sk-[a-zA-Z0-9]{32,}/` stops at the first hyphen, so `sk-ant-api03-...`
and `sk-proj-...` both sailed through while the audit reported "0
findings" across 1,958 files. Patterns moved to
`scripts/lib/secret-patterns.cjs` (mirroring `pii-hashes.cjs`),
broadened, and given a placeholder filter keyed on the **matched text**
rather than the file path — a path exemption would blanket-excuse a real
key dropped into a test file, which is one of the likelier ways a key
leaks. Ten tests, asserting both directions. Broadening it immediately
surfaced five live matches, all fixtures, which is how the filter came
to exist.
Also: `data/*.tmp` is now gitignored. The final `.json` and `.bak` were;
the `.tmp` they are written through was not, and `settings.json.tmp`
holds the API key and licence key in plaintext for the width of a
rename.
## Release — two config lines that make a release possible at all
`bundle.createUpdaterArtifacts` was **unset**, and it defaults to
`false`. Tauri therefore emitted no updater signature, `latest.json` was
never produced, and `release.yml` hard-requires it before publishing —
so the release stays a draft. The existing gate checked that the
workflow *asked* for `latest.json` and never that the bundler was
configured to *produce* it: text, not meaning.
`desktop-template.desktop` still registered `x-scheme-handler/4da`, four
months after #491 renamed the scheme to `fourda`, because a custom
`desktopTemplate` opts out of Tauri's generated entry and nothing in the
repo referenced the file. Also `StartupWMClass=4da` against a `fourda`
binary, and `{{exec_dir}}/{{exec_name}}` in the desktop action —
variables Tauri's template schema does not define, so they rendered
empty. `Exec=` now passes `%U`, without which a deep link arrives with
no URL at all.
`check-release-channel.cjs` now covers all of it, with four negative
tests.
**Download-page copy.** The live page advertises auto-updates the build
cannot perform, and explains the SmartScreen prompt as *"a new app still
building reputation — not a warning about the binary."* The shipped
installer is unsigned; that is a materially different prompt. Corrected
to say so plainly and to point at hash verification.
## Frontend
**Snooze was broken on every card, and told the user so in English.**
The frontend sends `'snooze'` from two call sites; `feedbackTypeMap` was
typed `Record<string, string>` and had no `snooze` key, so the lookup
yielded `undefined`, a `!` silenced it, the field was dropped from the
IPC payload, and both backend commands rejected — one on an unknown
action type, one on a missing required arg. The store then surfaced a
hardcoded-English "Feedback not fully saved" toast to all 13 locales.
That is issue #487, still live and louder than when it was filed.
Fixed end to end: a real `BehaviorAction::Snooze` (strength −0.4, half
of Dismiss, mirroring the frontend's own −0.05 vs −0.10 grading —
collapsing it into Dismiss would teach ACE that a deferral is a
rejection), arms in both commands, and the map retyped to
`Record<FeedbackAction, string>` so omitting a union member is a compile
error rather than a runtime drop. Toast routed through i18n.
**The crash screen's recovery button was invisible in light theme** —
`#fff` on `var(--color-border)`, which is `#DDDAD2` in light: about
1.3:1. Tokenized, along with the wrapper.
## Housekeeping
Four i18n scripts plus the CI validator iterated a `coach` namespace
that no locale directory has ever contained, which is why `i18n:status`
printed `coach: undefined`. Removed; the real set is ui / errors /
signals.
---
## Verification
- `cargo test --lib` — 4654 passed, 0 failed
- `pnpm run test` — full frontend suite green
- `pnpm run typecheck` — 0 errors
- `pnpm run audit:public-ready` — no findings, re-run after broadening
the patterns
- `node --test` on the three touched gate suites — 23 tests green,
including 11 new negative tests
- `cargo fmt --check`, `cargo clippy --lib` — clean
## Deliberately NOT in this PR
Real findings from the same audit that need their own measured change,
recorded here so they are not lost:
- **Windows code signing is dead** — SSL.com eSigner rejects the
credentials; the release workflow has 2 successes in 25 runs and last
ran 68 days ago. Account work plus a dry-run on a throwaway tag.
- **The download page still serves v1.0.0 from 20 April**, roughly 1,800
commits behind, and its release notes carry retired AD-030 claims.
- **Two CWD-dependent bugs** (`ace_context.rs:157`,
`monitoring_briefing.rs:894`) silently mis-weight an installed build;
the scheduled refresh inherits `C:\Windows\System32` as its working
directory.
- **`dependency_edges` is an unbounded append log** — 613,674 rows for
12,178 distinct tuples, 145 MB, +9 MB/day, and its only reader is a
test. Needs schema 114.
- **`office.rs` runs two third-party parsers on hostile bytes with no
`catch_unwind`**, where `pdf.rs` has one; and no HTTP adapter caps
response body size.
- **Entitlement revocation is unconsumed** — refresh credentials are
minted per customer and nothing reads them (PR #361).
- `privacy.llm_content_level` is documented in NETWORK.md but has no UI
control and is honoured by 1 of 9 LLM egress sites.
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.
Consumes the deployed lease server (
/api/license/refresh, PR #357) to deliver the at-scale licensing architecture: offline-verifiable short-lived entitlement tokens, auto-refreshed, with revocation driven live by Stripe.How it works
4DA-LIC-…refresh credential.4DA-…entitlement token (verified offline by the existingverify.rs— unchanged) and re-runs the exchange on startup + every 6h.Safety invariant (unit-tested)
Network error / HTTP 5xx / malformed response never downgrade a paying user (
KeepCurrent). Only an authenticated "not entitled" revokes. Durable credential stored in the keychain (survives asettings.jsonlicense-block wipe — the recurring "Signal→Free" bug class) with settings rehydration.Changes
settings/license/lease.rs(new) — refresh client +classify_response(unit-tested).settings/types.rs—LicenseConfig.refresh_key: Option<String>(serde default, zeroized, redacted).settings_commands_license.rs—activate_licensematches4DA-LIC-before the4DA-branch.app_setup.rs— 6-hourly refresh task.verify.rs/ reconcile / gating: untouched. Legacy4DA-<sig>+ Keygen keys still work. Additive, no migration.Verification
cargo test settings::license→ 27/27 pass (6 new lease tests incl. "5xx never revokes"; all reconcile/gating/serde-roundtrip tests still green).cargo fmtclean;clippyadds zero new warnings in changed files.Per intelligence doctrine, this ships behind a 7-day founder dogfood on a RELEASE build before any customer. The delivery switch (success page / recovery returning the
4DA-LICcredential instead of the legacy long token) is a deliberate follow-up, sequenced after this client is in users' hands so no buyer ever receives a credential an older app can't use.🤖 Generated with Claude Code