From 49a5f0222d6ddcf5006883be4ed0e5c496bcd77e Mon Sep 17 00:00:00 2001
From: Arthur
Date: Mon, 17 Aug 2026 23:58:29 +0800
Subject: [PATCH] Fix 0.24 audit blockers
---
AGENTS.md | 7 +-
CHANGELOG.md | 12 +-
Cargo.lock | 12 +-
Cargo.toml | 4 +-
README.md | 20 +-
contracts/registry-type-script/Cargo.lock | 2 +-
contracts/registry-type-script/Cargo.toml | 2 +-
contracts/registry-type-script/README.md | 3 +-
.../v0.23.0/cellscript-registry-type-script | Bin 0 -> 3352 bytes
.../v0.24.0/cellscript-registry-type-script | Bin 0 -> 3352 bytes
.../release-manifest.json | 10 +-
.../registry-type-script/tests/ckb_vm.rs | 2 +-
crates/cellscript-artifact-checker/Cargo.toml | 2 +-
crates/cellscript-ckb-adapter/Cargo.toml | 2 +-
crates/cellscript-fiber-adapter/Cargo.toml | 2 +-
crates/cellscript-tools/Cargo.toml | 2 +-
.../cellscript-tools/src/tooling_release.rs | 13 ++
crates/cellscript-wasm/Cargo.toml | 2 +-
docs/CELLSCRIPT_CKB_ADAPTER.md | 2 +-
docs/CELLSCRIPT_GATE_POLICY.md | 12 ++
docs/CELLSCRIPT_LS_IDL_REGISTRY_PROFILE.md | 4 +-
...KAGE_PROVENANCE_AND_DEPLOYMENT_IDENTITY.md | 4 +-
.../releases/CELLSCRIPT_0_24_RELEASE_NOTES.md | 20 +-
.../Tutorial-12-Phase1-Registry-End-to-End.md | 4 +-
docs/wiki/_Sidebar.md | 1 +
editors/vscode-cellscript | 2 +-
proposals/novaseal | 2 +-
scripts/cellscript_ckb_stateful_scenarios.sh | 8 +
scripts/cellscript_gate.sh | 55 ++++-
services/registry-api/README.md | 6 +-
services/registry-api/src/index.ts | 6 +-
.../registry-api/test/registry-api.test.ts | 26 ++-
services/registry-api/wrangler.example.toml | 2 +-
.../registry-artifact-verifier/Cargo.lock | 6 +-
.../registry-artifact-verifier/Cargo.toml | 2 +-
services/registry-verifier/Cargo.lock | 6 +-
services/registry-verifier/Cargo.toml | 2 +-
src/lib.rs | 5 +
src/package/mod.rs | 161 +++++++++++++-
src/package/registry.rs | 203 +++++++++++++++---
40 files changed, 536 insertions(+), 100 deletions(-)
create mode 100755 contracts/registry-type-script/artifacts/v0.23.0/cellscript-registry-type-script
create mode 100755 contracts/registry-type-script/artifacts/v0.24.0/cellscript-registry-type-script
diff --git a/AGENTS.md b/AGENTS.md
index 1b42d4f9..555f7d87 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -315,8 +315,11 @@ Existing command families to be aware of:
- `proposals/novaseal` is a submodule (`NovaSeal.git`, branch `main`). Same
for `proposals/evolving-dob/evolving-dob-profile-v1`.
- `tools/ckb-tx-measure` depends on `../ckb/util/jsonrpc-types` and
- `../ckb/util/types`; the gate builds the helper with CellScript's pinned
- Rust 1.97.1 toolchain so its declared `rust-version` remains enforceable.
+ `../ckb/util/types`; when release validation receives `--ckb-repo`, the gate
+ stages the helper's tracked workspace under `target/` so those same relative
+ paths resolve to the explicit checkout. The gate builds the helper with
+ CellScript's pinned Rust 1.97.1 toolchain so its declared `rust-version`
+ remains enforceable.
- `--primitive-strict 0.16` is the current production assurance gate; the
README mentions it and the policy lives in `docs/`.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f945f50..05aab5a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,16 @@
# Changelog
-## Unreleased
+## 0.24.0 - 2026-08-17
+
+- Close the 0.24 audit blockers: reject transitive executable resolvers and
+ unsafe Git refs, bound and origin-bind Registry downloads, preserve exact
+ LS-IDL Type-script identity, and stop exposing unexpected service errors.
+ Restore a deliberate Clippy boundary for legacy explicit-context helpers,
+ align package identities with 0.24, and carry the closed 0.23 release and
+ NovaSeal evidence forward.
+- Isolate every CKB-dependent gate check. The backend stateful wrapper accepts
+ `CELLSCRIPT_CKB_REPO`, and release modes propagate `--ckb-repo` to the staged
+ `ckb-tx-measure` workspace instead of silently using another sibling checkout.
- Remove the unreachable external RISC-V toolchain fallback and make the
audited internal assembler the sole ELF-emission path. Reassign `E2400` to
diff --git a/Cargo.lock b/Cargo.lock
index b92e72b7..1392a0df 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -314,7 +314,7 @@ dependencies = [
[[package]]
name = "cellscript"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -354,7 +354,7 @@ dependencies = [
[[package]]
name = "cellscript-artifact-checker"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"blake2b_simd",
"clap",
@@ -365,7 +365,7 @@ dependencies = [
[[package]]
name = "cellscript-ckb-adapter"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"ckb-hash",
@@ -388,7 +388,7 @@ dependencies = [
[[package]]
name = "cellscript-fiber-adapter"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"camino",
@@ -408,7 +408,7 @@ dependencies = [
[[package]]
name = "cellscript-tools"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"blake2b-ref",
@@ -431,7 +431,7 @@ dependencies = [
[[package]]
name = "cellscript-wasm"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"cellscript",
"serde",
diff --git a/Cargo.toml b/Cargo.toml
index 124516cd..b3566a61 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,7 +21,7 @@ resolver = "3"
[package]
name = "cellscript"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
autobins = false
@@ -82,7 +82,7 @@ blake2b_simd = "1.0"
toml = "0.8"
hex = "0.4"
semver = "1.0"
-cellscript-artifact-checker = { version = "=0.22.0", path = "crates/cellscript-artifact-checker" }
+cellscript-artifact-checker = { version = "=0.24.0", path = "crates/cellscript-artifact-checker" }
indexmap = "=2.2.6"
diff --git a/README.md b/README.md
index 08cfc346..1891ec9d 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](https://github.com/CellScript-Labs/CellScript/actions/workflows/ci.yml)
-[](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.22.0)
+[](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.23.0)
[](LICENSE-MIT)
[](Cargo.toml)
[](#target-profiles)
@@ -20,8 +20,8 @@ artifacts, together with typed metadata for auditing, policy checks, schema
binding, and scheduler-aware execution.
The current stable release is
-[CellScript v0.22.0](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.22.0).
-See the [0.22 release notes](docs/releases/CELLSCRIPT_0_22_RELEASE_NOTES.md)
+[CellScript v0.23.0](https://github.com/CellScript-Labs/CellScript/releases/tag/v0.23.0).
+See the [0.23 release notes](docs/releases/CELLSCRIPT_0_23_RELEASE_NOTES.md)
for its shipped surface, evidence boundaries, and migration checklist.
The completed 0.23 implementation scope is tracked in the
[0.23 release notes](docs/releases/CELLSCRIPT_0_23_RELEASE_NOTES.md); those
@@ -122,7 +122,7 @@ curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scr
Or pin a specific version:
```bash
-CELLSCRIPT_VERSION=0.22.0 curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scripts/install.sh | sh
+CELLSCRIPT_VERSION=0.23.0 curl -fsSL https://raw.githubusercontent.com/CellScript-Labs/CellScript/main/scripts/install.sh | sh
```
The release page publishes `SHA256SUMS` alongside all four platform archives.
@@ -130,7 +130,7 @@ The release page publishes `SHA256SUMS` alongside all four platform archives.
Build the exact published source instead:
```bash
-git clone --branch v0.22.0 --depth 1 https://github.com/CellScript-Labs/CellScript.git
+git clone --branch v0.23.0 --depth 1 https://github.com/CellScript-Labs/CellScript.git
cd CellScript
cargo install --locked --path .
```
@@ -732,7 +732,7 @@ policy defaults:
[package]
edition = "2026"
name = "token"
-version = "0.22.0"
+version = "0.24.0"
entry = "src/main.cell"
source_roots = ["src"]
@@ -916,9 +916,11 @@ the manual, CI, recovery, and external-wallet path.
- The default source-package authority is
`https://api.registry.cellscript.dev`; only publicly accepted statuses enter
ordinary version selection. `CELLSCRIPT_REGISTRY_API_URL` changes that API
- origin, while `CELLSCRIPT_REGISTRY_URL` explicitly selects the legacy
- Git/offline discovery authority. An unavailable production API does not
- silently downgrade to Git discovery.
+ origin, and `CELLSCRIPT_REGISTRY_STATIC_ORIGIN` explicitly trusts a separate
+ immutable-object origin for a private Registry. Snapshot downloads are
+ origin-bound; a lockfile cannot redirect the client to an unrelated host.
+ `CELLSCRIPT_REGISTRY_URL` selects the legacy Git/offline discovery authority.
+ An unavailable production API does not silently downgrade to Git discovery.
- Registry discovery may grow to include CellScript packages, verifier
artifacts, deployed artifact records, reproducible artifacts, and external
CKB tooling artifacts. Dependency resolution stays narrower than discovery.
diff --git a/contracts/registry-type-script/Cargo.lock b/contracts/registry-type-script/Cargo.lock
index 8e1964bd..2218782d 100644
--- a/contracts/registry-type-script/Cargo.lock
+++ b/contracts/registry-type-script/Cargo.lock
@@ -176,7 +176,7 @@ dependencies = [
[[package]]
name = "cellscript-registry-type-script"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"ckb-hash",
"ckb-std",
diff --git a/contracts/registry-type-script/Cargo.toml b/contracts/registry-type-script/Cargo.toml
index de75b24d..5be5777b 100644
--- a/contracts/registry-type-script/Cargo.toml
+++ b/contracts/registry-type-script/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-registry-type-script"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/contracts/registry-type-script/README.md b/contracts/registry-type-script/README.md
index 6c26ae72..0926bcaa 100644
--- a/contracts/registry-type-script/README.md
+++ b/contracts/registry-type-script/README.md
@@ -43,7 +43,8 @@ Reproduce the canonical Linux artifact with the pinned container digest:
contracts/registry-type-script/build_canonical_container.sh
```
-The deployable artifact is tracked under `artifacts/v0.22.0` and was produced
+The current deployable artifact is tracked under `artifacts/v0.24.0`; the
+identical historical release bytes remain under their versioned directories. It was produced
for the `x86_64-unknown-linux-gnu` host with the builder image digest recorded
in `release-manifest.json`. Rust/LLVM may order identical RISC-V functions
differently on another build host, so the script claims a byte-for-byte
diff --git a/contracts/registry-type-script/artifacts/v0.23.0/cellscript-registry-type-script b/contracts/registry-type-script/artifacts/v0.23.0/cellscript-registry-type-script
new file mode 100755
index 0000000000000000000000000000000000000000..9a756b8aba6aeb32444c747fdadeb54e29aa4bfe
GIT binary patch
literal 3352
zcmbtXeQXrR6`$Fi**$;M+&rl48i@~iF0JLu|EL(y@F)@$&?I!q9xQ3JEZZFbc3H
zF-+Iz$Gn-1?!QhK$dne{ET(0CUpK?h@LSF68dD;xzGhYwx`~_nH094k-OiNX^kaMN
zh}ZQUIeI)(8uX?)GAG5;{xatGzc$!@H9KFo5pTYKqXC%GJbl52^{&E(JTpAPJJuuo
zI>wJBaxp9&{0`X=GHF-CdO7sB02^i`j-*?7V-UA-JNf-2Lf;ZP#RHauab5AhOW+UV
zp;Lc3d+w9-7e+6B>PP}>lmUGk^i&BUKS=Oj=hof1Env;8*xhtMF9(}cW&>>%^mm|d
z0W5=q35&b_yK&=r!aWKKWDdxqg)X6u!b0-V}8I+17_wZyX!Wu=^m)E(c1NMl64$bue!#(Gj$!xTf_^@9o3aKe}>y`t10|2b;cl
z^^W&;`Sl|^bAHQ>MG|bHlo+%~aW$0s9{lp)ore!2@gvRBF5g&uNayJOK~9TA_aRBB
z=7KIAJMp|FJz`+Jt((HZ!i{aZCL`Zb|F%=LbxPiPiFQ6vC7R4Em$=_{t~VIRTsT`z
zqt>?c-q5C2W2ecy3yW1(_>lypjXZ|3j>4Hh``KbT5JKCBpU5Gej1KNS{zlWD!$oV+-!%_Ys-&kUmmFWNM>Q
z3Bi4Fw}~7-PWs}H5;?&`A|)-TPYe>-a+LI0dWal1MB3oi5O>+tJx<(j
z8`m2Vw)Cr&
z;Uld%E!-40%U$Dcg({X=m&e$pwzTHV;
z9!5!UzmPa#nUQE7%-2yfOG=-hQ_bO_d_fJ>UJMf6xsULLJ%o21CH&eU
zqOCnc_!q~C_S4%$&yeV3JyNcmM4A&5U?adrfQ|H8hB+^vMI54+Dzg?G-Le5iy`N)6
zoko129hi%O)kCzx8q%BOSTGT%^C>q@@W7&A9<)b|Wl%3sDxnIAVV9NEjI{Gz=%<$OE0(7xx`wIjC%KL6l8bxA3qC*QN^
z_jzI{Qh#TG&8Z#=k+^SZGEOj!x<5pkzmkZ}(x0w-pb7Y$F*V*EW1qqkXu;Rtv#>nL
zGIBN}0cU%@$n4JXE&4bGSY4&x%~Y19*{<`)?Y7~zW6il*PTR^U_EuUPV(%i`$03&*
zH;5oBzdwi|(^mAOr~Bm<7L?ha_(1qQ>SjI3O
z+-gAeliwT|a;s$*gs9M+
zFWqlIib%1#eHsmi2U_L8`{+ShNSJ=82!cJ*(E9L?2h_SzPU;Hr$J4CV{bD36T9sAz
zxk#FYi(VG-4RLMfr9hXpU
z*s97aPNU2u*umCIb02g{4R4~@E@o#?UcVk&-2u4_H_?-D|KYvyhuo
zc+no=f=FtVP;`RB-8p?=?m#DBm5~IzWho>1(K17
zvsC3`GLG>!hIu7z5;S6r=j(f!yf$;gcAS5gN6-A(R>0Uw{;q^o#HWiUlU2lVGK=8)
ztm(MeWOP#;g@9~1wYh@n42j#{_ec%7@_(LFIB?l|5SnDuq^bQ6>Gs;|j0iyvw
zS}!s%)4$8`?=lSEnddP7-kF+pv-UOXYqRctR9_nr%{ayq{n+p~%6Mef*=GG6tL02%
pkn`iS^RXKG8z!--Sx-04Q_MRK*|U
zo*%PjI=cNjoq$O#x>`)i?7ka@qG7k1*Ci$etG;JigiaGT^EBx%L|xCMkJ_{!;7GcKHU>~Dw~If3BjmS2hj`F(D6TX9cM1FvGI`eAvz%h7l(Mmk&UfkDO0f)%
zGAx(%-_IqU$K1n2XaEPTqlHxc5}j*O=%kd|TDhX6SnT_B>!&!=VdHT+6X{tQ7W<8;
zu9JmvB*sdK$(K`k^?#8wn>AFvg~~H1&j!P`P;5KJ8e)i!`6zapVrGr9yKbe}5XJf_
zWqHPPLH!VP%ej^Vav!4HW&f6?5zAjm9p#H_TJH4RK63rjE2pQn3DsP{W%yrX
zNsky*NjHc(j!mAy!1FJi8joWZg%hBe&0Aa13%2Ha^Q4|ob?PY`mo+ll&Y!@FE68GsI_
zt!uw?Y3OKmlJ7?8+RCGqNx#0j^SL+r--v(n>h>y6(EmzJ%Vf``rQfa$A8pBL<|err
z?izP1RI$vuJmy_$O$)XawmFUaUzSx*J@W(HY>ApYz&Yws3c4srVM)tRaF{&7$w2|
zeB!ueS|WLrUq>F+%M+yBf+7sHxcxDS8x;l$4S!2^m&Lgkxzv{7#Z4R41H+5^%OatH
zZ`8viT`NFDbJ+;*&Lo;UkMKoyq7@Yr&EX+@K@HJf2w>j1AM=IXn0Fn+{MtdRtv!SJ
z7sjymv)fp&mFPr0C|6E`=0uo!!_*t5-f)j)i1YGU*dcm}GGjr}ts5Zf{R%1S6!3+1
z>baPD^}5cfo`O8Qt^`YRx;eA}nFWZ7CBW>J5``+W%j^673>cjiQC8dO(eBYwqXU1Tp
z{>}oMQ#~BQasMXqf5ar}fe;RUD`A_ZFI~5Qar!$WV!S=_zo5^e1zmsN!m2mRsNReO
zl7Z{_Aau07ILX^13>HZ
zhXX)sZbctF-6yZGKxSW7nR>T}N;13t;IehimN;5i9obRP&)#GK7jg$;6~p|XF1t;b
z%(?`|dHo~v_H&Cq)!c->I|!(q(-0y}sf0A$MXsP<2~Fn*{ey0`^t=!iy7Hy_4WNhw
zsXL}%DBRy7`#*r$w2(0MKoJCcq@m@(9}lW^qny+k;!mVmtp~(NShOmu?sJhe3m3gC
zqQ@sP#fjgH&Mn!kginHcZyK17rqqy7HW&2wwP}kA^d_28Wk0gJV0UQjAVQ6wn|
z$t!Nqb4`q0oI?fOyp3k6*3V$+UN&U@L%Np&aew)Jgg?d8FTYYz0WydvOI0o;qnO{u
zFt4OdP>C4x^WD8nT${dOJHfxl!!v)f6)uAE_R^w@
z4Lkcl*H+f44S=>&7F$Ve!%u%)xw1NMbJfb~6`MVbQyJ&Yqsz{!s`~9Q#^b4aO=q^(
z)~PX0^~8wRAi9B9mwC4tw2tZk)+-8RXV&
zt@G9TJYK5xTB*n9f!x@t=l%b#KcWs;%fVVJHrkCIy`6z{7;Ub;p{oHu+%GaP)4tQN
z?=%$Ooo6ur-kF$9v-dUoYqRfu*k2nF%{ayueqz`gZ9JHLw%LEjdO1^>()sb(`B)GA
hh)Rq#`{~Acf_cZG^%=9Drv;E^{EvBdo@BoLzW|Qn5oG`X
literal 0
HcmV?d00001
diff --git a/contracts/registry-type-script/release-manifest.json b/contracts/registry-type-script/release-manifest.json
index 42689997..73466ccc 100644
--- a/contracts/registry-type-script/release-manifest.json
+++ b/contracts/registry-type-script/release-manifest.json
@@ -1,15 +1,15 @@
{
"schema": "cellscript-registry-type-script-release-v1",
- "version": "0.22.0",
+ "version": "0.24.0",
"target": "riscv64imac-unknown-none-elf",
- "artifact": "artifacts/v0.22.0/cellscript-registry-type-script",
+ "artifact": "artifacts/v0.24.0/cellscript-registry-type-script",
"canonical_build_host": "x86_64-unknown-linux-gnu",
"canonical_builder_image": "rust@sha256:77fac8b98f9f46062bb680b6d25d5bcaabfc400143952ebc572e924bcbedc3fa",
"artifact_bytes": 3352,
- "sha256": "6a7ab8eaa2281fe77ca8c7b092006c52f96006ac2c7e4b013f8f88b7bf1f742a",
- "ckb_data_hash": "0x8b6de99567accdca438818a55c16534ed10fc335f117709b1487fd2666808bfb",
+ "sha256": "0f48a8736360c121f6ae0f04ab4b0496834f6715d47e3284a0a07add609dede9",
+ "ckb_data_hash": "0x0dd596ade29e06e5bcc00f56abf36ecbe9afaa09f1b26a64436aa37854da622b",
"script_template": {
- "code_hash": "0x8b6de99567accdca438818a55c16534ed10fc335f117709b1487fd2666808bfb",
+ "code_hash": "0x0dd596ade29e06e5bcc00f56abf36ecbe9afaa09f1b26a64436aa37854da622b",
"hash_type": "data1",
"args_schema": "ckb_script_hash(custody_lock)",
"args_bytes": 32
diff --git a/contracts/registry-type-script/tests/ckb_vm.rs b/contracts/registry-type-script/tests/ckb_vm.rs
index 31c02d11..03226587 100644
--- a/contracts/registry-type-script/tests/ckb_vm.rs
+++ b/contracts/registry-type-script/tests/ckb_vm.rs
@@ -22,7 +22,7 @@ struct Scripts {
}
fn contract_binary() -> Bytes {
- let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("artifacts/v0.22.0/cellscript-registry-type-script");
+ let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("artifacts/v0.24.0/cellscript-registry-type-script");
std::fs::read(&path).unwrap_or_else(|error| panic!("read tracked canonical artifact {}: {error}", path.display())).into()
}
diff --git a/crates/cellscript-artifact-checker/Cargo.toml b/crates/cellscript-artifact-checker/Cargo.toml
index 42affa58..16696998 100644
--- a/crates/cellscript-artifact-checker/Cargo.toml
+++ b/crates/cellscript-artifact-checker/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-artifact-checker"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
description = "Bounded independent verifier for CellScript lowering records and CKB RISC-V artifacts"
diff --git a/crates/cellscript-ckb-adapter/Cargo.toml b/crates/cellscript-ckb-adapter/Cargo.toml
index 97f72ab1..5af32a89 100644
--- a/crates/cellscript-ckb-adapter/Cargo.toml
+++ b/crates/cellscript-ckb-adapter/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-ckb-adapter"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/crates/cellscript-fiber-adapter/Cargo.toml b/crates/cellscript-fiber-adapter/Cargo.toml
index b488bd48..0ec961fb 100644
--- a/crates/cellscript-fiber-adapter/Cargo.toml
+++ b/crates/cellscript-fiber-adapter/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-fiber-adapter"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/crates/cellscript-tools/Cargo.toml b/crates/cellscript-tools/Cargo.toml
index f3f71157..93664e1f 100644
--- a/crates/cellscript-tools/Cargo.toml
+++ b/crates/cellscript-tools/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-tools"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/crates/cellscript-tools/src/tooling_release.rs b/crates/cellscript-tools/src/tooling_release.rs
index 2b740b4b..fdfc0036 100644
--- a/crates/cellscript-tools/src/tooling_release.rs
+++ b/crates/cellscript-tools/src/tooling_release.rs
@@ -470,6 +470,19 @@ pub fn run(root: &Path) -> Result<()> {
!tx_measure_gate.contains("RUSTUP_TOOLCHAIN"),
"CKB transaction measure tooling must use CellScript's pinned Rust toolchain",
)?;
+ for token in [
+ "release_ckb_repo_from_args() {",
+ "staging_dir=\"$(mktemp -d \"$ROOT_DIR/target/cellscript-ckb-tx-measure.XXXXXX\")\"",
+ "cp tools/ckb-tx-measure/Cargo.toml tools/ckb-tx-measure/Cargo.lock",
+ "cp src/bin/ckb_tx_measure.rs",
+ "ln -s \"$ckb_repo\" \"$staging_dir/ckb\"",
+ ] {
+ require(gate_script.contains(token), format!("release CKB checkout propagation is missing '{token}'"))?;
+ }
+ require(
+ gate_script.matches("run_release_auxiliary_checks \"$ckb_repo\"").count() == 2,
+ "release and release-quick must both propagate the selected CKB checkout to auxiliary checks",
+ )?;
require(
gate_script.contains("--root \"$ROOT_DIR\" workspace-version"),
"release source identity must read the root package version from Cargo.toml",
diff --git a/crates/cellscript-wasm/Cargo.toml b/crates/cellscript-wasm/Cargo.toml
index 1afa4081..4533776d 100644
--- a/crates/cellscript-wasm/Cargo.toml
+++ b/crates/cellscript-wasm/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-wasm"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/docs/CELLSCRIPT_CKB_ADAPTER.md b/docs/CELLSCRIPT_CKB_ADAPTER.md
index 9955bab7..9e3d202b 100644
--- a/docs/CELLSCRIPT_CKB_ADAPTER.md
+++ b/docs/CELLSCRIPT_CKB_ADAPTER.md
@@ -524,7 +524,7 @@ cargo build -p cellscript-ckb-adapter --bin cellscript-deploy
# Build the canonical Registry Type Script deployment for external signing
export LOCK_ARG=0x$(cat ~/.ckb/default-lock-arg) # your secp256k1 lock arg
cellscript-deploy --rpc http://127.0.0.1:8114 --json build-deploy \
- --artifact contracts/registry-type-script/artifacts/v0.22.0/cellscript-registry-type-script \
+ --artifact contracts/registry-type-script/artifacts/v0.24.0/cellscript-registry-type-script \
--lock-arg $LOCK_ARG \
--name cellscript-registry-type-script \
--hash-type data1 \
diff --git a/docs/CELLSCRIPT_GATE_POLICY.md b/docs/CELLSCRIPT_GATE_POLICY.md
index b88ed5f1..893c5be6 100644
--- a/docs/CELLSCRIPT_GATE_POLICY.md
+++ b/docs/CELLSCRIPT_GATE_POLICY.md
@@ -165,6 +165,18 @@ and validates every step's commit, spent-input liveness, live outputs, cycles,
serialized size, and occupied capacity. `--stateful-scenarios` remains only as
an explicit option for bounded runs.
+The backend gate normally resolves that checkout as the sibling `../ckb`
+directory. When that path is occupied by another development worktree, set
+`CELLSCRIPT_CKB_REPO` to a separate clean checkout at the exact pinned revision;
+the stateful wrapper forwards it as the acceptance harness's `--ckb-repo`.
+This avoids modifying or stashing an unrelated CKB worktree during validation.
+
+For `release` and `release-quick`, pass the same checkout with `--ckb-repo`.
+The release gate stages the independent `ckb-tx-measure` workspace under
+`target/` with its tracked manifest, lockfile, and source so its relative CKB
+dependencies resolve against that explicit checkout too. The default remains
+the sibling `../ckb`; the tracked lockfile remains bound to the release pin.
+
The transaction matrix is produced by the native Rust acceptance harness and
is intentionally labelled as recipe-replayer evidence, not generated-builder
output. Separately, the gate runs the public `cellc action build` and
diff --git a/docs/CELLSCRIPT_LS_IDL_REGISTRY_PROFILE.md b/docs/CELLSCRIPT_LS_IDL_REGISTRY_PROFILE.md
index c877cbc6..70256812 100644
--- a/docs/CELLSCRIPT_LS_IDL_REGISTRY_PROFILE.md
+++ b/docs/CELLSCRIPT_LS_IDL_REGISTRY_PROFILE.md
@@ -107,10 +107,12 @@ uniquely identify executable data. More than one matching deployment returns
The compatibility route is:
```text
-GET /idl/:code_hash
+GET /idl/:code_hash[?data_hash=0x...]
```
It is retained for existing LS-IDL clients and returns the same original
+bytes for immutable `data`/`data1`/`data2` identities. A Type-hash candidate
+still requires `data_hash`; code hash alone never selects upgradeable code
bytes. New integrations should use the canonical route so network, hash type,
and data-hash identity are explicit.
diff --git a/docs/CELLSCRIPT_PACKAGE_PROVENANCE_AND_DEPLOYMENT_IDENTITY.md b/docs/CELLSCRIPT_PACKAGE_PROVENANCE_AND_DEPLOYMENT_IDENTITY.md
index b3265c13..5bc7ad92 100644
--- a/docs/CELLSCRIPT_PACKAGE_PROVENANCE_AND_DEPLOYMENT_IDENTITY.md
+++ b/docs/CELLSCRIPT_PACKAGE_PROVENANCE_AND_DEPLOYMENT_IDENTITY.md
@@ -454,7 +454,7 @@ source_hash = "blake2b:0xabcd..."
[package_build]
edition = "2026"
compatibility_profile_hash = "blake2b:0xprofile..."
-compiler_version = "0.22.0"
+compiler_version = "0.24.0"
target_profile = "ckb"
artifact_hash = "blake2b:0x1234..."
metadata_hash = "blake2b:0x5678..."
@@ -840,7 +840,7 @@ for audit, offline fixtures, and direct-Git fallback:
"version": "1.2.0",
"tag": "v1.2.0",
"source_hash": "blake2b:0xabcd...",
- "cellscript_version": "0.22.0",
+ "cellscript_version": "0.24.0",
"dependencies": {
"token": { "namespace": "cellscript", "version": "0.3.0" }
},
diff --git a/docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md b/docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md
index 8d9ac847..497fb65c 100644
--- a/docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md
+++ b/docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md
@@ -1,9 +1,9 @@
# CellScript 0.24 Development Release Notes
-**Status**: implementation-complete merge candidate; `dev`, `ci`, and
-`backend` passed on 2026-08-10. The refreshed iCKB evidence submodule commit
-`0e18ccd97bd75cac7de9211dc8d344c0bc08942f` is published and bound by the
-parent gitlink; the full release gate remains required before production claims
+**Status**: audit-remediation merge candidate. The refreshed iCKB evidence
+submodule commit `0e18ccd97bd75cac7de9211dc8d344c0bc08942f` is published and
+bound by the parent gitlink. The merge gates must pass for the exact candidate,
+and the full release gate remains required before production claims.
**Source edition**: 2026
@@ -301,13 +301,11 @@ or conversion of executable/copy artifacts into source dependencies.
## Validation
-The package/Registry closure passed `dev` and `ci` on 2026-08-10, with the CI
-website phase using the required Node 22 toolchain. The complete `backend` gate
-then passed from an isolated clean checkout containing the refreshed iCKB
-differential evidence, pinned CKB revision
-`f7fa4436737756f97a24e254f22c13a36316ecea`, and CKB SDK `v5.1.0`. This
-covered the compiler tests, Clippy, full strict backend audit, all 218 iCKB
-differential cases, and the production stateful CKB scenario harness:
+The 2026-08-10 `dev`, `ci`, and `backend` results predated later changes on the
+0.24 branch and are not evidence for this merge candidate. The exact audit-fix
+snapshot must pass all three commands below in an isolated checkout using Node
+22, CKB revision `f7fa4436737756f97a24e254f22c13a36316ecea`, and CKB SDK
+`v5.1.0` before merge:
```bash
./scripts/cellscript_gate.sh dev
diff --git a/docs/wiki/Tutorial-12-Phase1-Registry-End-to-End.md b/docs/wiki/Tutorial-12-Phase1-Registry-End-to-End.md
index 7331f6ba..8e6dd282 100644
--- a/docs/wiki/Tutorial-12-Phase1-Registry-End-to-End.md
+++ b/docs/wiki/Tutorial-12-Phase1-Registry-End-to-End.md
@@ -294,7 +294,9 @@ curl --fail \
--output idl.json
```
-The compatibility route `/idl/:code_hash` returns the same original bytes.
+The compatibility route `/idl/:code_hash` returns the same original bytes for
+immutable hash types. Type-hash deployments require `?data_hash=0x...` even on
+that route so an upgrade cannot resolve by code hash alone.
The Registry proves the document schema, raw-byte digest, executable suffix,
and deployment identity. It does not prove that the Lock Script correctly
implements the interface, and it is not a security audit. See the
diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md
index b82ceafa..94e80852 100644
--- a/docs/wiki/_Sidebar.md
+++ b/docs/wiki/_Sidebar.md
@@ -20,6 +20,7 @@
- [Spore and RGB++ Interoperability Boundaries](https://github.com/CellScript-Labs/CellScript/wiki/Spore-and-RGBPP-Interop-Boundaries)
- [BIP340 Verifier CellDep ABI](https://github.com/CellScript-Labs/CellScript/blob/nightly-0.24/docs/CELLSCRIPT_SIGNATURE_VERIFIER_ABI.md)
- [LS-IDL Registry Profile](https://github.com/CellScript-Labs/CellScript/blob/nightly-0.24/docs/CELLSCRIPT_LS_IDL_REGISTRY_PROFILE.md)
+- [CellScript 0.23 Release Notes](https://github.com/CellScript-Labs/CellScript/blob/v0.23.0/docs/releases/CELLSCRIPT_0_23_RELEASE_NOTES.md)
- [CellScript 0.22 Release Notes](https://github.com/CellScript-Labs/CellScript/blob/v0.22.0/docs/releases/CELLSCRIPT_0_22_RELEASE_NOTES.md)
- [CellScript 0.24 Development Release Notes](https://github.com/CellScript-Labs/CellScript/blob/nightly-0.24/docs/releases/CELLSCRIPT_0_24_RELEASE_NOTES.md)
- [Bounded Fiber Interoperability Guide](https://github.com/CellScript-Labs/CellScript/blob/nightly-0.24/examples/fiber/README.md)
diff --git a/editors/vscode-cellscript b/editors/vscode-cellscript
index 54bd33d2..0c9f426b 160000
--- a/editors/vscode-cellscript
+++ b/editors/vscode-cellscript
@@ -1 +1 @@
-Subproject commit 54bd33d2de2e9fe508818cad622a00e96a69f47f
+Subproject commit 0c9f426bdc617c4847f38da68fc300586bcc1428
diff --git a/proposals/novaseal b/proposals/novaseal
index 919f042f..e2ce6737 160000
--- a/proposals/novaseal
+++ b/proposals/novaseal
@@ -1 +1 @@
-Subproject commit 919f042f6e0c08aab31dd63fc99aec5d49e4e04d
+Subproject commit e2ce6737741c7b13c682ad278bba19644aeed93f
diff --git a/scripts/cellscript_ckb_stateful_scenarios.sh b/scripts/cellscript_ckb_stateful_scenarios.sh
index 99e21945..625c86de 100755
--- a/scripts/cellscript_ckb_stateful_scenarios.sh
+++ b/scripts/cellscript_ckb_stateful_scenarios.sh
@@ -3,4 +3,12 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+if [[ -n "${CELLSCRIPT_CKB_REPO:-}" ]]; then
+ exec "$SCRIPT_DIR/ckb_cellscript_acceptance.sh" \
+ --production \
+ --stateful-scenarios \
+ --ckb-repo "$CELLSCRIPT_CKB_REPO" \
+ "$@"
+fi
+
exec "$SCRIPT_DIR/ckb_cellscript_acceptance.sh" --production --stateful-scenarios "$@"
diff --git a/scripts/cellscript_gate.sh b/scripts/cellscript_gate.sh
index 574a9d94..4878be33 100755
--- a/scripts/cellscript_gate.sh
+++ b/scripts/cellscript_gate.sh
@@ -477,8 +477,50 @@ check_wasm_release_bundle() {
fi
}
+release_ckb_repo_from_args() {
+ local ckb_repo="$ROOT_DIR/../ckb"
+ while (($# > 0)); do
+ case "$1" in
+ --ckb-repo)
+ if (($# < 2)); then
+ printf 'missing value for --ckb-repo\n' >&2
+ return 2
+ fi
+ ckb_repo="$2"
+ shift 2
+ ;;
+ *)
+ shift
+ ;;
+ esac
+ done
+ printf '%s\n' "$ckb_repo"
+}
+
check_ckb_tx_measure_tool() {
- run cargo test --manifest-path tools/ckb-tx-measure/Cargo.toml --locked
+ local ckb_repo="$1"
+ local default_ckb_repo="$ROOT_DIR/../ckb"
+ if [[ ! -d "$ckb_repo" ]]; then
+ printf 'CKB checkout does not exist: %s\n' "$ckb_repo" >&2
+ return 1
+ fi
+ ckb_repo="$(cd "$ckb_repo" && pwd -P)"
+ if [[ -d "$default_ckb_repo" ]]; then
+ default_ckb_repo="$(cd "$default_ckb_repo" && pwd -P)"
+ if [[ "$ckb_repo" == "$default_ckb_repo" ]]; then
+ run cargo test --manifest-path tools/ckb-tx-measure/Cargo.toml --locked
+ return
+ fi
+ fi
+
+ local staging_dir
+ staging_dir="$(mktemp -d "$ROOT_DIR/target/cellscript-ckb-tx-measure.XXXXXX")"
+ mkdir -p "$staging_dir/cellscript/tools/ckb-tx-measure" "$staging_dir/cellscript/src/bin"
+ cp tools/ckb-tx-measure/Cargo.toml tools/ckb-tx-measure/Cargo.lock \
+ "$staging_dir/cellscript/tools/ckb-tx-measure/"
+ cp src/bin/ckb_tx_measure.rs "$staging_dir/cellscript/src/bin/"
+ ln -s "$ckb_repo" "$staging_dir/ckb"
+ run cargo test --manifest-path "$staging_dir/cellscript/tools/ckb-tx-measure/Cargo.toml" --locked
}
check_novaseal_rust_tooling() {
@@ -608,6 +650,7 @@ run_backend_gate() {
}
run_release_auxiliary_checks() {
+ local ckb_repo="$1"
require_cmd npm
run cargo run --quiet --locked -p cellscript-tools --bin cellscript-tools -- \
@@ -616,7 +659,7 @@ run_release_auxiliary_checks() {
check_ckb_release_docs
check_ckb_acceptance_boundaries
check_novaseal_acceptance_boundaries
- check_ckb_tx_measure_tool
+ check_ckb_tx_measure_tool "$ckb_repo"
check_novaseal_rust_tooling
check_novaseal_verifier_pinning
check_wasm_release_bundle
@@ -628,18 +671,22 @@ run_release_auxiliary_checks() {
}
run_release_quick_gate() {
+ local ckb_repo
+ ckb_repo="$(release_ckb_repo_from_args "$@")"
check_release_source_identity
run_ci_gate
- run_release_auxiliary_checks
+ run_release_auxiliary_checks "$ckb_repo"
run ./scripts/ckb_cellscript_acceptance.sh --compile-only --production "$@"
printf '\nCellScript backend shape report: %s\n' "$CELLSCRIPT_BACKEND_SHAPE_REPORT"
printf 'CellScript Molecule schema manifest report: %s\n' "$CELLSCRIPT_MOLECULE_SCHEMA_MANIFEST_REPORT"
}
run_release_gate() {
+ local ckb_repo
+ ckb_repo="$(release_ckb_repo_from_args "$@")"
check_release_source_identity
run_ci_gate
- run_release_auxiliary_checks
+ run_release_auxiliary_checks "$ckb_repo"
run ./scripts/ckb_cellscript_acceptance.sh --production --stateful-scenarios "$@"
printf '\nCellScript backend shape report: %s\n' "$CELLSCRIPT_BACKEND_SHAPE_REPORT"
printf 'CellScript Molecule schema manifest report: %s\n' "$CELLSCRIPT_MOLECULE_SCHEMA_MANIFEST_REPORT"
diff --git a/services/registry-api/README.md b/services/registry-api/README.md
index 81731554..51c5a352 100644
--- a/services/registry-api/README.md
+++ b/services/registry-api/README.md
@@ -174,7 +174,8 @@ The canonical LS-IDL lookup returns
`application/vnd.ckb.ls-idl+json` plus digest, coordinate, commitment, and
verification headers. `data_hash` is required for `hash_type=type`; ambiguous
matches return `409`. `/idl/:code_hash` is a compatibility route for existing
-clients and returns the same exact raw bytes.
+clients and returns the same exact raw bytes for immutable hash types; a
+Type-hash candidate still requires `?data_hash=0x...`.
## Publisher Authorisation
@@ -325,6 +326,9 @@ cellc publish --artifact-manifest Artifact.toml
```
`CELLSCRIPT_REGISTRY_API_URL` overrides the API base URL.
+`CELLSCRIPT_REGISTRY_STATIC_ORIGIN` declares a separate trusted object origin
+for private registries; source snapshots are rejected unless their origin is
+the configured API/static origin or the production/testnet static origin.
`CELLSCRIPT_CAPABILITY_PRIVATE_KEY_PKCS8_B64` supplies the delegated key in CI.
`CELLSCRIPT_REGISTRY_IDEMPOTENCY_KEY` pins the exact retry key.
diff --git a/services/registry-api/src/index.ts b/services/registry-api/src/index.ts
index af3155b6..54851a8c 100644
--- a/services/registry-api/src/index.ts
+++ b/services/registry-api/src/index.ts
@@ -201,7 +201,7 @@ export function registryRuntimeConfig(env: Env): RegistryRuntimeConfig {
throw new ApiError(503, "invalid_registry_environment", "REGISTRY_ENVIRONMENT must be production or testnet-sandbox");
}
export const CANONICAL_REGISTRY_TYPE_SCRIPT = Object.freeze({
- code_hash: "0x8b6de99567accdca438818a55c16534ed10fc335f117709b1487fd2666808bfb",
+ code_hash: "0x0dd596ade29e06e5bcc00f56abf36ecbe9afaa09f1b26a64436aa37854da622b",
hash_type: "data1",
});
export const CKB_MAINNET_SIGHASH_LOCK = Object.freeze({
@@ -722,7 +722,7 @@ async function handleLsIdlRead(
}
const candidate = candidates[0]!;
const deployment = candidate.deployment.evidence;
- if (!compatibilityRoute && deployment["hash_type"] === "type" && !dataHash) {
+ if (deployment["hash_type"] === "type" && !dataHash) {
throw new ApiError(
409,
"ls_idl_data_hash_required",
@@ -4700,7 +4700,7 @@ function errorResponse(error: unknown, requestId: string): Response {
const headers = corsHeaders(requestId);
const status = error instanceof ApiError ? error.status : 500;
const code = error instanceof ApiError ? error.code : "internal_error";
- const message = error instanceof Error ? error.message : "internal error";
+ const message = error instanceof ApiError ? error.message : "internal server error";
return json({ request_id: requestId, error: { code, message } }, status, headers);
}
diff --git a/services/registry-api/test/registry-api.test.ts b/services/registry-api/test/registry-api.test.ts
index 8f375c5d..d427890c 100644
--- a/services/registry-api/test/registry-api.test.ts
+++ b/services/registry-api/test/registry-api.test.ts
@@ -667,7 +667,7 @@ async function completeBrowserAuthorisationSession(
}, {}, { authorization: `Bearer ${browserToken}` });
}
-async function lsIdlLookupApp(idlBytes: Uint8Array) {
+async function lsIdlLookupApp(idlBytes: Uint8Array, hashType: "data1" | "type" = "data1") {
const store = new MemoryRegistryStore();
const idl = new TextDecoder().decode(idlBytes);
const digest = await sha256Hex(idlBytes);
@@ -718,7 +718,7 @@ async function lsIdlLookupApp(idlBytes: Uint8Array) {
network: "mainnet",
code_hash: codeHash,
data_hash: codeHash,
- hash_type: "data1",
+ hash_type: hashType,
dep_type: "code",
},
request_id: "test",
@@ -773,6 +773,23 @@ describe("registry api", () => {
expect(await formal.text()).toBe(idl);
});
+ it("requires exact code-cell data identity for Type-hash LS-IDL compatibility reads", async () => {
+ const idl = "{\"witness\":[]}\n";
+ const { app, codeHash } = await lsIdlLookupApp(new TextEncoder().encode(idl), "type");
+
+ const ambiguous = await get(app, `/idl/${codeHash.slice(2)}`);
+ expect(ambiguous.status).toBe(409);
+ expect((await ambiguous.json() as any).error.code).toBe("ls_idl_data_hash_required");
+
+ const compatibility = await get(app, `/idl/${codeHash.slice(2)}?data_hash=${codeHash}`);
+ expect(compatibility.status).toBe(200);
+ expect(await compatibility.text()).toBe(idl);
+
+ const formal = await get(app, `/v1/ckb/scripts/${codeHash}/interfaces/ls-idl?hash_type=type&data_hash=${codeHash}`);
+ expect(formal.status).toBe(200);
+ expect(await formal.text()).toBe(idl);
+ });
+
it.runIf(Boolean(process.env.CELLSCRIPT_CKB_IDL_CLIENT_REPO))(
"interoperates with the pinned upstream Rust client over the compatibility route",
async () => {
@@ -2803,7 +2820,10 @@ describe("registry api", () => {
);
expect(response.status).toBe(500);
- expect((await response.json() as any).error.code).toBe("internal_error");
+ const internalError = await response.json() as any;
+ expect(internalError.error.code).toBe("internal_error");
+ expect(internalError.error.message).toBe("internal server error");
+ expect(JSON.stringify(internalError)).not.toContain("static registry object write failed");
expect(store.packageVersions.get("cellscript/demo@1.2.3")?.availability_status).toBe("active");
expect(store.auditEvents.some((event) => event.event_type === "admin.package_version.status_updated")).toBe(false);
const staticEntryWrites = snapshots.filter((snapshot) => snapshot.key === "artifacts/cellscript/demo/releases/1.2.3.json");
diff --git a/services/registry-api/wrangler.example.toml b/services/registry-api/wrangler.example.toml
index f17f8445..9852a727 100644
--- a/services/registry-api/wrangler.example.toml
+++ b/services/registry-api/wrangler.example.toml
@@ -24,7 +24,7 @@ CLEANUP_QUOTA_EVENT_RETENTION_HOURS = "48"
NAMESPACE_CLAIM_COOLDOWN_SECONDS = "3600"
CKB_REGISTRY_SCAN_MAX_CELLS = "1000"
# Enable only after the canonical mainnet Registry Type Script is deployed.
-# REGISTRY_TYPE_SCRIPT_JSON = '{"code_hash":"0x8b6de99567accdca438818a55c16534ed10fc335f117709b1487fd2666808bfb","hash_type":"data1","args":"0x"}'
+# REGISTRY_TYPE_SCRIPT_JSON = '{"code_hash":"0x0dd596ade29e06e5bcc00f56abf36ecbe9afaa09f1b26a64436aa37854da622b","hash_type":"data1","args":"0x"}'
# REGISTRY_TYPE_SCRIPT_CELL_DEP_JSON = '{"out_point":{"tx_hash":"0x","index":"0x0"},"dep_type":"code"}'
# REGISTRY_COMMITMENT_LOCK_SCRIPT_JSON = '{"code_hash":"0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8","hash_type":"type","args":"0x"}'
# REGISTRY_COMMITMENT_LOCK_CELL_DEP_JSON = '{"out_point":{"tx_hash":"0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c","index":"0x0"},"dep_type":"dep_group"}'
diff --git a/services/registry-artifact-verifier/Cargo.lock b/services/registry-artifact-verifier/Cargo.lock
index 1b7980c1..82c49ff2 100644
--- a/services/registry-artifact-verifier/Cargo.lock
+++ b/services/registry-artifact-verifier/Cargo.lock
@@ -205,7 +205,7 @@ dependencies = [
[[package]]
name = "cellscript"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"base64",
@@ -235,7 +235,7 @@ dependencies = [
[[package]]
name = "cellscript-artifact-checker"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"blake2b_simd",
"clap",
@@ -245,7 +245,7 @@ dependencies = [
[[package]]
name = "cellscript-registry-artifact-verifier"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"base64",
diff --git a/services/registry-artifact-verifier/Cargo.toml b/services/registry-artifact-verifier/Cargo.toml
index 77adbda0..a3035f6f 100644
--- a/services/registry-artifact-verifier/Cargo.toml
+++ b/services/registry-artifact-verifier/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-registry-artifact-verifier"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/services/registry-verifier/Cargo.lock b/services/registry-verifier/Cargo.lock
index a0569ead..fd7c8784 100644
--- a/services/registry-verifier/Cargo.lock
+++ b/services/registry-verifier/Cargo.lock
@@ -205,7 +205,7 @@ dependencies = [
[[package]]
name = "cellscript"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"base64",
@@ -235,7 +235,7 @@ dependencies = [
[[package]]
name = "cellscript-artifact-checker"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"blake2b_simd",
"clap",
@@ -245,7 +245,7 @@ dependencies = [
[[package]]
name = "cellscript-registry-verifier"
-version = "0.22.0"
+version = "0.24.0"
dependencies = [
"anyhow",
"base64",
diff --git a/services/registry-verifier/Cargo.toml b/services/registry-verifier/Cargo.toml
index bcf30747..1d964853 100644
--- a/services/registry-verifier/Cargo.toml
+++ b/services/registry-verifier/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cellscript-registry-verifier"
-version = "0.22.0"
+version = "0.24.0"
edition = "2024"
rust-version = "1.97.1"
publish = false
diff --git a/src/lib.rs b/src/lib.rs
index b6cfe7f3..14a20ecc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,11 @@
//! CellScript - Domain-specific language compiler for CKB blockchain
//! Currently the backend can output RISC-V assembly or ELF artifacts.
+// The compiler's lowering and backend helpers intentionally carry explicit
+// context parameters. Keep their semantic refactors separate from release
+// hardening so Clippy does not force high-risk positional rewrites.
+#![allow(clippy::too_many_arguments)]
+
pub(crate) mod aggregate_lowering;
pub mod assumptions;
pub mod ast;
diff --git a/src/package/mod.rs b/src/package/mod.rs
index 399d8c66..6aecb00a 100644
--- a/src/package/mod.rs
+++ b/src/package/mod.rs
@@ -550,6 +550,36 @@ fn sanitize_node_component(value: &str) -> String {
.collect()
}
+fn validate_git_dependency_ref(kind: &str, value: &str) -> Result<()> {
+ if kind == "rev" {
+ if value.len() == 40 && value.bytes().all(|byte| byte.is_ascii_hexdigit()) {
+ return Ok(());
+ }
+ return Err(CompileError::without_span("git dependency rev must be a full 40-hex commit identity"));
+ }
+
+ let invalid_character = value
+ .chars()
+ .any(|character| character.is_ascii_control() || character.is_ascii_whitespace() || "~^:?*[\\".contains(character));
+ let invalid_component = value.split('/').any(|component| {
+ component.is_empty() || component.starts_with('.') || component.ends_with('.') || component.ends_with(".lock")
+ });
+ if value.is_empty()
+ || value.starts_with('-')
+ || value == "@"
+ || value.contains("..")
+ || value.contains("@{")
+ || invalid_character
+ || invalid_component
+ {
+ return Err(CompileError::without_span(format!(
+ "invalid git dependency {kind} '{}'; expected a canonical Git reference name",
+ value
+ )));
+ }
+ Ok(())
+}
+
fn package_node_id(package: &ResolvedPackage, options: &ResolutionOptions) -> String {
let source = match &package.source {
PackageSource::Local(path) => format!("path:{}", path.to_string_lossy().replace('\\', "/")),
@@ -793,7 +823,7 @@ dist/
let dependencies = self.selected_dependencies(&manifest, options, true)?;
for (alias, dep) in dependencies {
let node_id =
- self.resolve_dependency_from_root(&alias, &dep, &self.root.clone(), options, &mut Vec::new(), &mut Vec::new())?;
+ self.resolve_dependency_from_root(&alias, &dep, &self.root.clone(), options, true, &mut Vec::new(), &mut Vec::new())?;
self.root_dependencies.insert(alias, node_id);
}
@@ -1186,6 +1216,7 @@ dist/
dep: &Dependency,
base_root: &Path,
parent_options: &ResolutionOptions,
+ resolver_owner_is_root: bool,
stack_ids: &mut Vec,
stack_labels: &mut Vec,
) -> Result {
@@ -1196,6 +1227,12 @@ dist/
}
Dependency::Detailed(detailed) => {
if detailed.resolver.is_some() {
+ if !resolver_owner_is_root {
+ return Err(CompileError::without_span(format!(
+ "transitive dependency '{}' cannot invoke an external resolver; only the root package may declare executable resolution policy",
+ alias
+ )));
+ }
let normalized = self.resolve_external_dependency(alias, &package_name, detailed, base_root, parent_options)?;
let resolved = if let Some(git) = &normalized.git {
self.resolve_from_git_with_manifest(&package_name, git, &normalized)?
@@ -1275,8 +1312,15 @@ dist/
let child_dependencies = self.selected_dependencies(&manifest, &child_options, false)?;
let mut child_edges = BTreeMap::new();
for (child_alias, child_dep) in child_dependencies {
- let child_id =
- self.resolve_dependency_from_root(&child_alias, &child_dep, &resolved.path, &child_options, stack_ids, stack_labels)?;
+ let child_id = self.resolve_dependency_from_root(
+ &child_alias,
+ &child_dep,
+ &resolved.path,
+ &child_options,
+ false,
+ stack_ids,
+ stack_labels,
+ )?;
child_edges.insert(child_alias, child_id);
}
stack_ids.pop();
@@ -1780,7 +1824,24 @@ dist/
CompileError::without_span(format!("failed to create git cache directory '{}': {}", cache_dir.display(), e))
})?;
- let requested_ref = detailed.rev.as_ref().or(detailed.tag.as_ref()).or(detailed.branch.as_ref());
+ let requested_ref = if let Some(revision) = detailed.rev.as_ref() {
+ validate_git_dependency_ref("rev", revision).map_err(|error| {
+ CompileError::without_span(format!("git dependency '{}' from '{}' is invalid: {}", name, url, error.message))
+ })?;
+ Some(revision)
+ } else if let Some(tag) = detailed.tag.as_ref() {
+ validate_git_dependency_ref("tag", tag).map_err(|error| {
+ CompileError::without_span(format!("git dependency '{}' from '{}' is invalid: {}", name, url, error.message))
+ })?;
+ Some(tag)
+ } else if let Some(branch) = detailed.branch.as_ref() {
+ validate_git_dependency_ref("branch", branch).map_err(|error| {
+ CompileError::without_span(format!("git dependency '{}' from '{}' is invalid: {}", name, url, error.message))
+ })?;
+ Some(branch)
+ } else {
+ None
+ };
let cache_key = format!("{}#{}", url, requested_ref.map(String::as_str).unwrap_or("HEAD"));
let cache_name = format!("{}-{:016x}", name, simple_hash(&cache_key));
let clone_dir = cache_dir.join(&cache_name);
@@ -3460,6 +3521,98 @@ resolver = "local"
assert_eq!(locked.get_resolved()[target].version, "1.2.3");
}
+ #[cfg(unix)]
+ #[test]
+ fn transitive_dependency_cannot_invoke_external_resolver() {
+ let temp = tempdir().unwrap();
+ let root = temp.path();
+ write_path_package(root, "child", "child", "1.0.0");
+ let marker = root.join("transitive-resolver-ran");
+ let shell_digest = sha256_file(Path::new("/bin/sh")).unwrap();
+ std::fs::write(
+ root.join("child/Cell.toml"),
+ format!(
+ r#"
+[package]
+edition = "2026"
+name = "child"
+version = "1.0.0"
+
+[resolvers.untrusted]
+command = "/bin/sh"
+sha256 = "sha256:{shell_digest}"
+args = ["-c", "touch '{}'"]
+
+[dependencies.grandchild]
+version = "*"
+resolver = "untrusted"
+"#,
+ marker.display()
+ ),
+ )
+ .unwrap();
+ std::fs::write(
+ root.join("Cell.toml"),
+ r#"
+[package]
+edition = "2026"
+name = "app"
+version = "0.1.0"
+
+[dependencies.child]
+path = "child"
+"#,
+ )
+ .unwrap();
+
+ let error = PackageManager::new(root).resolve_dependencies().unwrap_err();
+ assert!(error.message.contains("transitive dependency 'grandchild' cannot invoke an external resolver"));
+ assert!(!marker.exists(), "transitive resolver command must never start");
+ }
+
+ #[cfg(unix)]
+ #[test]
+ fn git_dependency_tag_rejects_option_injection_before_git_runs() {
+ let temp = tempdir().unwrap();
+ let root = temp.path();
+ let dependency_repo = root.join("git-package");
+ write_path_package(root, "git-package", "git_package", "1.0.0");
+ for arguments in [
+ vec!["init", "-q"],
+ vec!["config", "user.email", "tests@cellscript.dev"],
+ vec!["config", "user.name", "CellScript Tests"],
+ vec!["add", "."],
+ vec!["commit", "-q", "-m", "initial"],
+ ] {
+ let status = std::process::Command::new("git").args(arguments).current_dir(&dependency_repo).status().unwrap();
+ assert!(status.success());
+ }
+ let marker = root.join("git-option-injection-ran");
+ std::fs::write(
+ root.join("Cell.toml"),
+ format!(
+ r#"
+[package]
+edition = "2026"
+name = "app"
+version = "0.1.0"
+
+[dependencies.remote]
+package = "git_package"
+git = "{}"
+tag = "--upload-pack=/bin/sh -c 'touch {}'"
+"#,
+ dependency_repo.display(),
+ marker.display()
+ ),
+ )
+ .unwrap();
+
+ let error = PackageManager::new(root).resolve_dependencies().unwrap_err();
+ assert!(error.message.contains("invalid git dependency tag"), "{}", error.message);
+ assert!(!marker.exists(), "invalid Git ref must be rejected before Git executes");
+ }
+
#[test]
fn moving_git_branch_changes_only_after_explicit_repin() {
let temp = tempdir().unwrap();
diff --git a/src/package/registry.rs b/src/package/registry.rs
index 4b0d4c4b..dbcbf940 100644
--- a/src/package/registry.rs
+++ b/src/package/registry.rs
@@ -29,8 +29,12 @@ use std::path::{Path, PathBuf};
pub const DEFAULT_REGISTRY_URL: &str = "https://github.com/cellscript/cellscript-registry";
pub const REGISTRY_URL_ENV: &str = "CELLSCRIPT_REGISTRY_URL";
pub const DEFAULT_PUBLIC_REGISTRY_ORIGIN: &str = "https://api.registry.cellscript.dev";
+pub const DEFAULT_PUBLIC_REGISTRY_STATIC_ORIGIN: &str = "https://registry.cellscript.dev";
+pub const DEFAULT_TESTNET_REGISTRY_ORIGIN: &str = "https://api.testnet.registry.cellscript.dev";
+pub const DEFAULT_TESTNET_REGISTRY_STATIC_ORIGIN: &str = "https://objects.testnet.registry.cellscript.dev";
pub const REGISTRY_API_URL_ENV: &str = "CELLSCRIPT_REGISTRY_API_URL";
pub const REGISTRY_ORIGIN_ENV: &str = "CELLSCRIPT_REGISTRY_ORIGIN";
+pub const REGISTRY_STATIC_ORIGIN_ENV: &str = "CELLSCRIPT_REGISTRY_STATIC_ORIGIN";
pub const REGISTRY_AUTH_PROTOCOL: &str = "cellscript-registry-auth-v1";
pub const AUTHORIZE_CAPABILITY_ACTION: &str = "authorize_capability";
pub const REVOKE_CAPABILITY_ACTION: &str = "revoke_capability";
@@ -496,8 +500,18 @@ fn lookup_public_registry(
if !response.status().is_success() {
return Err(CompileError::without_span(format!("public registry request '{}' returned HTTP {}", url, response.status())));
}
- let payload: PublicRegistryPackage = response
- .json()
+ if response.content_length().is_some_and(|length| length == 0 || length > MAX_PUBLIC_REGISTRY_METADATA_BYTES) {
+ return Err(CompileError::without_span("public registry metadata Content-Length exceeds the bounded response contract"));
+ }
+ let mut bytes = Vec::new();
+ response
+ .take(MAX_PUBLIC_REGISTRY_METADATA_BYTES + 1)
+ .read_to_end(&mut bytes)
+ .map_err(|error| CompileError::without_span(format!("failed to read public registry response '{url}': {error}")))?;
+ if bytes.is_empty() || bytes.len() as u64 > MAX_PUBLIC_REGISTRY_METADATA_BYTES {
+ return Err(CompileError::without_span("public registry metadata exceeds the bounded response contract"));
+ }
+ let payload: PublicRegistryPackage = serde_json::from_slice(&bytes)
.map_err(|error| CompileError::without_span(format!("public registry response '{}' is invalid: {error}", url)))?;
payload.into_resolution(namespace, name)
}
@@ -563,10 +577,21 @@ impl PublicRegistryPackage {
"artifact '{expected_namespace}/{expected_name}' is not a resolver-safe CellScript dependency"
)));
}
+ if self.releases.len() > MAX_PUBLIC_REGISTRY_RELEASES {
+ return Err(CompileError::without_span(format!(
+ "public registry package '{expected_namespace}/{expected_name}' exceeds the {MAX_PUBLIC_REGISTRY_RELEASES}-release response limit"
+ )));
+ }
let source = self.repository.filter(|value| !value.trim().is_empty()).unwrap_or_default();
let mut versions = Vec::with_capacity(self.releases.len());
let mut source_snapshots = BTreeMap::new();
for public_version in self.releases {
+ if public_version.registry_entry.versions.len() > MAX_PUBLIC_REGISTRY_RELEASES {
+ return Err(CompileError::without_span(format!(
+ "public registry version '{}' exceeds the bounded signed-version list",
+ public_version.release
+ )));
+ }
if public_version.registry_entry.schema_version != RegistryIndex::CURRENT_SCHEMA_VERSION
|| public_version.registry_entry.namespace != expected_namespace
|| public_version.registry_entry.name != expected_name
@@ -644,9 +669,70 @@ fn public_registry_release_status(verification: &str, availability: &str) -> Res
}
}
+#[cfg(feature = "cli")]
+const MAX_PUBLIC_REGISTRY_METADATA_BYTES: u64 = 1024 * 1024;
+#[cfg(feature = "cli")]
+const MAX_PUBLIC_REGISTRY_RELEASES: usize = 1024;
#[cfg(feature = "cli")]
const MAX_PUBLIC_SOURCE_SNAPSHOT_BYTES: u64 = 5 * 1024 * 1024;
+#[cfg(feature = "cli")]
+fn canonical_trusted_registry_origin(value: &str, label: &str) -> Result {
+ let url = reqwest::Url::parse(value).map_err(|error| CompileError::without_span(format!("{label} is invalid: {error}")))?;
+ let Some(host) = url.host_str() else {
+ return Err(CompileError::without_span(format!("{label} has no host")));
+ };
+ let host = host.trim_start_matches('[').trim_end_matches(']');
+ let loopback =
+ host.eq_ignore_ascii_case("localhost") || host.parse::().is_ok_and(|address| address.is_loopback());
+ if url.scheme() != "https" && !(url.scheme() == "http" && loopback) {
+ return Err(CompileError::without_span(format!(
+ "{label} must use HTTPS; plaintext HTTP is allowed only for an explicitly configured loopback Registry"
+ )));
+ }
+ if !url.username().is_empty() || url.password().is_some() || url.fragment().is_some() {
+ return Err(CompileError::without_span(format!("{label} must not contain credentials or a fragment")));
+ }
+ Ok(url.origin().ascii_serialization())
+}
+
+#[cfg(feature = "cli")]
+fn trusted_registry_snapshot_origins() -> Result> {
+ let api_origin = canonical_trusted_registry_origin(&resolver_registry_url(), "Registry API origin")?;
+ let mut origins = vec![api_origin.clone()];
+ if api_origin == DEFAULT_PUBLIC_REGISTRY_ORIGIN {
+ origins.push(DEFAULT_PUBLIC_REGISTRY_STATIC_ORIGIN.to_string());
+ } else if api_origin == DEFAULT_TESTNET_REGISTRY_ORIGIN {
+ origins.push(DEFAULT_TESTNET_REGISTRY_STATIC_ORIGIN.to_string());
+ }
+ if let Some(configured) =
+ std::env::var(REGISTRY_STATIC_ORIGIN_ENV).ok().map(|value| value.trim().to_string()).filter(|value| !value.is_empty())
+ {
+ origins.push(canonical_trusted_registry_origin(&configured, "Registry static origin")?);
+ }
+ origins.sort();
+ origins.dedup();
+ Ok(origins)
+}
+
+#[cfg(feature = "cli")]
+fn validate_registry_snapshot_url_against(url: &str, trusted_origins: &[String], label: &str) -> Result {
+ let parsed = reqwest::Url::parse(url).map_err(|error| CompileError::without_span(format!("{label} URL is invalid: {error}")))?;
+ if !parsed.username().is_empty() || parsed.password().is_some() || parsed.fragment().is_some() {
+ return Err(CompileError::without_span(format!("{label} URL must not contain credentials or a fragment")));
+ }
+ let origin = canonical_trusted_registry_origin(parsed.as_str(), &format!("{label} origin"))?;
+ if !trusted_origins.iter().any(|trusted| trusted == &origin) {
+ return Err(CompileError::without_span(format!("{label} URL origin '{origin}' is not a configured Registry object origin")));
+ }
+ Ok(parsed)
+}
+
+#[cfg(feature = "cli")]
+fn validate_registry_snapshot_url(url: &str, label: &str) -> Result {
+ validate_registry_snapshot_url_against(url, &trusted_registry_snapshot_origins()?, label)
+}
+
#[cfg(feature = "cli")]
#[derive(Debug, Deserialize)]
struct GeneratedSourceSnapshot {
@@ -684,7 +770,28 @@ pub fn materialize_public_source_snapshot(
version: &str,
expected_source_hash: &str,
) -> Result {
- validate_public_source_snapshot_descriptor(snapshot, expected_source_hash)?;
+ materialize_public_source_snapshot_against(
+ snapshot,
+ cache_root,
+ namespace,
+ name,
+ version,
+ expected_source_hash,
+ &trusted_registry_snapshot_origins()?,
+ )
+}
+
+#[cfg(feature = "cli")]
+fn materialize_public_source_snapshot_against(
+ snapshot: &PublicRegistrySourceSnapshot,
+ cache_root: &Path,
+ namespace: &str,
+ name: &str,
+ version: &str,
+ expected_source_hash: &str,
+ trusted_origins: &[String],
+) -> Result {
+ validate_public_source_snapshot_descriptor_against(snapshot, expected_source_hash, trusted_origins)?;
let bytes = download_public_source_snapshot(snapshot)?;
std::fs::create_dir_all(cache_root).map_err(|error| {
CompileError::without_span(format!("failed to create source snapshot cache '{}': {error}", cache_root.display()))
@@ -730,15 +837,7 @@ pub fn materialize_locked_public_source_snapshot(
if digest.len() != 64 || !digest.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(CompileError::without_span("locked Registry snapshot hash must contain 32 bytes of hex"));
}
- let parsed = reqwest::Url::parse(url)
- .map_err(|error| CompileError::without_span(format!("locked Registry snapshot URL is invalid: {error}")))?;
- if !matches!(parsed.scheme(), "http" | "https")
- || !parsed.username().is_empty()
- || parsed.password().is_some()
- || parsed.fragment().is_some()
- {
- return Err(CompileError::without_span("locked Registry snapshot URL must be HTTP(S) without credentials or a fragment"));
- }
+ let _parsed = validate_registry_snapshot_url(url, "locked Registry snapshot")?;
std::fs::create_dir_all(cache_root)?;
let target = cache_root.join(format!("{name}-snapshot-{digest}"));
if target.exists() {
@@ -851,7 +950,11 @@ pub fn materialize_public_source_snapshot(
}
#[cfg(feature = "cli")]
-fn validate_public_source_snapshot_descriptor(snapshot: &PublicRegistrySourceSnapshot, expected_source_hash: &str) -> Result<()> {
+fn validate_public_source_snapshot_descriptor_against(
+ snapshot: &PublicRegistrySourceSnapshot,
+ expected_source_hash: &str,
+ trusted_origins: &[String],
+) -> Result<()> {
if snapshot.schema != "cellscript-registry-immutable-bundle" {
return Err(CompileError::without_span(format!("unsupported public registry source snapshot schema '{}'", snapshot.schema)));
}
@@ -876,14 +979,7 @@ fn validate_public_source_snapshot_descriptor(snapshot: &PublicRegistrySourceSna
snapshot.content_type
)));
}
- let url = reqwest::Url::parse(&snapshot.url)
- .map_err(|error| CompileError::without_span(format!("public registry source snapshot URL is invalid: {error}")))?;
- if !matches!(url.scheme(), "http" | "https") || !url.username().is_empty() || url.password().is_some() || url.fragment().is_some()
- {
- return Err(CompileError::without_span(
- "public registry source snapshot URL must be an HTTP(S) URL without credentials or a fragment",
- ));
- }
+ let _url = validate_registry_snapshot_url_against(&snapshot.url, trusted_origins, "public Registry source snapshot")?;
Ok(())
}
@@ -2049,10 +2145,60 @@ left = "a"
size_bytes: 42,
content_type: "application/x-tar".to_string(),
};
- let error = validate_public_source_snapshot_descriptor(&snapshot, "source-hash").unwrap_err();
+ let error = validate_public_source_snapshot_descriptor_against(
+ &snapshot,
+ "source-hash",
+ &[DEFAULT_PUBLIC_REGISTRY_STATIC_ORIGIN.to_string()],
+ )
+ .unwrap_err();
assert!(error.to_string().contains("does not support source snapshot content type"));
}
+ #[cfg(feature = "cli")]
+ #[test]
+ fn registry_snapshot_urls_are_bound_to_explicit_trusted_origins() {
+ let production = vec![DEFAULT_PUBLIC_REGISTRY_STATIC_ORIGIN.to_string()];
+ assert!(validate_registry_snapshot_url_against(
+ "https://registry.cellscript.dev/source-snapshots/demo.json",
+ &production,
+ "snapshot",
+ )
+ .is_ok());
+ let internal =
+ validate_registry_snapshot_url_against("http://169.254.169.254/latest/meta-data/", &production, "snapshot").unwrap_err();
+ assert!(internal.message.contains("must use HTTPS") || internal.message.contains("not a configured Registry object origin"));
+
+ let development = vec!["http://127.0.0.1:8787".to_string()];
+ assert!(validate_registry_snapshot_url_against("http://127.0.0.1:8787/source-snapshots/demo.json", &development, "snapshot",)
+ .is_ok());
+ assert!(validate_registry_snapshot_url_against("http://127.0.0.1:8788/source-snapshots/demo.json", &development, "snapshot",)
+ .is_err());
+ }
+
+ #[cfg(feature = "cli")]
+ #[test]
+ fn public_registry_metadata_rejects_oversized_content_length() {
+ use std::io::{Read as _, Write as _};
+
+ let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
+ let address = listener.local_addr().unwrap();
+ let server = std::thread::spawn(move || {
+ let (mut stream, _) = listener.accept().unwrap();
+ let mut request = [0_u8; 4096];
+ let _ = stream.read(&mut request).unwrap();
+ write!(
+ stream,
+ "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n",
+ MAX_PUBLIC_REGISTRY_METADATA_BYTES + 1
+ )
+ .unwrap();
+ });
+
+ let error = lookup_public_registry(&format!("http://{address}"), "cellscript", "demo").unwrap_err();
+ server.join().unwrap();
+ assert!(error.message.contains("Content-Length exceeds the bounded response contract"));
+ }
+
#[cfg(feature = "cli")]
#[test]
fn public_source_snapshot_download_is_hash_bound_and_materialized_without_git() {
@@ -2103,8 +2249,17 @@ left = "a"
content_type: "application/vnd.cellscript.source-snapshot+json".to_string(),
};
let cache = tempfile::tempdir().unwrap();
- let materialized =
- materialize_public_source_snapshot(&descriptor, cache.path(), "cellscript", "demo", "1.2.3", &source_hash).unwrap();
+ let trusted_origins = vec![format!("http://{address}")];
+ let materialized = materialize_public_source_snapshot_against(
+ &descriptor,
+ cache.path(),
+ "cellscript",
+ "demo",
+ "1.2.3",
+ &source_hash,
+ &trusted_origins,
+ )
+ .unwrap();
server.join().unwrap();
assert_eq!(compute_source_hash(&materialized).unwrap(), source_hash);
assert_eq!(std::fs::read(materialized.join("src/main.cell")).unwrap(), source);