Go SDK
The Go SDK (secretspec-go) is a thin client over the libsecretspec C ABI,
loaded via purego (dlopen, no cgo).
Resolution happens in the Rust core, so the SDK inherits every provider with no
Go-side logic.
Quick start
Section titled “Quick start”package main
import (
"fmt"
"log"
secretspec "github.com/cachix/secretspec/secretspec-go"
)
func main() {
resolved, err := secretspec.New().
WithProvider("keyring://").
WithProfile("production").
WithReason("boot web app").
Load()
if err != nil {
log.Fatal(err)
}
fmt.Println(resolved.Provider, resolved.Profile)
db := resolved.Secrets["DATABASE_URL"]
fmt.Println(db.Get()) // the value, or the file path for as_path secrets
resolved.SetAsEnv() // export everything into the process environment
}
A missing required secret returns *MissingRequiredError; any other failure
returns *Error (with a stable .Kind).
Caller context
Section titled “Caller context”builder := secretspec.New().WithCaller(secretspec.CallerContext{ Name: "git", Version: "2.51.0", Operation: "credential_get", Resource: "github.com",})Caller context identifies the invoking integration in audit records but never
satisfies require_reason. Do not put credentials or secret values in it.
Inline specifications
Section titled “Inline specifications”Applications that own their declarations in code can resolve a strict JSON
inline specification without creating a temporary secretspec.toml file. Pass
the wire document to WithInlineSpec; baseDir resolves relative provider
paths just as a manifest’s directory would.
spec := map[string]any{ "project": map[string]any{"name": "my-app"}, "profiles": map[string]any{ "default": map[string]any{"secrets": map[string]any{ "API_TOKEN": map[string]any{"description": "API token"}, }}, },}resolved, err := secretspec.New(). WithInlineSpec(spec, "/logical/project"). WithReason("application startup"). Load()Inline specification v1 uses project, profiles, and each profile’s
secrets object; optional providers, scopes, profile defaults, and the
normal secret declaration fields are also supported. Unknown declaration fields
are rejected. project.extends resolves parent manifests relative to baseDir.
The SDK requires the native
secretspec_call capability for inline specs; an older library returns a
capability error rather than falling back to a filesystem search.
Scopes
Section titled “Scopes”Use WithScope("api") to resolve only a named [scopes.api] subset. The
selected name is available as Resolved.Scope and Report.Scope:
package main
import (
"log"
secretspec "github.com/cachix/secretspec/secretspec-go"
)
func main() {
resolved, err := secretspec.New().WithScope("api").Load()
if err != nil {
log.Fatal(err)
}
defer resolved.Close()
}
Typed access (codegen)
Section titled “Typed access (codegen)”Generate typed structs with secretspec schema plus
quicktype, then unmarshal resolved.FieldsJSON():
$ secretspec schema | quicktype -s schema --top-level SecretSpec --lang go -o secrets_gen.gopackage main
import (
"fmt"
"log"
secretspec "github.com/cachix/secretspec/secretspec-go"
)
func main() {
resolved, err := secretspec.New().Load()
if err != nil {
log.Fatal(err)
}
defer resolved.Close()
data, _ := resolved.FieldsJSON()
typed, _ := UnmarshalSecretSpec(data) // typed, generated
fmt.Println(typed.DatabaseURL)
}
Library discovery
Section titled “Library discovery”The native libsecretspec cdylib is resolved at runtime, in order:
- The
SECRETSPEC_FFI_LIBenvironment variable (an explicit path). - A library embedded at build time with
-tags embed_lib. - A Cargo
targetdirectory found by searching up from the working directory (the development path).
The SDK uses purego, so the cdylib is
loaded at runtime, not linked. Either install/build libsecretspec and set
SECRETSPEC_FFI_LIB, or stage the per-platform library into lib/ and build
with -tags embed_lib for a self-contained binary. The embedded library is
extracted to a per-user, owner-only cache directory at first use, and is not
distributed through the Go module proxy.
Static linking
Section titled “Static linking”For a self-contained binary with no runtime library to locate, build with
-tags static instead. This uses cgo and links libsecretspec.a directly
into the Go binary. In a development checkout:
$ bash scripts/stage-staticlib.sh
$ CGO_ENABLED=1 go build -tags static ./...Linking with pkg-config
Section titled “Linking with pkg-config”Install one library type with cargo-c:
# Use "static" (the default) or "shared"; use separate prefixes for both.$ bash libsecretspec/scripts/cinstall.sh "$PREFIX" staticThen use the same build command for either type:
$ PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig" CGO_ENABLED=1 go build -tags pkgconfig ./...Unlike staging, this also works for a go get dependency. A shared install in
a non-system prefix also requires PREFIX/lib in the platform’s runtime
library search path.