Skip to content

feat(guest): built-in launch token requirement#763

Merged
kvinwang merged 1 commit into
masterfrom
feat/launch-token-requirement
Jul 9, 2026
Merged

feat(guest): built-in launch token requirement#763
kvinwang merged 1 commit into
masterfrom
feat/launch-token-requirement

Conversation

@kvinwang

@kvinwang kvinwang commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #762.

Problem

The LAUNCH_TOKEN pattern is currently a pure app-layer convention (prelaunch script + APP_LAUNCH_TOKEN env). Every app has to wire the check itself, and there is no OS-enforced way to bind a deployment to a deployer-known secret.

Fix

  • Add requirements.launch_token_hash: Option<String> to dstack types and SDK compose-hash types.
  • Build the LAUNCH_TOKEN pattern into the OS image: when launch_token_hash is present, the guest reads the token from user_config at JSON path dstack / launch_token during boot policy verification — before any keys are provisioned — and fails closed unless its digest matches the compose-hash-measured value.
  • The digest is domain-separated: sha256("dstack-launch-token/v1:" || token) (canonical helper dstack_types::launch_token_hash, shell: printf 'dstack-launch-token/v1:%s' "$TOKEN" | sha256sum). This keeps it distinct from the legacy plain-sha256(token) top-level convention and from generic precomputed tables.
  • When launch_token_hash is absent, user_config is not parsed at all — it stays opaque application data.
  • Since launch_token_hash is public (part of app-compose.json), guessable tokens can be recovered offline; guests reject tokens shorter than 32 bytes as a fail-closed guardrail, and docs tell deployers to generate random tokens.
  • Docs spell out the protection boundary: the token transits user_config in plaintext, so a host that has hosted the deployment learns it; mitigations are fresh token per deployment + pruning stale compose hashes from the on-chain whitelist, or the app-layer encrypted-env pattern when the token must stay secret from the host.
  • Fail-closed properties:
    • missing/unreadable user_config, non-JSON content, missing or non-string dstack.launch_token, too-short token, malformed hash, or token mismatch all abort boot before key provisioning;
    • requirements still demands string manifest_version "3", and Requirements uses deny_unknown_fields, so older guests reject a compose that carries this field instead of silently ignoring it.
  • The existing top-level launch_token_hash app-layer convention (UI/vmm-cli + APP_LAUNCH_TOKEN env + prelaunch script) is unchanged and keeps working on all images; migrating tooling to the new requirement is a follow-up.

Verification

  • cargo fmt --check --all
  • cargo clippy -p dstack-types -p dstack-util -- -D warnings --allow unused_variables
  • cargo check --all-features
  • cargo test -p dstack-types / cargo test -p dstack-util
  • cd sdk/go && go build ./... && go vet ./...
  • cd sdk/js && npx tsc --noEmit
  • prek run --files <changed files>

Copilot AI review requested due to automatic review settings July 8, 2026 16:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a first-class, guest-enforced “launch token” requirement to bind a deployment to a deployer-known secret, moving the LAUNCH_TOKEN pattern from an application-layer convention into the dstack OS boot policy and corresponding SDK/type surfaces.

Changes:

  • Introduces requirements.launch_token_hash (SHA-256 hex) across Rust types and Go/JS/Python compose-hash SDKs.
  • Enforces the requirement in the guest during system setup by parsing user_config (dstack.launch_token) only when the requirement is present and failing closed on any mismatch/error.
  • Updates security documentation to describe the native launch-token behavior and fail-closed expectations.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
vmm/ui/src/composables/useVmManager.ts Adds the field to UI-side Requirements typing (but see review comment about schema alignment).
sdk/python/src/dstack_sdk/get_compose_hash.py Adds launch_token_hash to Python compose-hash requirements serialization.
sdk/js/src/get-compose-hash.ts Adds launch_token_hash to JS compose-hash requirements typing.
sdk/go/dstack/compose_hash.go Adds launch_token_hash to Go compose-hash requirements struct.
dstack-util/src/system_setup.rs Implements guest-side verification of requirements.launch_token_hash against user_config.dstack.launch_token, plus unit tests.
dstack-types/src/lib.rs Extends Requirements with launch_token_hash and updates deserialization tests.
docs/security/security-best-practices.md Documents native LAUNCH_TOKEN support via requirements.launch_token_hash.
docs/security/cvm-boundaries.md Clarifies .user-config handling when requirements.launch_token_hash is set.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread vmm/ui/src/composables/useVmManager.ts
@kvinwang kvinwang force-pushed the feat/launch-token-requirement branch 3 times, most recently from 6c44489 to 7fca012 Compare July 9, 2026 00:50
@kvinwang kvinwang requested a review from Leechael July 9, 2026 00:55
@kvinwang kvinwang force-pushed the feat/launch-token-requirement branch from 7fca012 to 40ef184 Compare July 9, 2026 01:23
Base automatically changed from feat/tdx-measure-acpi-requirement to master July 9, 2026 01:31
Add requirements.launch_token_hash so the previously app-layer
LAUNCH_TOKEN pattern is enforced by the OS image itself. When the
field is set, the guest reads the launch token from user_config at
JSON path dstack.launch_token during boot policy verification, before
any keys are provisioned, and fails closed unless its digest matches
the compose-hash-measured value. The digest is domain-separated --
sha256("dstack-launch-token/v1:" || token) -- to stay distinct from
the legacy plain-sha256 convention and generic precomputed tables,
and tokens shorter than 32 bytes are rejected since the hash is
public. When the field is absent, user_config is not parsed and stays
opaque application data.
@kvinwang kvinwang force-pushed the feat/launch-token-requirement branch from 40ef184 to e794f2b Compare July 9, 2026 01:38
@kvinwang kvinwang merged commit e5f138c into master Jul 9, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants