Lock a secret until a future moment — so that nobody, not even you, can read it early.
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
|
📸 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
idthataddreturns. It's the vault's authoritative key. Secret names are not unique and nothing enforces it, so never construct an id from a name;--namelookup 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:
- Google Cloud Console → enable the Gmail API.
- 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). - 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. - 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)
| File | Size | Uploaded | |
|---|---|---|---|
| timesafe-0.1.0.tar.gz | 1.5 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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