From 21449629390d3907a4b8df7f850bdc622339f6ce Mon Sep 17 00:00:00 2001 From: Richard McDaniel <1130888+rmcdaniel@users.noreply.github.com> Date: Thu, 5 Nov 2026 02:47:07 +0000 Subject: [PATCH] docs: bind the browser packet to the accepted SDK 212 artifact --- security/browser-headers/README.md | 16 +-- .../rust-browser-validation.json | 12 +-- security/browser-headers/rust-inventory.json | 14 +-- .../rust-local-browser-packet.json | 16 +-- .../rust-local-browser-plan.md | 100 ++++++++++++++++-- security/browser-headers/rust-policy.json | 12 +-- .../browser-headers/rust-rule.candidate.json | 12 +-- .../browser-headers/rust-stage-proposal.md | 28 ++--- 8 files changed, 148 insertions(+), 62 deletions(-) diff --git a/security/browser-headers/README.md b/security/browser-headers/README.md index 46c27f9ee34..235c23be145 100644 --- a/security/browser-headers/README.md +++ b/security/browser-headers/README.md @@ -9,8 +9,8 @@ The candidate is disabled and both approval flags remain false. ## Reviewed input and source prerequisite `rust-policy.json` binds the accepted Rust 4.12.0 public crate and the -[protected documentation run](https://github.com/durable-workflow/sdk-rust/actions/runs/41939116087), -artifact 13375154247. Its ZIP SHA-256 is recorded with its tooling revision. +[protected documentation run](https://github.com/durable-workflow/sdk-rust/actions/runs/41954374074), +artifact 13382143373. Its ZIP SHA-256 is recorded with its tooling revision. The full input contains 484 regular files and 372 HTML pages. The earlier artifact's `help.html` and `settings.html` contained executable @@ -18,7 +18,7 @@ links and inline history-back handlers. The generator refuses them. The merged [SDK source correction](https://github.com/durable-workflow/sdk-rust/pull/207) provides a safe crate-index fallback and an external history listener. The sole automatic protected build and deployment for the current proposal are successful -at tooling revision `1ea3bfc3965423534b5bbbed700371f126835fc3`. Public package/source +at tooling revision `2b45fd7b12a6e9460666045a3c7f644860caea3b`. Public package/source identities remain unchanged. Eight live identity, navigation, help/settings and API files matched this artifact on November 5, 2026. The [current staging proposal](rust-stage-proposal.md) records their hashes and the @@ -27,11 +27,11 @@ separate acceptance that remains pending. The current inventory describes that accepted published artifact. `source_preparation` is null. `history/` retains the earlier local derivative and the accepted SDK #207 artifact's browser result as historical evidence only. -The derivative was not a public artifact. The current artifact changes the build -identity and rustdoc's current-crate attribute on help/settings. Its executable -script hashes and generated rule are unchanged. That static comparison does not -carry forward the earlier browser pass. Current compatibility is unrun and the -rule remains disabled. +The derivative was not a public artifact. SDK #212 retains every page and asset +byte from the prior SDK #209 artifact. +Only the build identity changes. The executable script hashes and generated +rule are unchanged. That static comparison does not carry forward the earlier +browser pass. Current compatibility is unrun and the rule remains disabled. ## Policy and inventory diff --git a/security/browser-headers/rust-browser-validation.json b/security/browser-headers/rust-browser-validation.json index 58f49b4a612..156c8e4ac4a 100644 --- a/security/browser-headers/rust-browser-validation.json +++ b/security/browser-headers/rust-browser-validation.json @@ -3,16 +3,16 @@ "scope": "Current artifact source proposal only; browser, telemetry, TLS/HSTS and provider acceptance pending", "input_artifact": { "repository": "durable-workflow/sdk-rust", - "run_id": 41939116087, - "artifact_id": 13375154247, - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3", - "zip_bytes": 4071555, - "zip_sha256": "ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1" + "run_id": 41954374074, + "artifact_id": 13382143373, + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b", + "zip_bytes": 4071521, + "zip_sha256": "15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95" }, "source_preparation": null, "compatibility_accepted": false, "historical_result": "history/rust-sdk207-browser-validation.json", "next_action": "Security reviews the exact current-artifact proposal; General separately admits bounded compatibility and any later provider stage.", - "inventory_sha256": "8c4bc2eb1ef3ffeeb9788a9d0b44d7b718a2a56caff55b3d4531716a25b270d7", + "inventory_sha256": "01e0872a2d03726d52092362f7654285a81635f92f2f5bd30ecace505d14c71d", "csp_sha256": "b002ffc9f95abc8145319d8a045aef9f0184d323d81ff9aec5a9de54d2f2d710" } diff --git a/security/browser-headers/rust-inventory.json b/security/browser-headers/rust-inventory.json index ff5c5f6db39..5e3504f5f17 100644 --- a/security/browser-headers/rust-inventory.json +++ b/security/browser-headers/rust-inventory.json @@ -3,11 +3,11 @@ "origin": "https://rust.durable-workflow.com", "input_artifact": { "repository": "durable-workflow/sdk-rust", - "run_id": 41939116087, - "artifact_id": 13375154247, - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3", - "zip_bytes": 4071555, - "zip_sha256": "ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1" + "run_id": 41954374074, + "artifact_id": 13382143373, + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b", + "zip_bytes": 4071521, + "zip_sha256": "15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95" }, "source_preparation": null, "build_identity": { @@ -16,7 +16,7 @@ "version": "4.12.0", "crate_sha256": "620a8287af73c363dac61f0a91f20971a39f3b8cdc9513fbea85cdbd95a74ba4", "source_revision": "4b2440a5618ccc1c98960ea2e1e62127042c3590", - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3" + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b" }, "files": [ { @@ -27,7 +27,7 @@ { "path": "__build.json", "bytes": 332, - "sha256": "622b90e304c9ea1c2e89efeb4fb9145d64f86ca50f3bcedaf2f5f94fa773ce0d" + "sha256": "6338f8427799ac2cf9b0c2a1c08fcf1889b8887761f42c2d748dc0e0cfad8a8a" }, { "path": "activity_options/all.html", diff --git a/security/browser-headers/rust-local-browser-packet.json b/security/browser-headers/rust-local-browser-packet.json index f7c0422d199..30c3f04bb43 100644 --- a/security/browser-headers/rust-local-browser-packet.json +++ b/security/browser-headers/rust-local-browser-packet.json @@ -3,11 +3,11 @@ "run_status": "not-run", "input_artifact": { "repository": "durable-workflow/sdk-rust", - "run_id": 41939116087, - "artifact_id": 13375154247, - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3", - "zip_bytes": 4071555, - "zip_sha256": "ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1" + "run_id": 41954374074, + "artifact_id": 13382143373, + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b", + "zip_bytes": 4071521, + "zip_sha256": "15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95" }, "playwright_version": "1.64.0", "expected_chromium_version": "156.0.8078.4", @@ -27,9 +27,9 @@ "rust-browser.test.cjs": "0b2f731db13e4f095230673bfda3d97a36f71c5ef62b8f3702a7978740d17d61", "rust-browser-inputs.cjs": "43bb050185f05187e7d94a40d73b4fae965276696fe7356e6609b319c6b120ee", "rust-browser-source.test.cjs": "deafc5ce08806c148b189a48f11012cc0c03600122dd8fe34eb01e223f381952", - "rust-policy.json": "a357bcc5ea8ad96db5e601e4e1c1cb3c8c7eb3441caa9b331d0c2a906ffe84d8", - "rust-inventory.json": "8c4bc2eb1ef3ffeeb9788a9d0b44d7b718a2a56caff55b3d4531716a25b270d7", - "rust-rule.candidate.json": "bce0c66db4454bc16941a8f5dadb04706315e673d5e2c33f677e7728bafd0135", + "rust-policy.json": "c2441f7b0bf7aebc0c4ee491cda0470170231bc8baf14b4c2163552b8d2fd79c", + "rust-inventory.json": "01e0872a2d03726d52092362f7654285a81635f92f2f5bd30ecace505d14c71d", + "rust-rule.candidate.json": "90b37562aabf6ed8619fbb488ad764137731bec976d8e2ff5e7bfa382fba5634", "runtime/package.json": "a2cd697a8ed17ab196e4eac1bcb15ceec6da243a600a8f2584c8aabdadfc3c9d", "runtime/package-lock.json": "959580e91aae43c61d0bbfcf11c21eb52ecafcfadb088f166b322fd3988a47ae" } diff --git a/security/browser-headers/rust-local-browser-plan.md b/security/browser-headers/rust-local-browser-plan.md index 73368385a56..1b39da08754 100644 --- a/security/browser-headers/rust-local-browser-plan.md +++ b/security/browser-headers/rust-local-browser-plan.md @@ -11,12 +11,13 @@ production operation is proposed here. ## Inputs and provenance `rust-local-browser-packet.json` binds the executable/data closure and exact -runtime. Its artifact tuple is the accepted SDK #209 input already recorded in -`rust-policy.json`: artifact 13375154247, ZIP SHA-256 -`ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1`, -484 files / 372 HTML pages / 49 inline-script hashes. Public Rust 4.12.0, crate -source and tooling identities are preserved. `rust-rule.candidate.json` and all -policy/approval/browser-result files remain unchanged and disabled/unrun. +runtime. Its artifact tuple is the accepted SDK #212 input already recorded in +`rust-policy.json`: artifact 13382143373, ZIP SHA-256 +`15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95`, +484 files / 372 HTML pages / 49 inline-script hashes. Public Rust 4.12.0 crate +and source identities are preserved; tooling matches the accepted SDK #212 +build. The parsed rule, policy sources, approval flags and unrun status remain +unchanged. Input provenance fields now bind this artifact. The proposed runtime is the existing Playwright 1.64.0 generation, pinned to the official **linux/amd64** platform manifest in the packet. Registry metadata was @@ -39,7 +40,7 @@ membership, recheck the accepted protected-run/deployment identity and compare live `__build.json` with the exact artifact. Those are finite read-only checks. Stop if the live input has changed, the artifact has expired, or a required input is unavailable. Do not dispatch a replacement build or silently retarget the -packet. The input API expires this ZIP on November 6, 2026 at 00:09:56 UTC. +packet. The input API expires this ZIP on November 6, 2026 at 02:19:51 UTC. ## What the proposed check exercises @@ -72,6 +73,88 @@ Normal CI runs only `rust-browser-source.test.cjs` with fake routes and temporar input files, plus the existing header-policy controls. It never imports Playwright, launches a browser or runs the optional browser entrypoint. +## Required custody before execution + +[Security's source review](https://github.com/durable-workflow/durable-workflow.github.io/pull/477#issuecomment-6647872853) +requires verification outside Node before any packet code is loaded. The +in-process check cannot authenticate code that is already executing. The exact +current packet SHA-256 is +`2060309bf48d3e179abf55c0f2e1ffbc4908fcd7227b0cc4eb11089afde71f2a`. + +Before any separate preparation/run admission, General and Security must bind +an approved write-preventing mechanism and exclusive owner for all three public +staging trees. They must remain unchanged through the attempt. Host writers can +still change a read-only bind mount's source; bind flags and a prior hash alone +do not establish that custody. Ordinary mutable worktrees and downloaded caches +are not execution seals. An unavailable seal is a blocker, not authority to add +privileges, a helper, a writable mount or a new storage service. + +After that separate admission, use the following literal reviewed command with +the host's trusted isolated Python interpreter. Copy it from the reviewed +record, not from an unverified packet file. Do not load any packet Python/Node +module to perform this check. Bind the interpreter identity in the execution +record. The packet and runtime staging paths must be outside home/credential +trees, absolute, regular, free of symlink components and exclusively owned. +Copy only the eight manifest members and the manifest into the packet tree. +Exclude repository metadata, extra modules/configuration and `node_modules` +from this tree so they cannot shadow the separately sealed runtime dependency. + +```sh +/usr/bin/python3 -I - "$packet_directory" <<'PY' +import hashlib, json, stat, sys +from pathlib import Path, PurePosixPath + +root = Path(sys.argv[1]) +assert root.is_absolute() and root.resolve() == root +assert root.is_dir() and not root.is_symlink() +assert not root.is_relative_to(Path.home()) +manifest_path = root / 'rust-local-browser-packet.json' +assert stat.S_ISREG(manifest_path.lstat().st_mode) +manifest_bytes = manifest_path.read_bytes() +expected = '2060309bf48d3e179abf55c0f2e1ffbc4908fcd7227b0cc4eb11089afde71f2a' +assert hashlib.sha256(manifest_bytes).hexdigest() == expected +manifest = json.loads(manifest_bytes) +assert manifest['run_status'] == 'not-run' and len(manifest['files']) == 8 +expected_files = set(manifest['files']) | {'rust-local-browser-packet.json'} +actual_files = set() +for member in root.rglob('*'): + mode = member.lstat().st_mode + assert stat.S_ISREG(mode) or stat.S_ISDIR(mode) + relative = member.relative_to(root).as_posix() + if stat.S_ISDIR(mode): + assert relative == 'runtime' + else: + actual_files.add(relative) +assert actual_files == expected_files +for name, expected_hash in manifest['files'].items(): + relative = PurePosixPath(name) + assert not relative.is_absolute() and '..' not in relative.parts + member = root + for part in relative.parts: + member = member / part + assert not member.is_symlink() + assert stat.S_ISREG(member.lstat().st_mode) + assert hashlib.sha256(member.read_bytes()).hexdigest() == expected_hash +print('External packet and eight-member hash check passed; no packet code loaded.') +PY +``` + +This check is an integrity prerequisite. It does not create a seal or approve an +attempt. Record the separate write-prevention/custody proof and recheck the +mounts and cached image immediately before creating the admitted cell. + +Dependency preparation is separately held. Its future admitted environment +must have a container-only empty home, distinct empty user/global npm config +files, a task-private cache, a public registry and an explicit environment +allowlist. No host home/profile, `.npmrc`, token, credential directory or secret +proxy setting may be inherited. Set `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` and +`--ignore-scripts`; use the exact lock and verify both archive integrity values +and library version. Do not import Playwright during preparation. Enumerate and +hash the resulting dependency tree, reject external links, then establish its +approved immutable/exclusive custody before any browser code resolves it. +No such environment, dependency tree or seal has been created by this source +retarget. Missing preparation/image/custody proof leaves execution held. + ## Proposed execution and cleanup Documentation owns one attempt and cleanup. General names the admitted executor @@ -83,7 +166,8 @@ There is no automatic retry, host IPC, privileged mode, extra capability or published port. The writable tmpfs and private shared memory count within the container's memory cap. This is a proposed budget, not a reservation. -The following is the exact proposed launch shape after admission. Assign the +The following is the exact proposed launch shape after admission, the external +pre-load hash check and accepted custody proof above. Assign the three absolute input paths from the reviewed public-input preparation; reject commas/newlines in those paths. `packet_directory` contains only this public packet. `runtime_modules` is the prepared public dependency tree. Do not point diff --git a/security/browser-headers/rust-policy.json b/security/browser-headers/rust-policy.json index 979d0041636..583b527041c 100644 --- a/security/browser-headers/rust-policy.json +++ b/security/browser-headers/rust-policy.json @@ -4,15 +4,15 @@ "version": "4.12.0", "source_revision": "4b2440a5618ccc1c98960ea2e1e62127042c3590", "crate_sha256": "620a8287af73c363dac61f0a91f20971a39f3b8cdc9513fbea85cdbd95a74ba4", - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3" + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b" }, "input_artifact": { "repository": "durable-workflow/sdk-rust", - "run_id": 41939116087, - "artifact_id": 13375154247, - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3", - "zip_bytes": 4071555, - "zip_sha256": "ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1" + "run_id": 41954374074, + "artifact_id": 13382143373, + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b", + "zip_bytes": 4071521, + "zip_sha256": "15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95" }, "sources": { "script": [ diff --git a/security/browser-headers/rust-rule.candidate.json b/security/browser-headers/rust-rule.candidate.json index 0629eb0099e..3b66f5f9ee0 100644 --- a/security/browser-headers/rust-rule.candidate.json +++ b/security/browser-headers/rust-rule.candidate.json @@ -2,15 +2,15 @@ "schema": "durable-workflow.browser-security-rule-candidate/v1", "rollout_approved": false, "phase": "http_response_headers_transform", - "inventory_sha256": "8c4bc2eb1ef3ffeeb9788a9d0b44d7b718a2a56caff55b3d4531716a25b270d7", + "inventory_sha256": "01e0872a2d03726d52092362f7654285a81635f92f2f5bd30ecace505d14c71d", "compatibility_accepted": false, "input_artifact": { "repository": "durable-workflow/sdk-rust", - "run_id": 41939116087, - "artifact_id": 13375154247, - "tooling_revision": "1ea3bfc3965423534b5bbbed700371f126835fc3", - "zip_bytes": 4071555, - "zip_sha256": "ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1" + "run_id": 41954374074, + "artifact_id": 13382143373, + "tooling_revision": "2b45fd7b12a6e9460666045a3c7f644860caea3b", + "zip_bytes": 4071521, + "zip_sha256": "15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95" }, "source_preparation": null, "rule": { diff --git a/security/browser-headers/rust-stage-proposal.md b/security/browser-headers/rust-stage-proposal.md index 4d1a207347c..31acbebe83e 100644 --- a/security/browser-headers/rust-stage-proposal.md +++ b/security/browser-headers/rust-stage-proposal.md @@ -15,7 +15,7 @@ credentials or API were used. A source merge does not clear those gates. ## Exact input -The [Rust owner receipt](https://github.com/durable-workflow/sdk-rust/pull/209#issuecomment-6645951490) +The [Rust owner receipt](https://github.com/durable-workflow/sdk-rust/pull/212#issuecomment-6648011712) binds the following accepted automatic protected build: | Input | Value | @@ -23,15 +23,15 @@ binds the following accepted automatic protected build: | Package | Published `durable-workflow` 4.12.0 | | Public source | `4b2440a5618ccc1c98960ea2e1e62127042c3590` | | Crate SHA-256 | `620a8287af73c363dac61f0a91f20971a39f3b8cdc9513fbea85cdbd95a74ba4` | -| Tooling | `1ea3bfc3965423534b5bbbed700371f126835fc3` | -| Protected run | [41939116087](https://github.com/durable-workflow/sdk-rust/actions/runs/41939116087), push, attempt 1 | -| Successful deployment | `7561374287` | -| Pages artifact | `13375154247`, 4,071,555-byte ZIP | -| ZIP SHA-256 | `ff902191bdb90f7f1d047ecc5d27e9b8c5215a0239e4f11fcbf574f7ddab83d1` | +| Tooling | `2b45fd7b12a6e9460666045a3c7f644860caea3b` | +| Protected run | [41954374074](https://github.com/durable-workflow/sdk-rust/actions/runs/41954374074), push, attempt 1 | +| Successful deployment | `7563384998` | +| Pages artifact | `13382143373`, 4,071,521-byte ZIP | +| ZIP SHA-256 | `15f1e2745a0b268e1093820b3486c4910e8d1bf7b63062e562200586d3beef95` | | Extracted inventory | 484 regular files, 372 HTML pages, 49 executable inline-script hashes | -| Inventory file SHA-256 | `8c4bc2eb1ef3ffeeb9788a9d0b44d7b718a2a56caff55b3d4531716a25b270d7` | -| Policy file SHA-256 | `a357bcc5ea8ad96db5e601e4e1c1cb3c8c7eb3441caa9b331d0c2a906ffe84d8` | -| Candidate file SHA-256 | `bce0c66db4454bc16941a8f5dadb04706315e673d5e2c33f677e7728bafd0135` | +| Inventory file SHA-256 | `01e0872a2d03726d52092362f7654285a81635f92f2f5bd30ecace505d14c71d` | +| Policy file SHA-256 | `c2441f7b0bf7aebc0c4ee491cda0470170231bc8baf14b4c2163552b8d2fd79c` | +| Candidate file SHA-256 | `90b37562aabf6ed8619fbb488ad764137731bec976d8e2ff5e7bfa382fba5634` | The downloaded ZIP matched its GitHub API digest. Extraction rejected absolute paths, parent traversal and non-regular members. Compared with #472's immutable @@ -39,16 +39,18 @@ SDK #207 subject, only `__build.json`, `help.html` and `settings.html` differ. The two HTML changes replace `data-current-crate="activity_options"` with `data-current-crate="workflow_testing"`. All other files and every inventoried script/style/handler/origin record are identical. Public package/source identity -is preserved. The historical browser pass remains in -`history/rust-sdk207-browser-validation.json`; it does not qualify this artifact. +is preserved. SDK #212 retains every SDK #209 page and asset byte. Only its +`__build.json` changes from the accepted #476/#477 subject. The historical browser +pass remains in `history/rust-sdk207-browser-validation.json`; it does not qualify +this artifact. -At 01:02 UTC on November 5, 2026, these actual HTTPS responses returned 200 and +On November 5, 2026, these actual HTTPS responses returned 200 and matched the artifact byte for byte. All six proposed security headers were absent. This is a finite identity sample, not a live site-wide compatibility run. | Path at `https://rust.durable-workflow.com/` | SHA-256 | | --- | --- | -| `__build.json` | `622b90e304c9ea1c2e89efeb4fb9145d64f86ca50f3bcedaf2f5f94fa773ce0d` | +| `__build.json` | `6338f8427799ac2cf9b0c2a1c08fcf1889b8887761f42c2d748dc0e0cfad8a8a` | | `index.html` | `b2e918903002c3d863aab6eb77c44a01af5ece95d26bb433120d396cae055fae` | | `durable_workflow/index.html` | `8747b578d0749a534ae67416aeb79bca02b7e50c9c8194f85aae883cc99f0e20` | | `durable_workflow/struct.Client.html` | `1c4a15c7383e28a3af199e55ee217a157165f45e131dd6e708ef5788661606b1` |