| Version | Supported |
|---|---|
latest (main) |
✅ |
The letters never leave the phone except through the share sheet the user opens — Nanti has no
server, no account and never uploads a letter. On the device: a one-row ledger (letters delivered,
forgiven, last city, language) and a settle_log of impression ids and timestamps, in the app's
private SQLite database. Purchases go through RevenueCat and Google Play Billing; ads through
AdMob. The app holds only RevenueCat public SDK keys and AdMob public ids, sourced from the
environment at build time (.env.example).
- The client cannot pay its own tab.
debt = clamp(exported − ADS, 0, 1);ADSis a RevenueCat virtual currency the app only reads.tests/boundary.test.tsproves no client event lowers the debt,src/contains no settled-counter increment, no REST call and no secret, and the only path to thesettledstate is afterpollRewardVerificationreturns avirtual_currencyreward.tests/exhaustive.test.tswalks 70,602 ledger transitions and 299,592 settle-machine paths. - Field input cannot inject into the PDF. Every value is HTML-escaped (
tests/format.test.ts,tests/templates.test.ts). - No secret in the tree or its history. gitleaks (full history) and TruffleHog run on every push;
keys and the upload keystore live in
~/.config/nanti/, never in the repo.
Please do not open a public issue for security vulnerabilities. Instead, report them privately:
- Email edy.cu@live.com, or
- Use GitHub's private vulnerability reporting (Security → Report a vulnerability).
You'll get an acknowledgment within 48 hours and a resolution timeline after triage. Please give us a reasonable window to patch before public disclosure.