Skip to main content

time-safe — Encrypt Today. Unlock Tomorrow.

Lock a secret until a future moment — so that nobody, not even you, can read it early.

Python 3.12+ Textual drand/tlock CI Docs

time-safe secrets list

time-safe timelock-encrypts your secrets, stores only the ciphertext in a GitHub repo, and lets you reveal them only after a date you choose. The lock is enforced by cryptography — the drand distributed randomness beacon via tlock — not by an honour system.

No decryption key is ever stored, anywhere. Until the unlock time arrives, the key literally does not exist — so neither you, nor GitHub, nor anyone who copies the repo can decrypt early.

A keyboard-driven terminal app built with Textual. (Python rewrite of the original Java version.)


✨ Why it's different

  • 🔒 Cryptographic, not honour-system. Each secret is encrypted to a future drand round; the threshold-BLS signature that decrypts it isn't produced by the beacon network until that wall-clock time.
  • 🗝️ No keys, anywhere. The repo holds only ciphertext (vault/secrets/<id>.tle). Nothing to find, leak, or be tempted by.
  • 👁️ Reveal locally. Once unlocked, the app fetches the ciphertext + the now-public drand signature and decrypts in memory — the plaintext never touches disk.
  • 📧 Optional email delivery. A per-secret GitHub Actions workflow decrypts at the unlock time and emails you the plaintext via Gmail.
  • 🧰 Almost nothing local. Only a list of vaults lives on disk; GitHub tokens go in your OS keychain.

📸 Screens

Vaults Secret (ready) Add secret

🚀 Quick start

Requirements: Python 3.12+ and uv; the drand tle binary; a private GitHub repo + a token with contents + secrets + workflows write access.

# get the tlock CLI (pick your OS/arch from the releases page)
mkdir -p .tools
curl -sL https://github.com/drand/tlock/releases/download/v1.2.0/tlock_1.2.0_darwin_arm64.tar.gz | tar -xz -C .tools tle

# run it
uv sync
uv run timesafe

In the app:

Key Does
n / c initialize a new vault / connect to an existing one
a add a secret — name, a duration (30m, 2h, 7d, 1d12h), optional delivery email, text
d reveal a ready secret locally
s email a ready secret's plaintext to its address
r renew — re-lock a ready secret for a new duration
x delete · g re-link Gmail · q quit

🤖 Scripting it (non-interactive CLI)

Alongside the TUI there's a CLI for other programs to drive — no TTY, no prompts, works from cron or a systemd unit. Bare timesafe still opens the TUI.

uv tool install timesafe        # wheels bundle the `tle` binary — nothing else to fetch

# ...or, on a host with no usable modern Python (no pip, no venv, no container):
curl -sL https://github.com/louisalexander/time-safe/releases/latest/download/timesafe-linux-amd64 \
  -o ~/.local/bin/timesafe && chmod +x ~/.local/bin/timesafe

Point it at a vault with two environment variables, then:

export TIMESAFE_VAULT=me/my-vault TIMESAFE_GITHUB_TOKEN=ghp_...

# Generate a secret and lock it — never on screen, never in argv, never in shell history.
openssl rand -base64 32 | timesafe add --name break-glass --duration 10d --secret-stdin
# {"id":"3f2a91c4-...","name":"break-glass","unlock_at":"2026-08-17T12:00:00+00:00",
#  "round":19273645,"vault_path":"vault/secrets/3f2a91c4-....tle","pushed":true}

timesafe status --id "$ID" --json      # {"ready":false,"seconds_remaining":863995,...}
timesafe reveal --id "$ID"             # plaintext only, no trailing newline; exit 3 if not yet
timesafe list --json
timesafe init --vault me/new-vault --name seedbox --create   # idempotent; always private
Exit Meaning
0 ok
2 usage error
3 not yet unlockable — distinct from a failure, so callers can poll
4 vault / network error
5 no such secret

Errors are JSON on stderr with a stable code field. The same logic is importable — timesafe.api.add / status / reveal / list_secrets / init — if you'd rather skip the subprocess.

Persist the id that add returns. It's the vault's authoritative key. Secret names are not unique and nothing enforces it, so never construct an id from a name; --name lookup exists as a convenience and fails loudly if it's ambiguous.

📖 Full reference, including cron/systemd examples: CLI & automation

📧 Gmail delivery (one-time, optional)

Email uses your own Google Cloud OAuth client:

  1. Google Cloud Console → enable the Gmail API.
  2. OAuth consent screen: External, scope https://www.googleapis.com/auth/gmail.send; add your address as a Test user or Publish (publishing avoids the 7-day refresh-token expiry).
  3. Credentials → OAuth client ID → "Desktop app" (not "TVs and Limited Input devices" — Google's device flow rejects gmail.send; time-safe uses the loopback flow). Note the client id + secret.
  4. In time-safe press g, enter the Gmail address + client id/secret → Link → approve in the browser.

The refresh token + client secret are stored only as GitHub Actions secrets in the vault repo — never locally.

🛡️ Security model

  • Confidentiality before the unlock time is cryptographic (drand threshold) — the repo can be seen without leaking anything early.
  • After unlock, anyone with repo read access can decrypt — so keep the vault repo private.
  • Trust assumptions: the drand threshold isn't compromised before the unlock time, and drand mainnet stays live (a robust multi-org network; the chain hash is pinned per secret).
  • A real time-lock means the unlock time is immutable — there's no "extend." To re-time a secret, reveal it after unlock and add it again (or Renew a ready one).

📖 Full docs: https://louisalexander.github.io/time-safe/ — getting started · architecture · usage · security

🧑‍💻 Development

uv run pytest                                                  # unit + Textual Pilot tests
TIMESAFE_LIVE=1 uv run pytest tests/test_tle.py -k roundtrip   # live drand roundtrip
uv run python scripts/make_screenshots.py                      # regenerate docs/screenshots


Encrypt Today. Unlock Tomorrow.

Metadata

Release files for timesafe 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for timesafe 0.1.0
File Size Uploaded
timesafe-0.1.0.tar.gz 1.5 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for timesafe 0.1.0
File
timesafe-0.1.0-py3-none-manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
timesafe-0.1.0-py3-none-manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
timesafe-0.1.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
timesafe-0.1.0-py3-none-macosx_10_9_x86_64.whl Python 3 none macOS 10.9+ x86-64 Details

Total release size: 20.2 MB

Release files / timesafe-0.1.0.tar.gz

Download URL timesafe-0.1.0.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
1cd880151f17f4c96ef44159c81a90c21c1f5733decb113271fd54a26fbbb966
BLAKE2b-256 checksum
How to use checksums
330b0efa1fd805df057a6efc7dc02fd71d2826a9a38dae7f160fff3427bf882a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / timesafe-0.1.0-py3-none-manylinux2014_x86_64.whl

Download URL timesafe-0.1.0-py3-none-manylinux2014_x86_64.whl
Size 4.8 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
f2c1294c97b02a9879ddc53280bd3b8d3d6193455d085493e8796890833af520
BLAKE2b-256 checksum
How to use checksums
6d7862c3e50a6d9bf5672075ab395a4f36c8cb84c517aeba08c1849d7324cf7d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / timesafe-0.1.0-py3-none-manylinux2014_aarch64.whl

Download URL timesafe-0.1.0-py3-none-manylinux2014_aarch64.whl
Size 4.4 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
c473e91846e6a49ef305079c53e7640a45884ef011eedf8c29e9497d3cc2835e
BLAKE2b-256 checksum
How to use checksums
a51d5256bad2cef4e77b29809e590f48ca34fb13fc69d7ce074fd761af8a4ecd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / timesafe-0.1.0-py3-none-macosx_11_0_arm64.whl

Download URL timesafe-0.1.0-py3-none-macosx_11_0_arm64.whl
Size 4.6 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
795c436581d20e60c46babd6d9bd8a35e959b53ee3fabbff7ed762f86abeff43
BLAKE2b-256 checksum
How to use checksums
dbbc25e6456f07d3e3a7feebf99b7b180161d9452bd18c640f7779836b95a42a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / timesafe-0.1.0-py3-none-macosx_10_9_x86_64.whl

Download URL timesafe-0.1.0-py3-none-macosx_10_9_x86_64.whl
Size 4.9 MB
Tags Python 3 macOS 10.9+ x86-64
SHA-256 checksum
How to use checksums
64063ce6f98b94156f06ef4d4c84e0a564776622cddd1eac02942e5f1c3538bc
BLAKE2b-256 checksum
How to use checksums
9a953a5cf08e760862a4fd83673bc517d29589112642b56b6742e73a7557b848
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page