Skip to content

Add --verifiable flag to stellar contract build - #2709

Open
fnando wants to merge 1 commit into
contract-archivefrom
contract-verifiable-build
Open

fnando wants to merge 1 commit into
contract-archivefrom
contract-verifiable-build

Conversation

@fnando

@fnando fnando commented Aug 31, 2026

Copy link
Copy Markdown
Member

Stacked on #2731 (stellar contract archive). Review/merge that first; this PR is diffed against its branch and will be retargeted to main once it lands.

What

Adds a --verifiable flag to stellar contract build that performs a reproducible build inside a digest-pinned Docker container and stamps SEP-58 metadata (bldimg, source_uri, source_sha256, bldopt) into the resulting WASM so third parties can re-run the build and verify the output byte-for-byte. The container connection/resource flags are the existing container-build arguments, reused here.

A --verifiable build always generates the reproducible source archive (the same generator #2731 exposes as stellar contract archive), records its SHA-256 as source_sha256, writes a content-addressed copy to the data dir's archives/<sha256>.tar.gz, and builds from the extracted (permission-hardened) copy so the WASM comes from exactly the bytes that were hashed. Each contract is built with its own --package, forwarded to the build and recorded as a bldopt, so every WASM is independently reproducible; multi-contract workspaces build in a single container to share the crates download and target/. Every bldopt is recorded as valid shell syntax so a verifier can replay the exact invocation.

Why

SEP-58 defines how to verify that a deployed contract WASM came from a specific source built with a specific toolchain image. Until now the CLI had no built-in way to produce such a build — users had to assemble the docker invocation, run cargo inside it, and stamp the custom sections by hand. This makes it a first-class option on stellar contract build, building on the reproducible source archive from #2731.

@fnando
fnando requested a review from a team as a code owner August 31, 2026 17:17
Copilot AI balanced review requested due to automatic review settings August 31, 2026 17:17
@github-project-automation github-project-automation Bot moved this to Backlog (Not Ready) in DevX Aug 31, 2026
@socket-security

socket-security Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​tar@​0.4.4610010093100100
Updatedcargo/​ignore@​0.4.23 ⏵ 0.4.3398 -110093100100

View full report

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

Adds SEP-58 reproducible contract builds and source archive generation.

Changes:

  • Adds --verifiable build flags and provenance metadata.
  • Adds deterministic source archiving and contract archive.
  • Extends container execution, artifact handling, tests, and documentation.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
FULL_HELP_DOCS.md Documents new commands and flags.
cmd/soroban-cli/src/config/locator.rs Adds recursive permission hardening.
cmd/soroban-cli/src/config/data.rs Adds managed archive storage.
cmd/soroban-cli/src/commands/mod.rs Adds verifiable help heading.
cmd/soroban-cli/src/commands/contract/mod.rs Registers archive command.
cmd/soroban-cli/src/commands/contract/build/verifiable.rs Implements verifiable builds.
cmd/soroban-cli/src/commands/contract/build/source_archive.rs Implements reproducible archives.
cmd/soroban-cli/src/commands/contract/build/container.rs Shares container and artifact logic.
cmd/soroban-cli/src/commands/contract/build.rs Adds flags and dispatch.
cmd/soroban-cli/src/commands/contract/archive.rs Implements archive CLI.
cmd/soroban-cli/src/commands/container/shared.rs Adds streamed image pulling.
cmd/soroban-cli/Cargo.toml Adds archive dependencies.
cmd/crates/soroban-test/tests/it/build.rs Adds integration coverage.
Cargo.lock Locks dependency updates.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cmd/soroban-cli/src/commands/contract/build/container.rs Outdated
Comment thread cmd/soroban-cli/src/commands/contract/build/verifiable.rs
Comment thread cmd/soroban-cli/src/commands/contract/build/verifiable.rs
Comment thread cmd/soroban-cli/src/commands/contract/build/verifiable.rs
Comment thread cmd/soroban-cli/src/commands/contract/build/source_archive.rs Outdated
Comment thread cmd/soroban-cli/src/commands/contract/build/container.rs
Comment thread cmd/soroban-cli/src/commands/contract/build/verifiable.rs Outdated
Comment thread cmd/soroban-cli/src/commands/contract/build/source_archive.rs Outdated
@fnando
fnando force-pushed the contract-verifiable-build branch from d58a8a7 to fce6107 Compare August 31, 2026 17:40
Copilot AI review requested due to automatic review settings August 31, 2026 17:40

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

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

Suppressed comments (6)

cmd/soroban-cli/src/commands/contract/build/container.rs:304

  • Metadata keys are accepted as arbitrary strings by parse_meta_arg, but this records the key unescaped. A key containing whitespace makes the stamped bldopt split into multiple shell words, and shell metacharacters such as ; can execute commands when a verifier replays the joined options. Escape the key portion as well as the value (or reject non-shell-safe metadata keys).
                bldopts.push(format!("{key}={}", shell_escape::escape(v.into())));

cmd/soroban-cli/src/commands/contract/build/container.rs:760

  • The container is explicitly forced to write to /source/target via CARGO_TARGET_DIR, so using the host metadata target here breaks collection whenever the host has CARGO_TARGET_DIR or a configured target-dir. Plain builds then return a missing/stale path; for verifiable builds an absolute metadata path also causes join to discard the extracted root and can select an old host WASM instead of the newly built artifact. Collect from the forced target directory.
    let host_target = md.target_directory.as_std_path();

cmd/soroban-cli/src/commands/contract/build/source_archive.rs:207

  • This filter silently omits every symlink, including Git-tracked symlinked files and directories. Such links are part of the source tree and may be required by path dependencies or build scripts, so the archived source can fail to build or differ from the committed source. Preserve safe symlink entries deterministically, or reject them explicitly instead of dropping them.
        if entry.file_type().is_some_and(|t| t.is_file()) {
            files.push(entry.path().to_path_buf());
        }

cmd/soroban-cli/src/commands/contract/build/verifiable.rs:309

  • Hardening the extracted tree through this helper changes every file to mode 0600, removing executable bits from Git-tracked helper scripts. A contract whose build script invokes an executable from the repository will build normally but fail only in verifiable mode. Use source-specific hardening that preserves the owner execute bit while removing group/other access; keep config files at 0600.
    enforce_hardened_tree(tmp.path()).map_err(source_archive::Error::ArchiveExtract)?;

cmd/soroban-cli/src/commands/contract/build/source_archive.rs:129

  • Every nonzero git status result is treated as “not a repository.” Failures in a real repository (for example corrupt metadata, ownership checks, or configuration errors) therefore bypass the clean-tree requirement and allow an unverified working tree to be archived. First determine whether this is a work tree, and propagate status failures for repositories; only the explicit non-repository case should proceed.
    // Not a git repo (or git refused): can't verify cleanliness, proceed.
    if !status.status.success() {
        return Ok(false);
    }

cmd/soroban-cli/src/commands/contract/build/verifiable.rs:147

  • The documented contract says --verifiable implies --locked, but this branch knowingly performs an unlocked build. That can update dependency resolution relative to the archived lockfile, so the stamped source/image inputs no longer guarantee the advertised reproducibility. Reject images older than the --locked minimum for verifiable builds instead of degrading to an unlocked build.
    } else {
        print.warnln(
            "The build image's `contract build` does not support --locked; \
             building without it. Dependency drift may affect reproducibility.",
        );
    }

Comment thread cmd/soroban-cli/src/config/locator.rs
Copilot AI review requested due to automatic review settings August 31, 2026 18:33
@fnando
fnando force-pushed the contract-verifiable-build branch from fce6107 to 4988080 Compare August 31, 2026 18:33

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

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Suppressed comments (2)

cmd/soroban-cli/src/commands/contract/build/container.rs:304

  • Only escaping v does not make every recorded option valid shell syntax because parse_meta_arg allows metadata keys containing spaces or shell metacharacters. For example, --meta 'my key=value' is forwarded as one argv item but recorded as --meta=my key=value, which splits into two arguments during replay. Escape the key segment as well so the recorded bldopt round-trips.
                bldopts.push(format!("{key}={}", shell_escape::escape(v.into())));

cmd/soroban-cli/src/commands/contract/build/verifiable.rs:149

  • A verifiable build is documented to imply --locked, but an older pinned image reaches this branch and the build continues without it. That permits dependency resolution to drift between the original build and a verifier's replay, defeating the reproducibility guarantee. Please reject images whose CLI does not support --locked instead of producing a “verifiable” artifact without it.
    } else {
        print.warnln(
            "The build image's `contract build` does not support --locked; \
             building without it. Dependency drift may affect reproducibility.",
        );

Comment thread cmd/soroban-cli/src/commands/contract/archive.rs Outdated
Comment thread cmd/soroban-cli/src/commands/contract/archive.rs Outdated
Comment thread cmd/soroban-cli/src/config/locator.rs Outdated
@fnando
fnando force-pushed the contract-verifiable-build branch from 4988080 to f1ab06b Compare August 31, 2026 18:59
Copilot AI review requested due to automatic review settings August 31, 2026 18:59

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

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Suppressed comments (1)

cmd/soroban-cli/src/commands/contract/build/container.rs:304

  • SEP-58 defines each bldopt as one value passed verbatim as an argv argument (“as if single-quoted”), not as shell source to evaluate. Escaping only v stores literal quote characters: an original --meta=note=added on build is recorded as --meta=note='added on build', so a conforming verifier passes the apostrophes into the metadata value and cannot reproduce the WASM. Record the raw {key}={v} argument instead, and update the shell-roundtrip test/documentation accordingly.
                bldopts.push(format!("{key}={}", shell_escape::escape(v.into())));

Comment thread cmd/soroban-cli/src/commands/contract/build/verifiable.rs
Comment thread cmd/soroban-cli/src/commands/contract/build/verifiable.rs
Comment thread cmd/soroban-cli/src/commands/contract/build/source_archive.rs
@fnando
fnando force-pushed the contract-verifiable-build branch from f1ab06b to 42db3e5 Compare September 1, 2026 15:26
Copilot AI review requested due to automatic review settings September 1, 2026 15:26
@fnando
fnando requested review from a team and leighmcculloch and removed request for a team September 1, 2026 15:31

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

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

Suppressed comments (1)

cmd/soroban-cli/src/commands/contract/build/source_archive.rs:127

  • git status --porcelain hides files ignored by global excludes, .git/info/exclude, and parent ignore files. The archive walker explicitly disables those sources, so a machine-local file such as a globally ignored .env can pass this “clean tree” gate and then be included and persisted in the archive. Validate cleanliness against the actual selected archive entries (for example, reject selected files that are not tracked) so local ignored files cannot leak or make source_sha256 machine-specific.
        .arg("status")
        .arg("--porcelain")

Comment thread cmd/soroban-cli/src/commands/contract/build/source_archive.rs
@fnando fnando moved this from Backlog (Not Ready) to Needs Review in DevX Sep 4, 2026
@fnando fnando self-assigned this Sep 4, 2026
Copilot AI review requested due to automatic review settings September 17, 2026 18:54
@fnando
fnando force-pushed the contract-verifiable-build branch from 42db3e5 to 434b938 Compare September 17, 2026 18:54
@fnando
fnando changed the base branch from main to contract-archive September 17, 2026 18:54
@fnando
fnando added this pull request to stack #2732 September 17, 2026 18:56

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.

🟡 Changes recommended

Recorded build options and failure reproduction commands currently cannot reliably reproduce the original build.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 2
  • Review effort level: Balanced

if let Some(v) = value {
args.push(format!("{key}={v}"));
if record_bldopts {
bldopts.push(format!("{key}={}", shell_escape::escape(v.into())));
Comment on lines +195 to +199
container::run_in_container(
&image_ref,
&resolved.mount_root,
&container_cmds,
&env,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

2 participants