Startup broker resolution
Service launch can consume Secrets Broker refs declared in service.json without exposing resolved values outside the launch materialization boundary.
Manifest shape
Declare broker imports under broker.imports, then reference them from env with ${namespace.KEY} selectors:
{
"id": "api",
"name": "API",
"description": "Service using broker-backed config",
"env": {
"DATABASE_URL": "postgres://app:${database.PASSWORD}@db/service",
"API_TOKEN": "${service.API_TOKEN}"
},
"broker": {
"imports": [
{
"namespace": "shared/database",
"ref": "database.PASSWORD",
"as": "DB_PASSWORD",
"required": true
},
{
"namespace": "services/api",
"ref": "service.API_TOKEN",
"as": "API_TOKEN",
"required": false
}
]
}
}
Precedence at launch is:
- provider env from the selected runtime provider
- Service Lasso manifest/derived/global variables, with broker selectors substituted only for declared imports
- broker imports with
asnames that do not override existing manifest env keys - scoped broker writeback identity env, when the service declares broker writeback permissions
When scoped broker writeback identity env is issued, Service Lasso also records safe transport-binding metadata when the launcher identity is known. Unix launchers default to the current launcher UID as unix-uid. Windows launchers can provide a stable service-account or launcher SID with SERVICE_LASSO_BROKER_TRANSPORT_BINDING_KIND=windows-sid and SERVICE_LASSO_BROKER_TRANSPORT_BINDING_SUBJECT=<sid>. These values are metadata only; the scoped credential value remains secret and must not be logged or persisted.
When an installed or configured Secrets Broker helper and signing material are available, the launcher also asks secretsbroker admin launch-lease issue for a signed launch identity lease and injects it through SERVICE_LASSO_BROKER_IDENTITY_LEASE. The helper receives service id, workspace id, allowed refs/namespaces, allowed operations, expiry, one-time identity id, and optional transport binding. If the helper is unavailable or no signing key/API token is configured, the runtime keeps the existing local scoped credential path so bootstrap and tests remain compatible. The signed lease is launch-only authority and must not be written to lifecycle state, logs, diagnostics, issue comments, PR bodies, or persisted fixtures.
Raw broker values may be present only in the process environment/config handed to the launched service. They must not be written to logs, status payloads, diagnostics, issue comments, PR bodies, or test artifacts.
Startup pipeline
Broker policy authorizes delivery; a custom environment does not prevent later inspection by processes with sufficient OS rights. Protect the launched process and any inherited child environments using the deployment's identity and sandbox policy. See Service environment isolation for Linux procfs and Windows process-access limits.
The runtime startup path is formalized as:
- Compile a selector plan from service
envplusbroker.imports. - Deduplicate broker refs so each unique selector is looked up at most once per launch.
- Batch lookup the unique refs through the live Secrets Broker
POST /v1/resolveboundary. Core issues a one-time launch identity lease (no transport binding, because loopback HTTP rejects bound leases), authenticates with the operator API token, and maps typed outcomes back onto the original import refs. WhenSECRETSBROKER_NAMED_PIPE,SECRETSBROKER_UNIX_SOCKET, or operatoripcmetadata is set, that lookup uses the same named-pipe or Unix-socket HTTP client as the management proxy and KV/v1/kvroutes. Unconfigured runtimes keep loopback HTTP so development health URLs keep working. Tests and callers may still supply an explicitbrokerLookupto keep the plumbing path. - Classify every unresolved ref as one of:
missinglockedauth-requiredpolicy-deniedsource-unavailabledegraded
- For each declared
broker.writeback.generatedSecrets[]entry whosesourceisbroker:generateand whose ref is stillmissing, call Secrets BrokerPOST /v1/provisioning/operations/applywithgenerationMode: broker_generated. This is first-run onboard only. Discovery never writes KV. DefaultallowOverwrite: falseskips refs that already resolve. Then re-run the resolve lookup for those refs. - Fail closed before process spawn when any
required: trueimport is unresolved. - Materialize resolved values only into the launched service environment/config.
- Issue a scoped broker writeback identity when the service declares writeback permissions, including transport-binding metadata when available.
- Issue a broker-signed launch identity lease through the Secrets Broker helper when available and inject it only into the launched process environment.
- Emit safe metadata only: ref name, classification,
required,astarget, identity id, expiry, and transport-binding kind/subject when present.
Policy-denied refs are intentionally separate from missing refs. Operators should see that access was denied, not that config disappeared.
Cache invalidation
Apps that require a file opt into Broker-owned RAM WebDAV outputs with
broker.files[] or broker.templates[], then pass
${SERVICE_LASSO_SECRETS_DIR}/<name> through an app-supported environment
variable. Core asks Broker to provision using scoped refs and secret-free templates.
Broker resolves internally and returns the WebDAV directory before each fresh spawn;
file-only imports skip Core's plaintext lookup. Explicit env imports continue using
the startup resolution pipeline above. An import by itself does
not create a file. See the secure provisioning lesson
and RAM WebDAV delivery. Linux native paths
require the explicitly selected tmpfs alternative;
environment delivery stays supported.
Selector plans are cached by service manifest path/id and the effective env + broker.imports content. The cache invalidates when:
- an env template changes
- a broker import is added, removed, renamed, or changes
required/as - materialization templates change for config/install planning
The cache stores selector metadata only, never resolved broker values.
Safe diagnostics
Safe failure metadata example:
{
"ref": "database.PASSWORD",
"status": "locked",
"required": true,
"as": "DB_PASSWORD"
}
Unsafe output that must not be logged or returned:
DB_PASSWORD=...
access_token=...
client_secret=...
raw resolved secret values
Node Sample Service rotation and update testing
Git-managed source of truth: services/node-sample-service/. Workspace copies under workspace/canonical-services-root are demo runtime trees, not the authoring source.
Default/canonical services do not ship live KV secrets. node-sample-service declares:
- Non-secrets (plain env, not Broker):
NODE_SAMPLE_PUBLIC_LABEL,NODE_SAMPLE_FEATURE_FLAG,NODE_SAMPLE_PUBLIC_URL - Optional consumer import:
shared/sample/sample.API_TOKEN(KV pathshared/sample/sample.API_TOKEN) - First-run producer:
services/node-sample-service/sample.GENERATED_TOKEN(KV pathservices/node-sample-service/sample.GENERATED_TOKEN)
Operator steps (path/ref names only; never print values):
- Ensure
@secretsbrokeris ready, then startnode-sample-service. First start generates the missing producer once. - Confirm KV metadata list shows
services/(or the producer path). Admin at root previously showed empty because nothing was seeded. - Open
http://127.0.0.1:<service-port>/diagnostics. Checksecrets.generatedToken.presentandlast4only. - Rotate the producer through Secrets Broker rotation activate (or Admin KV write of a new version), then restart the sample.
last4should change. List/metadata still must not include values. - Update
NODE_SAMPLE_FEATURE_FLAGin the service config/Variables editor and restart. DiagnosticsnonSecrets.featureFlagshould change without a Broker provisioning apply.
Optional: write sample.API_TOKEN under shared/sample if you need a consumer-import path. It is not required for start.