mirror of
https://github.com/openai/codex.git
synced 2026-03-28 02:56:33 +03:00
Compare commits
3 Commits
pr15952
...
dev/cc/rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cce1f7345 | ||
|
|
4222b6978f | ||
|
|
95845cf6ce |
3
.bazelrc
3
.bazelrc
@@ -20,6 +20,9 @@ common:windows --host_platform=//:local_windows
|
||||
common --@rules_cc//cc/toolchains/args/archiver_flags:use_libtool_on_macos=False
|
||||
common --@llvm//config:experimental_stub_libgcc_s
|
||||
|
||||
# We need to use the sh toolchain on windows so we don't send host bash paths to the linux executor.
|
||||
common:windows --@rules_rust//rust/settings:experimental_use_sh_toolchain_for_bootstrap_process_wrapper
|
||||
|
||||
# TODO(zbarsky): rules_rust doesn't implement this flag properly with remote exec...
|
||||
# common --@rules_rust//rust/settings:pipelined_compilation
|
||||
|
||||
|
||||
10
.github/actions/setup-bazel-ci/action.yml
vendored
10
.github/actions/setup-bazel-ci/action.yml
vendored
@@ -57,11 +57,5 @@ runs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Use a very short path to reduce argv/path length issues, but avoid the
|
||||
# drive root because some Windows test launchers mis-handle MANIFEST paths there.
|
||||
"BAZEL_OUTPUT_USER_ROOT=D:\b" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
- name: Enable Git long paths (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: git config --global core.longpaths true
|
||||
# Use a very short path to reduce argv/path length issues.
|
||||
"BAZEL_OUTPUT_USER_ROOT=C:\" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
|
||||
90
.github/workflows/bazel.yml
vendored
90
.github/workflows/bazel.yml
vendored
@@ -17,7 +17,6 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref_name != 'main' }}
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 120
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -40,9 +39,9 @@ jobs:
|
||||
# - os: ubuntu-24.04-arm
|
||||
# target: aarch64-unknown-linux-gnu
|
||||
|
||||
# Windows
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-gnullvm
|
||||
# TODO: Enable Windows once we fix the toolchain issues there.
|
||||
#- os: windows-latest
|
||||
# target: x86_64-pc-windows-gnullvm
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
# Configure a human readable name for each job
|
||||
@@ -68,84 +67,8 @@ jobs:
|
||||
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
|
||||
shell: bash
|
||||
run: |
|
||||
bazel_targets=(
|
||||
//...
|
||||
# Keep V8 out of the ordinary Bazel CI path. Only the dedicated
|
||||
# canary and release workflows should build `third_party/v8`.
|
||||
-//third_party/v8:all
|
||||
)
|
||||
if [[ "${RUNNER_OS:-}" == "Windows" ]]; then
|
||||
# This is intentionally a foothold rather than the final Windows
|
||||
# Bazel suite. Keep a stable set of targets that are known not to
|
||||
# pull in the unresolved V8 dependency so we can land meaningful CI
|
||||
# coverage now and expand it once the V8 situation is understood.
|
||||
bazel_targets=(
|
||||
//codex-rs/analytics:analytics-unit-tests
|
||||
//codex-rs/ansi-escape:ansi-escape-unit-tests
|
||||
//codex-rs/app-server-protocol:app-server-protocol-schema_fixtures-test
|
||||
//codex-rs/app-server-protocol:app-server-protocol-unit-tests
|
||||
//codex-rs/apply-patch:apply-patch-all-test
|
||||
//codex-rs/apply-patch:apply-patch-unit-tests
|
||||
//codex-rs/async-utils:async-utils-unit-tests
|
||||
//codex-rs/codex-api:codex-api-unit-tests
|
||||
//codex-rs/codex-backend-openapi-models:codex-backend-openapi-models-unit-tests
|
||||
//codex-rs/codex-client:codex-client-unit-tests
|
||||
//codex-rs/codex-experimental-api-macros:codex-experimental-api-macros-unit-tests
|
||||
//codex-rs/config:config-unit-tests
|
||||
//codex-rs/connectors:connectors-unit-tests
|
||||
//codex-rs/core-skills:core-skills-unit-tests
|
||||
//codex-rs/exec-server:exec-server-unit-tests
|
||||
//codex-rs/execpolicy-legacy:execpolicy-legacy-all-test
|
||||
//codex-rs/execpolicy-legacy:execpolicy-legacy-unit-tests
|
||||
//codex-rs/execpolicy:execpolicy-unit-tests
|
||||
//codex-rs/features:features-unit-tests
|
||||
//codex-rs/feedback:feedback-unit-tests
|
||||
//codex-rs/file-search:file-search-unit-tests
|
||||
//codex-rs/git-utils:git-utils-unit-tests
|
||||
//codex-rs/hooks:hooks-unit-tests
|
||||
//codex-rs/instructions:instructions-unit-tests
|
||||
//codex-rs/keyring-store:keyring-store-unit-tests
|
||||
//codex-rs/network-proxy:network-proxy-unit-tests
|
||||
//codex-rs/otel:otel-unit-tests
|
||||
//codex-rs/plugin:plugin-unit-tests
|
||||
//codex-rs/process-hardening:process-hardening-unit-tests
|
||||
//codex-rs/protocol:protocol-unit-tests
|
||||
//codex-rs/responses-api-proxy:responses-api-proxy-unit-tests
|
||||
//codex-rs/rmcp-client:rmcp-client-unit-tests
|
||||
//codex-rs/rollout:rollout-unit-tests
|
||||
//codex-rs/sandboxing:sandboxing-unit-tests
|
||||
//codex-rs/secrets:secrets-unit-tests
|
||||
//codex-rs/shell-escalation:shell-escalation-unit-tests
|
||||
//codex-rs/skills:skills-unit-tests
|
||||
//codex-rs/state:state-unit-tests
|
||||
//codex-rs/stdio-to-uds:stdio-to-uds-unit-tests
|
||||
//codex-rs/terminal-detection:terminal-detection-unit-tests
|
||||
//codex-rs/tools:tools-unit-tests
|
||||
//codex-rs/utils/absolute-path:absolute-path-unit-tests
|
||||
//codex-rs/utils/approval-presets:approval-presets-unit-tests
|
||||
//codex-rs/utils/cache:cache-unit-tests
|
||||
//codex-rs/utils/cargo-bin:cargo-bin-unit-tests
|
||||
//codex-rs/utils/cli:cli-unit-tests
|
||||
//codex-rs/utils/elapsed:elapsed-unit-tests
|
||||
//codex-rs/utils/fuzzy-match:fuzzy-match-unit-tests
|
||||
//codex-rs/utils/home-dir:home-dir-unit-tests
|
||||
//codex-rs/utils/image:image-unit-tests
|
||||
//codex-rs/utils/json-to-toml:json-to-toml-unit-tests
|
||||
//codex-rs/utils/output-truncation:output-truncation-unit-tests
|
||||
//codex-rs/utils/path-utils:path-utils-unit-tests
|
||||
//codex-rs/utils/plugins:plugins-unit-tests
|
||||
//codex-rs/utils/pty:pty-unit-tests
|
||||
//codex-rs/utils/readiness:readiness-unit-tests
|
||||
//codex-rs/utils/rustls-provider:rustls-provider-unit-tests
|
||||
//codex-rs/utils/sleep-inhibitor:sleep-inhibitor-unit-tests
|
||||
//codex-rs/utils/stream-parser:stream-parser-unit-tests
|
||||
//codex-rs/utils/string:string-unit-tests
|
||||
//codex-rs/utils/template:template-unit-tests
|
||||
//codex-rs/windows-sandbox-rs:windows-sandbox-rs-unit-tests
|
||||
)
|
||||
echo "Windows smoke target count: ${#bazel_targets[@]}"
|
||||
fi
|
||||
|
||||
# Keep V8 out of the ordinary Bazel CI path. Only the dedicated
|
||||
# canary and release workflows should build `third_party/v8`.
|
||||
./.github/scripts/run-bazel-ci.sh \
|
||||
--print-failed-test-logs \
|
||||
--use-node-test-env \
|
||||
@@ -154,7 +77,8 @@ jobs:
|
||||
--test_verbose_timeout_warnings \
|
||||
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
||||
-- \
|
||||
"${bazel_targets[@]}"
|
||||
//... \
|
||||
-//third_party/v8:all
|
||||
|
||||
# Save bazel repository cache explicitly; make non-fatal so cache uploading
|
||||
# never fails the overall job. Only save when key wasn't hit.
|
||||
|
||||
19
MODULE.bazel
19
MODULE.bazel
@@ -44,27 +44,8 @@ bazel_dep(name = "apple_support", version = "2.1.0")
|
||||
bazel_dep(name = "rules_cc", version = "0.2.16")
|
||||
bazel_dep(name = "rules_platform", version = "0.1.0")
|
||||
bazel_dep(name = "rules_rs", version = "0.0.43")
|
||||
# `rules_rs` 0.0.43 does not model `x86_64-pc-windows-gnullvm` as a distinct
|
||||
# Windows exec platform, so patch it until upstream grows that support.
|
||||
single_version_override(
|
||||
module_name = "rules_rs",
|
||||
patch_strip = 1,
|
||||
patches = [
|
||||
"//patches:rules_rs_windows_gnullvm_exec.patch",
|
||||
],
|
||||
version = "0.0.43",
|
||||
)
|
||||
|
||||
rules_rust = use_extension("@rules_rs//rs/experimental:rules_rust.bzl", "rules_rust")
|
||||
# Build-script probe binaries inherit CFLAGS/CXXFLAGS from Bazel's C++
|
||||
# toolchain. On `windows-gnullvm`, llvm-mingw does not ship
|
||||
# `libssp_nonshared`, so strip the forwarded stack-protector flags there.
|
||||
rules_rust.patch(
|
||||
patches = [
|
||||
"//patches:rules_rust_windows_gnullvm_build_script.patch",
|
||||
],
|
||||
strip = 1,
|
||||
)
|
||||
use_repo(rules_rust, "rules_rust")
|
||||
|
||||
toolchains = use_extension("@rules_rs//rs/experimental/toolchains:module_extension.bzl", "toolchains")
|
||||
|
||||
4
MODULE.bazel.lock
generated
4
MODULE.bazel.lock
generated
@@ -1579,14 +1579,12 @@
|
||||
"cargo-1.93.0-aarch64-pc-windows-msvc.tar.xz": "155bff7a16aa7054e7ed7c3a82e362d4b302b3882d751b823e06ff63ae3f103d",
|
||||
"cargo-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "5998940b8b97286bb67facb1a85535eeb3d4d7a61e36a85e386e5c0c5cfe5266",
|
||||
"cargo-1.93.0-x86_64-apple-darwin.tar.xz": "95a47c5ed797c35419908f04188d8b7de09946e71073c4b72632b16f5b10dfae",
|
||||
"cargo-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "f19766837559f90476508140cb95cc708220012ec00a854fa9f99187b1f246b6",
|
||||
"cargo-1.93.0-x86_64-pc-windows-msvc.tar.xz": "e59c5e2baa9ec17261f2cda6676ebf7b68b21a860e3f7451c4d964728951da75",
|
||||
"cargo-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "c23de3ae709ff33eed5e4ae59d1f9bcd75fa4dbaa9fb92f7b06bfb534b8db880",
|
||||
"clippy-1.93.0-aarch64-apple-darwin.tar.xz": "0b6e943a8d12be0e68575acf59c9ea102daf795055fcbbf862b0bfd35ec40039",
|
||||
"clippy-1.93.0-aarch64-pc-windows-msvc.tar.xz": "07bcf2edb88cdf5ead2f02e4a8493e9b0ef935a31253fac6f9f3378d8023f113",
|
||||
"clippy-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "872ae6d68d625946d281b91d928332e6b74f6ab269b6af842338df4338805a60",
|
||||
"clippy-1.93.0-x86_64-apple-darwin.tar.xz": "e6d0b1afb9607c14a1172d09ee194a032bbb3e48af913d55c5a473e0559eddde",
|
||||
"clippy-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "b6f1f7264ed6943c59dedfb9531fbadcc3c0fcf273c940a63d58898b14a1060f",
|
||||
"clippy-1.93.0-x86_64-pc-windows-msvc.tar.xz": "25fb103390bf392980b4689ac09b2ec2ab4beefb7022a983215b613ad05eab57",
|
||||
"clippy-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "793108977514b15c0f45ade28ae35c58b05370cb0f22e89bd98fdfa61eabf55d",
|
||||
"rust-std-1.93.0-aarch64-apple-darwin.tar.xz": "8603c63715349636ed85b4fe716c4e827a727918c840e54aff5b243cedadf19b",
|
||||
@@ -1658,14 +1656,12 @@
|
||||
"rustc-1.93.0-aarch64-pc-windows-msvc.tar.xz": "a3ac1a8e411de8470f71b366f89d187718c431526912b181692ed0a18c56c7ad",
|
||||
"rustc-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "1a9045695892ec08d8e9751bf7cf7db71fe27a6202dd12ce13aca48d0602dbde",
|
||||
"rustc-1.93.0-x86_64-apple-darwin.tar.xz": "594bb293f0a4f444656cf8dec2149fcb979c606260efee9e09bcf8c9c6ed6ae7",
|
||||
"rustc-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "0cdaa8de66f5ce21d1ea73917efc5c64f408bda49f678ddde19465ced9d5ec63",
|
||||
"rustc-1.93.0-x86_64-pc-windows-msvc.tar.xz": "fa17677eee0d83eb055b309953184bf87ba634923d8897f860cda65d55c6e350",
|
||||
"rustc-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "00c6e6740ea6a795e33568cd7514855d58408a1180cd820284a7bbf7c46af715",
|
||||
"rustfmt-1.93.0-aarch64-apple-darwin.tar.xz": "0dd1faedf0768ef362f4aae4424b34e8266f2b9cf5e76ea4fcaf780220b363a0",
|
||||
"rustfmt-1.93.0-aarch64-pc-windows-msvc.tar.xz": "24eed108489567133bbfe40c8eacda1567be55fae4c526911b39eb33eb27a6cb",
|
||||
"rustfmt-1.93.0-aarch64-unknown-linux-gnu.tar.xz": "92e1acb45ae642136258b4dabb39302af2d53c83e56ebd5858bc969f9e5c141a",
|
||||
"rustfmt-1.93.0-x86_64-apple-darwin.tar.xz": "c8453b4c5758eb39423042ffa9c23ed6128cbed2b15b581e5e1192c9cc0b1d4e",
|
||||
"rustfmt-1.93.0-x86_64-pc-windows-gnullvm.tar.xz": "47167e9e78db9be4503a060dee02f4df2cda252da32175dbf44331f965a747b9",
|
||||
"rustfmt-1.93.0-x86_64-pc-windows-msvc.tar.xz": "5becc7c2dba4b9ab5199012cad30829235a7f7fb5d85a238697e8f0e44cbd9af",
|
||||
"rustfmt-1.93.0-x86_64-unknown-linux-gnu.tar.xz": "7f81f6c17d11a7fda5b4e1b111942fb3b23d30dcec767e13e340ebfb762a5e33"
|
||||
}
|
||||
|
||||
@@ -586,13 +586,14 @@ fn permissions_profiles_require_default_permissions() -> std::io::Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_profiles_reject_writes_outside_workspace_root() -> std::io::Result<()> {
|
||||
fn permissions_profiles_allow_writes_outside_workspace_root_with_read_only_legacy_fallback()
|
||||
-> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
let external_write_path = if cfg!(windows) { r"C:\temp" } else { "/tmp" };
|
||||
|
||||
let err = Config::load_from_base_config_with_overrides(
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
@@ -616,14 +617,45 @@ fn permissions_profiles_reject_writes_outside_workspace_root() -> std::io::Resul
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.path().to_path_buf(),
|
||||
)
|
||||
.expect_err("writes outside the workspace root should be rejected");
|
||||
)?;
|
||||
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
);
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("filesystem writes outside the workspace root"),
|
||||
"{err}"
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.can_write_path_with_cwd(Path::new(external_write_path), cwd.path())
|
||||
);
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.needs_direct_runtime_enforcement(
|
||||
config.permissions.network_sandbox_policy,
|
||||
cwd.path(),
|
||||
)
|
||||
);
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.can_write_path_with_cwd(codex_home.path().join("memories").as_path(), cwd.path())
|
||||
);
|
||||
assert_eq!(config.approvals_reviewer, ApprovalsReviewer::User);
|
||||
|
||||
let warnings = &config.startup_warnings;
|
||||
assert!(
|
||||
warnings.is_empty(),
|
||||
"external writes should no longer fail config load: {warnings:?}"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -734,6 +766,133 @@ fn permissions_profiles_allow_unknown_special_paths() -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_profiles_allow_tmpdir_write_with_read_only_legacy_fallback() -> std::io::Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"workspace".to_string(),
|
||||
PermissionProfileToml {
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
":tmpdir".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Write),
|
||||
)]),
|
||||
}),
|
||||
network: None,
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
let memories_root = codex_home.path().join("memories").abs();
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| {
|
||||
entry.access == FileSystemAccessMode::Write
|
||||
&& entry.path
|
||||
== FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
}
|
||||
})
|
||||
);
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.can_write_path_with_cwd(memories_root.as_path(), cwd.path())
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn permissions_profiles_allow_slash_tmp_write_with_read_only_legacy_fallback() -> std::io::Result<()>
|
||||
{
|
||||
let codex_home = TempDir::new()?;
|
||||
let cwd = TempDir::new()?;
|
||||
std::fs::write(cwd.path().join(".git"), "gitdir: nowhere")?;
|
||||
|
||||
let config = Config::load_from_base_config_with_overrides(
|
||||
ConfigToml {
|
||||
default_permissions: Some("workspace".to_string()),
|
||||
permissions: Some(PermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"workspace".to_string(),
|
||||
PermissionProfileToml {
|
||||
filesystem: Some(FilesystemPermissionsToml {
|
||||
entries: BTreeMap::from([(
|
||||
"/tmp".to_string(),
|
||||
FilesystemPermissionToml::Access(FileSystemAccessMode::Write),
|
||||
)]),
|
||||
}),
|
||||
network: None,
|
||||
},
|
||||
)]),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigOverrides {
|
||||
cwd: Some(cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.path().to_path_buf(),
|
||||
)?;
|
||||
|
||||
assert!(
|
||||
config
|
||||
.permissions
|
||||
.file_system_sandbox_policy
|
||||
.entries
|
||||
.iter()
|
||||
.any(|entry| {
|
||||
entry.access == FileSystemAccessMode::Write
|
||||
&& entry.path
|
||||
== FileSystemPath::Path {
|
||||
path: test_absolute_path("/tmp"),
|
||||
}
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
config.permissions.sandbox_policy.get(),
|
||||
&SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permissions_profiles_allow_unknown_special_paths_with_nested_entries() -> std::io::Result<()> {
|
||||
let config = load_workspace_permission_profile(PermissionProfileToml {
|
||||
|
||||
@@ -2154,17 +2154,18 @@ impl Config {
|
||||
default_permissions,
|
||||
&mut startup_warnings,
|
||||
)?;
|
||||
let mut sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
if matches!(sandbox_policy, SandboxPolicy::WorkspaceWrite { .. }) {
|
||||
if !file_system_sandbox_policy
|
||||
.get_writable_roots_with_cwd(resolved_cwd.as_path())
|
||||
.is_empty()
|
||||
{
|
||||
file_system_sandbox_policy = file_system_sandbox_policy
|
||||
.with_additional_writable_roots(
|
||||
resolved_cwd.as_path(),
|
||||
&additional_writable_roots,
|
||||
);
|
||||
sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
}
|
||||
let sandbox_policy = file_system_sandbox_policy
|
||||
.to_legacy_sandbox_policy(network_sandbox_policy, resolved_cwd.as_path())?;
|
||||
(
|
||||
configured_network_proxy_config,
|
||||
sandbox_policy,
|
||||
|
||||
@@ -83,7 +83,20 @@ pub enum FileSystemSpecialPath {
|
||||
#[ts(optional)]
|
||||
subpath: Option<PathBuf>,
|
||||
},
|
||||
/// Config-facing `:tmpdir` special path.
|
||||
///
|
||||
/// This is the broader restricted-policy temp bundle used by filesystem
|
||||
/// permission profiles. On Unix it expands to the temp env roots plus
|
||||
/// `/tmp` and `/tmp`'s canonical target. On Windows it expands to the
|
||||
/// platform temp env roots.
|
||||
Tmpdir,
|
||||
/// Legacy env-backed temp roots used when bridging old
|
||||
/// `SandboxPolicy::WorkspaceWrite` semantics into split filesystem policy.
|
||||
///
|
||||
/// Keep this narrower than [`FileSystemSpecialPath::Tmpdir`] so existing
|
||||
/// `exclude_tmpdir_env_var = false` behavior does not silently widen to the
|
||||
/// full temp bundle on Unix.
|
||||
TmpdirEnvVar,
|
||||
SlashTmp,
|
||||
/// WARNING: `:special_path` tokens are part of config compatibility.
|
||||
/// Do not make older runtimes reject newly introduced tokens.
|
||||
@@ -644,50 +657,57 @@ impl FileSystemSandboxPolicy {
|
||||
FileSystemSpecialPath::CurrentWorkingDirectory => {
|
||||
if entry.access.can_write() {
|
||||
workspace_root_writable = true;
|
||||
} else if entry.access.can_read()
|
||||
&& let Some(path) = resolve_file_system_special_path(
|
||||
} else if entry.access.can_read() {
|
||||
readable_roots.extend(resolve_file_system_special_paths(
|
||||
value,
|
||||
cwd_absolute.as_ref(),
|
||||
)
|
||||
{
|
||||
readable_roots.push(path);
|
||||
));
|
||||
}
|
||||
}
|
||||
FileSystemSpecialPath::ProjectRoots { subpath } => {
|
||||
if subpath.is_none() && entry.access.can_write() {
|
||||
workspace_root_writable = true;
|
||||
} else if let Some(path) =
|
||||
resolve_file_system_special_path(value, cwd_absolute.as_ref())
|
||||
{
|
||||
} else {
|
||||
let resolved_paths = resolve_file_system_special_paths(
|
||||
value,
|
||||
cwd_absolute.as_ref(),
|
||||
);
|
||||
if entry.access.can_write() {
|
||||
writable_roots.push(path);
|
||||
writable_roots.extend(resolved_paths);
|
||||
} else if entry.access.can_read() {
|
||||
readable_roots.push(path);
|
||||
readable_roots.extend(resolved_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
FileSystemSpecialPath::Tmpdir => {
|
||||
if entry.access.can_write() {
|
||||
tmpdir_writable = true;
|
||||
} else if entry.access.can_read()
|
||||
&& let Some(path) = resolve_file_system_special_path(
|
||||
slash_tmp_writable = true;
|
||||
} else if entry.access.can_read() {
|
||||
readable_roots.extend(resolve_file_system_special_paths(
|
||||
value,
|
||||
cwd_absolute.as_ref(),
|
||||
)
|
||||
{
|
||||
readable_roots.push(path);
|
||||
));
|
||||
}
|
||||
}
|
||||
FileSystemSpecialPath::TmpdirEnvVar => {
|
||||
if entry.access.can_write() {
|
||||
tmpdir_writable = true;
|
||||
} else if entry.access.can_read() {
|
||||
readable_roots.extend(resolve_file_system_special_paths(
|
||||
value,
|
||||
cwd_absolute.as_ref(),
|
||||
));
|
||||
}
|
||||
}
|
||||
FileSystemSpecialPath::SlashTmp => {
|
||||
if entry.access.can_write() {
|
||||
slash_tmp_writable = true;
|
||||
} else if entry.access.can_read()
|
||||
&& let Some(path) = resolve_file_system_special_path(
|
||||
} else if entry.access.can_read() {
|
||||
readable_roots.extend(resolve_file_system_special_paths(
|
||||
value,
|
||||
cwd_absolute.as_ref(),
|
||||
)
|
||||
{
|
||||
readable_roots.push(path);
|
||||
));
|
||||
}
|
||||
}
|
||||
FileSystemSpecialPath::Unknown { .. } => {}
|
||||
@@ -728,11 +748,6 @@ impl FileSystemSandboxPolicy {
|
||||
exclude_tmpdir_env_var: !tmpdir_writable,
|
||||
exclude_slash_tmp: !slash_tmp_writable,
|
||||
}
|
||||
} else if !writable_roots.is_empty() || tmpdir_writable || slash_tmp_writable {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"permissions profile requests filesystem writes outside the workspace root, which is not supported until the runtime enforces FileSystemSandboxPolicy directly",
|
||||
));
|
||||
} else {
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: read_only_access,
|
||||
@@ -747,13 +762,13 @@ impl FileSystemSandboxPolicy {
|
||||
let cwd_absolute = AbsolutePathBuf::from_absolute_path(cwd).ok();
|
||||
self.entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
resolve_entry_path(&entry.path, cwd_absolute.as_ref()).map(|path| {
|
||||
ResolvedFileSystemEntry {
|
||||
.flat_map(|entry| {
|
||||
resolve_entry_paths(&entry.path, cwd_absolute.as_ref())
|
||||
.into_iter()
|
||||
.map(|path| ResolvedFileSystemEntry {
|
||||
path,
|
||||
access: entry.access,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -875,7 +890,7 @@ impl From<&SandboxPolicy> for FileSystemSandboxPolicy {
|
||||
if !exclude_tmpdir_env_var {
|
||||
entries.push(FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
value: FileSystemSpecialPath::TmpdirEnvVar,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
});
|
||||
@@ -898,21 +913,21 @@ impl From<&SandboxPolicy> for FileSystemSandboxPolicy {
|
||||
fn resolve_file_system_path(
|
||||
path: &FileSystemPath,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
) -> Option<AbsolutePathBuf> {
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
match path {
|
||||
FileSystemPath::Path { path } => Some(path.clone()),
|
||||
FileSystemPath::Special { value } => resolve_file_system_special_path(value, cwd),
|
||||
FileSystemPath::Path { path } => vec![path.clone()],
|
||||
FileSystemPath::Special { value } => resolve_file_system_special_paths(value, cwd),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_entry_path(
|
||||
fn resolve_entry_paths(
|
||||
path: &FileSystemPath,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
) -> Option<AbsolutePathBuf> {
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
match path {
|
||||
FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
} => cwd.map(absolute_root_path_for_cwd),
|
||||
} => cwd.map(absolute_root_path_for_cwd).into_iter().collect(),
|
||||
_ => resolve_file_system_path(path, cwd),
|
||||
}
|
||||
}
|
||||
@@ -957,6 +972,7 @@ fn special_paths_share_target(left: &FileSystemSpecialPath, right: &FileSystemSp
|
||||
FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
)
|
||||
| (FileSystemSpecialPath::Tmpdir, FileSystemSpecialPath::Tmpdir)
|
||||
| (FileSystemSpecialPath::TmpdirEnvVar, FileSystemSpecialPath::TmpdirEnvVar)
|
||||
| (FileSystemSpecialPath::SlashTmp, FileSystemSpecialPath::SlashTmp) => true,
|
||||
(
|
||||
FileSystemSpecialPath::CurrentWorkingDirectory,
|
||||
@@ -993,11 +1009,13 @@ fn special_path_matches_absolute_path(
|
||||
value: &FileSystemSpecialPath,
|
||||
path: &AbsolutePathBuf,
|
||||
) -> bool {
|
||||
match value {
|
||||
FileSystemSpecialPath::Root => path.as_path().parent().is_none(),
|
||||
FileSystemSpecialPath::SlashTmp => path.as_path() == Path::new("/tmp"),
|
||||
_ => false,
|
||||
if matches!(value, FileSystemSpecialPath::Root) {
|
||||
return path.as_path().parent().is_none();
|
||||
}
|
||||
|
||||
resolve_cwd_independent_special_paths(value)
|
||||
.into_iter()
|
||||
.any(|candidate| candidate == *path)
|
||||
}
|
||||
|
||||
/// Orders resolved entries so the most specific path wins first, then applies
|
||||
@@ -1017,44 +1035,106 @@ fn absolute_root_path_for_cwd(cwd: &AbsolutePathBuf) -> AbsolutePathBuf {
|
||||
.unwrap_or_else(|err| panic!("cwd root must be an absolute path: {err}"))
|
||||
}
|
||||
|
||||
fn resolve_file_system_special_path(
|
||||
fn resolve_file_system_special_paths(
|
||||
value: &FileSystemSpecialPath,
|
||||
cwd: Option<&AbsolutePathBuf>,
|
||||
) -> Option<AbsolutePathBuf> {
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
match value {
|
||||
FileSystemSpecialPath::Root
|
||||
| FileSystemSpecialPath::Minimal
|
||||
| FileSystemSpecialPath::Unknown { .. } => None,
|
||||
| FileSystemSpecialPath::Unknown { .. } => Vec::new(),
|
||||
FileSystemSpecialPath::CurrentWorkingDirectory => {
|
||||
let cwd = cwd?;
|
||||
Some(cwd.clone())
|
||||
let Some(cwd) = cwd else {
|
||||
return Vec::new();
|
||||
};
|
||||
vec![cwd.clone()]
|
||||
}
|
||||
FileSystemSpecialPath::ProjectRoots { subpath } => {
|
||||
let cwd = cwd?;
|
||||
let Some(cwd) = cwd else {
|
||||
return Vec::new();
|
||||
};
|
||||
match subpath.as_ref() {
|
||||
Some(subpath) => {
|
||||
AbsolutePathBuf::resolve_path_against_base(subpath, cwd.as_path()).ok()
|
||||
}
|
||||
None => Some(cwd.clone()),
|
||||
Some(subpath) => AbsolutePathBuf::resolve_path_against_base(subpath, cwd.as_path())
|
||||
.into_iter()
|
||||
.collect(),
|
||||
None => vec![cwd.clone()],
|
||||
}
|
||||
}
|
||||
FileSystemSpecialPath::Tmpdir => {
|
||||
let tmpdir = std::env::var_os("TMPDIR")?;
|
||||
if tmpdir.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let tmpdir = AbsolutePathBuf::from_absolute_path(PathBuf::from(tmpdir)).ok()?;
|
||||
Some(tmpdir)
|
||||
}
|
||||
FileSystemSpecialPath::Tmpdir => resolve_tmpdir_paths(),
|
||||
FileSystemSpecialPath::TmpdirEnvVar => resolve_temp_env_paths(),
|
||||
FileSystemSpecialPath::SlashTmp => resolve_slash_tmp_path().into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_cwd_independent_special_paths(value: &FileSystemSpecialPath) -> Vec<AbsolutePathBuf> {
|
||||
match value {
|
||||
FileSystemSpecialPath::Root => Vec::new(),
|
||||
FileSystemSpecialPath::Minimal
|
||||
| FileSystemSpecialPath::CurrentWorkingDirectory
|
||||
| FileSystemSpecialPath::ProjectRoots { .. }
|
||||
| FileSystemSpecialPath::Unknown { .. } => Vec::new(),
|
||||
FileSystemSpecialPath::Tmpdir => resolve_tmpdir_paths(),
|
||||
FileSystemSpecialPath::TmpdirEnvVar => resolve_temp_env_paths(),
|
||||
FileSystemSpecialPath::SlashTmp => resolve_slash_tmp_path().into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_tmpdir_paths() -> Vec<AbsolutePathBuf> {
|
||||
let mut paths = resolve_temp_env_paths();
|
||||
if let Some(slash_tmp) = resolve_slash_tmp_path() {
|
||||
paths.push(slash_tmp.clone());
|
||||
if let Ok(realpath) = slash_tmp.as_path().canonicalize()
|
||||
&& let Ok(realpath) = AbsolutePathBuf::from_absolute_path(realpath)
|
||||
{
|
||||
paths.push(realpath);
|
||||
}
|
||||
FileSystemSpecialPath::SlashTmp => {
|
||||
#[allow(clippy::expect_used)]
|
||||
let slash_tmp = AbsolutePathBuf::from_absolute_path("/tmp").expect("/tmp is absolute");
|
||||
if !slash_tmp.as_path().is_dir() {
|
||||
return None;
|
||||
}
|
||||
Some(slash_tmp)
|
||||
}
|
||||
dedup_absolute_paths(paths, /*normalize_effective_paths*/ false)
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_temp_env_paths() -> Vec<AbsolutePathBuf> {
|
||||
dedup_absolute_paths(
|
||||
temp_env_var_keys()
|
||||
.iter()
|
||||
.filter_map(|key| resolve_temp_env_path(key))
|
||||
.collect(),
|
||||
/*normalize_effective_paths*/ false,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const fn temp_env_var_keys() -> &'static [&'static str] {
|
||||
&["TEMP", "TMP"]
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
const fn temp_env_var_keys() -> &'static [&'static str] {
|
||||
&["TMPDIR"]
|
||||
}
|
||||
|
||||
fn resolve_temp_env_path(key: &str) -> Option<AbsolutePathBuf> {
|
||||
let value = std::env::var_os(key)?;
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
AbsolutePathBuf::from_absolute_path(PathBuf::from(value)).ok()
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_slash_tmp_path() -> Option<AbsolutePathBuf> {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
#[allow(clippy::expect_used)]
|
||||
let slash_tmp = AbsolutePathBuf::from_absolute_path("/tmp").expect("/tmp is absolute");
|
||||
if !slash_tmp.as_path().is_dir() {
|
||||
return None;
|
||||
}
|
||||
Some(slash_tmp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1760,12 +1840,14 @@ mod tests {
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn tmpdir_special_path_canonicalizes_symlinked_tmpdir() {
|
||||
fn tmpdir_env_var_special_path_canonicalizes_symlinked_tmpdir() {
|
||||
if std::env::var_os(SYMLINKED_TMPDIR_TEST_ENV).is_none() {
|
||||
let output = std::process::Command::new(std::env::current_exe().expect("test binary"))
|
||||
.env(SYMLINKED_TMPDIR_TEST_ENV, "1")
|
||||
.arg("--exact")
|
||||
.arg("permissions::tests::tmpdir_special_path_canonicalizes_symlinked_tmpdir")
|
||||
.arg(
|
||||
"permissions::tests::tmpdir_env_var_special_path_canonicalizes_symlinked_tmpdir",
|
||||
)
|
||||
.output()
|
||||
.expect("run tmpdir subprocess test");
|
||||
|
||||
@@ -1812,7 +1894,7 @@ mod tests {
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
value: FileSystemSpecialPath::TmpdirEnvVar,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -1842,6 +1924,143 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn tmpdir_special_path_includes_tmpdir_env_and_tmp_namespace() {
|
||||
if std::env::var_os(SYMLINKED_TMPDIR_TEST_ENV).is_none() {
|
||||
let output = std::process::Command::new(std::env::current_exe().expect("test binary"))
|
||||
.env(SYMLINKED_TMPDIR_TEST_ENV, "1")
|
||||
.arg("--exact")
|
||||
.arg(
|
||||
"permissions::tests::tmpdir_special_path_includes_tmpdir_env_and_tmp_namespace",
|
||||
)
|
||||
.output()
|
||||
.expect("run tmpdir subprocess test");
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"tmpdir subprocess test failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
let real_tmpdir = cwd.path().join("real-tmpdir");
|
||||
let link_tmpdir = cwd.path().join("link-tmpdir");
|
||||
fs::create_dir_all(&real_tmpdir).expect("create real tmpdir");
|
||||
symlink_dir(&real_tmpdir, &link_tmpdir).expect("create symlinked tmpdir");
|
||||
|
||||
unsafe {
|
||||
std::env::set_var("TMPDIR", &link_tmpdir);
|
||||
}
|
||||
|
||||
let slash_tmp =
|
||||
AbsolutePathBuf::from_absolute_path("/tmp").expect("/tmp should be absolute");
|
||||
let slash_tmp_realpath = AbsolutePathBuf::from_absolute_path(
|
||||
slash_tmp
|
||||
.as_path()
|
||||
.canonicalize()
|
||||
.expect("canonicalize /tmp"),
|
||||
)
|
||||
.expect("absolute canonical /tmp");
|
||||
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
|
||||
assert!(policy.can_write_path_with_cwd(link_tmpdir.as_path(), cwd.path()));
|
||||
assert!(policy.can_write_path_with_cwd(Path::new("/tmp/codex-protocol"), cwd.path()));
|
||||
assert!(
|
||||
policy.can_write_path_with_cwd(
|
||||
slash_tmp_realpath
|
||||
.join("codex-protocol")
|
||||
.expect("valid tmp child")
|
||||
.as_path(),
|
||||
cwd.path(),
|
||||
)
|
||||
);
|
||||
|
||||
let writable_roots = policy.get_writable_roots_with_cwd(cwd.path());
|
||||
assert!(writable_roots.iter().any(|root| {
|
||||
root.root
|
||||
== AbsolutePathBuf::from_absolute_path(
|
||||
real_tmpdir.canonicalize().expect("canonicalize tmpdir"),
|
||||
)
|
||||
.expect("absolute canonical tmpdir")
|
||||
}));
|
||||
assert!(
|
||||
writable_roots
|
||||
.iter()
|
||||
.any(|root| root.root == slash_tmp_realpath)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tmpdir_write_without_workspace_root_falls_back_to_read_only_legacy_policy()
|
||||
-> std::io::Result<()> {
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
|
||||
let sandbox_policy = policy.to_legacy_sandbox_policy(
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
Path::new("/tmp/workspace"),
|
||||
)?;
|
||||
|
||||
assert_eq!(
|
||||
sandbox_policy,
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
);
|
||||
assert!(policy.needs_direct_runtime_enforcement(
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
Path::new("/tmp/workspace"),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_workspace_write_uses_tmpdir_env_var_special_path() {
|
||||
let file_system_policy = FileSystemSandboxPolicy::from(&SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: Vec::new(),
|
||||
read_only_access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: false,
|
||||
exclude_slash_tmp: true,
|
||||
});
|
||||
|
||||
assert!(file_system_policy.entries.iter().any(|entry| {
|
||||
entry.access == FileSystemAccessMode::Write
|
||||
&& entry.path
|
||||
== FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::TmpdirEnvVar,
|
||||
}
|
||||
}));
|
||||
assert!(!file_system_policy.entries.iter().any(|entry| {
|
||||
entry.access == FileSystemAccessMode::Write
|
||||
&& entry.path
|
||||
== FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Tmpdir,
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_access_with_cwd_uses_most_specific_entry() {
|
||||
let cwd = TempDir::new().expect("tempdir");
|
||||
|
||||
@@ -79,6 +79,7 @@ pub use crate::permissions::FileSystemSandboxKind;
|
||||
pub use crate::permissions::FileSystemSandboxPolicy;
|
||||
pub use crate::permissions::FileSystemSpecialPath;
|
||||
pub use crate::permissions::NetworkSandboxPolicy;
|
||||
use crate::permissions::resolve_temp_env_paths;
|
||||
pub use crate::request_permissions::RequestPermissionsArgs;
|
||||
pub use crate::request_user_input::RequestUserInputEvent;
|
||||
|
||||
@@ -1047,28 +1048,12 @@ impl SandboxPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
// Include $TMPDIR unless explicitly excluded. On macOS, TMPDIR
|
||||
// is per-user, so writes to TMPDIR should not be readable by
|
||||
// other users on the system.
|
||||
//
|
||||
// By comparison, TMPDIR is not guaranteed to be defined on
|
||||
// Linux or Windows, but supporting it here gives users a way to
|
||||
// provide the model with their own temporary directory without
|
||||
// having to hardcode it in the config.
|
||||
if !exclude_tmpdir_env_var
|
||||
&& let Some(tmpdir) = std::env::var_os("TMPDIR")
|
||||
&& !tmpdir.is_empty()
|
||||
{
|
||||
match AbsolutePathBuf::from_absolute_path(PathBuf::from(&tmpdir)) {
|
||||
Ok(tmpdir_path) => {
|
||||
roots.push(tmpdir_path);
|
||||
}
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Ignoring invalid TMPDIR value {tmpdir:?} for sandbox writable root: {e}",
|
||||
);
|
||||
}
|
||||
}
|
||||
// Include platform temp env roots unless explicitly excluded.
|
||||
// On Unix, this keeps the legacy TMPDIR behavior. On Windows,
|
||||
// this picks up TEMP/TMP so legacy workspace-write policies
|
||||
// keep matching the host temp directory.
|
||||
if !exclude_tmpdir_env_var {
|
||||
roots.extend(resolve_temp_env_paths());
|
||||
}
|
||||
|
||||
// For each root, compute subpaths that should remain read-only.
|
||||
@@ -4181,7 +4166,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_system_policy_rejects_legacy_bridge_for_non_workspace_writes() {
|
||||
fn file_system_policy_falls_back_to_read_only_legacy_bridge_for_non_workspace_writes() {
|
||||
let cwd = if cfg!(windows) {
|
||||
Path::new(r"C:\workspace")
|
||||
} else {
|
||||
@@ -4199,14 +4184,19 @@ mod tests {
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]);
|
||||
|
||||
let err = policy
|
||||
let legacy_policy = policy
|
||||
.to_legacy_sandbox_policy(NetworkSandboxPolicy::Restricted, cwd)
|
||||
.expect_err("non-workspace writes should be rejected");
|
||||
.expect("non-workspace writes should fall back to read-only");
|
||||
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("filesystem writes outside the workspace root"),
|
||||
"{err}"
|
||||
assert_eq!(
|
||||
legacy_policy,
|
||||
SandboxPolicy::ReadOnly {
|
||||
access: ReadOnlyAccess::Restricted {
|
||||
include_platform_defaults: false,
|
||||
readable_roots: Vec::new(),
|
||||
},
|
||||
network_access: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,12 +90,6 @@
|
||||
(allow file-read* file-test-existence file-write-data file-ioctl
|
||||
(literal "/dev/dtracehelper"))
|
||||
|
||||
; Scratch space so tools can create temp files.
|
||||
(allow file-read* file-test-existence file-write* (subpath "/tmp"))
|
||||
(allow file-read* file-write* (subpath "/private/tmp"))
|
||||
(allow file-read* file-write* (subpath "/var/tmp"))
|
||||
(allow file-read* file-write* (subpath "/private/var/tmp"))
|
||||
|
||||
; Allow reading standard config directories.
|
||||
(allow file-read* (subpath "/etc"))
|
||||
(allow file-read* (subpath "/private/etc"))
|
||||
|
||||
@@ -60,6 +60,31 @@ fn base_policy_allows_node_cpu_sysctls() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restricted_platform_defaults_do_not_include_tmp_write_rules() {
|
||||
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]);
|
||||
|
||||
let args = create_seatbelt_command_args_for_policies(
|
||||
vec!["/bin/true".to_string()],
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
Path::new("/"),
|
||||
false,
|
||||
None,
|
||||
);
|
||||
|
||||
let policy = seatbelt_policy_arg(&args);
|
||||
assert!(!policy.contains("(subpath \"/tmp\")"));
|
||||
assert!(!policy.contains("(subpath \"/private/tmp\")"));
|
||||
assert!(!policy.contains("(subpath \"/var/tmp\")"));
|
||||
assert!(!policy.contains("(subpath \"/private/var/tmp\")"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_seatbelt_args_routes_network_through_proxy_ports() {
|
||||
let policy = dynamic_network_policy(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
exports_files([
|
||||
"aws-lc-sys_memcmp_check.patch",
|
||||
"rules_rust_windows_gnullvm_build_script.patch",
|
||||
"rules_rs_windows_gnullvm_exec.patch",
|
||||
"rusty_v8_prebuilt_out_dir.patch",
|
||||
"v8_bazel_rules.patch",
|
||||
"v8_module_deps.patch",
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
diff --git a/rs/experimental/platforms/triples.bzl b/rs/experimental/platforms/triples.bzl
|
||||
--- a/rs/experimental/platforms/triples.bzl
|
||||
+++ b/rs/experimental/platforms/triples.bzl
|
||||
@@ -30,6 +30,7 @@
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-pc-windows-msvc",
|
||||
+ "x86_64-pc-windows-gnullvm",
|
||||
"aarch64-pc-windows-msvc",
|
||||
"x86_64-apple-darwin",
|
||||
"aarch64-apple-darwin",
|
||||
diff --git a/rs/experimental/toolchains/declare_rustc_toolchains.bzl b/rs/experimental/toolchains/declare_rustc_toolchains.bzl
|
||||
--- a/rs/experimental/toolchains/declare_rustc_toolchains.bzl
|
||||
+++ b/rs/experimental/toolchains/declare_rustc_toolchains.bzl
|
||||
@@ -10,6 +10,11 @@
|
||||
return "beta"
|
||||
return "stable"
|
||||
|
||||
+def _exec_triple_suffix(exec_triple):
|
||||
+ if exec_triple.system == "windows":
|
||||
+ return "{}_{}_{}".format(exec_triple.system, exec_triple.arch, exec_triple.abi)
|
||||
+ return "{}_{}".format(exec_triple.system, exec_triple.arch)
|
||||
+
|
||||
def declare_rustc_toolchains(
|
||||
*,
|
||||
version,
|
||||
@@ -23,15 +28,14 @@
|
||||
|
||||
for triple in execs:
|
||||
exec_triple = _parse_triple(triple)
|
||||
- triple_suffix = exec_triple.system + "_" + exec_triple.arch
|
||||
+ triple_suffix = _exec_triple_suffix(exec_triple)
|
||||
|
||||
rustc_repo_label = "@rustc_{}_{}//:".format(triple_suffix, version_key)
|
||||
cargo_repo_label = "@cargo_{}_{}//:".format(triple_suffix, version_key)
|
||||
clippy_repo_label = "@clippy_{}_{}//:".format(triple_suffix, version_key)
|
||||
|
||||
- rust_toolchain_name = "{}_{}_{}_rust_toolchain".format(
|
||||
- exec_triple.system,
|
||||
- exec_triple.arch,
|
||||
+ rust_toolchain_name = "{}_{}_rust_toolchain".format(
|
||||
+ triple_suffix,
|
||||
version_key,
|
||||
)
|
||||
|
||||
@@ -90,11 +94,8 @@
|
||||
target_key = sanitize_triple(target_triple)
|
||||
|
||||
native.toolchain(
|
||||
- name = "{}_{}_to_{}_{}".format(exec_triple.system, exec_triple.arch, target_key, version_key),
|
||||
- exec_compatible_with = [
|
||||
- "@platforms//os:" + exec_triple.system,
|
||||
- "@platforms//cpu:" + exec_triple.arch,
|
||||
- ],
|
||||
+ name = "{}_to_{}_{}".format(triple_suffix, target_key, version_key),
|
||||
+ exec_compatible_with = triple_to_constraint_set(triple),
|
||||
target_compatible_with = triple_to_constraint_set(target_triple),
|
||||
target_settings = [
|
||||
"@rules_rust//rust/toolchain/channel:" + channel,
|
||||
diff --git a/rs/experimental/toolchains/declare_rustfmt_toolchains.bzl b/rs/experimental/toolchains/declare_rustfmt_toolchains.bzl
|
||||
--- a/rs/experimental/toolchains/declare_rustfmt_toolchains.bzl
|
||||
+++ b/rs/experimental/toolchains/declare_rustfmt_toolchains.bzl
|
||||
@@ -1,6 +1,6 @@
|
||||
load("@rules_rust//rust:toolchain.bzl", "rustfmt_toolchain")
|
||||
load("@rules_rust//rust/platform:triple.bzl", _parse_triple = "triple")
|
||||
-load("//rs/experimental/platforms:triples.bzl", "SUPPORTED_EXEC_TRIPLES")
|
||||
+load("//rs/experimental/platforms:triples.bzl", "SUPPORTED_EXEC_TRIPLES", "triple_to_constraint_set")
|
||||
load("//rs/experimental/toolchains:toolchain_utils.bzl", "sanitize_version")
|
||||
|
||||
def _channel(version):
|
||||
@@ -10,6 +10,11 @@
|
||||
return "beta"
|
||||
return "stable"
|
||||
|
||||
+def _exec_triple_suffix(exec_triple):
|
||||
+ if exec_triple.system == "windows":
|
||||
+ return "{}_{}_{}".format(exec_triple.system, exec_triple.arch, exec_triple.abi)
|
||||
+ return "{}_{}".format(exec_triple.system, exec_triple.arch)
|
||||
+
|
||||
def declare_rustfmt_toolchains(
|
||||
*,
|
||||
version,
|
||||
@@ -22,14 +27,13 @@
|
||||
|
||||
for triple in execs:
|
||||
exec_triple = _parse_triple(triple)
|
||||
- triple_suffix = exec_triple.system + "_" + exec_triple.arch
|
||||
+ triple_suffix = _exec_triple_suffix(exec_triple)
|
||||
|
||||
rustc_repo_label = "@rustc_{}_{}//:".format(triple_suffix, version_key)
|
||||
rustfmt_repo_label = "@rustfmt_{}_{}//:".format(triple_suffix, rustfmt_version_key)
|
||||
|
||||
- rustfmt_toolchain_name = "{}_{}_{}_rustfmt_toolchain".format(
|
||||
- exec_triple.system,
|
||||
- exec_triple.arch,
|
||||
+ rustfmt_toolchain_name = "{}_{}_rustfmt_toolchain".format(
|
||||
+ triple_suffix,
|
||||
version_key,
|
||||
)
|
||||
|
||||
@@ -43,11 +47,8 @@
|
||||
)
|
||||
|
||||
native.toolchain(
|
||||
- name = "{}_{}_rustfmt_{}".format(exec_triple.system, exec_triple.arch, version_key),
|
||||
- exec_compatible_with = [
|
||||
- "@platforms//os:" + exec_triple.system,
|
||||
- "@platforms//cpu:" + exec_triple.arch,
|
||||
- ],
|
||||
+ name = "{}_rustfmt_{}".format(triple_suffix, version_key),
|
||||
+ exec_compatible_with = triple_to_constraint_set(triple),
|
||||
target_compatible_with = [],
|
||||
target_settings = [
|
||||
"@rules_rust//rust/toolchain/channel:" + channel,
|
||||
diff --git a/rs/experimental/toolchains/module_extension.bzl b/rs/experimental/toolchains/module_extension.bzl
|
||||
--- a/rs/experimental/toolchains/module_extension.bzl
|
||||
+++ b/rs/experimental/toolchains/module_extension.bzl
|
||||
@@ -37,6 +37,11 @@
|
||||
return "aarch64"
|
||||
return arch
|
||||
|
||||
+def _exec_triple_suffix(exec_triple):
|
||||
+ if exec_triple.system == "windows":
|
||||
+ return "{}_{}_{}".format(exec_triple.system, exec_triple.arch, exec_triple.abi)
|
||||
+ return "{}_{}".format(exec_triple.system, exec_triple.arch)
|
||||
+
|
||||
def _sanitize_path_fragment(path):
|
||||
return path.replace("/", "_").replace(":", "_")
|
||||
|
||||
@@ -181,7 +186,7 @@
|
||||
for triple in SUPPORTED_EXEC_TRIPLES:
|
||||
exec_triple = _parse_triple(triple)
|
||||
|
||||
- triple_suffix = exec_triple.system + "_" + exec_triple.arch
|
||||
+ triple_suffix = _exec_triple_suffix(exec_triple)
|
||||
rustc_name = "rustc_{}_{}".format(triple_suffix, version_key)
|
||||
|
||||
rustc_repository(
|
||||
@@ -230,7 +235,7 @@
|
||||
|
||||
for triple in SUPPORTED_EXEC_TRIPLES:
|
||||
exec_triple = _parse_triple(triple)
|
||||
- triple_suffix = exec_triple.system + "_" + exec_triple.arch
|
||||
+ triple_suffix = _exec_triple_suffix(exec_triple)
|
||||
|
||||
rustfmt_repository(
|
||||
name = "rustfmt_{}_{}".format(triple_suffix, version_key),
|
||||
@@ -1,38 +0,0 @@
|
||||
diff --git a/cargo/private/cargo_build_script.bzl b/cargo/private/cargo_build_script.bzl
|
||||
--- a/cargo/private/cargo_build_script.bzl
|
||||
+++ b/cargo/private/cargo_build_script.bzl
|
||||
@@ -120,6 +120,25 @@
|
||||
executable = True,
|
||||
)
|
||||
|
||||
+def _strip_stack_protector_for_windows_llvm_mingw(toolchain, args):
|
||||
+ """Drop stack protector flags unsupported by llvm-mingw build-script probes."""
|
||||
+ if "windows-gnullvm" not in toolchain.target_flag_value:
|
||||
+ return args
|
||||
+
|
||||
+ uses_llvm_mingw = False
|
||||
+ for arg in args:
|
||||
+ if "mingw-w64-" in arg:
|
||||
+ uses_llvm_mingw = True
|
||||
+ break
|
||||
+
|
||||
+ if not uses_llvm_mingw:
|
||||
+ return args
|
||||
+
|
||||
+ # llvm-mingw does not ship libssp_nonshared, so forwarding stack-protector
|
||||
+ # flags through CFLAGS/CXXFLAGS breaks build.rs probe binaries compiled via
|
||||
+ # cc-rs.
|
||||
+ return [arg for arg in args if not arg.startswith("-fstack-protector")]
|
||||
+
|
||||
def get_cc_compile_args_and_env(cc_toolchain, feature_configuration):
|
||||
"""Gather cc environment variables from the given `cc_toolchain`
|
||||
|
||||
@@ -503,6 +522,8 @@
|
||||
if not env["AR"]:
|
||||
env["AR"] = cc_toolchain.ar_executable
|
||||
|
||||
+ cc_c_args = _strip_stack_protector_for_windows_llvm_mingw(toolchain, cc_c_args)
|
||||
+ cc_cxx_args = _strip_stack_protector_for_windows_llvm_mingw(toolchain, cc_cxx_args)
|
||||
# Populate CFLAGS and CXXFLAGS that cc-rs relies on when building from source, in particular
|
||||
# to determine the deployment target when building for apple platforms (`macosx-version-min`
|
||||
# for example, itself derived from the `macos_minimum_os` Bazel argument).
|
||||
@@ -44,33 +44,43 @@ export function createTestClient(options: CreateTestClientOptions = {}): TestCli
|
||||
codexPathOverride: codexExecPath,
|
||||
baseUrl: options.baseUrl,
|
||||
apiKey: options.apiKey,
|
||||
config: mergeTestProviderConfig(options.baseUrl, options.config),
|
||||
config: mergeTestConfig(options.baseUrl, options.config),
|
||||
env,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function mergeTestProviderConfig(
|
||||
function mergeTestConfig(
|
||||
baseUrl: string | undefined,
|
||||
config: CodexConfigObject | undefined,
|
||||
): CodexConfigObject | undefined {
|
||||
if (!baseUrl || hasExplicitProviderConfig(config)) {
|
||||
return config;
|
||||
}
|
||||
const mergedConfig: CodexConfigObject | undefined =
|
||||
!baseUrl || hasExplicitProviderConfig(config)
|
||||
? config
|
||||
: {
|
||||
...config,
|
||||
// Built-in providers are merged before user config, so tests need a
|
||||
// custom provider entry to force SSE against the local mock server.
|
||||
model_provider: "mock",
|
||||
model_providers: {
|
||||
mock: {
|
||||
name: "Mock provider for test",
|
||||
base_url: baseUrl,
|
||||
wire_api: "responses",
|
||||
supports_websockets: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
const featureOverrides = mergedConfig?.features;
|
||||
|
||||
// Built-in providers are merged before user config, so tests need a custom
|
||||
// provider entry to force SSE against the local mock server.
|
||||
return {
|
||||
...config,
|
||||
model_provider: "mock",
|
||||
model_providers: {
|
||||
mock: {
|
||||
name: "Mock provider for test",
|
||||
base_url: baseUrl,
|
||||
wire_api: "responses",
|
||||
supports_websockets: false,
|
||||
},
|
||||
},
|
||||
...mergedConfig,
|
||||
// Disable plugins in SDK integration tests so background curated-plugin
|
||||
// sync does not race temp CODEX_HOME cleanup.
|
||||
features:
|
||||
featureOverrides && typeof featureOverrides === "object" && !Array.isArray(featureOverrides)
|
||||
? { ...featureOverrides, plugins: false }
|
||||
: { plugins: false },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -41,17 +41,11 @@ if not defined manifest if exist "%~dpn0.runfiles_manifest" set "manifest=%~dpn0
|
||||
if not defined manifest if exist "%~f0.exe.runfiles_manifest" set "manifest=%~f0.exe.runfiles_manifest"
|
||||
|
||||
if defined manifest if exist "%manifest%" (
|
||||
rem Read the manifest directly instead of shelling out to findstr. In the
|
||||
rem GitHub Windows runner, the nested `findstr` path produced
|
||||
rem `FINDSTR: Cannot open D:MANIFEST`, which then broke runfile resolution for
|
||||
rem Bazel tests even though the manifest file was present.
|
||||
for /f "usebackq tokens=1,* delims= " %%A in ("%manifest%") do (
|
||||
if "%%A"=="%logical_path%" (
|
||||
endlocal & set "%~1=%%B" & exit /b 0
|
||||
)
|
||||
if "%%A"=="%workspace_logical_path%" (
|
||||
endlocal & set "%~1=%%B" & exit /b 0
|
||||
)
|
||||
for /f "usebackq tokens=1,* delims= " %%A in (`findstr /b /c:"%logical_path% " "%manifest%"`) do (
|
||||
endlocal & set "%~1=%%B" & exit /b 0
|
||||
)
|
||||
for /f "usebackq tokens=1,* delims= " %%A in (`findstr /b /c:"%workspace_logical_path% " "%manifest%"`) do (
|
||||
endlocal & set "%~1=%%B" & exit /b 0
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user