# Security & Sandbox

DotCraft's security model has four layers: **file blacklist**, **workspace boundary**, **tool capability switches**, and **sandbox isolation**. Most projects only need a few lines in workspace config. Public deployments and external channel integrations should walk through the "Strict Deployment" section.

## Default Safety Baseline

When a workspace is created:

- File / Shell operations outside the workspace require approval (`Tools.File.RequireApprovalOutsideWorkspace = true` / `Tools.Shell.RequireApprovalOutsideWorkspace = true`)
- No blacklist (empty), supplement based on your machine
- All tools enabled (`EnabledTools = []`)
- Sandbox off (`Tools.Sandbox.Enabled = false`)

Minimal hardening for **personal local projects**:

```json
{
  "Security": {
    "BlacklistedPaths": [
      "~/.ssh",
      "~/.gnupg",
      "~/.aws"
    ]
  },
  "Tools": {
    "File": {
      "RequireApprovalOutsideWorkspace": true
    },
    "Shell": {
      "RequireApprovalOutsideWorkspace": true,
      "Timeout": 300
    }
  }
}
```

## File Blacklist

`Security.BlacklistedPaths` lists paths the agent must not access. The blacklist is global — CLI, Desktop, external channels, and automations all enforce it.

```json
{
  "Security": {
    "BlacklistedPaths": [
      "~/.ssh",
      "/etc/shadow",
      "C:\\Windows\\System32"
    ]
  }
}
```

Blacklist behavior:

- `ReadFile`, `WriteFile`, `EditFile`, `GrepFiles`, `FindFiles` on blacklisted paths are denied.
- Shell commands referencing blacklisted paths are denied.
- The blacklist takes precedence over workspace-boundary checks.
- Absolute paths and `~` expansion are supported; subpaths are also checked.

## Workspace Boundary

DotCraft analyzes paths before executing shell commands, covering Unix absolute paths, home-directory paths, env-var paths, Windows drive-letter paths, UNC paths, and common security device paths.

If any path in a command resolves outside the workspace:

- With `Tools.Shell.RequireApprovalOutsideWorkspace = false`, the command is denied.
- With `Tools.Shell.RequireApprovalOutsideWorkspace = true`, an approval request is sent to the active interaction source.

File tools also expand `~`, `$HOME`, `${HOME}`, and `%ENV%` to keep boundary checks consistent.

## Tool Capability Switches

| Field | Description | Default |
|---|---|---|
| `Tools.File.RequireApprovalOutsideWorkspace` | Approve file ops outside workspace | `true` |
| `Tools.File.MaxFileSize` | Max readable file size (bytes) | `10485760` |
| `Tools.File.RipgrepPath` | Optional `rg` path; empty tries `DOTCRAFT_RG_PATH`, `PATH`, then fallback | `""` |
| `Tools.Shell.RequireApprovalOutsideWorkspace` | Approve shell commands outside workspace | `true` |
| `Tools.Shell.Timeout` | Shell timeout (seconds) | `300` |
| `Tools.Shell.MaxOutputLength` | Max shell output length (chars) | `10000` |
| `Tools.Web.MaxChars` | Max chars for web fetch | `50000` |
| `Tools.Web.Timeout` | Web request timeout (seconds) | `300` |
| `Tools.Web.SearchMaxResults` | Default search result count | `5` |
| `Tools.Web.SearchProvider` | `Bing` / `Exa` | `Exa` |
| `Tools.Lsp.Enabled` | Enable built-in LSP tools | `false` |
| `Tools.Lsp.MaxFileSize` | Max LSP file size | `10485760` |

To allow only some tools, use top-level `EnabledTools`:

```json
{
  "EnabledTools": ["ReadFile", "GrepFiles", "WebSearch"]
}
```

## Hooks

Hooks let DotCraft run external commands during lifecycle events. They are useful for formatting, auditing, notifications, environment checks, and security guards. Start with an observing Hook, then add blocking Hooks once the behavior is clear.

### Quick Start

Add this to `.craft/config.json`:

```json
{
  "Hooks": {
    "Enabled": true,
    "Events": {
      "AfterToolCall": [
        {
          "name": "log-tool-call",
          "type": "command",
          "command": "node .craft/hooks/log-tool-call.js",
          "matcher": ".*",
          "timeoutSeconds": 10
        }
      ]
    }
  }
}
```

Example script that reads JSON from stdin and records the tool call:

```js
import fs from 'node:fs'

let input = ''
process.stdin.on('data', chunk => (input += chunk))
process.stdin.on('end', () => {
  fs.appendFileSync('.craft/hooks.log', input + '\n')
})
```

### Configuration Fields

| Field | Description |
|---|---|
| `Hooks.Enabled` | Enables Hooks |
| `Hooks.Events` | Hook lists grouped by event name |
| `name` | Hook name for logs and troubleshooting |
| `type` | Supports `"command"` |
| `command` | Shell command to run |
| `matcher` | Regex for matching tool names. Empty matches all tool-related events |
| `timeoutSeconds` | Hook timeout |

### Lifecycle Events

| Event | Purpose |
|---|---|
| `BeforeToolCall` | Check or block before tool calls |
| `AfterToolCall` | Log, format, or notify after tool calls |
| `BeforeTurn` | Prepare context before an Agent turn |
| `AfterTurn` | Record results or notify after an Agent turn |
| `BeforeAutomationTask` | Check before an automation task runs |
| `AfterAutomationTask` | Sync state after an automation task completes |

### stdin Input

Hook processes receive JSON through stdin. Fields vary by event type, and unused fields are omitted.

Tool-related events usually include:

```json
{
  "event": "BeforeToolCall",
  "workspace": "F:\\project",
  "sessionId": "thread-id",
  "toolName": "Exec",
  "arguments": {
    "command": "dotnet test"
  }
}
```

Turn-related events usually include:

```json
{
  "event": "AfterTurn",
  "workspace": "F:\\project",
  "sessionId": "thread-id",
  "summary": "Agent completed the turn"
}
```

### Exit Code Semantics

| Exit code | Meaning |
|---|---|
| `0` | Success, continue |
| Non-zero | Hook failed; `Before*` events can block the current operation |

Hook stdout is for logs and error messages. It does not modify tool arguments or tool results.

### Matcher

`matcher` is a regex and only applies to tool-related events:

| matcher | Matches |
|---|---|
| `WriteFile|EditFile` | File writes and edits |
| `Exec` | Shell commands |
| `.*` | All tools |

### Common Examples

**Auto-format after file writes**

```json
{
  "name": "format-after-edit",
  "type": "command",
  "command": "npm run format -- --write",
  "matcher": "WriteFile|EditFile",
  "timeoutSeconds": 60
}
```

**Shell command security guard**

```js
let input = ''
process.stdin.on('data', chunk => (input += chunk))
process.stdin.on('end', () => {
  const payload = JSON.parse(input)
  const command = payload.arguments?.command ?? ''
  if (/rm\s+-rf|Remove-Item\s+-Recurse/i.test(command)) {
    console.error('Dangerous command blocked by workspace Hook.')
    process.exit(1)
  }
})
```

**Tool call logging**

```js
import fs from 'node:fs'

let input = ''
process.stdin.on('data', chunk => (input += chunk))
process.stdin.on('end', () => {
  const payload = JSON.parse(input)
  fs.appendFileSync('.craft/tool-calls.log', JSON.stringify({
    event: payload.event,
    toolName: payload.toolName,
    at: new Date().toISOString()
  }) + '\n')
})
```

### Best Practices

- Keep Hook scripts small; put complex logic in project scripts.
- Append `|| true` to observing Hooks so failures do not block the main flow.
- Blocking Hooks should print clear error messages.
- Do not commit secrets; prefer env vars or global config.
- Use workspace-relative command paths to avoid cwd differences across entry points.

### Debugging

- Start with an observing Hook to inspect the stdin payload.
- Write raw input to `.craft/hooks-debug.log` while debugging.
- Add `|| true` to non-critical Hooks so temporary external-service failures do not block the agent.

## Sandbox (OpenSandbox)

[OpenSandbox](https://github.com/alibaba/OpenSandbox) isolates Shell and File tool execution inside a Docker container.

Prerequisites:

```bash
pip install opensandbox-server
opensandbox-server
```

Config:

```json
{
  "Tools": {
    "Sandbox": {
      "Enabled": true,
      "Domain": "localhost:5880",
      "Image": "ubuntu:latest",
      "NetworkPolicy": "allow",
      "SyncWorkspace": true
    }
  }
}
```

| Field | Description | Default |
|---|---|---|
| `Tools.Sandbox.Enabled` | Enable sandbox | `false` |
| `Tools.Sandbox.Domain` | OpenSandbox service address | `localhost:5880` |
| `Tools.Sandbox.ApiKey` | OpenSandbox API key | empty |
| `Tools.Sandbox.UseHttps` | Use HTTPS | `false` |
| `Tools.Sandbox.Image` | Container Docker image | `ubuntu:latest` |
| `Tools.Sandbox.TimeoutSeconds` | Sandbox timeout (seconds) | `600` |
| `Tools.Sandbox.Cpu` | Container CPU limit | `1` |
| `Tools.Sandbox.Memory` | Container memory limit | `512Mi` |
| `Tools.Sandbox.NetworkPolicy` | `deny` / `allow` / `custom` | `allow` |
| `Tools.Sandbox.AllowedEgressDomains` | Custom allowed egress domains | `[]` |
| `Tools.Sandbox.IdleTimeoutSeconds` | Idle timeout (seconds) | `300` |
| `Tools.Sandbox.SyncWorkspace` | Sync workspace into container | `true` |

## Strict Deployment Checklist

When DotCraft is exposed through external channels or the public internet, enable these together:

| Field | Recommended | Effect |
|---|---|---|
| `Tools.File.RequireApprovalOutsideWorkspace` | `true` | Approval required for outside-workspace files |
| `Tools.Shell.RequireApprovalOutsideWorkspace` | `true` | Approval required for outside-workspace shell |
| `Security.BlacklistedPaths` | Includes secret/credential dirs | Outright deny high-risk paths |
| `EnabledTools` | Keep only the necessary | Minimize tool surface area |
| `AppServer.WebSocket.Token` | Strong random token | Remote AppServer must authenticate |
| `Tools.Sandbox.Enabled` | `true` (if further isolation desired) | Shell / file ops in container |
| `SubAgent.MaxDepth` | `1` | Block recursive SubAgents |

## Scenarios

| Scenario | Recommendation |
|---|---|
| Personal local project | Keep outside-workspace approvals; add SSH / cloud credentials / password manager dirs to blacklist |
| Team shared workspace | Put security policy in workspace `.craft/config.json` so every entry enforces it |
| External channel or bot | Approvals on, restricted tools, strong tokens |
| Automation tasks | Per-task sandbox or tightened `EnabledTools` |

## Troubleshooting

### Command is inside the workspace but still denied

Check if the command string references an outside-workspace path: `~/.ssh`, `/etc`, `C:\Users`, or env-var expansions resolving outside.

### Sandbox does not start

Confirm Docker and `opensandbox-server` are running; check `Tools.Sandbox.Domain`, `ApiKey`, and network policy.

### Web search or fetch fails

Check `Tools.Web.SearchProvider`, `Tools.Web.Timeout`, `Tools.Web.MaxChars`, and the network environment.

### Hook does not run

Confirm `Hooks.Enabled = true`, the event name is correct, `matcher` matches the current tool name, and the command path works from the current workspace.

### Hook times out

Increase `timeoutSeconds` or move slow work to a background queue. Hooks are for short tasks, not long-running blockers.

### Hooks cannot modify tool arguments

By design. Hooks only observe or block.

## Related

- [Observability](./observability.md) — view approval and block records in Dashboard
- [SubAgents](./subagents.md) — `SubAgent.MaxDepth` and role tool policies
- [Samples](../resources/samples.md) — Hooks templates
- [Configuration Reference](../developing/configuration.md) — every `Security.*` and `Tools.*` field
