Introduction
Rune is an open-source, local-first secrets manager. It encrypts your secrets, keeps them sealed until you say otherwise, and hands them out to exactly the tokens you allow — from a CLI, a Web UI, an HTTP API or straight into a Spring Boot app.
rune CLI
Web UI /ui/
Java SDK Spring Boot
rune-server :2710 · sealed by default
~/.rune/rune.db AES-256-GCM · bbolt
The components
Why Rune?
Most teams don't need a secrets platform. They need somewhere better than .env files, Slack DMs and CI variables nobody remembers setting. Rune is a single pair of binaries you can run on a laptop or a VM:
- Encrypted by default. Every secret is encrypted with AES-256-GCM before it touches disk.
- Sealed by default. The encryption key only lives in memory after you unseal with your passphrase.
- Least privilege. Tokens get
read, write, delete and rotate per namespace — nothing more.
- History you can trust. Rotations create new versions; old ones stay encrypted and fetchable.
- Boring infrastructure. One embedded database file. No cluster, no consensus protocol, no ops team.
Five minutes, start to finish. The Quickstart takes you from install to your first secret in a Spring app.
Quickstart
From zero to an unsealed vault with your first secret. You'll need macOS or Linux and a terminal.
-
Install the binaries
The install script detects your OS and architecture and drops rune and rune-server into /usr/local/bin.
curl -fsSL https://raw.githubusercontent.com/runelock-io/rune/main/scripts/install.sh | sh
rune --version
-
Start the server and set a passphrase
On first run, rune-server creates the vault at ~/.rune/rune.db, asks for a passphrase and prints a root token. The token is shown once and is never stored in plain text — copy it now.
rune-server
Set A Passphrase (Required For Sealing and Unsealing):
────────────────────────────────────────────────────────────────────────
root token
────────────────────────────────────────────────────────────────────────
3f9c0e…(64 hex characters)…a17d
────────────────────────────────────────────────────────────────────────
shown once · not stored · full vault access
────────────────────────────────────────────────────────────────────────
rune local-first secrets vault
➜ api http://localhost:2710
➜ ui http://localhost:2710/ui/
➜ db /Users/you/.rune/rune.db
⬡ vault sealed — run rune unseal or open the web UI
Lose the passphrase and the vault can't be unsealed. There is no recovery path — that's the point. Put it in your password manager.
-
Log in and unseal
Leave the server running and open a second terminal.
rune login <root-token>
rune unseal # prompts for the passphrase, input hidden
rune status
✔ Token saved successfully
Enter passphrase:
✔ Vault Unsealed Successfully
🔓 Vault is Unsealed
-
Store and read a secret
rune put db-password 'hunter2-but-longer'
rune get db-password
✔ Secret Stored Successfully
[SECRET] hunter2-but-longer
✔ Secret retrieved
-
Give your app its own token
Never ship the root token. Create a namespace and a token that can only read it:
rune namespace create production
rune put db-password 'hunter2-but-longer' -n production
rune token create payments-api -n production:read
Then plug that token into the Java SDK or call the HTTP API directly.
Prefer clicking? Everything above also works in the Web UI at http://localhost:2710/ui/.
Installation
Rune ships as two binaries: rune (the CLI) and rune-server (the vault, API and Web UI). Both are built for darwin and linux on amd64 and arm64.
Install script
Recommended. Downloads the v1.0.0-RC release for your platform and moves both binaries to /usr/local/bin (uses sudo).
curl -fsSL https://raw.githubusercontent.com/runelock-io/rune/main/scripts/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/runelock-io/rune/main/scripts/install.sh | sh
Detected OS: darwin
Detected ARCH: arm64
Downloading Rune...
Installing Rune...
Rune installed successfully
Run:
rune --version
rune-server
Release binaries
Grab them yourself from GitHub Releases. Assets are named rune_<os>_<arch> and rune-server_<os>_<arch>.
VERSION=v1.0.0-RC OS=darwin ARCH=arm64 # OS: darwin|linux ARCH: amd64|arm64
BASE=https://github.com/runelock-io/rune/releases/download/$VERSION
curl -L $BASE/rune_${OS}_${ARCH} -o rune
curl -L $BASE/rune-server_${OS}_${ARCH} -o rune-server
chmod +x rune rune-server
sudo mv rune rune-server /usr/local/bin/
From source
Requires Go (see go.mod) and Node.js — the Web UI is built with Vite and embedded into rune-server.
git clone https://github.com/runelock-io/rune.git
cd rune
make install # builds ui/, then bin/rune and bin/rune-server, copies to /usr/local/bin
| Make target | What it does |
make cli-build | Builds bin/rune from ./cmd/cli |
make server-build | Builds the Web UI, then bin/rune-server from ./cmd/server |
make install | Both of the above, copied to /usr/local/bin |
make clean | Removes bin/ |
Homebrew
Not yet. A Homebrew formula is on the way. Until then, the install script is one line.
Verify
rune --version # rune v1.0.0-RC
rune # prints the banner and command list
Uninstall
sudo rm /usr/local/bin/rune /usr/local/bin/rune-server
rm -rf ~/.rune # ⚠ deletes the vault database and your saved CLI token
Architecture
Everything goes through rune-server. The CLI, the Web UI and the Java SDK are all clients of the same HTTP API.
rune CLI~/.rune/config.json
Web UIbrowser · /ui/
Java SDKSpring Boot starter
your scriptscurl · any HTTP client
HTTP · Authorization: Bearer <token>
rune-server :2710
auth middleware → RBAC check → secret / token / namespace services → seal manager
AES-256-GCM, key held in memory only while unsealed
~/.rune/rune.dbbbolt embedded key/value store
What lives where
| Path | Contents |
~/.rune/rune.db | The vault: encrypted secrets and versions, token records (hashed), namespaces, salt, a verification record. |
~/.rune/config.json | The token the CLI uses, saved by rune login. |
memory of rune-server | The derived encryption key — only between unseal and seal (or a restart). |
Request lifecycle
- The client sends a request with
Authorization: Bearer <token>. Only /status, /unseal, /health and /ui/ are public.
- The server SHA-256 hashes the token and looks up its record. Unknown or revoked tokens get
401.
- The handler checks the token's permission for the namespace (
read, write, delete, rotate). Failures get 403 [AUTHORIZATION DENIED].
- The service checks the vault is unsealed, then encrypts or decrypts with the in-memory key.
The CLI currently talks to http://localhost:2710, so run it on the same machine as rune-server. Remote apps can use the HTTP API or the Java SDK with any base URL.
Seal & unseal
A sealed vault is a locked box: secrets are on disk, encrypted, and the key to read them doesn't exist anywhere. Unsealing derives the key from your passphrase and keeps it in memory until you seal again.
Lifecycle
sealedafter start / restart / rune seal
rune unseal →
passphrase
unsealedkey held in memory
← rune seal
key cleared
How unsealing works
- On first start,
rune-server generates a random salt and asks you to set a passphrase.
- A key is derived from the passphrase and salt, and used to encrypt a known verification value. The passphrase itself is never stored.
- On
unseal, the server derives the key again and tries to decrypt the verification value. If it matches, the key is kept in memory and the vault is unsealed. If not: 401 Invalid Passphrase.
seal wipes the key from memory. Restarting the server has the same effect.
What works while sealed
| Operation | Sealed |
rune status, rune unseal, GET /health | yes |
| Reading secrets with a system token | yes |
| Everything else — put, get, rotate, tokens, namespaces | no — [OPERATION DENIED]: Vault is Sealed |
Seal the vault whenever you step away from a shared machine. It costs one command to get back: rune unseal.
Namespaces
A namespace is a folder for secrets. The same key can exist in several namespaces without conflict — db-password in staging and in production are two different secrets.
The default namespace
default always exists and can't be deleted. Any command without -n uses it.
Working with namespaces
rune namespace create production
rune namespace list
rune put stripe-key 'sk_live_…' -n production
rune namespace delete staging # asks for confirmation
Rules
- Creating, listing and deleting namespaces requires the root token and an unsealed vault.
- A namespace must exist before you can put secrets in it or grant a token access to it.
- Deleting a namespace deletes every secret in it.
A namespace per environment (dev, staging, production) or per service (payments, search) both work. Pick whichever matches how you'd want to hand out tokens.
Secrets & versioning
A secret is a key, a value and a namespace. Every value is encrypted individually with AES-256-GCM and a fresh random nonce, and every rotation is kept as a new, separately encrypted version.
Versions
rune put writes the secret as version 1.
rune rotate creates the next version (2, 3, …). Earlier versions stay readable with rune get --version N.
rune history lists every version with its created and rotated timestamps.
Running rune put on a key that already exists overwrites it and starts again at version 1. To keep history, use rune rotate.
Deleting
rune delete removes the secret and every version of it. Nothing lingers, including copies held for system tokens.
Keys
Keys are trimmed, a leading / is dropped, and a leading default/ is dropped. Use the -n flag for namespaces rather than putting them in the key.
Rollback — creating a new version from an old one — is on the roadmap for the next release. History stays append-only.
Rotation
Rotating a secret replaces its value with a freshly generated one and bumps the version. The old value stays in history, encrypted.
Manual rotation
rune rotate db-password -n production
SECRET ROTATED
─────────────────────────────────────────────
Namespace: production
Key: db-password
New Value: Vq3m9X0rK1…
─────────────────────────────────────────────
✔ Secret rotated successfully
The new value is 32 cryptographically random bytes, base64url-encoded (43 characters). Rotating requires the rotate permission on the namespace.
Rotation changes the value in Rune. If the secret is also a password inside another system (a database user, an API provider), update it there too — or read it from Rune at startup so your app always has the current one.
Automated rotation
Rune can rotate secrets for you on a schedule, so long-lived credentials don't stay long-lived. Every automated rotation is a normal rotation: a new version, the old one kept, and an entry in the audit log.
The configuration reference for automated rotation is being added to these docs. Rotation policies — rules per namespace — are coming in the next release.
Tokens & RBAC
Every request to Rune carries a token. A token says who is asking and what they may do, namespace by namespace.
Kinds of token
| Token | ID | Access |
| root | root | Everything, in every namespace. Created once, on first server start. Required to manage namespaces and tokens. |
| regular | tkn_… | Only the permissions you grant, in the namespaces you list. |
| system | stk_… | Read-only, one namespace, keeps working while sealed. See Service identities. |
Permissions
readget, list, history, versions
writeput
deletedelete a secret and its history
rotategenerate a new version
Granting access
Pass -n namespace:perm,perm once per namespace. A namespace with no permissions listed gets all four.
# CI can deploy to production, nothing else
rune token create ci-deploy -n production:read,write
# read production, read+write staging
rune token create dev-laptop -n production:read -n staging:read,write
# everything in sandbox
rune token create playground -n sandbox
What denial looks like
✖ [AUTHORIZATION DENIED]: Token cannot write to this namespace
Over HTTP that's a 403. Unknown tokens get 401 invalid token; revoked ones 401 token has been revoked.
How tokens are stored
A token is 32 random bytes, hex-encoded (64 characters). Rune stores only its SHA-256 hash, so the plain token is shown exactly once — when it's created.
Revoking
rune token list # find the ID
rune token revoke tkn_1730451234567890
Revocation is immediate. Revoking a system token also deletes the encrypted copies it used to read while sealed.
Service identities
Applications shouldn't borrow a human's token. A system token is an identity for one service: read-only, bound to one namespace, and able to read secrets even while the vault is sealed — so a restart of rune-server doesn't take your apps down with it.
How it works
- While unsealed, root creates a system token for a namespace.
- Rune derives a separate key from the plain token and re-encrypts that namespace's secrets with it. That key is itself stored encrypted under the vault key.
- When the vault is sealed, a request with the system token re-derives its key from the token and reads its own copy. Without the token, the copy can't be decrypted.
- Puts, rotations and deletes in the namespace keep the copies in sync. Revoking the token deletes them.
Create one
System tokens are created through the HTTP API with the root token:
curl -X POST http://localhost:2710/token/create-system \
-H "Authorization: Bearer $RUNE_ROOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"payments-api","namespace":"production"}'
{
"token": "9a41…e07c",
"record": {
"id": "stk_1730451234567890",
"name": "payments-api",
"namespace": "production",
"namespaces": [{ "namespace": "production", "permissions": ["read"] }],
"read_only": true,
"system": true
}
}
Pair a system token with the Java SDK: your Spring app keeps booting even if someone sealed the vault for the night.
Token expiration
Tokens can be given a lifetime. When it runs out, the token stops working — no clean-up ticket, no forgotten CI variable that still opens production two years later.
Why it matters
- Short-lived tokens for CI jobs and one-off scripts limit the blast radius of a leak.
- Long-lived service tokens can still be revoked at any time with
rune token revoke.
The flag reference for setting a token's lifetime is being added to these docs.
Audit logging
Every meaningful action in the vault leaves a record: which token, which operation, which secret, when, and whether it was allowed.
What gets recorded
| Field | Example |
| time | 2026-11-01T10:30:42Z |
| actor | token name / ID — ci-deploy |
| action | secret.get, secret.rotate, token.create, vault.unseal… |
| target | production/db-password |
| result | allowed or denied |
Denied requests are logged too — a token repeatedly bouncing off [AUTHORIZATION DENIED] is exactly what you want to notice.
CLI and API reference for querying the audit log is being added to these docs.
Security model
Rune is designed so that a copy of rune.db on its own is useless, and a leaked token is only as powerful as you made it.
Cryptography
| Secret encryption | AES-256-GCM, a fresh random nonce per write |
| Vault key | Derived from your passphrase and a random per-vault salt; held in memory only while unsealed |
| Passphrase check | Decrypting a known verification record — the passphrase is never stored |
| Tokens | 32 random bytes; stored as SHA-256 hashes |
| System token key | SHA-256 of a domain-separated prefix plus the plain token |
| Rotated values | 32 random bytes from the OS CSPRNG, base64url |
Protected against
- Someone copying
~/.rune/rune.db — they get ciphertext and hashes.
- A leaked app token reaching beyond its namespaces and permissions.
- Old values disappearing on rotation — they're versioned, not overwritten.
Your job
- The passphrase — there's no recovery. Store it somewhere safe.
- The root token — use it to administer, not to run apps.
~/.rune/config.json holds the CLI's token in plain text. Keep it readable only by you: chmod 600 ~/.rune/config.json.
- Transport —
rune-server speaks plain HTTP on :2710. Keep it on localhost or a private network, or put TLS in front of it.
- Shell history —
rune put key value lands in your history. Quote values, and consider a leading space with HISTCONTROL=ignorespace.
CLI overview
rune is a thin client for rune-server. It reads its token from ~/.rune/config.json and talks to http://localhost:2710.
rune # banner + command list
rune --version # or -v
rune <command> --help
Commands
| Command | Does | Needs |
login | Save a token for the CLI | — |
status | Sealed or unsealed? | — |
unseal | Unseal with your passphrase | passphrase |
seal | Seal, clearing the key from memory | token |
put | Store a secret | write |
get | Read a secret or one of its versions | read |
list | List keys in a namespace | read |
delete | Delete a secret and its history | delete |
rotate | Generate a new value | rotate |
history | Show versions | read |
namespace | create · list · delete | root |
token | create · list · revoke | root |
Namespaces
Secret commands take -n / --namespace. Leave it out and you get default.
Output conventions
✔ success
✖ error — usually the server's message, e.g. [OPERATION DENIED]: Vault is Sealed
➜ info
! warning
Colours are disabled when NO_COLOR is set or output isn't a terminal.
rune login
Saves a token to ~/.rune/config.json. Every other authenticated command uses it.
rune login <token>
✔ Token saved successfully
login doesn't contact the server — it just saves the token. A typo shows up on the next command as invalid token.
Switch identities by logging in again with another token. Without a saved token, commands fail with:
✖ [AUTHENTICATION FAILED] Please login first using: rune login <token>
status · unseal · seal
Three commands for the vault's state. See Seal & unseal for the why.
rune status
rune status
🔒 Vault is Sealed
No token needed.
rune unseal
Prompts for the passphrase with input hidden. No token needed.
Enter passphrase:
✔ Vault Unsealed Successfully
A wrong passphrase returns ✖ error: Invalid Passphrase.
rune seal
Clears the key from the server's memory. Requires a valid token.
✔ Vault Sealed Successfully
rune put
Encrypts a value and stores it under a key.
rune put <key> <value> [-n namespace]
Flags
-n, --namespacestringdefault: default
Namespace to store the secret in. Must exist.
Examples
rune put db-password 'hunter2-but-longer'
rune put stripe-key 'sk_live_51Hx…' -n production
✔ Secret Stored Successfully
Values containing $ trigger ! Detected '$' in value. Use quotes to avoid shell expansion. — single-quote them.
put on an existing key overwrites it at version 1. Use rotate to add a version.
Requires write on the namespace.
rune get
Decrypts and prints a secret. The vault must be unsealed.
rune get <key> [-n namespace] [--version N]
Flags
-n, --namespacestringdefault: default
Namespace containing the secret.
--versionintdefault: latest
Fetch a specific version from history.
Examples
rune get db-password -n production
rune get db-password -n production --version 2
[SECRET] hunter2-but-longer
✔ Secret retrieved
Requires read on the namespace.
rune list
Lists keys — never values.
rune list [-n namespace]
Stored Secrets:
[SECRET] db-password
[SECRET] redis-url
An empty namespace prints ➜ No stored keys found. Requires read.
rune delete
Deletes a secret and every version of it, after asking.
rune delete <key> [-n namespace]
Are you sure you want to delete secret 'redis-url' (namespace: production)? (y/N): y
✔ Secret deleted
Anything but y prints ➜ Operation Cancelled. Requires delete.
There's no undo. History goes with it.
rune rotate
Replaces the value with 32 random bytes (base64url) and creates the next version.
rune rotate <key> [-n namespace]
SECRET ROTATED
─────────────────────────────────────────────
Namespace: production
Key: db-password
New Value: Vq3m9X0rK1…
─────────────────────────────────────────────
✔ Secret rotated successfully
Requires rotate. The secret must already exist.
rune history
Shows every version of a secret with when it was created and rotated.
rune history <key> [-n namespace]
SECRET HISTORY
─────────────────────────────────────────────────────────
VERSION CREATED ROTATED
─────────────────────────────────────────────────────────
1 2026-10-02T09:12:04+05:30 -
2 2026-10-02T09:12:04+05:30 2026-10-09T14:03:51+05:30
3 2026-10-02T09:12:04+05:30 2026-11-01T10:30:42+05:30
─────────────────────────────────────────────────────────
Fetch any of them with rune get <key> --version N. Requires read.
rune namespace
Manage namespaces. All subcommands need the root token and an unsealed vault.
create
rune namespace create production
✔ [NAMESPACE CREATED] Created namespace
list
Namespaces:
[NAMESPACES] default
[NAMESPACES] production
delete
Are you sure you want to delete namespace 'staging'? (y/N): y
✔ Namespace deleted
Deletes every secret in the namespace. default can't be deleted.
rune token
Manage access tokens. All subcommands need the root token.
create
rune token create [name] -n <namespace[:perms]> [-n …]
nameargdefault: default
Display name. You can also pass --name.
-n, --namespacestring, repeatablerequired
namespace:perm,perm with perms from read, write, delete, rotate. Without :perms, all four.
rune token create ci-deploy -n production:read,write -n staging:read
✔ Token created successfully
ID: tkn_1730451234567890
Name: ci-deploy
Created At: 2026-11-01T10:30:42+05:30
production read, write
staging read
Token: 5b0e…(64 hex characters)…c91f
The token is printed once. Rune keeps only its hash.
list
Tokens:
ID NAME CREATED AT TYPE STATUS
-------------------------------------------------------------------------------------------------
root root 2026-10-01T09:00:00+05:30 regular active
tkn_1730451234567890 ci-deploy 2026-11-01T10:30:42+05:30 regular active
stk_1730451299999999 payments-api 2026-11-01T10:31:47+05:30 system active
revoke
rune token revoke tkn_1730451234567890
✔ Token revoked
rune-server
The vault. One process that stores encrypted secrets, enforces tokens, serves the HTTP API and hosts the Web UI.
Start it
rune-server
rune local-first secrets vault
➜ api http://localhost:2710
➜ ui http://localhost:2710/ui/
➜ db /Users/you/.rune/rune.db
⬡ vault sealed — run rune unseal or open the web UI
On the very first start it also asks you to set a passphrase and prints the root token once — see the Quickstart.
Defaults
| Port | 2710, all interfaces |
| API | http://localhost:2710 |
| Web UI | http://localhost:2710/ui/ |
| Database | ~/.rune/rune.db (bbolt) |
| Default namespace | default, created on every start if missing |
| State on start | Always sealed |
The server listens on all interfaces over plain HTTP. On a shared network, firewall port 2710 or put a TLS reverse proxy in front of it.
Running it as a service
Because the vault starts sealed, a restart means someone runs rune unseal again. Apps using system tokens keep reading in the meantime. A minimal systemd unit:
[Unit]
Description=Rune secrets vault
After=network.target
[Service]
ExecStart=/usr/local/bin/rune-server
User=rune
Restart=on-failure
[Install]
WantedBy=multi-user.target
Run the first start interactively — it needs a terminal to set the passphrase. After that the service can start unattended.
Routes
| Route | Auth |
/health, /status, /unseal | public |
/ui/ | public (static files — the UI then calls the API with a token) |
| everything else | bearer token — see the API reference |
Web UI
A browser interface to the same vault, built into rune-server. No separate install, no desktop app — open http://localhost:2710/ui/.
localhost:2710/ui/
runeDashboardSecretsNamespacesTokens
secrets24
namespaces4
rotated9
active tokens6
What you can do
- Unseal the vault from the browser.
- Dashboard — totals for secrets, namespaces, rotated secrets and tokens, secrets per namespace, tokens created per month.
- Secrets — browse by namespace, reveal, create, rotate, delete, inspect versions.
- Namespaces and tokens — create, scope with permissions, revoke.
Permissions
The UI goes through the same HTTP API and the same token rules as everything else: a scoped token can only see and change what it's allowed to. Dashboard stats, namespaces and token management need root.
Under the hood
React + Vite + Tailwind, compiled and embedded into the rune-server binary with Go's embed. Building from source? make server-build runs npm install && npm run build in ui/ first.
HTTP API
The CLI, Web UI and SDK are all built on this API. Anything that speaks HTTP can use Rune.
Base URL
http://localhost:2710
Authentication
Send a token as a bearer header on every request except /health, /status and /unseal.
curl -H "Authorization: Bearer $RUNE_TOKEN" \
"http://localhost:2710/secret/get?namespace=production&key=db-password"
Errors
Errors come back as a plain-text body with an HTTP status.
| Status | Body (examples) | Meaning |
400 | Invalid Request | Malformed JSON or missing parameters |
401 | missing token · invalid token · token has been revoked | Who are you? |
403 | [AUTHORIZATION DENIED]: Token cannot read from this namespace | Token lacks the permission, or root is required |
403 | [OPERATION DENIED]: Vault is Sealed | Unseal first |
404 | — | Secret or version not found |
Endpoints
Vaulthealth, status, unseal, seal
Secretsput, get, list, delete, rotate, versions
Tokensme, create, system, list, revoke
Namespaces & statscreate, list, delete, dashboard stats
Vault endpoints
Health
GET/healthpublic
Returns ok. Use it for liveness checks.
Status
GET/statuspublic
Returns sealed or unsealed as plain text.
Unseal
POST/unsealpublic
passphrasestringrequired
The vault passphrase.
curl -X POST http://localhost:2710/unseal \
-H "Content-Type: application/json" \
-d '{"passphrase":"…"}'
200 unsealed · 401 Invalid Passphrase · 500 Vault Not Initialized
Seal
POST/sealtoken
Clears the key from memory. Returns sealed.
Secrets endpoints
namespace defaults to default everywhere it's optional.
Put a secret
POST/secret/putwrite
curl -X POST http://localhost:2710/secret/put \
-H "Authorization: Bearer $RUNE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"db-password","value":"hunter2","namespace":"production"}'
HttpRequest req = HttpRequest.newBuilder(URI.create("http://localhost:2710/secret/put"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"key\":\"db-password\",\"value\":\"hunter2\",\"namespace\":\"production\"}"))
.build();
HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
body := strings.NewReader(`{"key":"db-password","value":"hunter2","namespace":"production"}`)
req, _ := http.NewRequest("POST", "http://localhost:2710/secret/put", body)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
Returns 201 Created.
Get a secret
GET/secret/get?namespace=&key=read
{ "value": "hunter2" }
Works while sealed for system tokens.
List keys
GET/secret/list?namespace=read
["db-password", "redis-url"]
Get all secrets in a namespace
GET/secret/all?namespace=read
{ "db-password": "hunter2", "redis-url": "redis://cache.internal:6379/0" }
Rotate
POST/secret/rotate?namespace=&key=rotate
{ "value": "Vq3m9X0rK1…" }
History
GET/secret/history?namespace=&key=read
[
{ "version": 1, "created_at": "2026-10-02T09:12:04+05:30", "updated_at": "2026-10-02T09:12:04+05:30", "rotated_at": "" },
{ "version": 2, "created_at": "2026-10-02T09:12:04+05:30", "updated_at": "2026-10-09T14:03:51+05:30", "rotated_at": "2026-10-09T14:03:51+05:30" }
]
A specific version
GET/secret/version?namespace=&key=&version=read
{ "value": "hunter2" }
Delete
POST/secret/deletedelete
{ "key": "redis-url", "namespace": "production" }
Removes the secret and all versions. Returns 200.
Tokens endpoints
Who am I?
GET/token/metoken
{
"id": "tkn_1730451234567890",
"name": "ci-deploy",
"created_at": "2026-11-01T10:30:42+05:30",
"revoked": false,
"namespaces": [{ "namespace": "production", "permissions": ["read", "write"] }]
}
Create a token
POST/token/createroot
namespacesarrayrequired
[{ "namespace": "production", "permissions": ["read","write"] }] — permissions from read, write, delete, rotate.
{ "token": "5b0e…c91f", "record": { "id": "tkn_…", "name": "ci-deploy", "namespaces": [ … ] } }
Create a system token
POST/token/create-systemroot
{ "name": "payments-api", "namespace": "production" }
Read-only, single namespace, reads while sealed. See Service identities.
List tokens
GET/token/listroot
Revoke
POST/token/revokeroot
{ "id": "tkn_1730451234567890" }
Namespaces & stats
All of these need the root token and an unsealed vault.
Create
POST/namespace/createroot
{ "name": "production" }
List
GET/namespace/listroot
["default", "production", "staging"]
Delete
POST/namespace/deleteroot
{ "name": "staging" }
Deletes the namespace and every secret in it. default is refused.
Stats
GET/statsroot
{
"total_secrets": 24,
"total_namespaces": 4,
"rotated_secrets": 9,
"total_tokens": 8,
"active_tokens": 6,
"revoked_tokens": 2,
"secrets_by_namespace": [{ "namespace": "production", "count": 11 }],
"tokens_by_month": [{ "month": "2026-11", "count": 3 }]
}
Spring Boot starter
rune-spring-boot-starter fetches every secret in a namespace at startup and exposes them as Spring properties — before any of your beans are created.
Install
<dependency>
<groupId>io.rune</groupId>
<artifactId>rune-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>
dependencies {
implementation 'io.rune:rune-spring-boot-starter:0.1.0'
}
Java 17+, Spring Boot 3. No extra HTTP or JSON libraries — it uses java.net.http.
Configure
rune:
url: http://localhost:2710
token: ${RUNE_TOKEN}
namespace: production
fail-fast: true
rune.urlstringrequired
Base URL of rune-server.
rune.tokenstringrequired
A token with read on the namespace. Prefer a system token — it reads while sealed.
rune.namespacestringdefault: default
Namespace to load.
rune.fail-fastbooleandefault: true
Fail startup if Rune is unreachable or misconfigured. With false, a warning is logged and the app starts without Rune properties.
rune.enabledbooleandefault: true
Turn the starter off entirely — handy for tests.
Don't commit the token. Pass it through an environment variable, as above.
Use secrets as properties
Each secret becomes rune.<key>, added as the highest-precedence property source:
@Configuration
class DataSourceConfig {
@Value("${rune.db-password}")
private String dbPassword;
}
spring:
datasource:
password: ${rune.db-password}
Use RuneClient
When both rune.url and rune.token are set, a RuneClient bean is available for runtime lookups:
@Service
class PaymentsService {
private final RuneClient rune;
PaymentsService(RuneClient rune) {
this.rune = rune;
}
String stripeKey() {
return rune.getSecret("production", "stripe-key");
}
List<String> keys() {
return rune.listKeys("production");
}
}
| Method | Calls |
String getSecret(String namespace, String key) | GET /secret/get |
List<String> listKeys(String namespace) | GET /secret/list |
Non-200 responses and connection failures throw RuneException.
How it works
Spring loads application.yml
Including rune.*.
RuneEnvironmentPostProcessor runs
Before any bean is created, it lists and fetches the namespace's secrets.
A rune property source is added first
So ${rune.<key>} wins over everything else.
Your beans start
With secrets in hand — nothing on disk, nothing in git.
Kubernetes next release
The next release brings Rune to your cluster. Two pieces are in the works.
Kubernetes secret mappingMap Rune namespaces and keys to Kubernetes, so workloads get their secrets from Rune without copy-pasting into manifests.
Auto secrets injectorHave secrets delivered to your pods automatically when they start.
Want early access or have a setup you'd like supported? Say so on GitHub issues.
Until then: run rune-server somewhere your pods can reach, give each service a system token, and read secrets at startup with the Java SDK or the HTTP API.
Troubleshooting
The messages you're most likely to meet, and what they mean.
[AUTHENTICATION FAILED] Please login first using: rune login <token>
The CLI has no saved token. Run rune login <token>.
invalid token / token has been revoked
The saved token doesn't exist (typo?) or was revoked. Log in with a valid one; root can check with rune token list.
[OPERATION DENIED]: Vault is Sealed
The server restarted or someone ran rune seal. Run rune unseal.
error: Invalid Passphrase
Wrong passphrase. There's no reset — if it's truly lost, the vault can't be decrypted.
[AUTHORIZATION DENIED]: Token cannot … this namespace
The token lacks that permission for that namespace. Check the -n flag (no flag means default), or create a token with the right scope.
[AUTHORIZATION DENIED]: Root Access Required
Namespace management, token management and stats need the root token.
[OPERATION DENIED]: Namespace does not exist
Create it first: rune namespace create <name>.
! Detected '$' in value. Use quotes to avoid shell expansion.
Your shell may have expanded part of the value before Rune saw it. Wrap it in single quotes.
connection refused
rune-server isn't running, or something else holds port 2710. Start it and check the banner.
Spring app fails at startup with [rune] rune.url and rune.token must be configured
Set both properties, or set rune.fail-fast: false / rune.enabled: false for environments without Rune.
Roadmap
Available in 1.0.0-RC
- Secret CRUD & encryption
- Seal / unseal
- Namespaces
- Token authentication & namespace-bound tokens
- Token RBAC
- Token expiration
- Audit logging
- Rotation — manual & automated
- Versioning & version-aware deletion
- Application / service identities
- Web UI
- Java SDK (Maven)
Next release
- Kubernetes secret mapping
- Auto secrets injector
- Secret rollback
- Rotation policies
Future
- Dynamic secrets
- Remote storage
- High availability
Follow along on GitHub. Rune is licensed under Apache 2.0.