Documentation
¶
Overview ¶
Package client is dotvault's public, importable Go API. It exposes dotvault's connectivity, token-resolution, login, and user-path conventions so any Go module can talk to the same Vault, authenticate the same way, and read from the exact path dotvault writes to — without re-implementing any of it and risking silent divergence.
dotvault remains the single source of truth for:
- connectivity (Vault address, TLS, CA),
- token-resolution order (DOTVAULT_TOKEN env → token file → interactive login; VAULT_TOKEN is deliberately ignored — it belongs to the `vault` CLI and must not leak into dotvault's session),
- the login flow itself (OIDC browser, LDAP with MFA),
- the convention mapping an authenticated user to a kv/users/<user>/... path.
Identity / path convention ¶
IMPORTANT: dotvault derives the <user> path segment from the OS user (the current account's username with any DOMAIN\ prefix stripped), NOT from the Vault token. A user logged in via OIDC as alice@corp whose OS account is "alice" has secrets at kv/users/alice/.... IdentityName returns this OS-derived name, and ReadUserSecret composes paths with it, so a consumer reads from exactly where dotvault's sync/enrolment writes. A consumer must therefore run as the same OS user as the dotvault that populated the secrets — typically true, since dotvault runs in the user's own context.
Typical use ¶
cfg, err := client.LoadConfig(client.DefaultConfigPath())
cli, err := client.New(cfg) // optionally: client.New(cfg, client.WithIdentity("alice"))
if err := cli.Authenticate(ctx); err != nil {
// categorise with errors.Is, one sentinel at a time. Authenticate
// yields ErrUnreachable or ErrAuthFailed (it consumes the no-token
// case and logs in); AuthenticateCached is what surfaces
// ErrLoginRequired.
// errors.Is(err, client.ErrUnreachable)
// errors.Is(err, client.ErrAuthFailed)
return err
}
tok, found, err := cli.ReadUserSecret(ctx, "gh", "oauth_token")
Example ¶
Example shows the typical consumer flow: load dotvault's system config, authenticate with the same precedence dotvault uses, then read a known per-user field. Errors are categorised via the exported sentinels.
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/goodtune/dotvault/client"
)
func main() {
ctx := context.Background()
cfg, err := client.LoadConfig(client.DefaultConfigPath())
if err != nil {
log.Fatalf("load dotvault config: %v", err)
}
cli, err := client.New(cfg)
if err != nil {
log.Fatalf("build client: %v", err)
}
// Authenticate: DOTVAULT_TOKEN → token file → interactive login.
if err := cli.Authenticate(ctx); err != nil {
switch {
case errors.Is(err, client.ErrUnreachable):
log.Fatalf("vault unreachable: %v", err)
case errors.Is(err, client.ErrAuthFailed):
log.Fatalf("login failed: %v", err)
default:
log.Fatalf("authenticate: %v", err)
}
}
// Read a known per-user field, e.g. the oauth_token written by the
// github enrolment engine. The (value, found, err) triple keeps a
// not-yet-enrolled secret distinct from a transport failure.
token, found, err := cli.ReadUserSecret(ctx, "gh", "oauth_token")
if err != nil {
log.Fatalf("read secret: %v", err)
}
if !found {
log.Fatal("gh/oauth_token not enrolled; run `dotvault enrol gh`")
}
fmt.Printf("resolved oauth_token (%d bytes)\n", len(token))
}
Output:
Index ¶
- Variables
- func DefaultConfigPath() string
- func DefaultTokenFile() (path string)
- type Client
- func (c *Client) Authenticate(ctx context.Context) error
- func (c *Client) AuthenticateCached(ctx context.Context) error
- func (c *Client) Browse(ctx context.Context, rawURL string) error
- func (c *Client) Clipboard(ctx context.Context, text string) error
- func (c *Client) IdentityName() (string, error)
- func (c *Client) Login(ctx context.Context) error
- func (c *Client) Notify(ctx context.Context, level, title, body, actionURL string) error
- func (c *Client) ReadKVField(ctx context.Context, mount, path, field string) (string, bool, error)
- func (c *Client) ReadUserSecret(ctx context.Context, service, field string) (string, bool, error)
- func (c *Client) Token() string
- type Config
- type Option
- type Reader
- type VaultConfig
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrLoginRequired indicates no usable cached token was found — neither // DOTVAULT_TOKEN nor the token file yielded a token that LookupSelf accepts. // It is returned by AuthenticateCached (which never prompts). Authenticate // does not return it: on a reachable Vault it consumes this condition and // proceeds to an interactive Login instead. ErrLoginRequired = errors.New("dotvault: login required (no valid cached token)") // ErrAuthFailed indicates the configured fresh-auth flow (Login, or the // login fallback inside Authenticate) ran but did not yield a usable // token. This covers a genuine auth failure (bad password, declined MFA, // OIDC callback error, no TTY for an LDAP prompt) as well as a // misconfigured auth method (an unsupported AuthMethod, or AuthMethod // "token" with no token on disk — for which Login has nothing to do). // It is distinct from ErrLoginRequired, which means a fresh login was // not attempted at all. ErrAuthFailed = errors.New("dotvault: authentication failed") // ErrDenied indicates Vault rejected a KV read with 401/403 — the token // is missing the required policy, or was revoked between the LookupSelf // check and the read (see TestReadKVField_Denied). Note that a 401/403 // from validating a *cached* token during AuthenticateCached is reported // as ErrLoginRequired instead (the token needs replacing, not the // caller's authority), so ErrDenied is the read-path authorisation // failure, not every 403 the package sees. ErrDenied = errors.New("dotvault: vault denied the request") // ErrUnreachable indicates the Vault server could not be reached // (DNS, connection refused, TLS handshake, timeout) or could not service // the request right now (5xx, or 429 rate-limiting) — i.e. a retryable // transport/availability problem rather than an authorisation decision. ErrUnreachable = errors.New("dotvault: vault unreachable") // not be completed by the peer dotvault named by TokenSocket: the socket // is not configured, the peer could not be reached (missing/stale socket, // SSH forward down), or the peer answered that it could not perform the // action (e.g. a 502 because a browser opener failed, or a 503 because it // was busy). It is the peer-side analogue of ErrUnreachable — a retryable // availability problem, distinct from a request the peer rejected as // invalid, which surfaces as a plain (uncategorised) error carrying the // peer's message. ErrPeerUnavailable = errors.New("dotvault: peer action unavailable") )
Sentinel errors expose a small, stable set of failure categories so callers can map outcomes onto metrics without string-matching. Every error returned by this package that fits one of these categories wraps the corresponding sentinel, so callers use errors.Is rather than comparing values directly.
The categories line up with the outcomes a consumer tracks:
success → nil error missing_token → ErrLoginRequired denied → ErrDenied, ErrAuthFailed unreachable → ErrUnreachable missing_field → (value, false, nil) from ReadKVField/ReadUserSecret
ErrAuthFailed covers an interactive login that started but did not yield a usable token (bad password, declined MFA, OIDC callback error). It is distinct from ErrLoginRequired, which means "no usable token was found and no interactive login was attempted"; a consumer that buckets outcomes for metrics can fold it into the same "denied" label as ErrDenied, but it is a separate sentinel so callers that want to distinguish "wrong creds" from "no creds offered" can.
Every categorised error wraps one of these sentinels with %w, so a caller can errors.Is it. Where there is an underlying Vault cause, that cause is wrapped too (a second %w), so the same value also errors.As to a *vaultapi.ResponseError; the no-token branch of AuthenticateCached has no such cause and wraps only the sentinel. The wrapped text comes from Vault's API error (which echoes the server response body, never the request token) plus the mount/path being read — none of it carries token material, so callers may log these errors verbatim.
New's own input-validation errors (nil config, missing address) are plain errors, not categorised: they are programmer errors surfaced before any Vault interaction, outside the sentinel taxonomy below.
Functions ¶
func DefaultConfigPath ¶
func DefaultConfigPath() string
DefaultConfigPath returns the platform-appropriate path to dotvault's system config file — the same file the daemon loads. On Linux this is /etc/xdg/dotvault/config.yaml (honouring XDG_CONFIG_DIRS).
func DefaultTokenFile ¶
func DefaultTokenFile() (path string)
DefaultTokenFile returns the platform-appropriate path to the Vault token file dotvault reads and writes (~/.dotvault-token), or "" if the OS home directory cannot be resolved.
paths.VaultTokenPath panics (via mustHomeDir) when os.UserHomeDir fails — acceptable inside the daemon, but a public library must not panic on a recoverable environment condition. We therefore guard it and return "" rather than fabricating a path. An empty token-file path is already well-defined throughout the package: token resolution simply skips the file and uses DOTVAULT_TOKEN only. Returning "" (not a relative ".dotvault-token", which would be cwd-dependent and could silently diverge from where the daemon looks) keeps that contract honest; a caller that needs a specific location sets Config.TokenFile explicitly.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client wraps a Vault client with dotvault's auth and KV-read conventions. Construct one with New. A Client is not safe for concurrent Authenticate / Login calls, but concurrent reads after authentication are fine.
func New ¶
New constructs a Client from cfg, applying any options. It builds the underlying Vault client (applying TLS/CA settings) but performs no network calls and does not authenticate — call Authenticate (or Login) before reading secrets.
Empty optional fields in cfg are filled with dotvault's defaults (KVMount "kv", UserPrefix "users/", TokenFile ~/.dotvault-token), so a directly constructed Config behaves the same as one returned by LoadConfig.
func (*Client) Authenticate ¶
Authenticate makes the Client hold a usable Vault token, following dotvault's precedence:
- DOTVAULT_TOKEN environment variable,
- the configured token file,
- if neither yields a token Vault accepts, the configured fresh-auth flow (OIDC browser / LDAP terminal prompt — the same path as `dotvault login`).
If Vault is unreachable, it returns an error wrapping ErrUnreachable without attempting an interactive login (no point prompting when the server is down). If a fresh login is required but fails, the error wraps ErrAuthFailed.
Use AuthenticateCached when interactive login must not happen (e.g. a side-effect-free health check).
func (*Client) AuthenticateCached ¶
AuthenticateCached resolves a usable Vault token, trying each source in turn and validating it with a LookupSelf: DOTVAULT_TOKEN, then the token file, then — if a peer socket is configured — by borrowing a live token from the peer dotvault over that socket. It never initiates an interactive login. It returns nil once a candidate validates, an error wrapping ErrLoginRequired if no source yields a usable token (missing, expired, or revoked), or an error wrapping ErrUnreachable if Vault cannot be reached to validate.
A cached token that a *reachable* Vault rejects does not end the search: the peer socket is tried next, so a remote host with a stale local token file but a live peer still recovers — mirroring the daemon's startup path, which clears a rejected token and then borrows. An ErrUnreachable from validating any candidate short-circuits immediately: when Vault is down a borrowed token could not be validated either, so there is nothing to gain by trying.
The socket borrow stays within the side-effect-free contract: it is a plain HTTP GET over a Unix socket (the equivalent of `curl --unix-socket <path> http://localhost/api/v1/token`) — no browser, no password prompt — so a consumer that runs on a host with no usable local token but a live peer socket (the SSH RemoteForward topology) authenticates without an interactive Login. Best-effort: a missing/stale socket simply yields no candidate.
This is the entry point for callers that must remain side-effect-free — no browser pop, no password prompt — such as a `doctor`/preflight check.
Example ¶
ExampleClient_AuthenticateCached shows the side-effect-free preflight a `doctor` subcommand would use: it never opens a browser or prompts. A missing or expired token surfaces as ErrLoginRequired rather than dropping the user into a login flow.
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/goodtune/dotvault/client"
)
func main() {
cfg, err := client.LoadConfig(client.DefaultConfigPath())
if err != nil {
log.Fatal(err)
}
cli, err := client.New(cfg)
if err != nil {
log.Fatal(err)
}
switch err := cli.AuthenticateCached(context.Background()); {
case err == nil:
fmt.Println("cached vault token is usable")
case errors.Is(err, client.ErrLoginRequired):
fmt.Println("run `dotvault login` (or let Authenticate prompt next run)")
case errors.Is(err, client.ErrUnreachable):
fmt.Println("vault is unreachable")
}
}
Output:
func (*Client) Browse ¶ added in v0.29.0
Browse asks the peer dotvault named by the configured TokenSocket to open rawURL in a browser on the peer's host — the programmatic equivalent of `dotvault browse <url>` and of
curl --unix-socket <TokenSocket> http://localhost/api/v1/remote/browse -d url=<rawURL>
It is for the headless-consumer topology: a program on a machine with no browser hands a URL back over the same SSH-forwarded socket it borrows its token from, so a browser-driven flow (an OAuth page, a report link) opens on the workstation where a human is looking. Unlike `dotvault browse`, the facade has no local fallback — a library on a headless box has no local browser to fall back to — so a peer that cannot be reached is an error, not a silent local open.
The URL is validated authoritatively by the peer endpoint (http/https only, a host, no embedded credentials); a rejected URL comes back as a plain (uncategorised) error carrying the peer's message. A peer that cannot be reached, or that reports it could not open the browser, wraps ErrPeerUnavailable. Returns nil once the peer reports the browser opened.
func (*Client) Clipboard ¶ added in v0.30.0
Clipboard asks the peer dotvault named by the configured TokenSocket to put text on the clipboard of the peer's host — the programmatic equivalent of `dotvault clipboard` and the third peer action over the same socket. Where Browse opens a login page on the workstation and Notify tells the user something happened, Clipboard delivers the value they need to paste — a one-time token, a device code — so a headless program can stage the credential right where the user's Ctrl+V is. Like the other peer actions there is no local fallback: an unreachable peer is an error, never a write to the headless host's (typically non-existent) clipboard.
The text is validated authoritatively by the peer endpoint (non-empty, valid UTF-8, no NUL bytes, at most 64 KiB) and written verbatim — the peer never logs the content, only its length. A rejected text comes back as a plain (uncategorised) error carrying the peer's message. A peer that cannot be reached, or that reports it could not write the clipboard, wraps ErrPeerUnavailable. Returns nil once the peer reports the clipboard set.
func (*Client) IdentityName ¶
IdentityName returns the <user> path segment dotvault uses to lay out kv/users/<user>/.... This is the OS username with any DOMAIN\ prefix stripped — NOT a value derived from the Vault token (display_name, entity name, or token metadata). Consumers reading per-user secrets MUST use this so they hit the same path dotvault writes to.
It performs no Vault call and takes no context: the value comes from the OS account the process runs as, unless overridden with WithIdentity. Callers that need secrets written by a given dotvault instance must either run as the same OS user or set WithIdentity to that user's name.
func (*Client) Login ¶
Login runs the configured fresh-auth flow unconditionally, ignoring any cached token — the equivalent of `dotvault login`. OIDC opens a browser; LDAP prompts for a password (and MFA) on the terminal. On success the new token is written to the configured token file (matching dotvault) and held on the Client. Any failure to produce a token — a genuine auth failure or a misconfigured auth method (unsupported AuthMethod, or "token" with nothing on disk) — returns an error wrapping ErrAuthFailed.
Login requires an interactive context for LDAP (a terminal on stdin); it will not prompt when stdin is not a TTY and instead returns an error wrapping ErrAuthFailed. Headless callers (including the Windows GUI-subsystem binary, which has no console) should drive auth through OIDC, or stick to AuthenticateCached and surface ErrLoginRequired to the operator.
func (*Client) Notify ¶ added in v0.29.0
Notify asks the peer dotvault named by the configured TokenSocket to raise a native desktop notification on the peer's host — the programmatic equivalent of `dotvault notify <level> <title> [body]`. It is the notification sibling of Browse over the same socket: a long-running job on a headless box surfaces a toast/notification on the workstation.
level must be one of "info", "warning", "error", "attention"; title is required; body and actionURL are optional. actionURL, when set, is an http/https link the notification takes the user to when clicked — clickable on Windows, appended to the body on macOS/Linux (see the notify docs). The level, text, and action URL are validated and sanitized authoritatively by the peer endpoint (the same neutralization the daemon applies before delivery), so a bad level, empty title, or malformed action URL comes back as a plain (uncategorised) error carrying the peer's message. A peer that cannot be reached, or that reports it could not deliver, wraps ErrPeerUnavailable.
func (*Client) ReadKVField ¶
ReadKVField reads a single field from a KV v2 secret at the given mount and path. It returns:
- (value, true, nil) when the secret exists and the field is present;
- ("", false, nil) when the secret exists but the field is absent, OR the secret path does not exist (both are "the field you asked for isn't there", which callers map to a missing_field outcome);
- ("", false, err) for transport/auth failures, wrapping ErrUnreachable or ErrDenied.
Caveat: Vault answers a read against a missing or disabled KV mount with a 404, which is indistinguishable here from a not-yet-written secret — both yield ("", false, nil). So a wrong mount (a mis-set kv_mount) reads as "not enrolled" rather than an error. A caller that wants to tell a misconfigured deployment apart from an un-enrolled user should verify the mount independently (e.g. a known-present sentinel path) rather than infer it from found == false.
Non-string field values are stringified via fmt's %v: numbers and bools render as you'd expect; a nested object or array renders as its Go-syntax form (map[...]/[...]). dotvault stores credential material as strings, so in practice the fields a consumer reads are already strings.
func (*Client) ReadUserSecret ¶
ReadUserSecret reads a single field from kv/users/<IdentityName>/<service>, using the configured KV mount and user prefix. It is IdentityName + ReadKVField composed, with dotvault owning the path layout end-to-end: {KVMount}/{UserPrefix}{identity}/{service}, field {field}.
Example: ReadUserSecret(ctx, "gh", "oauth_token") reads the oauth_token field of kv/users/<user>/gh. Return semantics match ReadKVField.
type Config ¶
type Config struct {
Vault VaultConfig
// TokenFile is the path to the Vault token file consulted after
// DOTVAULT_TOKEN. Empty means dotvault's platform default:
// .dotvault-token in the user's home directory (resolved via
// os.UserHomeDir). dotvault does not expose this in its YAML today;
// it is here as a programmatic override point and defaults to the
// canonical location.
TokenFile string
}
Config is the connectivity-and-auth view of dotvault's system config. It is a deliberately narrow projection of the full dotvault configuration: only the fields needed to talk to Vault, authenticate, and locate a user's secrets are exposed. Sync rules, enrolment definitions, web UI, and observability settings stay internal to dotvault and are not part of this surface.
A Config can be produced two ways:
- LoadConfig parses dotvault's on-disk system config (the same file the daemon reads), so a consumer inherits the operator's connectivity and auth settings verbatim. This is the recommended path — it keeps dotvault the single source of truth.
- Constructed directly by a caller that already knows its connectivity (useful for tests, or callers wiring values from another source). New applies the same defaults LoadConfig would (KVMount "kv", UserPrefix "users/", TokenFile ~/.dotvault-token).
func LoadConfig ¶
LoadConfig parses dotvault's system config at path and projects it onto the connectivity-and-auth Config. Pass DefaultConfigPath() for the canonical location. The file is parsed and validated by dotvault's own loader, so a malformed or incomplete config (missing vault.address, etc.) surfaces the same error the daemon would report.
On Windows, if Group Policy registry keys are present, dotvault loads its config from the registry and ignores the file; LoadConfig follows that same precedence via the shared loader.
type Option ¶
type Option func(*Client)
Option configures a Client at construction time. Options are the forward-compatible extension point for New: new behaviour can be added as an Option without changing New's signature, so existing callers keep compiling. See WithIdentity.
Options are applied to the already-built Client after the underlying Vault client is constructed, so they tune the Client's own behaviour. An option that needs to influence Vault-client construction itself (a custom HTTP transport, say) would require New to grow a separate build step first; the current options do not.
func WithIdentity ¶
WithIdentity overrides the identity segment used to lay out kv/users/<identity>/... paths. By default the Client derives it from the OS user (see IdentityName), which assumes the consumer runs as the same OS account as the dotvault that wrote the secrets. A consumer that runs under a different account (a service, a container) — or a test that needs a deterministic identity — sets this explicitly. It does not change the username used for an interactive LDAP login prompt, only the KV path.
The value is interpolated verbatim into the Vault KV path and is not sanitised — it is a caller-controlled value used by the caller's own token, and what that token can read is bounded by its Vault policy regardless of the path composed, so this grants no authority the token didn't already have. An empty string is ignored (the OS user is used).
type Reader ¶
type Reader interface {
// IdentityName returns the kv/users/<identity>/... path segment.
IdentityName() (string, error)
// ReadKVField reads one field of a KV v2 secret. See Client.ReadKVField.
ReadKVField(ctx context.Context, mount, path, field string) (string, bool, error)
// ReadUserSecret reads kv/users/<identity>/<service> field <field>.
ReadUserSecret(ctx context.Context, service, field string) (string, bool, error)
}
Reader is the read-side contract a consumer depends on after a Client is authenticated. It exists so downstream code can accept this narrow interface and substitute a fake in tests without standing up a Vault — the shape is owned here so every consumer fakes the same thing and the methods can't drift between them. *Client satisfies it.
Authentication (Authenticate/Login) is intentionally excluded: it has side effects (token file writes, browser/terminal interaction) that belong to process wiring, not to the unit under test. Construct and authenticate a real *Client in main; depend on Reader everywhere a secret is consumed.
Example ¶
ExampleReader shows how a consumer tests code that reads secrets by substituting a fake for the live client — the recommended pattern.
package main
import (
"context"
"fmt"
"log"
"github.com/goodtune/dotvault/client"
)
// fetchCreds is the kind of helper a consumer would write: it depends on the
// narrow client.Reader interface, never on *client.Client, so it can be unit
// tested against a fake without a live Vault.
func fetchCreds(ctx context.Context, r client.Reader) (string, error) {
tok, found, err := r.ReadUserSecret(ctx, "gh", "oauth_token")
if err != nil {
return "", err
}
if !found {
return "", fmt.Errorf("gh/oauth_token not enrolled")
}
return tok, nil
}
// fakeReader is a hand-written test double satisfying client.Reader. A
// consumer drops one of these into its own tests; no Vault, no network.
type fakeReader struct {
identity string
secrets map[string]string
}
func (f fakeReader) IdentityName() (string, error) { return f.identity, nil }
func (f fakeReader) ReadKVField(_ context.Context, _, _, _ string) (string, bool, error) {
return "", false, nil
}
func (f fakeReader) ReadUserSecret(_ context.Context, service, field string) (string, bool, error) {
v, ok := f.secrets[service+"/"+field]
return v, ok, nil
}
func main() {
r := fakeReader{
identity: "alice",
secrets: map[string]string{"gh/oauth_token": "ghp_fake"},
}
tok, err := fetchCreds(context.Background(), r)
if err != nil {
log.Fatal(err)
}
fmt.Println(tok)
}
Output: ghp_fake
type VaultConfig ¶
type VaultConfig struct {
// Address is the Vault server URL (e.g. https://vault.example.com:8200).
// Required.
Address string
// CACert is the path to a PEM CA bundle for verifying the Vault server.
CACert string
// TLSSkipVerify disables TLS verification. Insecure; for dev only.
TLSSkipVerify bool
// KVMount is the KV v2 mount that holds user secrets. Defaults to "kv".
KVMount string
// UserPrefix is the path prefix under which per-user secrets live.
// Defaults to "users/" and is normalised to carry exactly one trailing
// slash, so the full layout is {KVMount}/{UserPrefix}{identity}/{service}.
UserPrefix string
// AuthMethod is the fresh-auth method dotvault uses when no cached token
// is usable: "oidc", "ldap", "token", "mtls", or "mtls+tpm". A "+tpm"
// suffix on any base method (e.g. "oidc+tpm") additionally TPM-seals the
// cached token file at rest. Reads are transparent to this — a sealed
// token file is self-describing and unsealed automatically — so a consumer
// inherits the operator's setting verbatim and need not branch on it.
AuthMethod string
// AuthMount is the auth backend mount path (defaults per method: "oidc"
// or "ldap").
AuthMount string
// AuthRole is an optional Vault role passed to the auth method.
AuthRole string
// OIDCCallbackPort is the fixed local TCP port an interactive OIDC Login
// binds for the OAuth redirect_uri. Zero (the default) resolves to 8250,
// matching the `vault` CLI's own default; if that port is unavailable,
// Login falls back to a random port. Mirrors vault.oidc_callback_port.
OIDCCallbackPort int
// Policies is the least-privilege set of Vault policies the working token
// should carry. When non-empty, an interactive Login exchanges the login
// token for a child token restricted to exactly these policies (Vault
// enforces the subset rule). Empty inherits every policy the auth role
// granted — dotvault's historical behaviour. Mirrors vault.policies.
Policies []string
// NoDefaultPolicy strips the implicit `default` policy from the working
// token. Mirrors vault.no_default_policy. Combine with Policies to pin the
// token to exactly the capabilities the consumer needs.
NoDefaultPolicy bool
// TokenSocket is an optional path to a peer dotvault daemon's web-API
// Unix socket. When set, an interactive Login first tries to borrow a
// live token from the peer over the socket (the equivalent of
// `curl --unix-socket <path> http://localhost/api/v1/token`) before
// running the configured auth flow — the dotvault-to-dotvault sharing
// seam. A missing or stale socket is ignored. A leading ~ is expanded.
TokenSocket string
// APISocket is an optional path to the *local* dotvault daemon's API
// socket (mirrors the api section: the resolved api.unix.path, or the
// per-user runtime default when api.enabled is set without a path).
//
// It is the same endpoint as TokenSocket and is tried ahead of it,
// because the two differ in lifetime rather than capability: the local
// socket is served by the long-lived per-user daemon, while TokenSocket
// is typically an SSH RemoteForward that vanishes when the session drops.
// A consumer started inside an SSH session therefore keeps borrowing
// successfully after that session ends.
//
// Borrow direction only. The peer actions (Browse / Notify / Clipboard)
// deliberately keep using TokenSocket: their whole purpose is to reach
// the workstation where a human is looking, and sending them to the local
// daemon would open a browser on the headless host nobody is sitting at.
APISocket string
}
VaultConfig mirrors the connectivity + auth fields of dotvault's vault: config stanza.
Vault namespaces are not a dotvault YAML field; the underlying Vault client honours the VAULT_NAMESPACE environment variable, so namespaced deployments work without an explicit field here.