The first release supports variables directly on a workspace. Organization, project, priority, and attached variable sets are not available in private beta.
Categories
| Category | Runner behavior |
|---|---|
terraform |
Supplied as an OpenTofu input variable |
environment |
Supplied as a process environment entry |
Terraform variables also carry an hcl flag. Environment values are strings.
Keys are bounded to 256 characters, descriptions to 1,024 characters, and
values to 65,536 bytes. The native, dashboard, and OpenTofu-compatible
variable mutation envelopes are bounded to 131,072 bytes so the maximum value
fits with its JSON metadata. Credential-profile mutation documents use a
separate 100,000-byte envelope limit for their supported 64 KiB canonical
generic-secret map and bounded profile metadata.
Environment variables cannot use the runner-owned STACKSHIP_* or TFC_*
namespaces, TF_CLI_ARGS, any TF_CLI_ARGS_* subcommand override, TF_LOG,
any TF_LOG_* subsystem setting, TF_FORCE_LOCAL_BACKEND, or
TF_TOKEN_stackship_run. StackShip also reserves OpenTofu controls that can
redirect the managed workspace, data directory, provider process, plugin
cache or transport, encryption, or persistence behavior:
TF_CLI_CONFIG_FILE, TF_CLOUD_HOSTNAME, TF_CLOUD_ORGANIZATION,
TF_CLOUD_PROJECT, TF_DATA_DIR, TF_DISABLE_PLUGIN_TLS, TF_ENCRYPTION,
TF_IN_AUTOMATION, TF_PLUGIN_CACHE_DIR,
TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE, TF_REATTACH_PROVIDERS,
TF_STATE_PERSIST_INTERVAL, and TF_WORKSPACE. OpenTofu injects
TF_CLI_ARGS* into its command line and its raw logging controls can write
state or credentials to process output or disk, so StackShip reserves both
complete prefix families instead of maintaining a subcommand or subsystem
allowlist. The dashboard and both native and OpenTofu-compatible APIs reject
these names before persisting a variable, with guidance to choose a
provider-specific name. OpenTofu input variables remain available because
StackShip projects them into the separate TF_VAR_* namespace at execution
time.
Lifecycle
The native v1 API and dashboard support:
- list current workspace variable metadata;
- create a key and its first value version;
- replace the value with a new immutable version, or edit the description and HCL flag while keeping the stored value; and
- delete the variable.
Create, replace, and delete require an idempotency key. An exact replay returns the durable result; a changed request under the same key is rejected.
The current list response includes variable id, current version id, category, HCL flag, key, description, sensitivity, value digest, and timestamps.
In the dashboard, each row shows the key, a category badge using the same
labels as the create form (“OpenTofu input” and “Environment”), an HCL badge
when the flag is set, a Write-only badge for sensitive values, and the value
digest truncated with the full SHA-256 as a tooltip. The edit form sits
behind a per-row Edit action, and at most one edit form is open at a
time. Its value field is optional and labeled “New value (leave blank to
keep the current value)”: submitting without a value updates the
description and the OpenTofu-input HCL flag while the stored value — even a
sensitive one — stays untouched. Sensitive values remain write-only and are
never prefilled into the form. Changing the sensitive flag requires
re-entering the value; the dashboard asks for it inline before any request
is sent, and the API enforces the same rule with
400 WORKSPACE_VARIABLE_VALUE_REQUIRED.
After a successful dashboard save, the variable row announces either that a new immutable version was created or that the metadata was updated with the stored value unchanged, and keeps that confirmation visible while the updated metadata is rendered. Any submitted value is cleared before the confirmation is shown; sensitive plaintext never appears in the message.
The create card also offers Add several at once: paste KEY=value
lines to create non-sensitive variables of the selected category, one
idempotent create per line. Blank lines and # comments are skipped;
malformed lines are reported by line number before any request is sent, and
a partial failure reports how many variables were created. Members without
workspace:admin see a quiet read-only notice above the list.
Sensitive values
When sensitive is true:
- the API encrypts the value before database persistence;
- a random per-version AES-256-GCM data key is wrapped by the internal credential broker;
- authenticated additional data binds the organization, workspace, variable, version, and category;
- the database stores ciphertext, nonce, wrapped key, key version, and digest;
- list and mutation responses return
value: null; and - replacing the secret creates a new immutable version rather than changing old ciphertext.
The dashboard clears the submitted sensitive value from component state after the request settles. There is no product endpoint for reading sensitive plaintext.
Non-sensitive variables may return their current value. Their digest is still recorded so an immutable run input can bind the value it used.
Run snapshots
A managed run resolves the current variable versions before execution and stores version ids and digests in its immutable input snapshot. Non-sensitive values may be carried in that snapshot. Sensitive values appear only as secret version references for the credential broker.
Speculative runs omit sensitive workspace-variable references. Manifest publication fails closed if a speculative snapshot contains one.
Generic secret credential profiles
Use a generic_secret credential profile when a plan or apply must receive a
customer-managed token, password, or other environment value for which no
federation flow exists. Profile versions accept between 1 and 32 unique
environment entries. Their canonical UTF-8 JSON representation may be at most
64 KiB. Values cannot contain a NUL code point because operating-system
process environments cannot represent it. The API rejects such a value before
sealing, and the credential broker and runner enforce the same boundary again
before decryption output can reach process creation.
Plan and apply sandboxes have unrestricted public outbound network access, so
every generic secret injected into a run is reachable by customer
configuration, providers, data sources, modules, and processes such as
local-exec. StackShip does not infer or restrict a destination from an
environment name or secret value. Trust the repository and its dependencies,
scope each secret to the minimum authority and lifetime, and prefer
provider-native short-lived federation when it is available.
Names must match [A-Za-z_][A-Za-z0-9_]{0,127}. StackShip rejects process
control names such as PATH, HOME, NODE_OPTIONS, LD_PRELOAD, OpenTofu
control names such as TF_CLI_CONFIG_FILE; command-line overrides
TF_CLI_ARGS and TF_CLI_ARGS_*; raw logging settings TF_LOG and
TF_LOG_*; the managed-target, data, provider, plugin, encryption, and
persistence controls listed above; and the reserved STACKSHIP_* and TFC_*
namespaces. Publication and the runner independently enforce the same
reserved-name contract, and also reject any collision with a workspace
environment variable, a TF_VAR_* projection, another credential, or another
runner-owned name.
The dashboard validates reserved names before submission. After a rejected generic-secret request, it clears every plaintext value while preserving the non-secret environment names and risk acknowledgements so the operator can correct the request and re-enter only the secrets. A successful request clears the complete form.
The value map is write-only:
- the API sends it directly to the internal credential broker for sealing;
- the broker encrypts it with a random AES-256-GCM data key;
- the broker wraps that data key with the active, broker-only platform key;
- authenticated context binds the envelope to the exact organization, workspace, profile-version id, and sorted environment-name set; and
- PlanetScale stores only the ciphertext, nonce, wrapped key, key version, and a keyed digest derived from the random data key.
Equal plaintext in two versions does not produce the same digest. The API also reduces the secret-bearing part of an idempotent create request to a domain-separated HMAC before the ordinary request fingerprint reaches PlanetScale. A database reader therefore cannot use either field to test plaintext guesses without a key held outside the database.
List, create, and verification responses return configuration: null plus the
safe environment-name metadata. There is no plaintext read endpoint. A
version remains pending until the broker successfully decrypts and validates
its stored envelope; only then can it become current. A profile with no
verified current version is a detached draft: it is visible to operators but
does not participate in run snapshots. Once a current version exists, run
snapshotting fails closed unless that version remains verified and valid for
the requested run.
New generic secret versions use plan_and_apply. They are never valid for
plan, apply, or speculative_plan, including a same-repository pull
request. New cloud federation versions use plan_and_apply, or
speculative_plan when explicitly selected for pull-request plans. A run
snapshot contains only the selected credential and profile-version ids. The
execution manifest adds only the environment names, expiry, and an opaque
one-time handle.
Archive and revoke
Archiving a credential profile detaches it from its workspace without deleting
the profile, its immutable versions, or audit history. Revoking an exact
version marks it disabled, records when it was disabled, and removes its
current marker. The API can revoke a current or historical version for
incident response. The dashboard presents the first revoke control only on the
current verified version and requires a separate destructive confirmation for
both revoke and archive.
In the dashboard, credential profiles across every workspace appear in one filterable table with spelled-out provider names, and a profile page opens from its profile ID alone — when a link omits the workspace, StackShip resolves the owning workspace server-side. The profile page leads with version status and verification; the provider trust template sits beside the version awaiting verification with a copy control; and the archive control lives in a separate confirmation-guarded section at the end of the page.
These transitions take effect at use time, not only when a run is created. StackShip re-resolves an exact pinned credential before manifest issuance and again before runner redemption. A revoked version or archived profile therefore cannot enter a new snapshot and cannot be redeemed by an already queued run. The run receives no replacement credential automatically.
Both actions require credential:write and append an organization audit event.
They never erase a sealed generic-secret envelope or expose its plaintext.
Before the dashboard submits a generic secret version, the operator must separately acknowledge that plaintext enters the selected runner sandbox, infrastructure code and providers can access it, log redaction cannot make malicious infrastructure configuration safe, and federation is preferred whenever it is available. The API remains authoritative for the write-only shape and phase restrictions; dashboard acknowledgements do not make untrusted infrastructure configuration safe.
The handle is redeemable only by the authenticated runner for the exact organization, workspace, run, attempt, phase, sandbox, input digest, credential, profile version, environment-name set, and expiry used at issue time. A mismatched redemption does not consume it; one exact successful redemption deletes it atomically. The envelope is fetched from PlanetScale and presented only to the broker during redemption. It is never placed in a Cloudflare Workflow argument, Durable Object handle record, sandbox launch request, GitHub payload, policy input, audit detail, analytics event, or log.
Prefer federation
Use AWS, Azure, or Google Cloud federation profiles for cloud access whenever possible. Federation issues short-lived, phase-bound identity material and does not place a long-lived provider secret into the run environment.
The first release supports the commercial AWS partition. AWS GovCloud and AWS China profiles are rejected rather than being issued through an incompatible global STS or trust contract.