Skip to content

Compose hash computed on raw bytes, not canonicalized JSON #550

Closed

Description

@pbeza

The compose hash computation in dstack/vmm/src/app/qemu.rs and dstack/dstack-util/src/system_setup.rs runs SHA-256 over the raw file bytes of app-compose.json rather than a canonicalized form, so semantically identical JSON documents with different whitespace or key ordering produce different hashes.

Root Cause

The compose hash is computed as SHA-256(raw_file_bytes) of the app-compose.json file. This means that semantically identical JSON documents with different whitespace, key ordering, or Unicode normalization produce different hashes. The VMM and guest both compute the hash this way, so any byte-level difference breaks attestation verification even if the configuration is functionally identical.

dstack issue #411 acknowledges this problem; no fix has been implemented.

Attack Path

  1. Operator reformats app-compose.json (e.g., a tool normalizes whitespace or reorders keys)
  2. The compose hash changes despite no semantic change to the configuration
  3. Attestation verification fails because the expected hash no longer matches
  4. Operator may be tempted to disable or weaken hash verification to work around the issue
  5. Conversely, an attacker who can inject semantically different but byte-identical-hash JSON (hash collision) could bypass verification — though SHA-256 collision resistance makes this impractical

Impact

Primarily an operational reliability issue. Legitimate configuration changes that don't affect semantics break attestation. This creates pressure to weaken verification or maintain exact byte-level copies of compose files across all infrastructure.

Suggested Fix

Canonicalize JSON before hashing using a deterministic serialization format (e.g., JCS — JSON Canonicalization Scheme, RFC 8785):

let canonical = jcs::canonicalize(&compose_json)?;
let hash = Sha256::digest(&canonical);

Note: This issue was created automatically. The vulnerability report was generated by Claude and has not been verified by a human.

Activity

  1. kvinwang commented on Mar 26, 2026

    @kvinwang
    Collaborator

    Thanks for raising this. This behavior is intentional: dstack treats the compose JSON as an opaque blob and hashes the raw bytes, so any character-level change, including whitespace or ordering, is considered a different application code and results in a different measurement. This keeps the attestation model simple and avoids having to define and maintain a canonicalization layer. If we decide to support canonicalization in the future, we would introduce it explicitly and document the expected stability guarantees, but for now the behavior you observed is by design.

  2. added
    securitySecurity-related issue, report, or hardening work
    security: reportPublic security report or already-public security finding
    security: documentedSecurity report answered by documentation or threat-model clarification
    on Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity-related issue, report, or hardening worksecurity: documentedSecurity report answered by documentation or threat-model clarificationsecurity: reportPublic security report or already-public security finding

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions