SIAX Technology (sax3l)

@siax/secret-resolver (0.1.0)

Published 2026-09-16 09:35:08 +00:00 by admin

Installation

@siax:registry=https://git.cloud.siax.io/api/packages/sax3l/npm/
npm install @siax/secret-resolver@0.1.0
"@siax/secret-resolver": "0.1.0"

About this package

P002 (Security/Secret/Provenance Incident Closure): resolveSecretReference() -- takes a siax.secret-reference record and resolves it through Infisical (the self-hosted secret engine, vault.siax.io) at deploy/boot time, so applications hold a SecretReference (id/repo/key/environment/provider) instead of a raw value in source or config. Closes the gap P002 recon found: the schema existed with zero runtime consumers anywhere in the estate, and the only two existing conventions (siax-integrations' secret_ref: string convention, its infisical-adapter) were explicitly dev-only/unimplemented skeletons. Zero external dependencies -- @siax/schemas is a workspace-internal link only.

@siax/secret-resolver

P002 (Security / Secret / Provenance Incident Closure, Master Execution Contract): resolveSecretReference() / resolveSecretReferences() / resolveSecretReferencesToEnv() — takes a siax.secret-reference record and resolves it through Infisical (the self-hosted secret engine at vault.siax.io) at deploy/boot time. Zero external dependencies — @siax/schemas is a workspace-internal link only. Node ESM (.mjs), Node ≥ 20 — same convention as @siax/schemas, @siax/write-guard and @siax/doctor.

Why this package exists

P002 recon (2026-09-02) asked two direct questions and got two honest "no" answers:

  1. Is siax.secret-reference actually used anywhere, or a stub? A stub. Zero runtime consumers anywhere in the estate — the schema existed, was well-formed, and was imported by nothing outside its own test file.
  2. Does any code let apps request a secret by reference and resolve it at deploy/boot? No real one existed. Two competing, non-production conventions were found instead:
    • siax-integrations/packages/core/src/secret-ref.ts — its own header comment: "secret_refs were structurally-validated strings that were never actually resolved anywhere." The only implementation is explicitly labeled "DEV-ONLY resolver... NOT the production secret backend".
    • siax-integrations/packages/infisical-adapter/src/adapter.ts — an explicit "P0 skeleton". No resolveSecret method exists at all; getHealth()/testConnection() always return "unknown"/stub results; ADAPTER.md states lifecycle_status: "prototype".

This package is the first thing in the estate that actually performs the resolve step: SecretReference in -> real Infisical HTTP call -> value out, with the value never touching disk, git, or a log line.

Layout

packages/secret-resolver/
  src/
    infisical-client.mjs   # zero-dep Infisical REST client (Universal Auth login + raw-secret GET)
    resolve-secret.mjs     # resolveSecretReference() / resolveSecretReferences() -- validates + resolves
    boot.mjs                # resolveSecretReferencesToEnv() -- the actual boot-time entrypoint, the ONLY function that mutates anything
    index.mjs
  test/
    infisical-client.test.mjs
    resolve-secret.test.mjs
    boot.test.mjs

Core API

import { resolveSecretReference, resolveSecretReferencesToEnv } from '@siax/secret-resolver';

// One secret:
const { key, value } = await resolveSecretReference(
  { id: 'r1', repo: 'st0re', key: 'ST0RE_API_TOKEN', environment: 'prod', provider: 'infisical' },
  { projectId: process.env.SECRET_RESOLVER_INFISICAL_PROJECT_ID }, // or set the env var and omit this
);

// At boot, for a whole app:
await resolveSecretReferencesToEnv(myApp.requiredSecretReferences, {
  projectId: '...',
}); // process.env.ST0RE_API_TOKEN etc. are now set, or the whole call rejected -- never a partial boot.

Every function is fail-closed: an invalid SecretReference (fails @siax/schemas' validateSecretReference), a missing projectId, or any Infisical auth/network/not-found failure throws a typed error. Nothing in this package returns undefined on failure — see each module's own "fail-closed" test cases.

Configuration (env vars)

Env var Purpose
SECRET_RESOLVER_INFISICAL_API_URL Infisical base URL. Defaults to https://vault.siax.io.
SECRET_RESOLVER_INFISICAL_CLIENT_ID / SECRET_RESOLVER_INFISICAL_CLIENT_SECRET Universal Auth machine-identity credential pair. Two env vars, not one — see "Why two env vars" below.
SECRET_RESOLVER_INFISICAL_PROJECT_ID Infisical project/workspace ID. See "Known gap" below for why this can't come from the SecretReference itself.

Why two env vars, not one pre-minted token

A single long-lived static bearer token in an env var is exactly the pattern P002 recon flagged repeatedly as estate-wide architecture debt (the aud0 DEPLOY.md static-token finding, the shared *_AUTH_TOKEN convention across st0re/api0/m0b). Universal Auth's client-id + client-secret exchange for a short-lived access token (cached in-memory, never persisted) is the smaller blast radius of the two options actually available today, without introducing a new architecture this pass wasn't scoped to design.

Known gap: siax.secret-reference has no projectId field

P002 recon confirmed the schema's real, live shape is exactly {id, repo, key, environment, provider, rotationPolicy?, lastRotatedAt?} — not the {provider, path, key, version, tenantScope, projectScope, rotationPolicy} shape an earlier task framing implied. There is no path, version, tenantScope, or projectScope field, and critically no Infisical project/workspace ID field. Infisical's real API needs one to resolve anything.

This package does not invent a guess (e.g. "derive projectId from repo") — a wrong guess here would silently resolve the wrong secret, strictly worse than refusing to resolve at all. projectId must be supplied out-of-band, via options.projectId or the SECRET_RESOLVER_INFISICAL_PROJECT_ID env var — see MissingProjectIdError.

Not decided in this pass (flagged, not silently assumed): whether siax.secret-reference should grow a projectId field (a P003 schema change, estate-wide compatibility implications, out of this package's scope) or whether a repo -> projectId lookup table belongs in this package instead. A follow-up pass needs Simon's steer on which.

repo currently maps to an Infisical secret path (/${repo}) by default — matching the --path=/coolify-style convention already live in the siax-secrets operator CLI wrapper (see ../../standard/execution-reports/P002-SECURITY-SECRET-PROVENANCE-CLOSURE.md §1). Override via options.secretPathForReference for a different layout.

Why Infisical, not OpenBao

P002 recon confirmed Infisical is the currently-selected self-hosted secret engine — it's what the siax-secrets operator wrapper talks to, what siax-integrations/packages/infisical-adapter (however skeletal) is already scaffolded around, and what siax-integrations/CLAUDE.md's own agent rule names explicitly ("NEVER hardcode secrets — use Infisical"). Introducing OpenBao instead would be a materially bigger, riskier architecture change than this task's scope ("closing the resolver gap for the already-selected engine") calls for — not attempted here.

Honest status — what is real vs. not yet verified

Same convention as packages/write-guard/src/aud0-client.mjs's "HONEST STATUS" note: whether https://vault.siax.io is reachable from wherever a given app actually boots, and whether its live Infisical version matches the REST shape this client assumes (POST /api/v1/auth/universal-auth/login, GET /api/v3/secrets/raw/{name}), was not verified from this repo/pass. Every test in test/*.test.mjs runs against an injected fake fetch — zero real network calls, and this pass used no live Infisical credentials (none were available, none were requested from a live system). A follow-up pass must run one real login + get-secret round trip against the actual deployed instance, with a real, scoped-down machine identity requested from Simon, before this is trusted in a real boot path.

What this package deliberately does NOT do

  • Does not shell out to the siax-secrets CLI wrapper. That wrapper (hardened in an earlier P002 pass, see the execution report referenced above) is an operator safety net for humans running ad-hoc commands. This package is the machine-to-machine boot-time path — a different layer, talking to Infisical's HTTP API directly.
  • Does not mint, rotate, or discover credentials. The machine identity it authenticates with must already exist and be supplied by the caller. Provisioning that identity is out of this package's scope.
  • Does not log a resolved value or an access token anywhere, including in thrown errors — every error class in infisical-client.mjs carries status codes, URLs, and key names only.

Dependencies

Dependencies

ID Version
@siax/schemas workspace:*
Details
npm
2026-09-16 09:35:08 +00:00
2
UNLICENSED
latest
14 KiB
Assets (1)
Versions (1) View all
0.1.0 2026-09-16