Deep Dive into Open Agent SDK Security and Session Persistence in Swift: SessionStore, Permission Policies, Sandbox, and Hooks

Open Agent SDK provides four layers of security for Swift agents: session persistence, permission control, sandbox isolation, and Hook-based auditing. Together, they solve three core agent problems: poor memory, weak control, and limited traceability. Keywords: SessionStore, Sandbox, PermissionPolicy.

Technical specification snapshot

Parameter Details
Project Open Agent SDK
Language Swift
Core topic Agent session persistence and security governance
Protocols / mechanisms Filesystem permissions, tool permission policies, command sandboxing, lifecycle Hooks
Session storage ~/.open-agent-sdk/sessions/
Core dependencies Swift Concurrency, FileManager, JSON, shell execution capabilities
Repository terryso/open-agent-sdk-swift
GitHub stars Not provided in the source

Open Agent SDK establishes an agent security baseline through four layers

A truly usable agent must do more than answer questions. It must preserve memory over time, execute under control, and support traceable auditing. Open Agent SDK breaks this problem into four subsystems: SessionStore handles memory, PermissionPolicy handles authorization, SandboxSettings handles isolation, and HookRegistry handles interception and tracking.

The value of this design lies in layered defense. Even if one policy layer is misconfigured, the remaining layers can still provide protection. This approach fits production-grade agents that run for long periods and invoke tools.

SessionStore makes agent sessions recoverable and traceable

SessionStore is implemented as an actor, which makes it a natural fit for concurrent session reads and writes. By default, it stores each session in its own directory. The core file is transcript.json, which records both the message array and metadata.

let sessionStore = SessionStore() // Default storage path
let customStore = SessionStore(sessionsDir: "/custom/path") // Custom path

This code initializes a session store and supports both the default directory and a custom deployment path.

It covers the full lifecycle: save, load, list, fork, and delete. The save operation preserves the original creation time, updates only updatedAt, sets directory permissions to 0700, and sets file permissions to 0600. By default, only the current user can access the data.

try await sessionStore.save(
    sessionId: "analysis-session",
    messages: messages,
    metadata: PartialSessionMetadata(
        cwd: "/project", // Current working directory
        model: "claude-sonnet-4-6", // Current model
        summary: "Code analysis session" // Session summary
    )
)

This code serializes messages and metadata to disk for later recovery and auditing.

Session recovery strategy determines how an agent continues context

The SDK provides three recovery modes: explicit recovery through sessionId, automatic continuation through continueRecentSession, and a fork-then-resume flow through forkSession + resumeSessionAt.

This recovery model works especially well for code analysis, long-running investigations, and multi-step workflows. Forking is particularly valuable because it gives the agent a way to run conversation branch experiments without contaminating the original session timeline.

let agent = createAgent(options: AgentOptions(
    apiKey: apiKey,
    model: "claude-sonnet-4-6",
    sessionStore: sessionStore,
    continueRecentSession: true // Automatically resume the most recent session
))

This code lets the agent automatically continue the latest context at startup, which reduces the cost of manually providing a session ID.

Session ID validation is the first defense against path traversal

SessionStore uses a restrained but effective security model. It prevents session IDs from containing /, \, or .., which avoids malicious path construction that could access files outside the session directory.

private func validateSessionId(_ sessionId: String) throws {
    let forbidden = ["/", "\\", ".."]
    for item in forbidden {
        if sessionId.contains(item) {
            throw SDKError.sessionError(message: "Invalid Session ID") // Block directory traversal
        }
    }
}

This code blocks sessionId-based path traversal attacks by rejecting dangerous characters.

PermissionPolicy provides finer-grained control than simple confirmation prompts

Permission control is not just about allowing or denying execution. Open Agent SDK defines six modes—default, plan, auto, acceptEdits, dontAsk, and bypassPermissions—to balance automation with the cost of human confirmation.

The real power, however, comes from canUseTool and the PermissionPolicy protocol. The former is ideal for quickly injecting custom logic, while the latter is better suited to reusable and composable policy governance.

let agent = createAgent(options: AgentOptions(
    apiKey: apiKey,
    model: "claude-sonnet-4-6",
    permissionMode: .bypassPermissions,
    canUseTool: { tool, input, context in
        if tool.name == "Bash" {
            return .deny("Bash is not allowed") // Block a high-risk tool
        }
        return nil // Let later rules handle it
    }
))

This code intercepts the Bash tool through a callback and demonstrates fine-grained authorization based on tool name.

Policy composition is the key engineering value of this permission system

The SDK includes built-in allowlists, denylists, read-only policies, and composite policies. CompositePolicy evaluates child policies in order, and if any child policy denies the action, the entire request is denied. This model is well suited to least-privilege production environments.

let policy = CompositePolicy(policies: [
    ToolNameDenylistPolicy(deniedToolNames: ["Bash"]), // Disable Bash first
    ReadOnlyPolicy() // Then restrict execution to read-only tools
])

This code combines a denylist with a read-only constraint to build a maintainable multi-rule permission model.

SandboxSettings narrows “allowed to execute” into “allowed to execute within boundaries”

The permission layer answers whether a tool can be called. The sandbox layer answers what the tool can reach after it runs. This distinction is especially important for Bash, file writes, and network access.

SandboxSettings supports path allowlists, path denylists, command allowlists, command denylists, and network domain constraints. Its core goal is to restrict potentially dangerous actions to clearly defined boundaries.

let sandbox = SandboxSettings(
    allowedReadPaths: ["/project/"], // Only allow reads from the project directory
    allowedWritePaths: ["/project/build/"], // Only allow writes to the build directory
    deniedPaths: ["/etc/", "/var/"], // Explicitly deny system directories
    deniedCommands: ["rm", "sudo"], // Block destructive commands
    allowNestedSandbox: false
)

This code defines a minimal executable boundary and fits controlled code analysis scenarios.

Path normalization and command parsing determine sandbox trustworthiness

SandboxChecker first normalizes paths by resolving .., ., and symbolic links, then performs prefix and segment-boundary checks. This prevents /project/ from being bypassed by a disguised path like /project-backup/.

On the command side, it detects shell metacharacters, extracts the basename, and recursively inspects wrapped commands such as bash -c. For complex commands that cannot be parsed reliably, it denies them by default. This is conservative, but it is the correct engineering tradeoff.

let allowed = SandboxChecker.isCommandAllowed(
    "bash -c \"rm -rf /tmp\"",
    settings: sandbox
)
print(allowed) // false, the nested dangerous command is detected recursively

This code demonstrates the sandbox’s recursive inspection of nested shell commands.

HookRegistry gives the agent an auditable and enforceable lifecycle bus

If the first three layers define the rules, the Hook system connects those rules to runtime behavior. The SDK provides more than 20 lifecycle events that cover critical points such as before and after tool calls, permission denials, task creation, configuration changes, and session start and end.

Hooks support two implementation styles: function Hooks for in-process logic and shell Hooks for external script integration. Both can read context through HookInput and return HookOutput to block execution, send notifications, rewrite inputs, or update permissions.

let registry = HookRegistry()

await registry.register(.preToolUse, definition: HookDefinition(
    handler: { input in
        return HookOutput(
            message: "Detected a dangerous tool invocation",
            block: true, // Block execution immediately
            reason: "Security policy matched"
        )
    },
    matcher: "Bash" // Match only the Bash tool
))

This code intercepts Bash before execution and works well as a second layer of protection for high-risk operations.

The engineering value of Hooks lies in closing the audit loop, not just blocking actions

Hooks do more than stop operations. They can send notifications, inject system messages, modify tool inputs, and even update permission behavior at runtime. Combined with serial execution and timeout handling, HookRegistry can reliably act as the agent’s audit bus.

In enterprise scenarios, this means you can record every tool invocation, every permission denial, and every context switch to build a complete behavioral trail.

All four layers are necessary to build a security model that works in practice

Using any one module in isolation is not enough. A stronger engineering practice is to let SessionStore maintain state continuity, PermissionPolicy restrict tool categories, SandboxSettings narrow the operational boundary, and HookRegistry provide auditability and runtime intervention.

let policy = CompositePolicy(policies: [
    ToolNameDenylistPolicy(deniedToolNames: ["Bash"]),
    ReadOnlyPolicy()
])

let agent = createAgent(options: AgentOptions(
    apiKey: "sk-...",
    model: "claude-sonnet-4-6",
    systemPrompt: "You are a read-only code analysis assistant.",
    permissionMode: .bypassPermissions,
    canUseTool: canUseTool(policy: policy), // Permission layer
    sessionStore: sessionStore, // Session layer
    hookRegistry: registry, // Audit layer
    sandbox: sandbox, // Sandbox layer
    sessionId: "analysis-session"
))

This code injects all four subsystems into the agent to create an execution environment that is recoverable, controllable, and auditable.

This design is especially well suited to long-running code-oriented agents

From the implementation details, Open Agent SDK does not rely on a single security mechanism. Instead, it uses a compositional design with layered constraints. Its value goes beyond preventing mistakes. It moves agents closer to real production standards.

For agent frameworks in the Swift ecosystem, this explicit modularization of sessions, permissions, sandboxing, and Hooks offers a highly practical reference model.

FAQ

1. What fundamentally distinguishes SessionStore from a normal message cache?

SessionStore does more than cache a message array. It persists metadata, supports listing and pagination, enables session forking, and enforces secure filesystem permissions. In practice, it behaves more like a session database for the full agent lifecycle.

2. If PermissionPolicy already exists, why is Sandbox still necessary?

PermissionPolicy determines whether a tool is allowed to execute. Sandbox determines which paths, commands, and network boundaries the tool can access after execution begins. The first is authorization, and the second is isolation. They cannot replace each other.

3. Which scenarios are the best fit for HookRegistry?

The most common use cases are audit logging, dangerous tool interception, permission denial alerts, tool input rewriting, and integration with external scripts. It is ideal for connecting agent runtime events to internal enterprise monitoring and security systems.

AI Readability Summary

This article systematically breaks down the four major security subsystems in Open Agent SDK for Swift: SessionStore, PermissionPolicy, SandboxSettings, and HookRegistry. It explains how they work together to deliver session recovery, permission control, command isolation, and audit interception.