Skip to content

Commit 6880a84

Browse files
authored
Add per-instance safety config with rules, confirm mode, and global safety (#318)
* add per-instance safety config with rules, confirm mode, and global safety.json SafetyGuard evaluates operations against rules (allow/block/confirm) and levels. Rules match by HTTP method+path, job ID, or CLI command ID with glob patterns. Confirmation mode softens blocks into interactive prompts. Global safety config loaded from {configDir}/safety.json or SFCC_SAFETY_CONFIG env var. Merges with per-instance dw.json: level uses max, confirm uses OR, instance rules take priority over global rules. * fix: temporarily allow WebDAV cleanup in job export/import When a job export or import passes safety evaluation, the subsequent WebDAV DELETE (archive cleanup) was still caught by the HTTP middleware's level check. Now the commands add a temporary rule allowing DELETE on Impex paths for the duration of the operation. Also adds SafetyGuard.temporarilyAddRule() for arbitrary temporary rules. * enforce command safety rules generically in BaseCommand and fix compound operations - Evaluate command rules in BaseCommand.init() so every command is checked against safety rules automatically (e.g., { command: "code:deploy", action: "block" }) - Add temporary DELETE allow rules for code:deploy and code:watch to prevent internal WebDAV cleanup from being blocked by safety middleware - Simplify job commands to only evaluate job-specific rules (command rules now handled generically) - Update safety guide to document automatic command rule enforcement
1 parent b868991 commit 6880a84

26 files changed

Lines changed: 2155 additions & 141 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@salesforce/b2c-tooling-sdk': minor
3+
'@salesforce/b2c-cli': minor
4+
'@salesforce/b2c-dx-docs': patch
5+
---
6+
7+
Added per-instance safety configuration with rule-based actions (allow/block/confirm) and interactive confirmation mode. Safety can now be configured in `dw.json` with granular rules for HTTP paths, job IDs, and CLI commands.

docs/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const guidesSidebar = [
4949
{text: 'Analytics Reports (CIP/CCAC)', link: '/guide/analytics-reports-cip-ccac'},
5050
{text: 'IDE Integration', link: '/guide/ide-integration'},
5151
{text: 'Scaffolding', link: '/guide/scaffolding'},
52+
{text: 'Safety Mode', link: '/guide/safety'},
5253
{text: 'Security', link: '/guide/security'},
5354
{text: 'Storefront Next', link: '/guide/storefront-next'},
5455
{text: 'MRT Utilities', link: '/guide/mrt-utilities'},

docs/cli/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ b2c sandbox list # ✅ Allowed
5151
b2c sandbox create --realm test # ❌ Blocked
5252
```
5353

54-
Safety Mode operates at the HTTP layer and cannot be bypassed by command-line flags. See the [Security Guide](/guide/security#operational-security-safety-mode) for detailed information.
54+
Safety Mode operates at the HTTP layer and cannot be bypassed by command-line flags. See the [Safety Mode](/guide/safety) guide for detailed information.
5555

5656
### Other Environment Variables
5757

docs/guide/configuration.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ You can configure the CLI using environment variables:
8484
| `MRT_PROJECT` | MRT project slug (`SFCC_MRT_PROJECT` also supported) |
8585
| `MRT_ENVIRONMENT` | MRT environment name (`SFCC_MRT_ENVIRONMENT`, `MRT_TARGET` also supported) |
8686
| `MRT_CLOUD_ORIGIN` | MRT API origin URL override (`SFCC_MRT_CLOUD_ORIGIN` also supported) |
87-
| `SFCC_SAFETY_LEVEL` | Safety mode: `NONE`, `NO_DELETE`, `NO_UPDATE`, `READ_ONLY` (see [Safety Mode](/guide/security#operational-security-safety-mode)) |
87+
| `SFCC_SAFETY_LEVEL` | Safety mode: `NONE`, `NO_DELETE`, `NO_UPDATE`, `READ_ONLY` (see [Safety Mode](/guide/safety)) |
88+
| `SFCC_SAFETY_CONFIRM` | Enable confirmation mode for safety: `true` or `1` (see [Safety Mode](/guide/safety#confirmation-mode)) |
89+
| `SFCC_SAFETY_CONFIG` | Path to global safety config file (see [Safety Mode](/guide/safety#global-safety-config)) |
8890

8991
## .env File
9092

@@ -150,6 +152,8 @@ For projects that work with multiple instances, use the `configs` array:
150152
}
151153
```
152154

155+
Each instance can have its own `safety` configuration for per-instance operational safety. See [Safety Mode](/guide/safety#per-instance-configuration) for details.
156+
153157
Use the `-i` or `--instance` flag to select a specific configuration:
154158

155159
```bash

docs/guide/safety.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
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.

docs/guide/security.md

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -66,41 +66,9 @@ This project uses [NPM trusted publishers](https://docs.npmjs.com/trusted-publis
6666

6767
## Operational Security: Safety Mode
6868

69-
The CLI includes a **Safety Mode** feature via CLI checks and HTTP middleware that prevents accidental or unwanted destructive operations. This is particularly important when:
69+
The CLI includes a **Safety Mode** feature that prevents accidental or unwanted destructive operations via HTTP middleware and command-level checks. Safety mode supports configurable levels, per-instance and global rules, and interactive confirmation.
7070

71-
- Providing the CLI as a tool to AI agents/LLMs
72-
- Working in production environments
73-
- Training new team members
74-
- Running commands from untrusted scripts
75-
76-
### Safety Levels
77-
78-
Configure via the `SFCC_SAFETY_LEVEL` environment variable:
79-
80-
| Level | Description | Blocks |
81-
|-------|-------------|--------|
82-
| `NONE` | No restrictions (default) | Nothing |
83-
| `NO_DELETE` | Prevent deletions | DELETE operations |
84-
| `NO_UPDATE` | Prevent deletions and destructive updates | DELETE + reset/stop/restart |
85-
| `READ_ONLY` | Read-only mode | All writes (POST/PUT/PATCH/DELETE) |
86-
87-
### Usage
88-
89-
```bash
90-
# Default - no restrictions
91-
export SFCC_SAFETY_LEVEL=NONE
92-
93-
# Prevent deletions
94-
export SFCC_SAFETY_LEVEL=NO_DELETE
95-
96-
# Prevent deletions and destructive updates
97-
export SFCC_SAFETY_LEVEL=NO_UPDATE
98-
99-
# Read-only mode
100-
export SFCC_SAFETY_LEVEL=READ_ONLY
101-
```
102-
103-
Environment variables are used instead of command-line flags because LLMs control commands and flags, but not the environment.
71+
See the **[Safety Mode](/guide/safety)** guide for full documentation.
10472

10573
## Best Practices
10674

0 commit comments

Comments
 (0)