Help us shape the future of Go! Take the 2026 Go Developer Survey

secrets

package
v0.37.1 Latest Latest
Warning

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

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package secrets resolves a named secret (an API token, a serial, ...) from a fixed, security-conscious chain and nothing else.

Resolution order (first hit wins):

  1. the process environment — os.Getenv(NAME), trimmed; empty is "unset"
  2. the user-scope file — $XDG_CONFIG_HOME/bentoo/secrets (else ~/.config/bentoo/secrets)
  3. the system-scope file — /etc/bentoo/secrets

The files are ".env" style: NAME=value per line, "# ..." comments, an optional "export " prefix, and matched surrounding quotes stripped from the value. A missing file is a miss, never an error; a present-but-unreadable user file is ErrUnreadable, while a permission error on the root-owned system file is a silent miss (a normal user cannot read it by design).

Config files (config.yaml) are deliberately NOT part of this chain: a secret never belongs in a checked-in or world-readable config, so this package will not read them.

One deliberate exception lives OUTSIDE this package: the ${VAR} header expansion in packages.toml is an env-only substitution and stays on os.Getenv by design — it must never consult the secrets files, so it does not route through this package.

The package depends only on the Go standard library.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnreadable = errors.New("secrets: file present but unreadable")

ErrUnreadable wraps the underlying cause when a secrets file exists but its contents cannot be read or parsed. Callers match it with errors.Is. A missing file is deliberately NOT this error — absence is a normal "no secret set".

Functions

func Lookup

func Lookup(name string) (value string, found bool, err error)

Lookup resolves name across the fixed chain env → user file → system file and returns the first hit.

  • found=false, err=nil → not set anywhere (the normal "no secret" case)
  • found=true, err=nil → value holds the resolved secret
  • err != nil → a file exists but could not be read or parsed

The environment is consulted first: a non-empty os.Getenv(name) (trimmed) is returned without touching any file; an empty or whitespace-only value is treated as unset and falls through. A missing file is a miss, never an error. A present-but-unreadable user-scope file — including EISDIR or a permission failure — yields ErrUnreadable so a chmod-000 token can never silently degrade to "anonymous"; a permission error on the root-owned system-scope file is instead a silent miss.

func Paths

func Paths() []string

Paths returns the secrets file locations Lookup searches, in order. Callers use it to build actionable "set NAME or add it to one of: ..." messages. It reflects exactly what Lookup consults — the same entries in the same order, with the internal scope tag dropped.

func Resolved

func Resolved() []string

Resolved returns a copy of every non-empty value Lookup has returned in this process, each once, longest first (ties in lexical order), so that a scrubber replacing them in order redacts a value whole before any shorter value it contains. It is safe to call while Lookup runs on other goroutines.

func Scrub

func Scrub(s, secret string) string

Scrub removes secret from s before it is logged or wrapped into an error, so a transport error echoing a request URL (or any string built from the secret) cannot leak it. Moved verbatim from autoupdate.scrubSecret.

func UserPath

func UserPath() (string, bool)

UserPath returns the location of the user-scope secrets file — the one an unprivileged user can actually create and chmod 600 — and whether the chain has one at all. When $HOME is unresolvable pathsFn drops the user-scope entry entirely and UserPath reports ("", false).

Callers building a "put your secret here" message MUST use this instead of Paths()[0]. Paths mirrors the RESOLUTION ORDER, so index 0 is the user file only WHEN a user file exists; with $HOME unresolvable — precisely the case of the snapshot systemd timer running as root — index 0 is the root-owned /etc/bentoo/secrets, and the message then tells an unprivileged user to write their secret into a 0600 file they cannot open. That failure is silent (Paths() is never empty) and the bad path looks plausible, which is what makes it worth a dedicated accessor.

The scope is read from each entry's own tag and never from its position, so this stays correct if the chain ever grows — the same rule that fixed F-1 inside Lookup.

Types

type Logger

type Logger interface {
	Warn(format string, args ...interface{})
}

Logger is the minimal logging surface secrets needs. It is defined locally so this package keeps depending on the standard library alone: the log/slog logger (internal/common/logging) reads secrets.Resolved, so secrets cannot import it back without a cycle.

Jump to

Keyboard shortcuts

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