Skip to main content

1. What are we monitoring?

LumenWipe closes Stellar accounts non-custodially. It deploys no contracts of its own, holds no user funds, and has no admin key over anyone’s assets. The private key that authorizes a close never leaves the user’s browser. That shapes this plan: most of what the threat model covers is client-side or API-side and leaves no on-chain trace, so the on-chain surface is small and specific, and the rest is listed in §3 as off-chain monitoring rather than padded into on-chain rules that could never fire. What LumenWipe does have on the ledger is two service accounts that hold a signing key, and the third-party protocol contracts it reads and builds exits against. The registry carries a validUntil date and every entry’s wasmHash. Keeping that file current is the single most important piece of inventory hygiene here - a stale entry is how a monitor, or an exit, quietly starts working against the wrong code.

2. What could go wrong?

Carried over from the threat model, which runs one STRIDE pass per surface. That document identifies threats by surface and category rather than by number, so each threat below is given a stable id of the form S<surface>.<Category>.<n>.

3. What does exploitation look like on-chain?

Threats with no on-chain effect, monitored off-chain instead. The threat model’s surfaces 1, 2, 3, 5 and 6
  • client-side key handling, the session state machine, API transaction construction, allowance revocation and Soroban conversion - either never touch the ledger or are indistinguishable on-chain from a legitimate user-authorized action, because that is exactly what they are: the user signs in their own browser with their own key. A compromised frontend serving hostile bytes is the clearest example; the control against it is verifyCloseTransaction, the client-side trust anchor, not a ledger watcher. These are covered by application logging, the adversarial test suite and the integration suite in CI, and are recorded here so the gap is deliberate rather than overlooked.

4. What will we monitor for?

Two of these are already enforced in code rather than only watched. S7.Tampering.1.M.1 is the monitoring counterpart of a control that exists: an unknown code version blocks the exit before any contract state is read, and mainnet-registry.integration.test.ts runs that comparison. S4a.Elevation.1.M.1 monitors a risk whose primary control is operational - the mediator holds no spendable surplus - so the alert exists to catch the funding policy being broken, not to be the only thing standing in the way.

How the live ones run

.github/workflows/onchain-monitors.yml runs both live monitors at 06:00 UTC daily, and on demand from the Actions tab. Both are read-only against public endpoints: no account, no transaction, no funds, no secret. S7.Tampering.1.M.1 reads each verifiedLive mainnet entry’s executable hash over RPC and compares it to the registry. It also runs on every pull request as part of the integration suite, but the schedule is what makes it a monitor: a contract can be upgraded during a week when nobody opens a pull request. S4a.Information.1.M.1 and S4b.Information.1.M.1 query Horizon for the last 200 transactions touching each service account and count the ones it sourced. Measured on the testnet sponsor on 2026-09-18: 8 transactions touching the account, 0 sourced from it. The mediator: 0 as well. The threshold is that measurement, not a guess. A failure opens a GitHub issue, assigned rather than merely labelled, and the next clean run closes it. That is the same delivery path the nightly end-to-end suite uses. It suits a registry mismatch, where exits already fail closed and the response is to re-verify at a desk. It does not suit a suspected key compromise, which needs someone woken up. Setting up that channel is the next thing this plan needs, and until it exists the critical rows in section 5 describe a response with no paging behind it.

5. What happens when an alert fires?

Status is honest, not aspirational. Three monitors are Active and run daily: the registry hash comparison and the two service-key checks. Four are Planned, and each is waiting on something specific rather than on effort. S4a.Spoofing.1.M.1 and S4b.Elevation.1.M.1 have to decode a transaction’s operations to judge its shape, which is more than a scheduled query does. S4b.Denial.1.M.1 needs a rate baseline that does not exist until there is real usage to measure. S4a.Elevation.1.M.1 watches a balance floor that only means something once the mainnet mediator carries one. The three live ones watch testnet accounts, because those are the only service accounts that exist. Adding the mainnet mediator and sponsor is one line each in the workflow once they are deployed.

6. Did we do a good job?

  • Does every threat have a monitor, or a recorded reason it cannot be watched on-chain? Yes. The two signing-key surfaces and the exit-adapter surface have monitors; surfaces 1, 2, 3, 5 and 6 are recorded in §3 as having no distinguishable on-chain effect, with the controls that do cover them named.
  • Is every threshold grounded in a baseline? Five of seven are grounded in a baseline of zero, which is exact: these are events that never occur in normal operation. S4b.Denial.1.M.1’s rate threshold is explicitly deferred until there is 30 days of real usage to set it from, rather than guessed now.
  • Does every monitor have a response, an owner and a status? Yes, in §5.
  • Have any monitors fired? No. The registry comparison has not reported drift. The two service-key checks report zero sourced transactions for both accounts. Each was also run against an account that does source transactions, to confirm it fails when it should rather than passing because it never looks.
  • Are the addresses current? Verified 2026-09-18: both testnet accounts resolve on Horizon, and the sponsor is confirmed as fee_account on the linked transaction. The mainnet rows are empty because those accounts do not exist yet.
  • Any threats that only surface off-chain? Yes, and they are the majority - see §3. That is a consequence of the non-custodial design rather than a gap in this plan: there is no pooled balance to watch, because there is no pool.

7. Maintenance

Revisit whenever the contracts, the addresses or the threat model change, and at every release milestone. The concrete triggers: a new entry or a bumped validUntil in contract-registry.json, a mediator or sponsor key rotation, a new signing key of any kind, and the deployment of the mainnet mediator and sponsor accounts, which is when the live checks gain their mainnet addresses and the remaining Planned rows become meaningful.