feat: add selective Workdir symlink policies
This commit is contained in:
@@ -1599,12 +1599,30 @@ pub struct ScopeRule {
|
||||
/// direct children. Defaults to `true`.
|
||||
#[serde(default = "default_recursive")]
|
||||
pub recursive: bool,
|
||||
/// Which path identity an allow rule uses when symbolic links are
|
||||
/// encountered. Deny rules always inspect both identities.
|
||||
#[serde(default)]
|
||||
pub symlink_policy: SymlinkPolicy,
|
||||
}
|
||||
|
||||
fn default_recursive() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Symbolic-link identity used by one filesystem allow rule.
|
||||
///
|
||||
/// `Resolved` is the least authority and the default: access is matched
|
||||
/// against the provider-resolved target. `Logical` intentionally grants the
|
||||
/// path as presented through the Workdir, even when it aliases another target.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum SymlinkPolicy {
|
||||
#[default]
|
||||
Resolved,
|
||||
Logical,
|
||||
}
|
||||
|
||||
/// Permission lattice used by [`ScopeRule`].
|
||||
///
|
||||
/// The derived `Ord` instance follows declaration order, so
|
||||
@@ -1623,6 +1641,25 @@ pub enum Permission {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn scope_rule_defaults_to_resolved_symlink_policy() {
|
||||
let rule: ScopeRule = serde_json::from_value(serde_json::json!({
|
||||
"target": "/workspace",
|
||||
"permission": "read"
|
||||
}))
|
||||
.unwrap();
|
||||
assert!(rule.recursive);
|
||||
assert_eq!(rule.symlink_policy, SymlinkPolicy::Resolved);
|
||||
|
||||
let logical: ScopeRule = serde_json::from_value(serde_json::json!({
|
||||
"target": "/workspace",
|
||||
"permission": "read",
|
||||
"symlink_policy": "logical"
|
||||
}))
|
||||
.unwrap();
|
||||
assert_eq!(logical.symlink_policy, SymlinkPolicy::Logical);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worker_state_snapshot_apply_is_monotonic_and_detects_conflicts() {
|
||||
let mut current = WorkerStateSnapshot::initial(4);
|
||||
@@ -2462,6 +2499,7 @@ mod tests {
|
||||
target: "/tmp/work".into(),
|
||||
permission: Permission::Write,
|
||||
recursive: true,
|
||||
symlink_policy: Default::default(),
|
||||
}],
|
||||
});
|
||||
let json = serde_json::to_string(&method).unwrap();
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::{
|
||||
PasteArtifactRef, PendingSubmissionSummary, PendingSubmissionsSnapshot, Permission,
|
||||
RewindSummary, RewindTarget, RewindTargetId, RunResult, ScopeRule, Segment, SessionContentPart,
|
||||
SessionEntryProvenance, SessionMessageRole, SessionSnapshot, SessionSnapshotEntry,
|
||||
SessionSnapshotEntryData, SessionToolAttachment, SubmissionDisposition, ToolResultDisposition,
|
||||
TurnResult, UploadedFileAvailability, UploadedFileRef, WorkerBusyState,
|
||||
SessionSnapshotEntryData, SessionToolAttachment, SubmissionDisposition, SymlinkPolicy,
|
||||
ToolResultDisposition, TurnResult, UploadedFileAvailability, UploadedFileRef, WorkerBusyState,
|
||||
WorkerCommandAcknowledgement, WorkerCommandDisposition, WorkerCommandEnvelope,
|
||||
WorkerCommandKind, WorkerEvent, WorkerMaintenanceState, WorkerRunState, WorkerState,
|
||||
WorkerStateSnapshot, WorkerStatus,
|
||||
@@ -64,6 +64,7 @@ pub fn generated_protocol_types() -> String {
|
||||
push_decl::<ToolResultDisposition>(&cfg, &mut output);
|
||||
push_decl::<ErrorCode>(&cfg, &mut output);
|
||||
push_decl::<Permission>(&cfg, &mut output);
|
||||
push_decl::<SymlinkPolicy>(&cfg, &mut output);
|
||||
push_decl::<InFlightToolCallState>(&cfg, &mut output);
|
||||
push_decl::<CommandStatus>(&cfg, &mut output);
|
||||
push_decl::<CommandStream>(&cfg, &mut output);
|
||||
|
||||
Reference in New Issue
Block a user