Enable V8 sandboxing for source-built builds (#21146)

## Summary

This is the first PR in the V8 in-process sandboxing rollout.

It adds the build-system and Rust feature plumbing needed to support
sandboxed V8 builds, then enables sandboxing by default for the
source-built Bazel V8 path that we control directly. It deliberately
keeps the published `rusty_v8` artifact workflows on their current
non-sandboxed contract so this PR can land and ship independently before
we change any released artifacts.

## Rollout plan

- [x] **PR 1: land sandbox plumbing and default source-built Bazel V8 to
sandboxed mode**

- [ ] **PR 2: publish sandbox-enabled release artifacts and add
compatibility validation**
- Produce sandboxed artifact pairs for every released Cargo target that
does not already use the source-built Bazel path.
- Add CI coverage that consumes those sandboxed artifacts and verifies:
    - `codex-v8-poc` reports sandbox enabled
    - `codex-code-mode` builds/tests against the sandboxed path

- [ ] **PR 3: switch release consumers to sandboxed artifacts by
default**
  - Update released artifact selectors/checksums.
- Enable the Rust `v8_enable_sandbox` feature in the default release
path.
- Make the sandboxed artifact family the normal path for published
builds.

- [ ] **PR 4: remove rollout-only compatibility paths**
- Remove the temporary non-sandbox release compatibility config once the
new default has shipped and baked.
  - Keep the invariant tests permanently.
This commit is contained in:
Channing Conger
2026-05-05 14:36:37 -07:00
committed by GitHub
Unverified
parent bb2257e3f5
commit 36460387ec
13 changed files with 126 additions and 22 deletions
+10
View File
@@ -183,5 +183,15 @@ common:ci-v8 --build_metadata=TAG_os=linux
common:ci-v8 --config=remote
common:ci-v8 --strategy=remote
# Source-built Bazel V8 artifacts use the in-process sandbox by default. This
# does not affect Cargo's default prebuilt rusty_v8 path.
common --@v8//:v8_enable_pointer_compression=True
common --@v8//:v8_enable_sandbox=True
# Keep currently published rusty_v8 release artifacts non-sandboxed until the
# artifact migration ships matching Rust feature selection for Cargo consumers.
common:v8-release-compat --@v8//:v8_enable_pointer_compression=False
common:v8-release-compat --@v8//:v8_enable_sandbox=False
# Optional per-user local overrides.
try-import %workspace%/user.bazelrc
+31 -8
View File
@@ -63,8 +63,10 @@ def bazel_output_files(
platform: str,
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> list[Path]:
expression = "set(" + " ".join(labels) + ")"
bazel_configs = bazel_configs or []
result = subprocess.run(
[
"bazel",
@@ -72,6 +74,7 @@ def bazel_output_files(
"-c",
compilation_mode,
f"--platforms=@llvm//platforms:{platform}",
*[f"--config={config}" for config in bazel_configs],
"--output=files",
expression,
],
@@ -87,7 +90,9 @@ def bazel_build(
platform: str,
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> None:
bazel_configs = bazel_configs or []
subprocess.run(
[
"bazel",
@@ -95,6 +100,7 @@ def bazel_build(
"-c",
compilation_mode,
f"--platforms=@llvm//platforms:{platform}",
*[f"--config={config}" for config in bazel_configs],
*labels,
],
cwd=ROOT,
@@ -106,13 +112,14 @@ def ensure_bazel_output_files(
platform: str,
labels: list[str],
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> list[Path]:
outputs = bazel_output_files(platform, labels, compilation_mode)
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
if all(path.exists() for path in outputs):
return outputs
bazel_build(platform, labels, compilation_mode)
outputs = bazel_output_files(platform, labels, compilation_mode)
bazel_build(platform, labels, compilation_mode, bazel_configs)
outputs = bazel_output_files(platform, labels, compilation_mode, bazel_configs)
missing = [str(path) for path in outputs if not path.exists()]
if missing:
raise SystemExit(f"missing built outputs for {labels}: {missing}")
@@ -187,8 +194,9 @@ def single_bazel_output_file(
platform: str,
label: str,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> Path:
outputs = ensure_bazel_output_files(platform, [label], compilation_mode)
outputs = ensure_bazel_output_files(platform, [label], compilation_mode, bazel_configs)
if len(outputs) != 1:
raise SystemExit(f"expected exactly one output for {label}, found {outputs}")
return outputs[0]
@@ -198,11 +206,17 @@ def merged_musl_archive(
platform: str,
lib_path: Path,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> Path:
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode)
llvm_ranlib = single_bazel_output_file(platform, LLVM_RANLIB_LABEL, compilation_mode)
llvm_ar = single_bazel_output_file(platform, LLVM_AR_LABEL, compilation_mode, bazel_configs)
llvm_ranlib = single_bazel_output_file(
platform,
LLVM_RANLIB_LABEL,
compilation_mode,
bazel_configs,
)
runtime_archives = [
single_bazel_output_file(platform, label, compilation_mode)
single_bazel_output_file(platform, label, compilation_mode, bazel_configs)
for label in MUSL_RUNTIME_ARCHIVE_LABELS
]
@@ -233,11 +247,13 @@ def stage_release_pair(
target: str,
output_dir: Path,
compilation_mode: str = "fastbuild",
bazel_configs: list[str] | None = None,
) -> None:
outputs = ensure_bazel_output_files(
platform,
[release_pair_label(target)],
compilation_mode,
bazel_configs,
)
try:
@@ -254,7 +270,7 @@ def stage_release_pair(
staged_library = output_dir / staged_archive_name(target, lib_path)
staged_binding = output_dir / f"src_binding_release_{target}.rs"
source_archive = (
merged_musl_archive(platform, lib_path, compilation_mode)
merged_musl_archive(platform, lib_path, compilation_mode, bazel_configs)
if is_musl_archive_target(target, lib_path)
else lib_path
)
@@ -293,6 +309,12 @@ def parse_args() -> argparse.Namespace:
stage_release_pair_parser.add_argument("--platform", required=True)
stage_release_pair_parser.add_argument("--target", required=True)
stage_release_pair_parser.add_argument("--output-dir", required=True)
stage_release_pair_parser.add_argument(
"--bazel-config",
action="append",
default=[],
dest="bazel_configs",
)
stage_release_pair_parser.add_argument(
"--compilation-mode",
default="fastbuild",
@@ -330,6 +352,7 @@ def main() -> int:
target=args.target,
output_dir=Path(args.output_dir),
compilation_mode=args.compilation_mode,
bazel_configs=args.bazel_configs,
)
return 0
if args.command == "resolved-v8-crate-version":
@@ -25,7 +25,10 @@ TOP_LEVEL_NAME_EXCEPTIONS = {
UTILITY_NAME_EXCEPTIONS = {
"path-utils": "codex-utils-path",
}
MANIFEST_FEATURE_EXCEPTIONS = {}
MANIFEST_FEATURE_EXCEPTIONS = {
"codex-rs/code-mode/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
"codex-rs/v8-poc/Cargo.toml": {"sandbox": ("v8/v8_enable_sandbox",)},
}
OPTIONAL_DEPENDENCY_EXCEPTIONS = set()
INTERNAL_DEPENDENCY_FEATURE_EXCEPTIONS = {}
+2
View File
@@ -111,6 +111,7 @@ jobs:
-c
opt
"--platforms=@llvm//platforms:${PLATFORM}"
--config=v8-release-compat
"${pair_target}"
"${extra_targets[@]}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
@@ -134,6 +135,7 @@ jobs:
--platform "${PLATFORM}" \
--target "${TARGET}" \
--compilation-mode opt \
--bazel-config v8-release-compat \
--output-dir "dist/${TARGET}"
- name: Upload staged musl artifacts
+2
View File
@@ -105,6 +105,7 @@ jobs:
bazel_args=(
build
"--platforms=@llvm//platforms:${PLATFORM}"
--config=v8-release-compat
"${pair_target}"
"${extra_targets[@]}"
--build_metadata=COMMIT_SHA=$(git rev-parse HEAD)
@@ -127,6 +128,7 @@ jobs:
python3 .github/scripts/rusty_v8_bazel.py stage-release-pair \
--platform "${PLATFORM}" \
--target "${TARGET}" \
--bazel-config v8-release-compat \
--output-dir "dist/${TARGET}"
- name: Upload staged musl artifacts
+12
View File
@@ -327,6 +327,18 @@ crate.annotation(
"RUSTY_V8_SRC_BINDING_PATH": "$(execpath @v8_targets//:rusty_v8_binding_for_target)",
},
crate = "v8",
# Keep the Rust feature aligned with the source-built Bazel artifacts.
# Windows MSVC still consumes upstream non-sandboxed prebuilts.
crate_features_select = {
"aarch64-apple-darwin": ["v8_enable_sandbox"],
"aarch64-pc-windows-gnullvm": ["v8_enable_sandbox"],
"aarch64-unknown-linux-gnu": ["v8_enable_sandbox"],
"aarch64-unknown-linux-musl": ["v8_enable_sandbox"],
"x86_64-apple-darwin": ["v8_enable_sandbox"],
"x86_64-pc-windows-gnullvm": ["v8_enable_sandbox"],
"x86_64-unknown-linux-gnu": ["v8_enable_sandbox"],
"x86_64-unknown-linux-musl": ["v8_enable_sandbox"],
},
gen_build_script = "on",
patch_args = ["-p1"],
patches = [
+3
View File
@@ -9,6 +9,9 @@ doctest = false
name = "codex_code_mode"
path = "src/lib.rs"
[features]
sandbox = ["v8/v8_enable_sandbox"]
[lints]
workspace = true
+4
View File
@@ -3,6 +3,10 @@ load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "v8-poc",
crate_name = "codex_v8_poc",
crate_features = select({
"@rules_rs//rs/experimental/platforms/constraints:windows_msvc": [],
"//conditions:default": ["sandbox"],
}),
deps_extra = ["@crates//:v8"],
)
+3
View File
@@ -8,6 +8,9 @@ license.workspace = true
name = "codex_v8_poc"
path = "src/lib.rs"
[features]
sandbox = ["v8/v8_enable_sandbox"]
[lints]
workspace = true
+16
View File
@@ -12,6 +12,17 @@ pub fn embedded_v8_version() -> &'static str {
v8::V8::get_version()
}
/// Returns whether the linked V8 library was built with the in-process sandbox.
#[must_use]
pub fn linked_v8_has_sandbox() -> bool {
unsafe extern "C" {
fn v8__V8__IsSandboxEnabled() -> bool;
}
// `rusty_v8` exposes this symbol for its own sandbox verification tests.
unsafe { v8__V8__IsSandboxEnabled() }
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
@@ -53,6 +64,11 @@ mod tests {
assert!(!super::embedded_v8_version().is_empty());
}
#[test]
fn sandbox_feature_matches_linked_v8() {
assert_eq!(super::linked_v8_has_sandbox(), cfg!(feature = "sandbox"));
}
#[test]
fn evaluates_integer_addition() {
assert_eq!(evaluate_expression("1 + 2"), "3");
+1
View File
@@ -331,6 +331,7 @@ def codex_rust_crate(
rust_test(
name = unit_test_binary,
crate = name,
crate_features = crate_features,
deps = all_crate_deps(normal = True, normal_dev = True) + maybe_deps + deps_extra,
# Unit tests also compile to standalone Windows executables, so
# keep their stack reserve aligned with binaries and integration
+29 -11
View File
@@ -65,10 +65,19 @@ index 9648e4a..88efd41 100644
":should_add_rdynamic": ["-rdynamic"],
"//conditions:default": [],
diff --git a/orig/v8-14.6.202.11/BUILD.bazel b/mod/v8-14.6.202.11/BUILD.bazel
index 85f31b7..7314584 100644
index 85f31b7..bbc351b 100644
--- a/orig/v8-14.6.202.11/BUILD.bazel
+++ b/mod/v8-14.6.202.11/BUILD.bazel
@@ -303,7 +303,7 @@ v8_int(
@@ -148,6 +148,8 @@ v8_flag(name = "v8_enable_trace_maps")
v8_flag(name = "v8_enable_v8_checks")
+v8_flag(name = "v8_enable_sandbox")
+
v8_flag(name = "v8_enable_verify_csa")
v8_flag(name = "v8_enable_verify_heap")
@@ -303,7 +305,7 @@ v8_int(
# If no explicit value for v8_enable_pointer_compression, we set it to 'none'.
v8_string(
name = "v8_enable_pointer_compression",
@@ -77,7 +86,15 @@ index 85f31b7..7314584 100644
)
# Default setting for v8_enable_pointer_compression.
@@ -4077,28 +4077,14 @@ filegroup(
@@ -503,6 +505,7 @@ v8_config(
"v8_enable_slow_dchecks": "ENABLE_SLOW_DCHECKS",
"v8_enable_runtime_call_stats": "V8_RUNTIME_CALL_STATS",
"v8_enable_snapshot_native_code_counters": "V8_SNAPSHOT_NATIVE_CODE_COUNTERS",
+ "v8_enable_sandbox": "V8_ENABLE_SANDBOX",
"v8_enable_trace_maps": "V8_TRACE_MAPS",
"v8_enable_turbofan": "V8_ENABLE_TURBOFAN",
"v8_enable_v8_checks": "V8_ENABLE_CHECKS",
@@ -4077,28 +4080,14 @@ filegroup(
}),
)
@@ -112,7 +129,7 @@ index 85f31b7..7314584 100644
)
filegroup(
@@ -4405,6 +4391,20 @@ genrule(
@@ -4405,6 +4394,20 @@ genrule(
srcs = [
"include/js_protocol.pdl",
"src/inspector/inspector_protocol_config.json",
@@ -133,7 +150,7 @@ index 85f31b7..7314584 100644
],
outs = [
"include/inspector/Debugger.h",
@@ -4426,15 +4426,19 @@ genrule(
@@ -4426,15 +4429,19 @@ genrule(
"src/inspector/protocol/Schema.cpp",
"src/inspector/protocol/Schema.h",
],
@@ -157,7 +174,7 @@ index 85f31b7..7314584 100644
],
)
@@ -4448,6 +4451,15 @@ filegroup(
@@ -4448,6 +4455,15 @@ filegroup(
],
)
@@ -173,7 +190,7 @@ index 85f31b7..7314584 100644
filegroup(
name = "d8_files",
srcs = [
@@ -4567,16 +4579,9 @@ cc_library(
@@ -4567,16 +4583,9 @@ cc_library(
],
)
@@ -192,7 +209,8 @@ index 85f31b7..7314584 100644
+ actual = "@simdutf//:simdutf",
)
@@ -4593,7 +4598,7 @@ v8_library(
v8_library(
@@ -4593,7 +4602,7 @@ v8_library(
copts = ["-Wno-implicit-fallthrough"],
icu_deps = [
":icu/generated_torque_definitions_headers",
@@ -201,7 +219,7 @@ index 85f31b7..7314584 100644
],
icu_srcs = [
":generated_regexp_special_case",
@@ -4608,7 +4613,7 @@ v8_library(
@@ -4608,7 +4617,7 @@ v8_library(
],
deps = [
":lib_dragonbox",
@@ -210,7 +228,7 @@ index 85f31b7..7314584 100644
":lib_fp16",
":simdutf",
":v8_libbase",
@@ -4664,6 +4669,7 @@ alias(
@@ -4664,6 +4673,7 @@ alias(
alias(
name = "core_lib_icu",
actual = "icu/v8",
@@ -218,7 +236,7 @@ index 85f31b7..7314584 100644
)
v8_library(
@@ -4715,7 +4721,7 @@ v8_binary(
@@ -4715,7 +4725,7 @@ v8_binary(
],
deps = [
":v8_libbase",
+9 -2
View File
@@ -3,14 +3,21 @@
This directory wires the `v8` crate to exact-version Bazel inputs.
Bazel consumer builds use:
- upstream `denoland/rusty_v8` release archives on Windows
- source-built V8 archives on Darwin, GNU Linux, and musl Linux
- upstream `denoland/rusty_v8` release archives on Windows MSVC
- source-built V8 archives on Darwin, GNU Linux, musl Linux, and Windows GNU
- `openai/codex` release assets for published musl release pairs
Cargo builds still use prebuilt `rusty_v8` archives by default. Only Bazel
overrides `RUSTY_V8_ARCHIVE`/`RUSTY_V8_SRC_BINDING_PATH` in `MODULE.bazel` to
select source-built local archives for its consumer builds.
Source-built Bazel V8 artifacts enable V8's in-process sandbox by default, and
the Bazel `v8` crate feature selection tracks those targets. A full consumer
rollout still needs matching sandbox-enabled archives for every non-source-built
target. Until that artifact migration lands, the rusty_v8 publishing workflows
use `--config=v8-release-compat` to preserve the current non-sandboxed release
artifact contract.
Current pinned versions:
- Rust crate: `v8 = =146.4.0`