rune docs v1.0.0-RC GitHub

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.

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.

  1. 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
  2. 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.

  3. 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
  4. 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
  5. 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 targetWhat it does
make cli-buildBuilds bin/rune from ./cmd/cli
make server-buildBuilds the Web UI, then bin/rune-server from ./cmd/server
make installBoth of the above, copied to /usr/local/bin
make cleanRemoves 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

PathContents
~/.rune/rune.dbThe vault: encrypted secrets and versions, token records (hashed), namespaces, salt, a verification record.
~/.rune/config.jsonThe token the CLI uses, saved by rune login.
memory of rune-serverThe derived encryption key — only between unseal and seal (or a restart).

Request lifecycle

  1. The client sends a request with Authorization: Bearer <token>. Only /status, /unseal, /health and /ui/ are public.
  2. The server SHA-256 hashes the token and looks up its record. Unknown or revoked tokens get 401.
  3. The handler checks the token's permission for the namespace (read, write, delete, rotate). Failures get 403 [AUTHORIZATION DENIED].
  4. 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

  1. On first start, rune-server generates a random salt and asks you to set a passphrase.
  2. A key is derived from the passphrase and salt, and used to encrypt a known verification value. The passphrase itself is never stored.
  3. 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.
  4. seal wipes the key from memory. Restarting the server has the same effect.

What works while sealed

OperationSealed
rune status, rune unseal, GET /healthyes
Reading secrets with a system tokenyes
Everything else — put, get, rotate, tokens, namespacesno — [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

TokenIDAccess
rootrootEverything, in every namespace. Created once, on first server start. Required to manage namespaces and tokens.
regulartkn_…Only the permissions you grant, in the namespaces you list.
systemstk_…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

  1. While unsealed, root creates a system token for a namespace.
  2. 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.
  3. 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.
  4. 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

FieldExample
time2026-11-01T10:30:42Z
actortoken name / ID — ci-deploy
actionsecret.get, secret.rotate, token.create, vault.unseal…
targetproduction/db-password
resultallowed 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 encryptionAES-256-GCM, a fresh random nonce per write
Vault keyDerived from your passphrase and a random per-vault salt; held in memory only while unsealed
Passphrase checkDecrypting a known verification record — the passphrase is never stored
Tokens32 random bytes; stored as SHA-256 hashes
System token keySHA-256 of a domain-separated prefix plus the plain token
Rotated values32 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

CommandDoesNeeds
loginSave a token for the CLI—
statusSealed or unsealed?—
unsealUnseal with your passphrasepassphrase
sealSeal, clearing the key from memorytoken
putStore a secretwrite
getRead a secret or one of its versionsread
listList keys in a namespaceread
deleteDelete a secret and its historydelete
rotateGenerate a new valuerotate
historyShow versionsread
namespacecreate · list · deleteroot
tokencreate · list · revokeroot

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

Port2710, all interfaces
APIhttp://localhost:2710
Web UIhttp://localhost:2710/ui/
Database~/.rune/rune.db (bbolt)
Default namespacedefault, created on every start if missing
State on startAlways 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

RouteAuth
/health, /status, /unsealpublic
/ui/public (static files — the UI then calls the API with a token)
everything elsebearer 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/.

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.

StatusBody (examples)Meaning
400Invalid RequestMalformed JSON or missing parameters
401missing token · invalid token · token has been revokedWho are you?
403[AUTHORIZATION DENIED]: Token cannot read from this namespaceToken lacks the permission, or root is required
403[OPERATION DENIED]: Vault is SealedUnseal first
404—Secret or version not found

Endpoints

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
keystringrequired
valuestringrequired
namespacestringdefault
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
namestring
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");
    }
}
MethodCalls
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

  1. Spring loads application.yml

    Including rune.*.

  2. RuneEnvironmentPostProcessor runs

    Before any bean is created, it lists and fetches the namespace's secrets.

  3. A rune property source is added first

    So ${rune.<key>} wins over everything else.

  4. 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.

Was this page helpful? Suggest an edit on GitHub →
Rune · open source · Apache 2.0 rune home