burnafter

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

README

🔥burnafter

Secrets that burn after reading

An ephemeral secret storage library and command line utility with binary verification.

Overview

burnafter is a Go utility and library that provides time-limited, in-memory secret storage with strong binary authentication and persistence in the system keyring.

It's designed for scenarios where you need to store sensitive data temporarily (like API keys, tokens, or passwords) that survives across command invocations and want to ensure only a specific application binary can access it.

Key Features

  • Minimal dependencies: Almost no expternal dependencies
  • Ephemeral Storage: Secrets stored in the system keyring or in memory, never written to disk (unless the fallback mechanism kicks in)
  • Binary Verification: Only the exact binary that stored a secret can retrieve it (via SHA256 hash verification)
  • Time-Limited: Secrets expire based on inactivity timeout or at an absolute deadline
  • Process Isolation: Uses server session IDs to prevent replaying older keys on server restarts
  • On-Demand Key Derivation: Encryption keys derived when needed, never stored in memory
  • Encrypted at Rest: Secrets encrypted in memory using AES-256-GCM
  • Automatic Cleanup: Server shuts down after inactivity period or secret expiration
  • No Persistency: Client binary updates invalidate access (by design)

Security Model

For a complete overview of the security model, see the Security and Architecture document.

How It Works
Architecture (Server Mode)

Applications wishing to store secrets simply need to use the burnafter client library. When secrets are stored, burnafter launches a small daemon that handles client authentication and secret encryption and in-memory storage:

graph TD
    Client["Client Binary<br/>(your app)"]
    subgraph daemon["🔥 burnafter daemon"]
      Auth(Client binary authorization)
    end

    subgraph keyring["🐧system keyring"]
      Secrets["Secrets<br/>(Encrypted)"]
    end

    Client -->|Unix Socket + gRPC| daemon
    daemon --> keyring

    style Client fill:#e1f5ff,stroke:#01579b,stroke-width:2px
    style Secrets font-size:0.9em,fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
  1. Client Authentication:

    • Client binary hash (SHA256)
    • Client nonce (constant defined or embedded in client binary)
    • Server verifies client via Unix socket peer credentials (using SO_PEERCRED)
  2. Key Derivation (HKDF-SHA256):

    key = HKDF(
        client_binary_hash +
        compile_time_nonce +
        server_session_id +
        secret_name,
        random_salt
    )
    
  3. Storage:

    • Secrets are encrypted with a derived key
    • Keys are never stored (derived on-demand)
    • Server session ID lost on restart (makes secrets unrecoverable)
    • Secrets are kept in the system keyring, or in memory only if keyring not avilable (see fallback mechanism)
  4. Access Control:

    • Server verifies client binary hash matches the expected client digest
    • Server checks client-defined nonce matches
    • Secrets expire based on TTL or on inactivity threshold
Threat Model

Designed for defense in depth scenarios, burnafter is not intended to be 100% impregnable but rather to help keep secrets safer and to be used together with other protection mechhanisms.

Protects Against
  • Accidental secret leakage to logs/files
  • Other programs accessing secrets
  • Long lived secrets persisting in the filesystem
  • Key exposure
Does NOT Protect Against
  • Debuggers attached to server process
  • Root/admin users attaching to server process

Usage

burnafter is designed to work as a library, but it also offers a command line utility that can be used to store secrets from shell scripts, etc.

Basic Commands
# Store a secret (with 2-hour TTL)
burnafter store api-key "my-secret-token" 7200

# Retrieve a secret
burnafter get api-key

# Check if the server is running
burnafter ping

Command line flags

Flags in the cli utility are simple for now, you can enable debugging and control where the server unix socket is written.

-socket string    Unix socket path (default: auto-generated based on binary hash)
-debug            Enable debug output
Examples

Here are some usage examples, see the QUICKSTART.md for a more in depth guide.

Store GitHub Token
# Store for 4 hours (default)
burnafter store github-token "ghp_xxxxxxxxxxxx"

# Store for 1 hour (3600 seconds)
burnafter store github-token "ghp_xxxxxxxxxxxx" 3600
Use in Scripts

For scripting, use the burnafter binary. It is limited to simple I/O but feel free to suggest features to improve it.

#!/bin/bash

# Secrets can be stored on first run:
burnafter store db-password "$(pass show prod/db)"

# Retrieve secret in a script
DB_PASS=$(burnafter get db-password)
mysql -u user -p"$DB_PASS" mydatabase
Library Integration

For Go applications, use burnafter as a library for programmatic access:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/carabiner-dev/burnafter"
	"github.com/carabiner-dev/burnafter/options"
)

func main() {
	ctx := context.Background()

	// Create a new client with default options
	client := burnafter.NewClient(options.DefaultClient)

	// Connect to the server (auto-starts if not running)
	if err := client.Connect(ctx); err != nil {
		log.Fatalf("Failed to connect: %v", err)
	}
	defer client.Close()

	// Store a secret with 2-hour TTL
	err := client.Store(ctx, "api-key", "your-secret-token",
		options.WithTTL(7200))
	if err != nil {
		log.Fatalf("Failed to store secret: %v", err)
	}
	fmt.Println("Secret stored successfully")

	// Retrieve the secret
	secret, err := client.Get(ctx, "api-key")
	if err != nil {
		log.Fatalf("Failed to get secret: %v", err)
	}
	fmt.Printf("Retrieved secret: %s\n", secret)

	// Use the secret in your application
	// ... your API calls, database connections, etc.
}

Advanced Configuration:

// Custom client options
client := burnafter.NewClient(&options.Client{
	Nonce: "my-app-v1.0",  // Optional: version-specific nonce
	Common: options.Common{
		DefaultTTL:   2 * time.Hour,
		Debug:        true,
		MaxSecrets:   200,
		MaxSecretSize: 2 * 1024 * 1024, // 2 MB
	},
})

// Store with absolute expiration time
expiry := time.Now().Add(24 * time.Hour).Unix()
err := client.Store(ctx, "temp-token", "secret",
	options.WithAbsoluteExpiration(expiry))

Limits and Resource Management

Burnafter enforces limits to prevent resource exhaustion:

  • Maximum Secrets: 100 secrets per server instance (configurable via options.MaxSecrets)
  • Maximum Secret Size: 1 MB per secret (configurable via options.MaxSecretSize)

Notes:

  • Updating an existing secret does not count against the maximum secret limit
  • Limits can be customized when using burnafter as a library by modifying options.MaxSecrets and options.MaxSecretSize
  • Expired secrets are automatically cleaned up, freeing space for new secrets

How The Server Works

  1. Auto-Start: Client automatically starts server if it's not running
  2. Daemon Mode: Server detaches and runs in background
  3. Automatic Shutdown: Server shuts down automatically when:
    • No activity is detected for 10 minutes (inactivity timeout), OR
    • All secrets have expired or have been deleted (no secrets remaining)
  4. Cleanup: Expired secrets removed every minute
  5. Unix Socket: Listens on auto-generated hash-based path or custom path

Development

Requirements
  • Go 1.24.11+
  • protoc (for regenerating proto files)
  • Linux/MacOs (for SO_PEERCRED support)
Build
go build -o burnafter ./cmd/burnafter
Generate Protobuf Code
./generate.sh

Contributing

Contributions are always welcome! Please open an issue or feel free to submit PR with feature enhacements, bug fixes, etc.

This project was writen with 🧡 by Carabiner Systems, Inc and is released under the Apache-2.0 license.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrServerStartFailed = errors.New("server failed to start (and fallback mode is disabled)")

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the burnafter client.

This is the library that applications use to spin up the embedded server used to store and retrieve secrets.

func NewClient

func NewClient(opts *options.Client, clientOpts ...Option) *Client

NewClient creates a new client instance.

func (*Client) Close

func (c *Client) Close() error

Close closes the connection to the server.

func (*Client) Connect

func (c *Client) Connect(ctx context.Context) error

Connect establishes the connection to the server. If the server is not running, this function spawns the forked process to start listening.

If NoServer option is set or server startup fails, fallback mode is used.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, name string) error

Delete removes a secret from fallback encrypted file storage Note: Delete is only supported in fallback mode currently

func (*Client) Get

func (c *Client) Get(ctx context.Context, name string) (string, error)

Get retrieves a secret from the server or fallback encrypted file storage

func (*Client) IsServerRunning

func (c *Client) IsServerRunning(ctx context.Context) bool

isServerRunning checks if the server is responding

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping checks if the server is alive

func (*Client) Store

func (c *Client) Store(ctx context.Context, name, secret string, funcs ...options.StoreOptsFn) error

Store stores a secret on the server or in fallback encrypted file storage

type Option added in v0.2.0

type Option func(*Client)

Option configures a Client.

func WithServerLauncher added in v0.2.0

func WithServerLauncher(l ServerLauncher) Option

WithServerLauncher enables embedded-server mode by supplying the launcher (typically embedded.Launch). Without it, a client falls back to file or in-memory storage instead of spawning a server.

type ServerLauncher added in v0.2.0

type ServerLauncher func(ctx context.Context, opts *options.Client) error

ServerLauncher starts the burnafter server as a detached subprocess configured with the given options. The embedded-server implementation lives in the github.com/carabiner-dev/burnafter/embedded package (embedded.Launch); keeping it out of the core lets programs that only use the in-memory or encrypted-file modes avoid linking the multi-megabyte embedded server binary.

Directories

Path Synopsis
cmd
burnafter command
burnafter-server command
Package main provides a minimal server-only entry point for the burnafter dæmon.
Package main provides a minimal server-only entry point for the burnafter dæmon.
Package embedded provides the embedded burnafter server and the launcher that starts it.
Package embedded provides the embedded burnafter server and the launcher that starts it.
hack
gzip command
Command gzip compresses a file to <file>.gz using Go's compress/gzip and then removes the original, mimicking `gzip -f`.
Command gzip compresses a file to <file>.gz using Go's compress/gzip and then removes the original, mimicking `gzip -f`.
internal
Package secrets exposes the public interface for secret storage implementations the most basic of these is the memory driver that burnafter uses when it cannot access more secure system managed storage.
Package secrets exposes the public interface for secret storage implementations the most basic of these is the memory driver that burnafter uses when it cannot access more secure system managed storage.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL