secrets

package
v0.8.56 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package secrets stores small dejima-held secrets (webhook HMAC keys and the like) at rest in the OS keychain when one is available — the macOS login Keychain via `security`, or the Secret Service (libsecret) via `secret-tool` on Linux — and falls back to a 0600 file under ~/.dejima/secrets otherwise.

The fallback is deliberate, not a failure mode: a `--system` daemon starts at boot *before any login*, when the login keychain is still locked, so a keychain write/read can fail until someone logs in once. Rather than hard-fail (the trap the roadmap calls out — reporting "never configured" when the keychain is merely locked), every operation degrades to the file store. So a secret set while the keychain was locked lands in the file and is still readable; secrets set while unlocked live in the keychain.

Index

Constants

View Source
const BackendEnvVar = "DEJIMA_SECRETS_BACKEND"

BackendEnvVar forces the storage backend. Set it to "file" to skip the OS keychain entirely.

Two real uses beyond preference: tests must not write into the operator's login Keychain (entries would survive the run and need manual cleanup), and an operator on a headless box may prefer the file store rather than a keychain that is locked more often than not.

Variables

View Source
var ErrNotFound = errors.New("secret not found")

ErrNotFound is returned when a named secret isn't in the island's store.

Functions

func Fingerprint added in v0.8.28

func Fingerprint(value string) string

Fingerprint is the first 8 hex characters of the value's SHA-256.

func Reserved added in v0.8.28

func Reserved(name string) bool

Reserved reports whether name is rejected, without building an error — for callers that only need the predicate (e.g. UI hints).

func ValidateName added in v0.8.28

func ValidateName(name string) error

ValidateName reports whether name may be used as a secret. The error names the reason, since "rejected" without a why invites the operator to work around it.

Matching is CASE-INSENSITIVE. Environment variables are case-sensitive on Unix, but `http_proxy` and `HTTP_PROXY` are both honoured by common tooling, so a case-sensitive check would leave the lowercase spelling wide open.

Types

type IslandStore added in v0.8.28

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

IslandStore manages per-island secrets on top of the keychain-backed Store.

func OpenIsland added in v0.8.28

func OpenIsland() (*IslandStore, error)

OpenIsland builds an IslandStore over the platform keychain (with the file fallback the Store already provides).

func (*IslandStore) Backend added in v0.8.28

func (s *IslandStore) Backend() string

Backend names where values are actually kept, for diagnostics.

func (*IslandStore) Get added in v0.8.28

func (s *IslandStore) Get(island, name string) (string, error)

Get returns a secret's VALUE. Daemon-internal only — this is what materialization uses. It must never be reachable from an API handler; the value has no route outward by design.

func (*IslandStore) List added in v0.8.28

func (s *IslandStore) List(island string) ([]Meta, error)

List returns the island's secrets as metadata, name-sorted. Values are not included and cannot be — Meta has no field for one.

func (*IslandStore) Names added in v0.8.28

func (s *IslandStore) Names(island string) ([]string, error)

Names returns the island's secret names, sorted. Safe for an in-island caller: knowing WHICH secrets exist is useful to an agent and reveals nothing the environment wouldn't already.

func (*IslandStore) Purge added in v0.8.28

func (s *IslandStore) Purge(island string) error

Purge removes an island's secrets — values and metadata — so they never outlive the island they were scoped to.

func (*IslandStore) Redact added in v0.8.28

func (s *IslandStore) Redact(island, text string) string

Redact replaces this island's stored values in text with a named placeholder. Used to mask secrets in `dejima logs`, which is the likeliest real leak — a tool echoing its own configuration.

func (*IslandStore) Redactor added in v0.8.28

func (s *IslandStore) Redactor(island string) func(string) string

Redactor snapshots this island's values ONCE and returns a masking function.

Use it for streams. Redact re-reads the store on every call, which is fine for a one-shot string but wrong for a log follow that may run for hours: it would re-read the keychain per line, costing both throughput and (on macOS) a stream of keychain access prompts.

The snapshot means a secret set mid-stream isn't masked until the next call — acceptable, since it also wasn't in the environment of the process that wrote those lines. Returns identity when there's nothing to mask, so the common case costs nothing.

func (*IslandStore) Remove added in v0.8.28

func (s *IslandStore) Remove(island, name string) error

Remove deletes a secret. Removing one that isn't there is not an error — teardown paths shouldn't have to check first.

func (*IslandStore) Set added in v0.8.28

func (s *IslandStore) Set(island, name, value, setBy string) (Meta, error)

Set stores or rotates a secret.

Rotation preserves CreatedAt and bumps UpdatedAt, so the TUI can show both "added" and "last rotated" — rotation being the actual defence here, whether it's happening should be visible.

func (*IslandStore) Values added in v0.8.28

func (s *IslandStore) Values(island string) (map[string]string, error)

Values returns every name/value pair for materialization. Daemon-internal, same rule as Get.

A name present in metadata whose value can't be read is SKIPPED rather than failing the whole set: on a --system daemon the keychain may still be locked at boot, and one unreadable secret shouldn't stop an island from starting with the others.

type Meta added in v0.8.28

type Meta struct {
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	SetBy     string    `json:"set_by,omitempty"`

	// Fingerprint is sha256(value)[:8]. Values are never shown after entry —
	// not even a prefix, since leading bytes are often the identifying,
	// high-entropy part and are exactly what leaks into screenshots. A
	// fingerprint leaks nothing and still lets an operator confirm the stored
	// secret matches their copy by hashing it locally.
	Fingerprint string `json:"fingerprint"`

	// RequireApproval is stored from v1 but NOT enforced: per-use approval needs
	// brokered fetch (the agent must ask), which environment injection cannot
	// observe. Persisting it now means that lands without a migration.
	// See docs/secrets-manager-spec.md § Deferred.
	RequireApproval bool `json:"require_approval,omitempty"`
}

Meta is everything about a secret EXCEPT its value. This is what the API, the TUI and the SDK see; there is deliberately no field for a value, so no code path can serialize one outward by accident.

type Store

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

Store reads/writes secrets, preferring the OS keychain and falling back to a file. It's safe for concurrent use.

func Open

func Open() (*Store, error)

Open builds a Store: the platform keychain backend if its CLI is on PATH, plus the always-available file fallback.

func (*Store) Backend

func (s *Store) Backend() string

Backend names the active keychain backend ("macos-keychain", "secret-service", or "file" when no keychain is usable) — for diagnostics.

func (*Store) Delete

func (s *Store) Delete(account string) error

Delete removes the secret from both backends (best-effort on the keychain).

func (*Store) Get

func (s *Store) Get(account string) (string, bool, error)

Get returns the secret for account. It prefers the keychain; on a miss, a locked keychain, or any keychain error it falls through to the file store, so a secret written under either backend is found.

func (*Store) Set

func (s *Store) Set(account, value string) error

Set stores the secret. It writes the keychain when one is usable (and clears any stale file copy so the keychain stays authoritative); if the keychain is unavailable or locked, it writes the file fallback instead.

Jump to

Keyboard shortcuts

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