|
| 1 | +--- |
| 2 | +description: Configure Safety Mode to prevent accidental destructive operations with safety levels, per-instance rules, confirmation mode, and global policies. |
| 3 | +--- |
| 4 | + |
| 5 | +# Safety Mode |
| 6 | + |
| 7 | +The CLI and SDK include a **Safety Mode** feature that prevents accidental or unwanted destructive operations via HTTP middleware and command-level checks. This is particularly important when: |
| 8 | + |
| 9 | +- Providing the CLI as a tool to AI agents/LLMs |
| 10 | +- Working in production environments |
| 11 | +- Training new team members |
| 12 | +- Running commands from untrusted scripts |
| 13 | + |
| 14 | +## Quick Start |
| 15 | + |
| 16 | +Set an environment variable to enable safety for all operations: |
| 17 | + |
| 18 | +```bash |
| 19 | +export SFCC_SAFETY_LEVEL=NO_DELETE |
| 20 | +``` |
| 21 | + |
| 22 | +Or configure per-instance in `dw.json`: |
| 23 | + |
| 24 | +```json |
| 25 | +{ |
| 26 | + "hostname": "prod.example.com", |
| 27 | + "safety": { |
| 28 | + "level": "NO_UPDATE", |
| 29 | + "confirm": true |
| 30 | + } |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +## Safety Levels |
| 35 | + |
| 36 | +Safety levels provide broad protection by category: |
| 37 | + |
| 38 | +| Level | Description | Blocks | |
| 39 | +|-------|-------------|--------| |
| 40 | +| `NONE` | No restrictions (default) | Nothing | |
| 41 | +| `NO_DELETE` | Prevent deletions | DELETE operations | |
| 42 | +| `NO_UPDATE` | Prevent deletions and destructive updates | DELETE + reset/stop/restart | |
| 43 | +| `READ_ONLY` | Read-only mode | All writes (POST/PUT/PATCH/DELETE) | |
| 44 | + |
| 45 | +Levels apply to all HTTP requests made through the SDK. They are enforced by middleware, so they work regardless of which SDK method or CLI command initiates the request. |
| 46 | + |
| 47 | +### Setting a Level |
| 48 | + |
| 49 | +**Environment variable** (recommended for AI agent environments): |
| 50 | + |
| 51 | +```bash |
| 52 | +export SFCC_SAFETY_LEVEL=NO_UPDATE |
| 53 | +``` |
| 54 | + |
| 55 | +Environment variables are preferred over command-line flags because LLMs control commands and flags, but not the environment. |
| 56 | + |
| 57 | +**Per-instance in `dw.json`**: |
| 58 | + |
| 59 | +```json |
| 60 | +{ |
| 61 | + "hostname": "prod.example.com", |
| 62 | + "safety": { |
| 63 | + "level": "READ_ONLY" |
| 64 | + } |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +**Global config** (see [Global Safety Config](#global-safety-config)): |
| 69 | + |
| 70 | +```json |
| 71 | +{ |
| 72 | + "level": "NO_DELETE" |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +When multiple sources set a level, the **most restrictive** wins. |
| 77 | + |
| 78 | +## Safety Rules |
| 79 | + |
| 80 | +Rules provide granular control over specific operations. Each rule matches an operation and specifies an action (`allow`, `block`, or `confirm`). Rules are evaluated in order -- the first matching rule wins. |
| 81 | + |
| 82 | +### Rule Actions |
| 83 | + |
| 84 | +| Action | Behavior | |
| 85 | +|--------|----------| |
| 86 | +| `allow` | Operation is permitted -- overrides level restrictions | |
| 87 | +| `block` | Operation is refused | |
| 88 | +| `confirm` | Operation requires interactive confirmation before proceeding | |
| 89 | + |
| 90 | +### Rule Matchers |
| 91 | + |
| 92 | +Rules support three matcher types. All patterns use glob syntax (via [minimatch](https://github.com/isaacs/minimatch)). |
| 93 | + |
| 94 | +#### HTTP Method + Path |
| 95 | + |
| 96 | +Matches HTTP requests by method and URL path. Use this for fine-grained control over API endpoints: |
| 97 | + |
| 98 | +```json |
| 99 | +{ "method": "DELETE", "path": "/code_versions/*", "action": "block" } |
| 100 | +``` |
| 101 | + |
| 102 | +`method` and `path` can be used independently or together. When both are specified, both must match. |
| 103 | + |
| 104 | +#### Job ID |
| 105 | + |
| 106 | +Matches OCAPI job execution by job ID. This catches both direct job commands and the underlying HTTP requests: |
| 107 | + |
| 108 | +```json |
| 109 | +{ "job": "sfcc-site-archive-import", "action": "block" } |
| 110 | +{ "job": "sfcc-site-archive-*", "action": "confirm" } |
| 111 | +``` |
| 112 | + |
| 113 | +#### CLI Command ID |
| 114 | + |
| 115 | +Matches CLI commands by their oclif command ID. Command rules are enforced automatically for **every** command before `run()` executes -- no per-command opt-in is needed: |
| 116 | + |
| 117 | +```json |
| 118 | +{ "command": "sandbox:delete", "action": "confirm" } |
| 119 | +{ "command": "sandbox:*", "action": "block" } |
| 120 | +{ "command": "code:deploy", "action": "block" } |
| 121 | +``` |
| 122 | + |
| 123 | +### Evaluation Order |
| 124 | + |
| 125 | +1. **Rules** are checked in order. The first matching rule's action wins. |
| 126 | + - An `allow` rule overrides even the strictest safety level -- it represents a deliberate user choice. |
| 127 | + - A `block` rule blocks even if the level would allow. |
| 128 | + - A `confirm` rule requires interactive confirmation. |
| 129 | +2. If no rule matches, the **level** determines the outcome: |
| 130 | + - If the level blocks the operation and `confirm: true` is set, confirmation is required instead of a hard block. |
| 131 | + - If the level blocks, the operation is blocked. |
| 132 | + - Otherwise, the operation is allowed. |
| 133 | + |
| 134 | +## Confirmation Mode |
| 135 | + |
| 136 | +When `confirm: true` is set, operations that would be blocked by the safety level are softened to require interactive confirmation instead of being refused outright: |
| 137 | + |
| 138 | +```json |
| 139 | +{ |
| 140 | + "hostname": "staging.example.com", |
| 141 | + "safety": { |
| 142 | + "level": "NO_DELETE", |
| 143 | + "confirm": true |
| 144 | + } |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +You can also enable confirmation mode via the `SFCC_SAFETY_CONFIRM` environment variable: |
| 149 | + |
| 150 | +```bash |
| 151 | +export SFCC_SAFETY_CONFIRM=true |
| 152 | +``` |
| 153 | + |
| 154 | +::: warning Non-Interactive Environments |
| 155 | +In non-interactive environments (MCP server, piped stdin, CI/CD), confirmation is not possible. Operations that require confirmation will be **blocked** instead. |
| 156 | +::: |
| 157 | + |
| 158 | +## Per-Instance Configuration |
| 159 | + |
| 160 | +Configure safety per instance in `dw.json` using the `safety` object. This is especially useful in multi-instance configurations where different instances have different risk profiles: |
| 161 | + |
| 162 | +```json |
| 163 | +{ |
| 164 | + "configs": [ |
| 165 | + { |
| 166 | + "name": "dev", |
| 167 | + "hostname": "dev.example.com", |
| 168 | + "safety": { "level": "NONE" } |
| 169 | + }, |
| 170 | + { |
| 171 | + "name": "staging", |
| 172 | + "hostname": "staging.example.com", |
| 173 | + "safety": { |
| 174 | + "level": "NO_DELETE", |
| 175 | + "confirm": true, |
| 176 | + "rules": [ |
| 177 | + { "job": "sfcc-site-archive-export", "action": "allow" } |
| 178 | + ] |
| 179 | + } |
| 180 | + }, |
| 181 | + { |
| 182 | + "name": "production", |
| 183 | + "hostname": "prod.example.com", |
| 184 | + "safety": { "level": "READ_ONLY" } |
| 185 | + } |
| 186 | + ] |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +## Global Safety Config |
| 191 | + |
| 192 | +Safety can be configured globally (across all projects and instances) using a `safety.json` file in the CLI's config directory. |
| 193 | + |
| 194 | +| Platform | Default Location | |
| 195 | +|----------|-----------------| |
| 196 | +| macOS | `~/Library/Application Support/@salesforce/b2c-cli/safety.json` | |
| 197 | +| Linux | `~/.config/b2c/safety.json` (or `$XDG_CONFIG_HOME`) | |
| 198 | +| Windows | `%LOCALAPPDATA%\@salesforce\b2c-cli\safety.json` | |
| 199 | + |
| 200 | +Override the file location with the `SFCC_SAFETY_CONFIG` environment variable: |
| 201 | + |
| 202 | +```bash |
| 203 | +export SFCC_SAFETY_CONFIG=/path/to/safety.json |
| 204 | +``` |
| 205 | + |
| 206 | +The file has the same shape as the `safety` object in `dw.json`: |
| 207 | + |
| 208 | +```json |
| 209 | +{ |
| 210 | + "level": "NO_DELETE", |
| 211 | + "confirm": true, |
| 212 | + "rules": [ |
| 213 | + { "job": "sfcc-site-archive-import", "action": "confirm" }, |
| 214 | + { "command": "sandbox:delete", "action": "block" } |
| 215 | + ] |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +This is useful for enforcing baseline safety policies -- for example, when providing the CLI as a tool to AI agents. |
| 220 | + |
| 221 | +## Configuration Merge |
| 222 | + |
| 223 | +Safety configuration is merged from three sources (all optional): |
| 224 | + |
| 225 | +| Source | Sets | |
| 226 | +|--------|------| |
| 227 | +| Environment variables (`SFCC_SAFETY_LEVEL`, `SFCC_SAFETY_CONFIRM`) | Level, confirm | |
| 228 | +| Per-instance `dw.json` `safety` object | Level, confirm, rules | |
| 229 | +| Global `safety.json` | Level, confirm, rules | |
| 230 | + |
| 231 | +The merge strategy: |
| 232 | + |
| 233 | +- **Level**: most restrictive wins across all sources |
| 234 | +- **Confirm**: enabled if **any** source enables it |
| 235 | +- **Rules**: instance rules are checked first, then global rules. Since evaluation is first-match-wins, instance rules can override global policy. |
| 236 | +- **Explicit `allow` rules always win.** They represent a deliberate user choice and override any level restriction. |
| 237 | + |
| 238 | +### Example |
| 239 | + |
| 240 | +Given this global config: |
| 241 | + |
| 242 | +```json |
| 243 | +{ |
| 244 | + "level": "NO_UPDATE", |
| 245 | + "rules": [ |
| 246 | + { "job": "sfcc-site-archive-*", "action": "block" } |
| 247 | + ] |
| 248 | +} |
| 249 | +``` |
| 250 | + |
| 251 | +And this instance config: |
| 252 | + |
| 253 | +```json |
| 254 | +{ |
| 255 | + "safety": { |
| 256 | + "rules": [ |
| 257 | + { "job": "sfcc-site-archive-export", "action": "allow" } |
| 258 | + ] |
| 259 | + } |
| 260 | +} |
| 261 | +``` |
| 262 | + |
| 263 | +The result: |
| 264 | +- Level is `NO_UPDATE` (from global) |
| 265 | +- Export jobs are **allowed** (instance rule matches first, overriding the global block) |
| 266 | +- Import jobs are **blocked** (falls through to the global rule) |
| 267 | +- DELETE requests are **blocked** (level `NO_UPDATE` blocks destructive operations) |
| 268 | + |
| 269 | +## Environment Variables Reference |
| 270 | + |
| 271 | +| Variable | Description | |
| 272 | +|----------|-------------| |
| 273 | +| `SFCC_SAFETY_LEVEL` | Safety level: `NONE`, `NO_DELETE`, `NO_UPDATE`, `READ_ONLY` | |
| 274 | +| `SFCC_SAFETY_CONFIRM` | Enable confirmation mode: `true` or `1` | |
| 275 | +| `SFCC_SAFETY_CONFIG` | Path to global safety config file | |
| 276 | + |
| 277 | +## SDK Usage |
| 278 | + |
| 279 | +The safety system is available to SDK consumers via the `SafetyGuard` class: |
| 280 | + |
| 281 | +```typescript |
| 282 | +import { SafetyGuard, resolveEffectiveSafetyConfig, withSafetyConfirmation } from '@salesforce/b2c-tooling-sdk'; |
| 283 | + |
| 284 | +// Create a guard from config |
| 285 | +const guard = new SafetyGuard({ |
| 286 | + level: 'NO_UPDATE', |
| 287 | + rules: [{ job: 'sfcc-site-archive-export', action: 'allow' }], |
| 288 | +}); |
| 289 | + |
| 290 | +// Evaluate an operation |
| 291 | +const evaluation = guard.evaluate({ type: 'job', jobId: 'sfcc-site-archive-export' }); |
| 292 | +// evaluation.action === 'allow' |
| 293 | + |
| 294 | +// Assert (throws SafetyBlockedError or SafetyConfirmationRequired) |
| 295 | +guard.assert({ type: 'http', method: 'DELETE', path: '/items/1' }); |
| 296 | + |
| 297 | +// Confirmation flow with retry |
| 298 | +const result = await withSafetyConfirmation( |
| 299 | + guard, |
| 300 | + () => doSomethingDangerous(), |
| 301 | + async (eval) => promptUser(`Safety: ${eval.reason}. Proceed?`), |
| 302 | +); |
| 303 | +``` |
| 304 | + |
| 305 | +The HTTP middleware uses `SafetyGuard` internally, so all HTTP requests through SDK clients are evaluated automatically. CLI commands and other consumers can use the guard directly for richer safety interaction. |
0 commit comments