mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Use ApiPathString in app-server filesystem permission paths (#28367)
## Why Clients running an app-server on one OS and an exec-server on another OS need to be able to pass sandbox config to app-server that refers to resources on the executor's foreign OS. ## What `AbsolutePathBuf` can't represent these paths and we don't want users to be exposed to `PathUri` yet, so this moves the public app-server API to be expressed in terms of `ApiPathString`. Stacked on #28165. - change app-server v2 filesystem permission paths, including legacy read/write roots, to `ApiPathString` - localize API paths through `PathUri` when converting into the current native core permission types - make path-bearing permission conversions fallible and surface localization failures instead of silently treating malformed grants as ordinary denials - propagate conversion failures through app-server and TUI approval handling - regenerate the app-server JSON and TypeScript schemas - leave migration TODOs on native-path conversions so they can be removed once core permission paths use `PathUri`
This commit is contained in:
committed by
GitHub
Unverified
parent
d959664420
commit
ecfe174d5f
@@ -19,6 +19,7 @@ codex-experimental-api-macros = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-shell-command = { workspace = true }
|
||||
codex-utils-absolute-path = { workspace = true }
|
||||
codex-utils-path-uri = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
+6
-3
@@ -27,7 +27,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -37,7 +37,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -84,6 +84,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"CommandAction": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -286,7 +289,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
+6
-3
@@ -27,7 +27,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -37,7 +37,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -58,6 +58,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
@@ -71,7 +74,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
+6
-7
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"AbsolutePathBuf": {
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
@@ -27,7 +23,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -37,7 +33,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -58,6 +54,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
@@ -71,7 +70,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -117,7 +117,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -164,6 +164,9 @@
|
||||
"AgentPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"AppBranding": {
|
||||
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
|
||||
"properties": {
|
||||
@@ -1318,7 +1321,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
+6
-3
@@ -27,7 +27,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -37,7 +37,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -84,6 +84,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"ApplyPatchApprovalParams": {
|
||||
"properties": {
|
||||
"callId": {
|
||||
@@ -640,7 +643,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
+6
-3
@@ -6020,7 +6020,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/v2/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -6030,7 +6030,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/v2/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -6135,6 +6135,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"AppBranding": {
|
||||
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
|
||||
"properties": {
|
||||
@@ -9372,7 +9375,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/v2/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
+6
-3
@@ -265,7 +265,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -275,7 +275,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -380,6 +380,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"AppBranding": {
|
||||
"description": "EXPERIMENTAL - app metadata returned by app-list APIs.",
|
||||
"properties": {
|
||||
@@ -5685,7 +5688,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
Generated
+6
-3
@@ -27,7 +27,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -37,7 +37,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -58,6 +58,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"AutoReviewDecisionSource": {
|
||||
"description": "[UNSTABLE] Source that produced a terminal approval auto-review decision.",
|
||||
"enum": [
|
||||
@@ -78,7 +81,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
Generated
+6
-3
@@ -27,7 +27,7 @@
|
||||
"read": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -37,7 +37,7 @@
|
||||
"write": {
|
||||
"description": "This will be removed in favor of `entries`.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
@@ -58,6 +58,9 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ApiPathString": {
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
@@ -71,7 +74,7 @@
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
"$ref": "#/definitions/ApiPathString"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* A UTF-8 path for preserving raw path compatibility at the app-server API
|
||||
* boundary while Codex migrates to [`PathUri`].
|
||||
*
|
||||
* Supports storing arbitrary strings read from the API and converting to and
|
||||
* from [`PathUri`] using an explicitly selected native path convention.
|
||||
*
|
||||
* When converting from [`PathUri`], "native" refers to the supplied
|
||||
* [`PathConvention`], which may be foreign to the operating system running
|
||||
* this process. The inner string is private so path-producing code must convert
|
||||
* from [`AbsolutePathBuf`] or use [`Self::from_path_uri`] instead of bypassing
|
||||
* the intended conversion boundary. Non-UTF-8 paths are converted to UTF-8
|
||||
* lossily because this API value is serialized as a JSON string.
|
||||
*
|
||||
* Deserialization accepts any UTF-8 string without interpreting or validating
|
||||
* it. That unrestricted construction path is intentionally available only to
|
||||
* serde: Codex-internal code cannot construct this type directly from a raw
|
||||
* `String` and is instead encouraged to convert through [`PathUri`] or
|
||||
* [`AbsolutePathBuf`]. Relative path text remains valid until an operation
|
||||
* such as [`Self::to_path_uri`] requires an absolute path.
|
||||
*/
|
||||
export type ApiPathString = string;
|
||||
@@ -3,6 +3,7 @@
|
||||
export type { AbsolutePathBuf } from "./AbsolutePathBuf";
|
||||
export type { AgentMessageInputContent } from "./AgentMessageInputContent";
|
||||
export type { AgentPath } from "./AgentPath";
|
||||
export type { ApiPathString } from "./ApiPathString";
|
||||
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
|
||||
export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse";
|
||||
export type { AuthMode } from "./AuthMode";
|
||||
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { ApiPathString } from "../ApiPathString";
|
||||
import type { FileSystemSandboxEntry } from "./FileSystemSandboxEntry";
|
||||
|
||||
export type AdditionalFileSystemPermissions = {
|
||||
/**
|
||||
* This will be removed in favor of `entries`.
|
||||
*/
|
||||
read: Array<AbsolutePathBuf> | null,
|
||||
read: Array<ApiPathString> | null,
|
||||
/**
|
||||
* This will be removed in favor of `entries`.
|
||||
*/
|
||||
write: Array<AbsolutePathBuf> | null, globScanMaxDepth?: number, entries?: Array<FileSystemSandboxEntry>, };
|
||||
write: Array<ApiPathString> | null, globScanMaxDepth?: number, entries?: Array<FileSystemSandboxEntry>, };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { ApiPathString } from "../ApiPathString";
|
||||
import type { FileSystemSpecialPath } from "./FileSystemSpecialPath";
|
||||
|
||||
export type FileSystemPath = { "type": "path", path: AbsolutePathBuf, } | { "type": "glob_pattern", pattern: string, } | { "type": "special", value: FileSystemSpecialPath, };
|
||||
export type FileSystemPath = { "type": "path", path: ApiPathString, } | { "type": "glob_pattern", pattern: string, } | { "type": "special", value: FileSystemSpecialPath, };
|
||||
|
||||
@@ -3504,7 +3504,7 @@ mod tests {
|
||||
additional_permissions: Some(v2::AdditionalPermissionProfile {
|
||||
network: None,
|
||||
file_system: Some(v2::AdditionalFileSystemPermissions {
|
||||
read: Some(vec![absolute_path("/tmp/allowed")]),
|
||||
read: Some(vec![absolute_path("/tmp/allowed").into()]),
|
||||
write: None,
|
||||
glob_scan_max_depth: None,
|
||||
entries: None,
|
||||
|
||||
@@ -37,6 +37,7 @@ use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_with::serde_as;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -687,9 +688,11 @@ impl From<CoreGuardianAssessmentAction> for GuardianApprovalReviewAction {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GuardianApprovalReviewAction> for CoreGuardianAssessmentAction {
|
||||
fn from(value: GuardianApprovalReviewAction) -> Self {
|
||||
match value {
|
||||
impl TryFrom<GuardianApprovalReviewAction> for CoreGuardianAssessmentAction {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: GuardianApprovalReviewAction) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
GuardianApprovalReviewAction::Command {
|
||||
source,
|
||||
command,
|
||||
@@ -742,9 +745,9 @@ impl From<GuardianApprovalReviewAction> for CoreGuardianAssessmentAction {
|
||||
permissions,
|
||||
} => Self::RequestPermissions {
|
||||
reason,
|
||||
permissions: permissions.into(),
|
||||
permissions: permissions.try_into()?,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@ use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use codex_utils_path_uri::PathConvention;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::io;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::PathBuf;
|
||||
use ts_rs::TS;
|
||||
@@ -54,9 +57,9 @@ impl From<CoreNetworkApprovalContext> for NetworkApprovalContext {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct AdditionalFileSystemPermissions {
|
||||
/// This will be removed in favor of `entries`.
|
||||
pub read: Option<Vec<AbsolutePathBuf>>,
|
||||
pub read: Option<Vec<ApiPathString>>,
|
||||
/// This will be removed in favor of `entries`.
|
||||
pub write: Option<Vec<AbsolutePathBuf>>,
|
||||
pub write: Option<Vec<ApiPathString>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub glob_scan_max_depth: Option<NonZeroUsize>,
|
||||
@@ -65,27 +68,32 @@ pub struct AdditionalFileSystemPermissions {
|
||||
pub entries: Option<Vec<FileSystemSandboxEntry>>,
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
|
||||
fn from(value: CoreFileSystemPermissions) -> Self {
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl From<CoreFileSystemPermissions<AbsolutePathBuf>> for AdditionalFileSystemPermissions {
|
||||
fn from(value: CoreFileSystemPermissions<AbsolutePathBuf>) -> Self {
|
||||
if let Some((read, write)) = value.legacy_read_write_roots() {
|
||||
let mut entries = Vec::with_capacity(
|
||||
read.as_ref().map_or(0, Vec::len) + write.as_ref().map_or(0, Vec::len),
|
||||
);
|
||||
if let Some(paths) = read.as_ref() {
|
||||
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
path: FileSystemPath::Path {
|
||||
path: ApiPathString::from_abs_path(path),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}));
|
||||
}
|
||||
if let Some(paths) = write.as_ref() {
|
||||
entries.extend(paths.iter().map(|path| FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: path.clone() },
|
||||
path: FileSystemPath::Path {
|
||||
path: ApiPathString::from_abs_path(path),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}));
|
||||
}
|
||||
Self {
|
||||
read,
|
||||
write,
|
||||
read: read.map(|paths| paths.iter().map(ApiPathString::from_abs_path).collect()),
|
||||
write: write.map(|paths| paths.iter().map(ApiPathString::from_abs_path).collect()),
|
||||
glob_scan_max_depth: None,
|
||||
entries: Some(entries),
|
||||
}
|
||||
@@ -106,21 +114,50 @@ impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AdditionalFileSystemPermissions> for CoreFileSystemPermissions {
|
||||
fn from(value: AdditionalFileSystemPermissions) -> Self {
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl TryFrom<AdditionalFileSystemPermissions> for CoreFileSystemPermissions<AbsolutePathBuf> {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: AdditionalFileSystemPermissions) -> Result<Self, Self::Error> {
|
||||
let mut permissions = if let Some(entries) = value.entries {
|
||||
Self {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(CoreFileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
.map(CoreFileSystemSandboxEntry::<AbsolutePathBuf>::try_from)
|
||||
.collect::<io::Result<_>>()?,
|
||||
glob_scan_max_depth: None,
|
||||
}
|
||||
} else {
|
||||
CoreFileSystemPermissions::from_read_write_roots(value.read, value.write)
|
||||
let read = value
|
||||
.read
|
||||
.map(|paths| {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
path.to_path_uri(PathConvention::native())
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?
|
||||
.to_abs_path()
|
||||
})
|
||||
.collect::<io::Result<Vec<_>>>()
|
||||
})
|
||||
.transpose()?;
|
||||
let write = value
|
||||
.write
|
||||
.map(|paths| {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
path.to_path_uri(PathConvention::native())
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?
|
||||
.to_abs_path()
|
||||
})
|
||||
.collect::<io::Result<Vec<_>>>()
|
||||
})
|
||||
.transpose()?;
|
||||
CoreFileSystemPermissions::from_read_write_roots(read, write)
|
||||
};
|
||||
permissions.glob_scan_max_depth = value.glob_scan_max_depth;
|
||||
permissions
|
||||
Ok(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +193,7 @@ pub struct RequestPermissionProfile {
|
||||
pub file_system: Option<AdditionalFileSystemPermissions>,
|
||||
}
|
||||
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl From<CoreRequestPermissionProfile> for RequestPermissionProfile {
|
||||
fn from(value: CoreRequestPermissionProfile) -> Self {
|
||||
Self {
|
||||
@@ -165,12 +203,17 @@ impl From<CoreRequestPermissionProfile> for RequestPermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequestPermissionProfile> for CoreRequestPermissionProfile {
|
||||
fn from(value: RequestPermissionProfile) -> Self {
|
||||
Self {
|
||||
impl TryFrom<RequestPermissionProfile> for CoreRequestPermissionProfile {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: RequestPermissionProfile) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
network: value.network.map(CoreNetworkPermissions::from),
|
||||
file_system: value.file_system.map(CoreFileSystemPermissions::from),
|
||||
}
|
||||
file_system: value
|
||||
.file_system
|
||||
.map(CoreFileSystemPermissions::<AbsolutePathBuf>::try_from)
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,16 +274,20 @@ impl From<FileSystemSpecialPath> for CoreFileSystemSpecialPath {
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
// TODO(anp): Rename this type to distinguish it from the generic protocol FileSystemPath.
|
||||
pub enum FileSystemPath {
|
||||
Path { path: AbsolutePathBuf },
|
||||
Path { path: ApiPathString },
|
||||
GlobPattern { pattern: String },
|
||||
Special { value: FileSystemSpecialPath },
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemPath> for FileSystemPath {
|
||||
fn from(value: CoreFileSystemPath) -> Self {
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl From<CoreFileSystemPath<AbsolutePathBuf>> for FileSystemPath {
|
||||
fn from(value: CoreFileSystemPath<AbsolutePathBuf>) -> Self {
|
||||
match value {
|
||||
CoreFileSystemPath::Path { path } => Self::Path { path },
|
||||
CoreFileSystemPath::Path { path } => Self::Path {
|
||||
path: ApiPathString::from_abs_path(&path),
|
||||
},
|
||||
CoreFileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
|
||||
CoreFileSystemPath::Special { value } => Self::Special {
|
||||
value: value.into(),
|
||||
@@ -249,15 +296,23 @@ impl From<CoreFileSystemPath> for FileSystemPath {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileSystemPath> for CoreFileSystemPath {
|
||||
fn from(value: FileSystemPath) -> Self {
|
||||
match value {
|
||||
FileSystemPath::Path { path } => Self::Path { path },
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl TryFrom<FileSystemPath> for CoreFileSystemPath<AbsolutePathBuf> {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: FileSystemPath) -> Result<Self, Self::Error> {
|
||||
Ok(match value {
|
||||
FileSystemPath::Path { path } => Self::Path {
|
||||
path: path
|
||||
.to_path_uri(PathConvention::native())
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?
|
||||
.to_abs_path()?,
|
||||
},
|
||||
FileSystemPath::GlobPattern { pattern } => Self::GlobPattern { pattern },
|
||||
FileSystemPath::Special { value } => Self::Special {
|
||||
value: value.into(),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,8 +324,9 @@ pub struct FileSystemSandboxEntry {
|
||||
pub access: FileSystemAccessMode,
|
||||
}
|
||||
|
||||
impl From<CoreFileSystemSandboxEntry> for FileSystemSandboxEntry {
|
||||
fn from(value: CoreFileSystemSandboxEntry) -> Self {
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl From<CoreFileSystemSandboxEntry<AbsolutePathBuf>> for FileSystemSandboxEntry {
|
||||
fn from(value: CoreFileSystemSandboxEntry<AbsolutePathBuf>) -> Self {
|
||||
Self {
|
||||
path: value.path.into(),
|
||||
access: value.access.into(),
|
||||
@@ -278,12 +334,14 @@ impl From<CoreFileSystemSandboxEntry> for FileSystemSandboxEntry {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
|
||||
fn from(value: FileSystemSandboxEntry) -> Self {
|
||||
Self {
|
||||
path: value.path.into(),
|
||||
impl TryFrom<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry<AbsolutePathBuf> {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: FileSystemSandboxEntry) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
path: value.path.try_into()?,
|
||||
access: value.access.to_core(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,6 +433,7 @@ pub struct AdditionalPermissionProfile {
|
||||
pub file_system: Option<AdditionalFileSystemPermissions>,
|
||||
}
|
||||
|
||||
// TODO(anp): Remove this conversion once core permission paths use PathUri.
|
||||
impl From<CoreAdditionalPermissionProfile> for AdditionalPermissionProfile {
|
||||
fn from(value: CoreAdditionalPermissionProfile) -> Self {
|
||||
Self {
|
||||
@@ -384,12 +443,17 @@ impl From<CoreAdditionalPermissionProfile> for AdditionalPermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AdditionalPermissionProfile> for CoreAdditionalPermissionProfile {
|
||||
fn from(value: AdditionalPermissionProfile) -> Self {
|
||||
Self {
|
||||
impl TryFrom<AdditionalPermissionProfile> for CoreAdditionalPermissionProfile {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: AdditionalPermissionProfile) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
network: value.network.map(CoreNetworkPermissions::from),
|
||||
file_system: value.file_system.map(CoreFileSystemPermissions::from),
|
||||
}
|
||||
file_system: value
|
||||
.file_system
|
||||
.map(CoreFileSystemPermissions::<AbsolutePathBuf>::try_from)
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,12 +469,17 @@ pub struct GrantedPermissionProfile {
|
||||
pub file_system: Option<AdditionalFileSystemPermissions>,
|
||||
}
|
||||
|
||||
impl From<GrantedPermissionProfile> for CoreAdditionalPermissionProfile {
|
||||
fn from(value: GrantedPermissionProfile) -> Self {
|
||||
Self {
|
||||
impl TryFrom<GrantedPermissionProfile> for CoreAdditionalPermissionProfile {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(value: GrantedPermissionProfile) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
network: value.network.map(CoreNetworkPermissions::from),
|
||||
file_system: value.file_system.map(CoreFileSystemPermissions::from),
|
||||
}
|
||||
file_system: value
|
||||
.file_system
|
||||
.map(CoreFileSystemPermissions::<AbsolutePathBuf>::try_from)
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ use codex_protocol::user_input::UserInput as CoreUserInput;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::test_support::PathBufExt;
|
||||
use codex_utils_absolute_path::test_support::test_path_buf;
|
||||
use codex_utils_path_uri::ApiPathString;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
@@ -379,8 +380,8 @@ fn external_agent_config_import_params_accept_legacy_plugin_details() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_execution_request_approval_rejects_relative_additional_permission_paths() {
|
||||
let err = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
|
||||
fn command_execution_request_approval_localization_rejects_relative_additional_permission_paths() {
|
||||
let params = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
|
||||
"threadId": "thr_123",
|
||||
"turnId": "turn_123",
|
||||
"itemId": "call_123",
|
||||
@@ -401,12 +402,14 @@ fn command_execution_request_approval_rejects_relative_additional_permission_pat
|
||||
"proposedNetworkPolicyAmendments": null,
|
||||
"availableDecisions": null
|
||||
}))
|
||||
.expect_err("relative additional permission paths should fail");
|
||||
assert!(
|
||||
err.to_string()
|
||||
.contains("AbsolutePathBuf deserialized without a base path"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
.expect("API paths should deserialize before localization");
|
||||
let additional_permissions = params
|
||||
.additional_permissions
|
||||
.expect("additional permissions should be present");
|
||||
|
||||
let err = CoreAdditionalPermissionProfile::try_from(additional_permissions)
|
||||
.expect_err("relative additional permission paths should fail localization");
|
||||
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -451,12 +454,12 @@ fn permissions_request_approval_uses_request_permission_profile() {
|
||||
}),
|
||||
file_system: Some(AdditionalFileSystemPermissions {
|
||||
read: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_only_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_only_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
write: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_write_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_write_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
glob_scan_max_depth: None,
|
||||
entries: None,
|
||||
@@ -465,7 +468,8 @@ fn permissions_request_approval_uses_request_permission_profile() {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoreRequestPermissionProfile::from(params.permissions),
|
||||
CoreRequestPermissionProfile::try_from(params.permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
CoreRequestPermissionProfile {
|
||||
network: Some(CoreNetworkPermissions {
|
||||
enabled: Some(true),
|
||||
@@ -559,7 +563,8 @@ fn additional_file_system_permissions_preserves_canonical_entries() {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
CoreFileSystemPermissions::from(permissions),
|
||||
CoreFileSystemPermissions::try_from(permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
core_permissions
|
||||
);
|
||||
}
|
||||
@@ -574,23 +579,25 @@ fn additional_file_system_permissions_populates_entries_for_legacy_roots() {
|
||||
);
|
||||
|
||||
let permissions = AdditionalFileSystemPermissions::from(core_permissions.clone());
|
||||
let read_only_api_path = ApiPathString::from_abs_path(&read_only_path);
|
||||
let read_write_api_path = ApiPathString::from_abs_path(&read_write_path);
|
||||
|
||||
assert_eq!(
|
||||
permissions,
|
||||
AdditionalFileSystemPermissions {
|
||||
read: Some(vec![read_only_path.clone()]),
|
||||
write: Some(vec![read_write_path.clone()]),
|
||||
read: Some(vec![read_only_api_path.clone()]),
|
||||
write: Some(vec![read_write_api_path.clone()]),
|
||||
glob_scan_max_depth: None,
|
||||
entries: Some(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: read_only_path,
|
||||
path: read_only_api_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: read_write_path,
|
||||
path: read_write_api_path,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
@@ -598,7 +605,8 @@ fn additional_file_system_permissions_populates_entries_for_legacy_roots() {
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
CoreFileSystemPermissions::from(permissions),
|
||||
CoreFileSystemPermissions::try_from(permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
core_permissions
|
||||
);
|
||||
}
|
||||
@@ -667,12 +675,12 @@ fn permissions_request_approval_response_uses_granted_permission_profile_without
|
||||
}),
|
||||
file_system: Some(AdditionalFileSystemPermissions {
|
||||
read: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_only_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_only_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
write: Some(vec![
|
||||
AbsolutePathBuf::try_from(PathBuf::from(read_write_path))
|
||||
.expect("path must be absolute"),
|
||||
serde_json::from_value(json!(read_write_path))
|
||||
.expect("API path string should deserialize")
|
||||
]),
|
||||
glob_scan_max_depth: None,
|
||||
entries: None,
|
||||
@@ -681,7 +689,8 @@ fn permissions_request_approval_response_uses_granted_permission_profile_without
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
CoreAdditionalPermissionProfile::from(response.permissions),
|
||||
CoreAdditionalPermissionProfile::try_from(response.permissions)
|
||||
.expect("API paths should convert to native paths"),
|
||||
CoreAdditionalPermissionProfile {
|
||||
network: Some(CoreNetworkPermissions {
|
||||
enabled: Some(true),
|
||||
|
||||
Reference in New Issue
Block a user