← 返回日报
精读 预计 2 分钟

SecretSpec 0.13: SDKs for Python, Node.js, Go, Ruby, and Haskell

摘要

SecretSpec 0.13 发布 SDKs,支持 Python(pyo3)、Node.js(napi-rs)、Go(purego)、Ruby(C extension)、Haskell(Haskell FFI),均为 Rust 核心薄包装,同一解析逻辑、提供商、profile、fallback 与 generator。每个 SDK 使用相同 builder load () 流程,resolve () 返回 secret map,缺失 required secret 抛出 MissingRequiredError,path secrets 返回文件路径并自动 cleanup,access reason 驱动审计日志与 require reason policy。另提供 secretspec-ffi C ABI 可供自定义绑定,并通过 JSON Schema 驱动每个语言的 type system,无需手写 emitter。

荐读理由

SecretSpec 0.13 提供 Python、Node.js、Go、Ruby、Haskell 原生 SDK,每个都是薄薄一层封装同一 Rust 内核,零配置就按 manifest 拉取所有声明的 secrets,支持相同 providers、profiles、fallback chains 和 audit log。Python 用 pyo3 wheel,Go 用 purego,Haskell 用 FFI,一天内所有语言都能立即用新增 provider。

原文

SecretSpec 0.13: SDKs for Python, Node.js, Go, Ruby, and Haskell

Jul 3, 2026

Domen Kožar

SecretSpec separates what secrets an application needs, declared in secretspec.toml, from where the values live, a provider like your system keyring, 1Password, or Vault. Until now, reading those resolved secrets at runtime meant the CLI or the Rust SDK. If your service was written in Python or Go, you shelled out to secretspec run or reimplemented resolution yourself.

SecretSpec 0.13 closes that gap. It ships native SDKs for five languages: Python, Node.js / TypeScript, Go, Ruby, and Haskell. Each resolves the exact secrets your manifest declares, through the same providers, profiles, fallback chains, and generators as the CLI, with no per-language configuration.

Native bindings over one resolver

Section titled “Native bindings over one resolver”

Every SDK is a thin client over the same Rust core that powers the CLI. No provider logic, profile resolution, chain fallback, as_path materialization, or secret generation lives in the binding. A provider added to SecretSpec works in every language the day it lands, and every SDK behaves identically.

The binding strategy is chosen per ecosystem:

  • Python: a pyo3 extension, statically linked, shipped as a self-contained cp39-abi3 wheel.

  • Node.js: a napi-rs addon with prebuilt per-platform packages.

  • Ruby: a native C extension (mkmf) with the resolver statically linked into a platform gem.

  • Go: the secretspec-ffi C ABI loaded at runtime via purego (no cgo).

  • Haskell: the same C ABI, linked at build time through the Haskell FFI.

The same three steps, in your language

Section titled “The same three steps, in your language”

Each SDK mirrors the vocabulary of the Rust derive crate: a builder that takes a provider, a profile, and an access reason, then load() to resolve, then a map of secrets you can read or export into the environment.

# Python
from secretspec import

resolved = (
    builder()
    .with_provider("keyring://")
    .with_profile("production")
    .with_reason("boot web app")
    .load()
)
print(resolved.secrets["DATABASE_URL"].get)  # value, or file path for as_path
resolved.set_as_env()                         # export into os.environ
// Node.js / TypeScript
const {  } = require('secretspec');

const resolved = .builder()
  .withProvider('keyring://')
  .withProfile('production')
  .withReason('boot web app')
  .load();
console.log(resolved.secrets.DATABASE_URL.get()); // value, or as_path file path
resolved.setAsEnv();                              // export into process.env
// Go
resolved, err := secretspec.().
    ("keyring://").
    ("production").
    ("boot web app").
    ()
fmt.(resolved.["DATABASE_URL"].()) // value, or as_path file path
resolved.()                                 // export into the environment
# Ruby
resolved = ::.builder
                                 .with_provider("keyring://")
                                 .with_profile("production")
                                 .with_reason("boot web app")
                                 .load
puts resolved.secrets["DATABASE_URL"].get # value, or as_path file path
resolved.set_as_env!                      # export into ENV
-- Haskell
resolved <-

    ( S.builder
        &"keyring://"
        &"production"
        &"boot web app"
    )
-- export into the environment

Across all of them, load() resolves every declared secret, a missing required secret raises a typed MissingRequiredError, and as_path secrets come back as a readable file path with a cleanup that removes the backing temp file. The access reason feeds the same audit log and require_reason policy from 0.12, so a Go service is as accountable as the CLI.

Write your own binding

Section titled “Write your own binding”

Under all five SDKs sits a new crate, secretspec-ffi: a small, versioned C ABI for resolving secrets. If we do not ship your language yet, you can bind to it directly. It also exposes the public Rust building blocks the SDKs share, Secrets::resolve() and Secrets::report(), so a Rust program reaches the same value-carrying and value-free entry points.

Typed secrets, one schema for every language

Section titled “Typed secrets, one schema for every language”

secretspec.toml already knows the shape of your secrets, so 0.13 can hand that shape to your type system. secretspec schema emits a JSON Schema for your manifest, the union of all profiles or one profile with --profile. Pipe it through quicktype to generate idiomatic typed classes in any language, then populate them from each SDK’s fields() map:

secretspec schema | quicktype -s schema --top-level  --lang python -o secrets_gen.py
typed =from_dict(resolved.fields())
print(typed.database_url)  # typed str

One schema drives every language’s type system, with no hand-written emitter per language.

Install

Section titled “Install”

pip install secretspec                              # Python
npm install secretspec                              # Node.js / TypeScript
gem install secretspec                              # Ruby
go get github.com/cachix/secretspec/secretspec-go   # Go

For Haskell, add secretspec from Hackage to your build-depends. The CLI and Rust SDK upgrade as usual:

cargo install secretspec

See the SDK overview for the per-language guides. Questions or feedback? Join us on Discord.

Lobsters · 2 赞 · 0 评 讨论 → 阅读原文 →

这条对你有帮助吗?