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
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
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
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
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)
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.go
Terminal window
package 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
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
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 ./...
Terminal window
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" static
Terminal window
Then use the same build command for either type:
$ PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig" CGO_ENABLED=1 go build -tags pkgconfig ./...
Terminal window
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.
Last updated Oct 08, 2026