625 lines · 20.0 KB · Zig
RawDownload
const std = @import("std");const diff_mod = @import("../output/diff.zig");const types = @import("../shared/types.zig"); pub const max_file_target_components: usize = 256;pub const max_file_target_proof_bytes: usize = 32 * 1024;pub const max_external_scope_tail_bytes: usize = 4 * 1024; pub const PermissionTargetKind = enum {    target,    parent,}; pub const FileTargetDisposition = enum {    target_entry,    existing_parent,    create_parent,}; pub const FileIdentity = struct {    device: u64,    inode: u64,    kind: std.Io.File.Kind,}; pub const PathSpan = struct {    start: usize,    end: usize,}; pub const VerifiedTraversalAnchor = struct {    scope: enum { workspace, external },    path_end: usize,    identity: FileIdentity,    relative_components: []const PathSpan,}; pub const TraversalDirectoryState = union(enum) {    existing: FileIdentity,    create: struct {        permission_target_index: usize,    },}; pub const TraversalDirectory = struct {    component_index: usize,    path_end: usize,    state: TraversalDirectoryState,}; pub const EvaluatedPermissionTarget = struct {    kind: PermissionTargetKind,    disposition: FileTargetDisposition,    path_end: usize,    expected_identity: ?FileIdentity,    rule: types.RuleDecision,    session_grant_allowed: bool,}; pub const ResolvedPermissionTarget = struct {    kind: PermissionTargetKind,    disposition: FileTargetDisposition,    path_end: usize,    expected_identity: ?FileIdentity,}; /// Owns the policy-neutral canonical target and filesystem identity proof for/// one decoded write_file or edit_file call.pub const ResolvedFileMutationTargets = struct {    canonical_target_path: []const u8,    anchor: VerifiedTraversalAnchor,    traversal_directories: []const TraversalDirectory,    items: []const ResolvedPermissionTarget,    owns_memory: bool = true,     pub fn deinit(self: *ResolvedFileMutationTargets, alloc: std.mem.Allocator) void {        if (!self.owns_memory) return;        alloc.free(@constCast(self.canonical_target_path));        alloc.free(@constCast(self.anchor.relative_components));        alloc.free(@constCast(self.traversal_directories));        alloc.free(@constCast(self.items));        self.owns_memory = false;    }     pub fn relinquish(self: *ResolvedFileMutationTargets) void {        self.owns_memory = false;    }     pub fn anchorPath(self: ResolvedFileMutationTargets) []const u8 {        return self.canonical_target_path[0..self.anchor.path_end];    }     pub fn component(self: ResolvedFileMutationTargets, span: PathSpan) []const u8 {        return self.canonical_target_path[span.start..span.end];    }     pub fn traversalPath(self: ResolvedFileMutationTargets, entry: TraversalDirectory) []const u8 {        return self.canonical_target_path[0..entry.path_end];    }     pub fn permissionPath(self: ResolvedFileMutationTargets, item: ResolvedPermissionTarget) []const u8 {        return self.canonical_target_path[0..item.path_end];    }     pub fn proofValid(self: ResolvedFileMutationTargets) bool {        const canonical = self.canonical_target_path;        const components = self.anchor.relative_components;        const items = self.items;        if (!targetPathShapeValid(self, canonical, components)) return false;        if (!permissionTargetsValid(self, components, items)) return false;        const charge = proofCharge(            canonical.len,            components.len,            self.traversal_directories.len,            items.len,        ) orelse return false;        return charge <= max_file_target_proof_bytes;    }}; pub const PolicyEvaluatedFileTargets = struct {    canonical_target_path: []const u8,    anchor: VerifiedTraversalAnchor,    traversal_directories: []const TraversalDirectory,    items: []const EvaluatedPermissionTarget,    prompt_required: bool,     pub fn deinitOwned(self: *PolicyEvaluatedFileTargets, alloc: std.mem.Allocator) void {        alloc.free(@constCast(self.canonical_target_path));        alloc.free(@constCast(self.anchor.relative_components));        alloc.free(@constCast(self.traversal_directories));        alloc.free(@constCast(self.items));        self.* = undefined;    }     pub fn anchorPath(self: PolicyEvaluatedFileTargets) []const u8 {        return self.canonical_target_path[0..self.anchor.path_end];    }     pub fn component(self: PolicyEvaluatedFileTargets, span: PathSpan) []const u8 {        return self.canonical_target_path[span.start..span.end];    }     pub fn traversalPath(self: PolicyEvaluatedFileTargets, entry: TraversalDirectory) []const u8 {        return self.canonical_target_path[0..entry.path_end];    }     pub fn permissionPath(self: PolicyEvaluatedFileTargets, item: EvaluatedPermissionTarget) []const u8 {        return self.canonical_target_path[0..item.path_end];    }     pub fn authorizationEql(        a: PolicyEvaluatedFileTargets,        b: PolicyEvaluatedFileTargets,    ) bool {        if (!std.mem.eql(u8, a.canonical_target_path, b.canonical_target_path)) return false;        if (a.anchor.scope != b.anchor.scope) return false;        if (a.anchor.path_end != b.anchor.path_end) return false;        if (!std.meta.eql(a.anchor.identity, b.anchor.identity)) return false;        if (!sliceEql(PathSpan, a.anchor.relative_components, b.anchor.relative_components)) return false;        if (!sliceEql(TraversalDirectory, a.traversal_directories, b.traversal_directories)) return false;        if (!sliceEql(EvaluatedPermissionTarget, a.items, b.items)) return false;        return a.prompt_required == b.prompt_required;    }     pub fn proofValid(self: PolicyEvaluatedFileTargets) bool {        const canonical = self.canonical_target_path;        const components = self.anchor.relative_components;        const items = self.items;        if (!targetPathShapeValid(self, canonical, components)) return false;        if (!permissionTargetsValid(self, components, items)) return false;        const charge = proofCharge(            canonical.len,            components.len,            self.traversal_directories.len,            items.len,        ) orelse return false;        return charge <= max_file_target_proof_bytes;    }}; pub fn proofCharge(    canonical_path_len: usize,    component_count: usize,    traversal_count: usize,    item_count: usize,) ?usize {    var total = canonical_path_len;    total = checkedProofCharge(total, component_count, @sizeOf(PathSpan)) orelse        return null;    total = checkedProofCharge(total, traversal_count, @sizeOf(TraversalDirectory)) orelse        return null;    return checkedProofCharge(        total,        item_count,        @sizeOf(EvaluatedPermissionTarget),    );} fn targetPathShapeValid(    targets: anytype,    canonical: []const u8,    components: []const PathSpan,) bool {    if (canonical.len == 0 or canonical.len > std.Io.Dir.max_path_bytes) return false;    if (!std.fs.path.isAbsolute(canonical)) return false;    if (targets.anchor.path_end == 0 or targets.anchor.path_end > canonical.len) return false;    if (targets.anchor.identity.kind != .directory) return false;     if (components.len == 0 or components.len > max_file_target_components) {        return false;    }    if (targets.traversal_directories.len != components.len - 1) return false;     var expected_start = targets.anchor.path_end;    if (expected_start < canonical.len and std.fs.path.isSep(canonical[expected_start])) {        expected_start += 1;    }    for (components) |component| {        if (component.start != expected_start) return false;        if (component.end <= component.start or component.end > canonical.len) return false;        expected_start = component.end;        if (expected_start < canonical.len and std.fs.path.isSep(canonical[expected_start])) {            expected_start += 1;        }    }    return components[components.len - 1].end == canonical.len;} fn permissionTargetsValid(    targets: anytype,    components: []const PathSpan,    items: anytype,) bool {    if (components.len == 0) return false;    if (targets.traversal_directories.len != components.len - 1) return false;    if (items.len < 2 or items.len > max_file_target_components + 1) {        return false;    }    if (!permissionItemMatches(        items[0],        .target,        .target_entry,        targets.canonical_target_path.len,        items[0].expected_identity,    )) return false;     var used_items: [max_file_target_components + 1]bool = @splat(false);    used_items[0] = true;    used_items[1] = true;     var create_seen = false;    for (targets.traversal_directories, 0..) |entry, index| {        if (entry.component_index != index) return false;        if (entry.path_end != components[index].end) return false;        switch (entry.state) {            .existing => |identity| {                if (create_seen or identity.kind != .directory) return false;            },            .create => |create| {                create_seen = true;                if (create.permission_target_index >= items.len) return false;                if (used_items[create.permission_target_index] and                    create.permission_target_index != 1)                {                    return false;                }                const item = items[create.permission_target_index];                if (!permissionItemMatches(                    item,                    .parent,                    .create_parent,                    entry.path_end,                    null,                )) return false;                used_items[create.permission_target_index] = true;            },        }    }     if (targets.traversal_directories.len == 0) {        if (!permissionItemMatches(            items[1],            .parent,            .existing_parent,            targets.anchor.path_end,            targets.anchor.identity,        )) return false;    } else {        const parent = targets.traversal_directories[targets.traversal_directories.len - 1];        switch (parent.state) {            .existing => |identity| {                if (!permissionItemMatches(                    items[1],                    .parent,                    .existing_parent,                    parent.path_end,                    identity,                )) return false;            },            .create => |create| {                if (create.permission_target_index != 1) return false;                if (!permissionItemMatches(                    items[1],                    .parent,                    .create_parent,                    parent.path_end,                    null,                )) return false;            },        }    }    for (used_items[0..items.len]) |used| {        if (!used) return false;    }    return true;} fn permissionItemMatches(    item: anytype,    kind: PermissionTargetKind,    disposition: FileTargetDisposition,    path_end: usize,    expected_identity: ?FileIdentity,) bool {    return item.kind == kind and        item.disposition == disposition and        item.path_end == path_end and        optionalIdentityEql(item.expected_identity, expected_identity);} fn checkedProofCharge(base: usize, count: usize, item_size: usize) ?usize {    const bytes = std.math.mul(usize, count, item_size) catch return null;    return std.math.add(usize, base, bytes) catch null;} fn optionalIdentityEql(a: ?FileIdentity, b: ?FileIdentity) bool {    if (a) |actual| {        const expected = b orelse return false;        return std.meta.eql(actual, expected);    }    return b == null;} test "policy target proof validation accepts and rejects owned target shapes" {    const canonical = if (std.fs.path.sep == '\\')        "C:\\workspace\\parent\\note.txt"    else        "/workspace/parent/note.txt";    const anchor_end = if (std.fs.path.sep == '\\')        "C:\\workspace".len    else        "/workspace".len;    const parent_start = anchor_end + 1;    const parent_end = parent_start + "parent".len;    const target_start = parent_end + 1;    const target_end = target_start + "note.txt".len;     const anchor_identity: FileIdentity = .{        .device = 1,        .inode = 10,        .kind = .directory,    };    const parent_identity: FileIdentity = .{        .device = 1,        .inode = 11,        .kind = .directory,    };    const target_identity: FileIdentity = .{        .device = 1,        .inode = 12,        .kind = .file,    };    const components = [_]PathSpan{        .{ .start = parent_start, .end = parent_end },        .{ .start = target_start, .end = target_end },    };    const traversal = [_]TraversalDirectory{        .{            .component_index = 0,            .path_end = parent_end,            .state = .{ .existing = parent_identity },        },    };    const items = [_]EvaluatedPermissionTarget{        .{            .kind = .target,            .disposition = .target_entry,            .path_end = canonical.len,            .expected_identity = target_identity,            .rule = .allow,            .session_grant_allowed = false,        },        .{            .kind = .parent,            .disposition = .existing_parent,            .path_end = parent_end,            .expected_identity = parent_identity,            .rule = .allow,            .session_grant_allowed = false,        },    };    const policy: PolicyEvaluatedFileTargets = .{        .canonical_target_path = canonical,        .anchor = .{            .scope = .workspace,            .path_end = anchor_end,            .identity = anchor_identity,            .relative_components = &components,        },        .traversal_directories = &traversal,        .items = &items,        .prompt_required = false,    };     try std.testing.expect(policy.proofValid());     var corrupted_components = components;    corrupted_components[0].start = anchor_end;    var corrupted_policy = policy;    corrupted_policy.anchor.relative_components = &corrupted_components;    try std.testing.expect(!corrupted_policy.proofValid());     const unused_items = [_]EvaluatedPermissionTarget{        items[0],        items[1],        items[0],    };    var unused_item_policy = policy;    unused_item_policy.items = &unused_items;    try std.testing.expect(!unused_item_policy.proofValid());} test "file target proof charge rejects arithmetic overflow" {    try std.testing.expect(proofCharge(std.math.maxInt(usize), 1, 0, 0) == null);} pub const FileTargetPolicyResult = union(enum) {    target_resolution_failure: FileTargetResolutionFailure,    policy_denied: struct { target_index: usize },    evaluated: PolicyEvaluatedFileTargets,}; pub const FileTargetPreparationResult = union(enum) {    target_resolution_failure: FileTargetResolutionFailure,    prepared: ResolvedFileMutationTargets,}; pub const FileTargetResolutionFailure = enum {    path_outside_workspace,    file_not_found,    home_not_set,    invalid_path,    workspace_unavailable,    too_many_path_components,    policy_proof_too_large,    access_denied,    not_directory,    symlink_loop,    name_too_long,    bad_path_name,    path_state_changed,    unsupported_path_operation,    filesystem_unavailable,}; pub const Kind = enum {    write,    edit,}; pub const FileApprovalIntent = enum {    mutation,    equality_disclosure,}; pub const FileApprovalScope = union(enum) {    workspace_files,    external_tree: []const u8,}; pub const FileGrantOffer = struct {    grants: []const types.PermissionGrant,    scope: FileApprovalScope,}; pub fn isToolName(tool_name: []const u8) bool {    return std.mem.eql(u8, tool_name, "write_file") or        std.mem.eql(u8, tool_name, "edit_file");} pub fn resultIsNoop(tool_name: []const u8, output: []const u8) bool {    return isToolName(tool_name) and        std.mem.startsWith(u8, output, "No changes to ");} test "persisted result no-op requires a file mutation tool and canonical output" {    try std.testing.expect(resultIsNoop("write_file", "No changes to note.txt"));    try std.testing.expect(resultIsNoop("edit_file", "No changes to note.txt"));    try std.testing.expect(!resultIsNoop("read_file", "No changes to note.txt"));

Lines 1–500 of 625.