{"id":50891686,"url":"https://github.com/jonnonz1/deadman-10","last_synced_at":"2026-06-15T21:31:02.166Z","repository":{"id":364172874,"uuid":"1266741283","full_name":"jonnonz1/deadman-10","owner":"jonnonz1","description":"Durable dead-man's switch in a single Go binary — if you stop checking in, an age-encrypted vault is released to your beneficiary. drand timelock + Shamir shares let it fire with no server or operator; optional permanent delivery via Arweave.","archived":false,"fork":false,"pushed_at":"2026-06-12T00:18:04.000Z","size":129,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T01:08:45.743Z","etag":null,"topics":["age-encryption","arweave","cli","dead-mans-switch","digital-legacy","drand","encryption","golang","security","shamir-secret-sharing","timelock"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonnonz1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":"THREAT_MODEL.md","audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T23:03:27.000Z","updated_at":"2026-06-12T00:18:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jonnonz1/deadman-10","commit_stats":null,"previous_names":["jonnonz1/deadman-10"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jonnonz1/deadman-10","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonnonz1%2Fdeadman-10","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonnonz1%2Fdeadman-10/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonnonz1%2Fdeadman-10/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonnonz1%2Fdeadman-10/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonnonz1","download_url":"https://codeload.github.com/jonnonz1/deadman-10/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonnonz1%2Fdeadman-10/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34381758,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["age-encryption","arweave","cli","dead-mans-switch","digital-legacy","drand","encryption","golang","security","shamir-secret-sharing","timelock"],"created_at":"2026-06-15T21:31:01.311Z","updated_at":"2026-06-15T21:31:02.156Z","avatar_url":"https://github.com/jonnonz1.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# deadman-10\n\nA small, durable **dead-man switch**: if you stop checking in, your encrypted\nfile or folder is released to a beneficiary who can open it — and, in durable\nmode, it can release itself with **no server and no operator at all**.\n\nOne static Go binary. The switch only ever holds **ciphertext** it cannot read.\n\n```\n  while you check in ............ nothing happens\n  you go quiet ................... WARN, escalating reminders\n  you stay quiet ................ FIRE: the encrypted vault is released\n  beneficiary (and only them) ... decrypts it\n```\n\n## Why it's unusual\n\nA normal service is kept alive by someone who pays for and fixes it. A dead-man\nswitch is the opposite — **the operator eventually becomes permanently unable to\noperate it**. So the design separates two concerns:\n\n- **Confidentiality** — the vault is encrypted to the beneficiary's key, and the\n  host keeps only *public* keys: the private keys are printed once at setup for\n  you to store offline. So compromising the host leaks **nothing readable** — it\n  holds ciphertext only. (Trade-off: keep the beneficiary key, or your own offline\n  copy of it, if you want to recover the vault yourself while alive.)\n- **Integrity** — the vault is signed at seal time with an offline owner key, so\n  the beneficiary can verify it really is yours and reject a forged substitute.\n- **Liveness** — a check-in timer decides *when* to release. In durable mode the\n  \"timer\" is the global [drand](https://drand.love) beacon, so the release needs\n  no host of yours to be alive.\n\nSee [`HOSTING.md`](HOSTING.md) (where to run it) and [`TIMELOCK.md`](TIMELOCK.md)\n(the cryptography) for the full design.\n\n## Install\n\n```bash\ngo build -o dms ./cmd/dms      # single binary, no runtime deps\n```\n\n## Quickstart (simple mode — host-run)\n\n```bash\ndms init                       # generate owner + beneficiary keys, arm the switch\n                               # add --dev to keep the beneficiary key locally for testing\ndms seal ~/secrets/            # encrypt a file OR folder -\u003e vault.age (ciphertext)\ndms checkin                    # proof of life (resets the timer)\ndms status                     # stage + time until warn/fire\ndms watch                      # one timer tick (run on a schedule)\ndms verify --out ./recovered   # prove recovery works\n```\n\nRun `dms watch` on a schedule (cron / launchd / a cheap always-on host). Set\n`\"notifier\": \"webhook\"` + a Slack webhook URL in `config.json` for delivery with\nno UI present.\n\n### See a full warn→fire cycle in seconds\n\nThresholds are in minutes and overridable for demos — but only with an explicit\n`--demo`, so a stray env var can never silently shorten the fuse in production:\n\n```bash\nDMS_WARN_AFTER_MINUTES=0 DMS_FIRE_AFTER_MINUTES=999999 dms watch --demo   # WARN\nDMS_WARN_AFTER_MINUTES=0 DMS_FIRE_AFTER_MINUTES=0      dms watch --demo   # FIRE\ndms verify --out ./recovered                                # beneficiary opens it\ndms checkin                                                 # re-arm\n```\n\n## Durable mode (no operator) — the interesting part\n\nThis is the construction from [`TIMELOCK.md`](TIMELOCK.md): the vault key is split\n**K-of-N** with Shamir's Secret Sharing, and **exactly one share** is timelock-\nencrypted to a future drand round. The beneficiary holds the other K−1 offline.\n\n```bash\ndms timelock-arm ~/secrets/ --unlock-seconds 31536000   # ~1 year backstop\n#  kept on this host (each is safe alone):\n#    vault.age                   the ciphertext (public-safe)\n#    share-timelocked.age        opens itself at the drand round (MUTABLE storage only)\n#    RECOVERY-CARD.txt           printable instructions for the beneficiary\n#  written to MOVE-OFFBOX-then-delete/ — move off this host NOW, then delete:\n#    share-beneficiary-1.txt     give to the beneficiary, store OFFLINE\n#    share-raw-owner.txt         owner-only; feeds `timelock-rearm`\n\n# while you're alive, push the deadline out on each check-in — the backstop\n# tracks your liveness, and the beneficiary's shares stay valid:\ndms timelock-rearm --raw-share /your/offline/share-raw-owner.txt\n\n# once the unlock round is reached:\ndms timelock-recover --shares-dir \u003ctheir-shares\u003e --out ./recovered\n```\n\nThe host keeps only the timelocked share — never a reconstructable K-of-N set —\nand the raw share lives offline with you, supplied only at re-arm time.\n\n**Why a public timelock doesn't leak your secrets:** when the timelocked share\nbecomes world-readable at the unlock time, it is *one* Shamir share — which\nreveals **nothing** on its own. Only someone who *also* holds the beneficiary's\nK−1 shares can reconstruct the key. (This is proven by the\n`TestTimelockedShareAloneRevealsNothing` test, and the whole arm→recover cycle is\nproven against the **live drand network** in `TestCLITimelockArmRecover`.)\n\nThis means the release can survive your death, the host's death, and the bill\nlapsing — while never broadcasting your secrets to the world.\n\n## The recovery card\n\nThe most common reason a dead-man switch fails in practice is that the\nbeneficiary doesn't know what to do. `dms timelock-arm` writes a\n`RECOVERY-CARD.txt` (and `dms recovery-card` prints one anytime) spelling out what\nthey hold, where the data is, the exact command to run, and the warning that\n**losing their shares makes the vault unrecoverable**. Print it. Test it with the\nactual beneficiary.\n\n## End to end: who holds what, and two worked setups\n\nEvery setup is three parties and a strict custody split. If the host is ever\ncompromised, the attacker gets ciphertext and public keys — nothing readable,\nand nothing that lets them forge a vault in your name.\n\n| party | holds | must never hold |\n|---|---|---|\n| **the host** (Pi / VPS) | `vault.age` + `vault.age.sig`, public keys, timer state, `share-timelocked.age` | any private key; a full K-of-N share set |\n| **you** (owner) | owner signing key, check-in key, `share-raw-owner.txt` — all offline | — |\n| **beneficiary** | printed recovery card + beneficiary key (simple) or their K−1 shares (durable) | — |\n\n### Example 1 — the family handover (simple mode)\n\n**Who:** Alex (owner) and Sam (spouse, beneficiary). A Raspberry Pi or $5 VPS.\n**What:** `~/handover/` — password-manager emergency kit, account inventory, a letter.\n\n```bash\n# 1. on the host, one command sets up everything:\nmkdir ~/switch \u0026\u0026 cd ~/switch\ndms quickstart --owner Alex --path ~/handover/ --beneficiary Sam\n```\n\nTwo private keys print **once and are not kept on the host**: the beneficiary\nkey goes to Sam (printed, stored with the recovery card), the owner signing key\ngoes in Alex's safe. Then wire up the routine:\n\n```bash\n# 2. schedule the watcher — always an absolute --dir, so cron can't\n#    silently run in the wrong directory (it fails loud, by design):\n# crontab: */30 * * * * /usr/local/bin/dms --dir /home/alex/switch watch\n\n# 3. notifications with no UI present — in config.json:\n#      \"notifier\": \"webhook\", \"webhook_url\": \"https://hooks.slack.com/...\"\ndms notify-test\n\n# 4. proof of life: once a week, from Alex's laptop\nssh pi 'dms --dir /home/alex/switch checkin'\n```\n\nUpdating the vault later means re-sealing with the offline signing key —\n`dms seal ~/handover/ --owner-sign-key \u003cfile\u003e` — it never lives on the host.\n\n**What firing looks like:** Alex goes quiet → day 7 the webhook starts nagging\n(plenty of time to abort with a single `checkin`) → day 21 the vault is released\nto the outbox and the webhook announces where. Sam follows the card: `dms verify`\nchecks the seal-time signature against Alex's public signing key, then decrypts\nwith Sam's own key. Run one `--demo` fire-drill with Sam *before* you trust it.\n\n### Example 2 — the no-operator estate backstop (durable mode)\n\n**Who:** you and a sibling. No infrastructure that must outlive you.\n**What:** `~/estate/` — will, deeds, final instructions.\n\n```bash\ndms timelock-arm ~/estate/ --unlock-seconds 15552000    # ~6 months\n```\n\nThen distribute, which is the whole job:\n\n- `share-beneficiary-1.txt` → print two copies, sibling keeps them in two places\n- `share-raw-owner.txt` → your offline USB, then **delete `MOVE-OFFBOX-then-delete/`**\n- `RECOVERY-CARD.txt` → printed, stored with the will\n- `vault.age` → anywhere durable, it's public-safe (cloud drive, even Arweave)\n- `share-timelocked.age` → durable but **mutable** storage the sibling can reach\n  (a shared cloud folder — never Arweave, because re-arming must replace it)\n\nWhile you're alive, push the deadline out (e.g. monthly, from your own machine —\nthe raw share never touches a host; without `--unlock-seconds` the deadline\nresets to the ~1-year default):\n\n```bash\ndms timelock-rearm --raw-share /Volumes/usb/share-raw-owner.txt --unlock-seconds 15552000\n```\n\nIf you stop, the drand round arrives and the timelocked share simply becomes\nreadable — no server of yours involved. The sibling follows the card:\n\n```bash\ndms timelock-recover --shares-dir ~/from-the-envelope --out ./recovered\n```\n\n### Use both legs\n\nThe pulse (warn in days, fire in weeks) reacts fast while the host lives; the\ntimelock (months) survives the host, the operator, and the unpaid bill. Arm both\nover the same folder and make re-arming part of your check-in habit. Two\ntimescales, each covering the other's failure mode — see\n[`HOSTING.md`](HOSTING.md) for host options and check-in patterns.\n\n## Commands\n\n```\nquickstart [--owner N --path P --beneficiary B --yes]  guided one-step setup\ninit [--dev] [--force] [--auth-checkin]   generate keys, arm the switch\nseal \u003cpath\u003e [--owner-sign-key F]  encrypt + sign a file or folder into the vault\ncheckin [--checkin-key F]     record proof of life (signed if --auth-checkin)\nstatus [--json]               stage and deadlines\nwatch [--json]                one timer tick: warn or fire if due\nfire --force                  manually trigger the fire path (testing)\nverify [--id PATH] [--out D]  decrypt the vault to prove recovery\ntimelock-arm \u003cpath\u003e [--unlock-seconds N] [--shares-out D]  durable arming\ntimelock-rearm --raw-share F [--unlock-seconds N]   push the deadline forward\ntimelock-recover [--shares-dir D] [--out DIR]       durable recovery\nrecovery-card                 print beneficiary instructions\nnotify-test                   send a test notification\n```\n\nGlobal flags: `--dir \u003chome\u003e` (or `$DMS_HOME`) selects the switch directory —\ngive schedulers an absolute path; `--demo` is the only way the\n`DMS_*_MINUTES` compressed-timer overrides take effect.\n\n## Architecture\n\n```\ncmd/dms/                 the CLI\ninternal/vault           age encrypt/decrypt of a file or folder (tar+gzip)\ninternal/shamir          self-contained GF(256) K-of-N secret sharing\ninternal/timelock        drand timelock wrapper (seal/open one share)\ninternal/custody         the share-timelock construction (Arm/Recover)\ninternal/engine          the check-in timer (stage, warn, fire-once, re-arm)\ninternal/release         Publisher + Notifier interfaces; file/dry-run/webhook\ninternal/keys            age keypair generation\ninternal/config          settings (thresholds, custody, storage, notifier)\ninternal/recoverycard    the beneficiary recovery instructions\n```\n\nClean interfaces throughout: swapping the host, the storage backend, or the\nnotifier never touches the cryptography.\n\n## Status \u0026 limitations\n\n- ✅ Simple mode and durable (share-timelock) mode both run, end-to-end, with the\n  durable path verified against the live drand beacon.\n- ✅ **Arweave** permanent storage is wired (a stdlib-only uploader, verified\n  against an arlocal devnet). `dry_run=true` stays the default; a real upload\n  requires `dry_run=false` *and* a funded JWK wallet, and refuses otherwise.\n  Publish only the vault ciphertext — never the timelocked share (see\n  [`TIMELOCK.md`](TIMELOCK.md) §9 on permanence trade-offs).\n- ⚠️ **Test your setup.** A dead-man switch you never fire-drill is Schrödinger's\n  backup. Run `dms fire --force` / `timelock-recover` and confirm the beneficiary\n  path actually works.\n- ⚠️ **Firing is irreversible disclosure.** The WARN window is your last chance to\n  stop it; tune the thresholds in weeks.\n- ⚠️ **Quantum:** both age (X25519) and drand (BLS) are classically secure but\n  quantum-breakable on a multi-decade horizon. Keep the inner vault re-encryptable.\n\n## Testing\n\n```bash\ngo test ./...                                   # fast unit + CLI suite\ngo test -tags=integration ./internal/timelock/  # live drand round-trip\nDMS_INTEGRATION=1 go test ./cmd/dms/            # live arm→recover through the binary\n```\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonnonz1%2Fdeadman-10","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonnonz1%2Fdeadman-10","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonnonz1%2Fdeadman-10/lists"}