Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/protected-pr-ci.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"platform_verifier",
"static_checks",
"rust",
"candidate_ci"
"candidate_ci",
"wasm"
],
"supplemental_candidate_ci": true
}
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,41 @@ jobs:
- name: Audit xtask dependencies
run: cargo audit --file xtask/Cargo.lock

wasm:
name: WebAssembly
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Check JavaScript runtimes
run: |
node --version
node -e 'if (Number(process.versions.node.split(".")[0]) < 20) process.exit(1)'
firefox --version

- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
cache: false
target: wasm32-unknown-unknown
toolchain: 1.95.0

- name: Install wasm-pack
uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2
with:
checksum: true
fallback: none
tool: wasm-pack@0.15.0

- name: Validate WebAssembly
run: cargo +1.95.0 xtask wasm

required_ci:
name: Required CI
if: ${{ always() }}
Expand All @@ -206,6 +241,7 @@ jobs:
- platform_verifier
- static_checks
- rust
- wasm
permissions: {}
runs-on: linux-amd64-cpu4
timeout-minutes: 5
Expand All @@ -216,6 +252,7 @@ jobs:
PLATFORM_VERIFIER_RESULT: ${{ needs.platform_verifier.result }}
STATIC_RESULT: ${{ needs.static_checks.result }}
RUST_RESULT: ${{ needs.rust.result }}
WASM_RESULT: ${{ needs.wasm.result }}
run: |
require_success() {
local job="$1"
Expand All @@ -232,3 +269,4 @@ jobs:
require_success "Protected checkout verifier matrix" "${PLATFORM_VERIFIER_RESULT}"
require_success "Documentation and Protobuf" "${STATIC_RESULT}"
require_success "Rust matrix" "${RUST_RESULT}"
require_success "WebAssembly" "${WASM_RESULT}"
69 changes: 67 additions & 2 deletions .github/workflows/pr-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,69 @@ jobs:
run: cargo xtask ci
working-directory: candidate

wasm:
name: WebAssembly
needs: authorize
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
env:
CARGO_HOME: ${{ github.workspace }}/.protected-ci/cargo-home
CARGO_TARGET_DIR: ${{ github.workspace }}/.protected-ci/cargo-target
RUSTUP_HOME: ${{ github.workspace }}/.protected-ci/rustup-home
steps:
- name: Check out protected policy
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
path: policy
persist-credentials: false
ref: ${{ needs.authorize.outputs.policy_sha }}

- name: Check out and verify exact candidate
uses: ./policy/.github/actions/protected-candidate-checkout
with:
base-sha: ${{ needs.authorize.outputs.base_sha }}
candidate-path: candidate
fetch-depth: 1
head-sha: ${{ needs.authorize.outputs.head_sha }}
policy-path: policy
repository: ${{ needs.authorize.outputs.head_repository }}

- name: Check JavaScript runtimes
run: |
node --version
node -e 'if (Number(process.versions.node.split(".")[0]) < 20) process.exit(1)'
firefox --version

- name: Set up Rust
uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
cache: false
target: wasm32-unknown-unknown
toolchain: 1.95.0

- name: Install wasm-pack
uses: taiki-e/install-action@43aecc8d72668fbcfe75c31400bc4f890f1c5853 # v2.83.2
with:
checksum: true
fallback: none
tool: wasm-pack@0.15.0

- name: Run protected WebAssembly validation against candidate
env:
ACTIONS_CACHE_URL: ""
ACTIONS_ID_TOKEN_REQUEST_TOKEN: ""
ACTIONS_ID_TOKEN_REQUEST_URL: ""
ACTIONS_RESULTS_URL: ""
ACTIONS_RUNTIME_TOKEN: ""
GH_TOKEN: ""
GITHUB_TOKEN: ""
run: >-
cargo run --locked --manifest-path policy/xtask/Cargo.toml --
wasm --candidate-root "${GITHUB_WORKSPACE}/candidate"

results:
name: Collect protected job results
if: ${{ always() }}
Expand All @@ -533,6 +596,7 @@ jobs:
- static_checks
- rust
- candidate_ci
- wasm
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
Expand All @@ -548,10 +612,11 @@ jobs:
PLATFORM_VERIFIER_RESULT: ${{ needs.platform_verifier.result }}
RUST_RESULT: ${{ needs.rust.result }}
STATIC_CHECKS_RESULT: ${{ needs.static_checks.result }}
WASM_RESULT: ${{ needs.wasm.result }}
WORKFLOW_LINT_RESULT: ${{ needs.workflow_lint.result }}
run: >-
printf 'results_json={"commit_policy":"%s","workflow_lint":"%s","platform_verifier":"%s","static_checks":"%s","rust":"%s","candidate_ci":"%s"}\n'
printf 'results_json={"commit_policy":"%s","workflow_lint":"%s","platform_verifier":"%s","static_checks":"%s","rust":"%s","candidate_ci":"%s","wasm":"%s"}\n'
"${COMMIT_POLICY_RESULT}" "${WORKFLOW_LINT_RESULT}"
"${PLATFORM_VERIFIER_RESULT}" "${STATIC_CHECKS_RESULT}"
"${RUST_RESULT}" "${CANDIDATE_CI_RESULT}"
"${RUST_RESULT}" "${CANDIDATE_CI_RESULT}" "${WASM_RESULT}"
>> "${GITHUB_OUTPUT}"
28 changes: 28 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ Run from the repository root:

```shell
cargo xtask ci
cargo xtask wasm
cargo xtask package-content
cargo xtask update-spec
cargo xtask update-spec --ref origin/dev/example-branch
Expand Down Expand Up @@ -293,6 +294,33 @@ Keep the cargo-machete version aligned with hosted CI. The
names across all features, but remains an unused-dependency heuristic; retain
the all-target, all-feature Clippy and test checks as the compilation proof.

### WebAssembly validation

Install the pinned helper and Rust target before running WebAssembly checks.
Node.js 20 or newer and Firefox must also be on `PATH`.

```shell
rustup target add --toolchain 1.95.0 wasm32-unknown-unknown
cargo install --locked wasm-pack --version 0.15.0
cargo xtask wasm
```

`cargo xtask wasm` checks `yaml-sigil-core`, `yaml-sigil-transcription`,
`yaml-sigil-signing`, `yaml-sigil-verification`, and `yaml-sigil-wasm` for
`wasm32-unknown-unknown` with Rust 1.95.0. It checks the boundary with default
features and `json-schema-validate`, runs the shared schema-enabled suite under
Node.js and headless Firefox, exercises the generated JavaScript API, and
reports optimized raw `web` sizes for both feature sets.

The task sets `CARGO_TARGET_DIR` to a temporary directory for every project
Cargo and `wasm-pack` child process. On ordinary completion, including a
validation error, it explicitly removes that directory and reports cleanup
failures. Abrupt process termination can bypass cleanup. The task also rejects
`.wasm` files retained anywhere in the workspace. Keep the tool version,
install commands, test features, and documentation synchronized with
`xtask/src/wasm.rs`. Do not add this task to provider-specific automation
without a separate review of artifact retention and external Actions.

Hosted CI declares these checks as independent steps. Keep its command coverage,
`xtask/src/ci.rs`, and the exact-command documentation above aligned when
changing the validation sequence. Do not make the xtask read, parse, or test
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"crates/yaml-sigil-transcription",
"crates/yaml-sigil-signing",
"crates/yaml-sigil-verification",
"crates/yaml-sigil-wasm",
"crates/yaml-sigil-test-keys",
"crates/yaml-sigil-conformance",
]
Expand Down Expand Up @@ -45,7 +46,9 @@ ed25519-dalek = { version = "2.1", default-features = false, features = [
"alloc",
"fast",
"rand_core",
"zeroize",
] }
js-sys = { version = "=0.3.104", default-features = false, features = ["std"] }
jsonschema = { version = "0.46.5", default-features = false }
p256 = { version = "0.13", default-features = false, features = ["ecdsa"] }
rand = "0.8"
Expand All @@ -54,6 +57,9 @@ serde_json = "1.0"
noyalib = { version = "0.0.26", default-features = false, features = ["std"] }
tracing = "0.1"
tokio = { version = "1", default-features = false }
wasm-bindgen = "=0.2.127"
wasm-bindgen-test = "=0.3.77"
zeroize = { version = "1.8", default-features = false, features = ["alloc"] }

[workspace.lints.rust]
unsafe_code = "forbid"
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ Treat only the document bytes returned by `VerifierState::Verified` as
authenticated. The caller remains responsible for choosing the artifact form
and deciding which public keys are trusted.

### [`yaml-sigil-wasm`](./crates/yaml-sigil-wasm/README.md)

This unpublished, source-only crate exposes compose, decompose, sign, and
verify operations to browser and Node.js WebAssembly consumers. It provides a
narrow JavaScript boundary over the implementation crates without changing
their native Rust APIs.

### Signing and verification flow

1. A producer uses `yaml-sigil-signing` to sign a document.
Expand Down Expand Up @@ -150,6 +157,27 @@ Run the focused E2E fixture check with:
cargo test -p yaml-sigil-conformance --test e2e_buildtime_keys
```

### WebAssembly validation

The source-only `yaml-sigil-wasm` crate targets `wasm32-unknown-unknown`. The
repository does not publish or retain generated WebAssembly or an npm package.
Install the Rust 1.95 target and pinned helper before running the local task.
Node.js 20 or newer and Firefox must also be on `PATH`.

```shell
rustup target add --toolchain 1.95.0 wasm32-unknown-unknown
cargo install --locked wasm-pack --version 0.15.0
cargo xtask wasm
```

The task checks the runtime crates and both boundary feature sets, runs the
schema-enabled suite under Node.js and headless Firefox, exercises the
generated JavaScript API, and reports optimized raw sizes. It explicitly
removes all temporary project build output and reports cleanup failures. It
also fails if a `.wasm` file is retained in the workspace. Read the
[`yaml-sigil-wasm` README](crates/yaml-sigil-wasm/README.md) for selectors,
result classes, key encodings, copy semantics, and cryptographic limitations.

## Coverage and profiling

Install the report tools with Cargo:
Expand Down
10 changes: 2 additions & 8 deletions crates/yaml-sigil-core/src/tier_a_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,20 @@

//! Vendored JSON Schema validation for parsed [`SignatureDocument`] values.

use std::path::PathBuf;
use std::sync::OnceLock;

use serde_json::Value as JsonValue;

use crate::error::CoreError;
use crate::signature_doc::SignatureDocument;

fn schema_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("spec/schema/YamlSigilSignature.v1alpha1.schema.json")
}
const TIER_A_SCHEMA: &str = include_str!("../spec/schema/YamlSigilSignature.v1alpha1.schema.json");

fn compiled_schema() -> Result<&'static jsonschema::Validator, CoreError> {
static SCHEMA: OnceLock<Result<jsonschema::Validator, String>> = OnceLock::new();
let validator = SCHEMA.get_or_init(|| {
let schema_src =
std::fs::read_to_string(schema_path()).map_err(|e| format!("read schema: {e}"))?;
let schema: JsonValue =
serde_json::from_str(&schema_src).map_err(|e| format!("parse schema JSON: {e}"))?;
serde_json::from_str(TIER_A_SCHEMA).map_err(|e| format!("parse schema JSON: {e}"))?;
jsonschema::validator_for(&schema).map_err(|e| format!("compile schema: {e}"))
});
match validator {
Expand Down
4 changes: 2 additions & 2 deletions crates/yaml-sigil-signing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ workspace = true
[dependencies]
base64 = "0.22"
buffa = { workspace = true }
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
p256 = { version = "0.13.2", features = ["ecdsa"] }
ed25519-dalek = { workspace = true }
p256 = { workspace = true }
thiserror = "2"
tracing = "0.1"
yaml-sigil-core = { workspace = true, default-features = false }
Expand Down
4 changes: 2 additions & 2 deletions crates/yaml-sigil-verification/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ workspace = true
[dependencies]
base64 = "0.22"
curve25519-dalek = "4.1"
ed25519-dalek = { version = "2.1", features = ["rand_core"] }
p256 = { version = "0.13.2", features = ["ecdsa"] }
ed25519-dalek = { workspace = true }
p256 = { workspace = true }
sha2 = "0.10"
signature = "2.2"
tracing = "0.1"
Expand Down
43 changes: 43 additions & 0 deletions crates/yaml-sigil-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# SPDX-FileCopyrightText: Copyright 2026 NVIDIA CORPORATION & AFFILIATES
# SPDX-License-Identifier: Apache-2.0

[package]
name = "yaml-sigil-wasm"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "Browser WebAssembly boundary for YamlSigil v1alpha1"
license.workspace = true
repository.workspace = true
readme = "README.md"
publish = false

[lib]
crate-type = ["cdylib", "rlib"]

[lints]
workspace = true

[features]
browser-tests = []
json-schema-validate = [
"yaml-sigil-core/json-schema-validate",
"yaml-sigil-signing/json-schema-validate",
"yaml-sigil-verification/json-schema-validate",
]

[dependencies]
ed25519-dalek = { workspace = true }
js-sys = { workspace = true }
p256 = { workspace = true }
wasm-bindgen = { workspace = true }
yaml-sigil-core = { workspace = true }
yaml-sigil-signing = { workspace = true }
yaml-sigil-transcription = { workspace = true }
yaml-sigil-verification = { workspace = true }
zeroize = { workspace = true }

[dev-dependencies]
console_error_panic_hook = "0.1"
wasm-bindgen-test = { workspace = true }
Loading