Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/safety-rules-and-confirm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@salesforce/b2c-tooling-sdk': minor
'@salesforce/b2c-cli': minor
'@salesforce/b2c-dx-docs': patch
---

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.
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const guidesSidebar = [
{text: 'Analytics Reports (CIP/CCAC)', link: '/guide/analytics-reports-cip-ccac'},
{text: 'IDE Integration', link: '/guide/ide-integration'},
{text: 'Scaffolding', link: '/guide/scaffolding'},
{text: 'Safety Mode', link: '/guide/safety'},
{text: 'Security', link: '/guide/security'},
{text: 'Storefront Next', link: '/guide/storefront-next'},
{text: 'MRT Utilities', link: '/guide/mrt-utilities'},
Expand Down
2 changes: 1 addition & 1 deletion docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ b2c sandbox list # ✅ Allowed
b2c sandbox create --realm test # ❌ Blocked
```

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.
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.

### Other Environment Variables

Expand Down
6 changes: 5 additions & 1 deletion docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ You can configure the CLI using environment variables:
| `MRT_PROJECT` | MRT project slug (`SFCC_MRT_PROJECT` also supported) |
| `MRT_ENVIRONMENT` | MRT environment name (`SFCC_MRT_ENVIRONMENT`, `MRT_TARGET` also supported) |
| `MRT_CLOUD_ORIGIN` | MRT API origin URL override (`SFCC_MRT_CLOUD_ORIGIN` also supported) |
| `SFCC_SAFETY_LEVEL` | Safety mode: `NONE`, `NO_DELETE`, `NO_UPDATE`, `READ_ONLY` (see [Safety Mode](/guide/security#operational-security-safety-mode)) |
| `SFCC_SAFETY_LEVEL` | Safety mode: `NONE`, `NO_DELETE`, `NO_UPDATE`, `READ_ONLY` (see [Safety Mode](/guide/safety)) |
| `SFCC_SAFETY_CONFIRM` | Enable confirmation mode for safety: `true` or `1` (see [Safety Mode](/guide/safety#confirmation-mode)) |
| `SFCC_SAFETY_CONFIG` | Path to global safety config file (see [Safety Mode](/guide/safety#global-safety-config)) |

## .env File

Expand Down Expand Up @@ -150,6 +152,8 @@ For projects that work with multiple instances, use the `configs` array:
}
```

Each instance can have its own `safety` configuration for per-instance operational safety. See [Safety Mode](/guide/safety#per-instance-configuration) for details.

Use the `-i` or `--instance` flag to select a specific configuration:

```bash
Expand Down
305 changes: 305 additions & 0 deletions docs/guide/safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
---
description: Configure Safety Mode to prevent accidental destructive operations with safety levels, per-instance rules, confirmation mode, and global policies.
---

# Safety Mode

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:

- Providing the CLI as a tool to AI agents/LLMs
- Working in production environments
- Training new team members
- Running commands from untrusted scripts

## Quick Start

Set an environment variable to enable safety for all operations:

```bash
export SFCC_SAFETY_LEVEL=NO_DELETE
```

Or configure per-instance in `dw.json`:

```json
{
"hostname": "prod.example.com",
"safety": {
"level": "NO_UPDATE",
"confirm": true
}
}
```

## Safety Levels

Safety levels provide broad protection by category:

| Level | Description | Blocks |
|-------|-------------|--------|
| `NONE` | No restrictions (default) | Nothing |
| `NO_DELETE` | Prevent deletions | DELETE operations |
| `NO_UPDATE` | Prevent deletions and destructive updates | DELETE + reset/stop/restart |
| `READ_ONLY` | Read-only mode | All writes (POST/PUT/PATCH/DELETE) |

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.

### Setting a Level

**Environment variable** (recommended for AI agent environments):

```bash
export SFCC_SAFETY_LEVEL=NO_UPDATE
```

Environment variables are preferred over command-line flags because LLMs control commands and flags, but not the environment.

**Per-instance in `dw.json`**:

```json
{
"hostname": "prod.example.com",
"safety": {
"level": "READ_ONLY"
}
}
```

**Global config** (see [Global Safety Config](#global-safety-config)):

```json
{
"level": "NO_DELETE"
}
```

When multiple sources set a level, the **most restrictive** wins.

## Safety Rules

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.

### Rule Actions

| Action | Behavior |
|--------|----------|
| `allow` | Operation is permitted -- overrides level restrictions |
| `block` | Operation is refused |
| `confirm` | Operation requires interactive confirmation before proceeding |

### Rule Matchers

Rules support three matcher types. All patterns use glob syntax (via [minimatch](https://github.com/isaacs/minimatch)).

#### HTTP Method + Path

Matches HTTP requests by method and URL path. Use this for fine-grained control over API endpoints:

```json
{ "method": "DELETE", "path": "/code_versions/*", "action": "block" }
```

`method` and `path` can be used independently or together. When both are specified, both must match.

#### Job ID

Matches OCAPI job execution by job ID. This catches both direct job commands and the underlying HTTP requests:

```json
{ "job": "sfcc-site-archive-import", "action": "block" }
{ "job": "sfcc-site-archive-*", "action": "confirm" }
```

#### CLI Command ID

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:

```json
{ "command": "sandbox:delete", "action": "confirm" }
{ "command": "sandbox:*", "action": "block" }
{ "command": "code:deploy", "action": "block" }
```

### Evaluation Order

1. **Rules** are checked in order. The first matching rule's action wins.
- An `allow` rule overrides even the strictest safety level -- it represents a deliberate user choice.
- A `block` rule blocks even if the level would allow.
- A `confirm` rule requires interactive confirmation.
2. If no rule matches, the **level** determines the outcome:
- If the level blocks the operation and `confirm: true` is set, confirmation is required instead of a hard block.
- If the level blocks, the operation is blocked.
- Otherwise, the operation is allowed.

## Confirmation Mode

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:

```json
{
"hostname": "staging.example.com",
"safety": {
"level": "NO_DELETE",
"confirm": true
}
}
```

You can also enable confirmation mode via the `SFCC_SAFETY_CONFIRM` environment variable:

```bash
export SFCC_SAFETY_CONFIRM=true
```

::: warning Non-Interactive Environments
In non-interactive environments (MCP server, piped stdin, CI/CD), confirmation is not possible. Operations that require confirmation will be **blocked** instead.
:::

## Per-Instance Configuration

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:

```json
{
"configs": [
{
"name": "dev",
"hostname": "dev.example.com",
"safety": { "level": "NONE" }
},
{
"name": "staging",
"hostname": "staging.example.com",
"safety": {
"level": "NO_DELETE",
"confirm": true,
"rules": [
{ "job": "sfcc-site-archive-export", "action": "allow" }
]
}
},
{
"name": "production",
"hostname": "prod.example.com",
"safety": { "level": "READ_ONLY" }
}
]
}
```

## Global Safety Config

Safety can be configured globally (across all projects and instances) using a `safety.json` file in the CLI's config directory.

| Platform | Default Location |
|----------|-----------------|
| macOS | `~/Library/Application Support/@salesforce/b2c-cli/safety.json` |
| Linux | `~/.config/b2c/safety.json` (or `$XDG_CONFIG_HOME`) |
| Windows | `%LOCALAPPDATA%\@salesforce\b2c-cli\safety.json` |

Override the file location with the `SFCC_SAFETY_CONFIG` environment variable:

```bash
export SFCC_SAFETY_CONFIG=/path/to/safety.json
```

The file has the same shape as the `safety` object in `dw.json`:

```json
{
"level": "NO_DELETE",
"confirm": true,
"rules": [
{ "job": "sfcc-site-archive-import", "action": "confirm" },
{ "command": "sandbox:delete", "action": "block" }
]
}
```

This is useful for enforcing baseline safety policies -- for example, when providing the CLI as a tool to AI agents.

## Configuration Merge

Safety configuration is merged from three sources (all optional):

| Source | Sets |
|--------|------|
| Environment variables (`SFCC_SAFETY_LEVEL`, `SFCC_SAFETY_CONFIRM`) | Level, confirm |
| Per-instance `dw.json` `safety` object | Level, confirm, rules |
| Global `safety.json` | Level, confirm, rules |

The merge strategy:

- **Level**: most restrictive wins across all sources
- **Confirm**: enabled if **any** source enables it
- **Rules**: instance rules are checked first, then global rules. Since evaluation is first-match-wins, instance rules can override global policy.
- **Explicit `allow` rules always win.** They represent a deliberate user choice and override any level restriction.

### Example

Given this global config:

```json
{
"level": "NO_UPDATE",
"rules": [
{ "job": "sfcc-site-archive-*", "action": "block" }
]
}
```

And this instance config:

```json
{
"safety": {
"rules": [
{ "job": "sfcc-site-archive-export", "action": "allow" }
]
}
}
```

The result:
- Level is `NO_UPDATE` (from global)
- Export jobs are **allowed** (instance rule matches first, overriding the global block)
- Import jobs are **blocked** (falls through to the global rule)
- DELETE requests are **blocked** (level `NO_UPDATE` blocks destructive operations)

## Environment Variables Reference

| Variable | Description |
|----------|-------------|
| `SFCC_SAFETY_LEVEL` | Safety level: `NONE`, `NO_DELETE`, `NO_UPDATE`, `READ_ONLY` |
| `SFCC_SAFETY_CONFIRM` | Enable confirmation mode: `true` or `1` |
| `SFCC_SAFETY_CONFIG` | Path to global safety config file |

## SDK Usage

The safety system is available to SDK consumers via the `SafetyGuard` class:

```typescript
import { SafetyGuard, resolveEffectiveSafetyConfig, withSafetyConfirmation } from '@salesforce/b2c-tooling-sdk';

// Create a guard from config
const guard = new SafetyGuard({
level: 'NO_UPDATE',
rules: [{ job: 'sfcc-site-archive-export', action: 'allow' }],
});

// Evaluate an operation
const evaluation = guard.evaluate({ type: 'job', jobId: 'sfcc-site-archive-export' });
// evaluation.action === 'allow'

// Assert (throws SafetyBlockedError or SafetyConfirmationRequired)
guard.assert({ type: 'http', method: 'DELETE', path: '/items/1' });

// Confirmation flow with retry
const result = await withSafetyConfirmation(
guard,
() => doSomethingDangerous(),
async (eval) => promptUser(`Safety: ${eval.reason}. Proceed?`),
);
```

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.
36 changes: 2 additions & 34 deletions docs/guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,41 +66,9 @@ This project uses [NPM trusted publishers](https://docs.npmjs.com/trusted-publis

## Operational Security: Safety Mode

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:
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.

- Providing the CLI as a tool to AI agents/LLMs
- Working in production environments
- Training new team members
- Running commands from untrusted scripts

### Safety Levels

Configure via the `SFCC_SAFETY_LEVEL` environment variable:

| Level | Description | Blocks |
|-------|-------------|--------|
| `NONE` | No restrictions (default) | Nothing |
| `NO_DELETE` | Prevent deletions | DELETE operations |
| `NO_UPDATE` | Prevent deletions and destructive updates | DELETE + reset/stop/restart |
| `READ_ONLY` | Read-only mode | All writes (POST/PUT/PATCH/DELETE) |

### Usage

```bash
# Default - no restrictions
export SFCC_SAFETY_LEVEL=NONE

# Prevent deletions
export SFCC_SAFETY_LEVEL=NO_DELETE

# Prevent deletions and destructive updates
export SFCC_SAFETY_LEVEL=NO_UPDATE

# Read-only mode
export SFCC_SAFETY_LEVEL=READ_ONLY
```

Environment variables are used instead of command-line flags because LLMs control commands and flags, but not the environment.
See the **[Safety Mode](/guide/safety)** guide for full documentation.

## Best Practices

Expand Down
Loading
Loading