Skip to content
Open
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
26 changes: 26 additions & 0 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,32 @@ These flags are available on all commands that interact with B2C instances:
| `--username`, `-u` | `SFCC_USERNAME` | Username for Basic Auth |
| `--password`, `-p` | `SFCC_PASSWORD` | Password/access key for Basic Auth |

### Safety Mode

Safety Mode provides protection against accidental or unwanted destructive operations. This is particularly important when using the CLI in automated environments, CI/CD pipelines, or as a tool for AI agents.

| Environment Variable | Values | Description |
| ---------------------- | ------ | ----------- |
| `SFCC_SAFETY_LEVEL` | `NONE` (default) | No restrictions |
| | `NO_DELETE` | Block DELETE operations |
| | `NO_UPDATE` | Block DELETE and destructive operations (reset, stop, restart) |
| | `READ_ONLY` | Block all write operations (GET only) |

**Example:**
```bash
# Prevent deletions in CI/CD
export SFCC_SAFETY_LEVEL=NO_DELETE
b2c sandbox create --realm test # ✅ Allowed
b2c sandbox delete test-id # ❌ Blocked

# Read-only mode for reporting
export SFCC_SAFETY_LEVEL=READ_ONLY
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.

### Other Environment Variables

| Environment Variable | Description |
Expand Down
39 changes: 39 additions & 0 deletions docs/guide/ci-cd.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,3 +438,42 @@ Logs are always human-readable on stderr. The `--json` flag only controls the st
All actions automatically configure:

- **`NO_COLOR=1`** — clean log output without ANSI colors

## Best Practices

### Use Safety Mode

Enable [Safety Mode](/guide/security#operational-security-safety-mode) in CI/CD to prevent accidental destructive operations:

```yaml
jobs:
deploy:
runs-on: ubuntu-latest
env:
# Prevent accidental deletions
SFCC_SAFETY_LEVEL:NO_DELETE
steps:
- uses: SalesforceCommerceCloud/b2c-developer-tooling/actions/setup@v1
- name: Deploy code
run: b2c code deploy
```

**Safety Levels for CI/CD:**

- **`NO_DELETE`** (Recommended for most CI/CD) - Prevents deletions but allows deployments and updates
- **`NO_UPDATE`** (Strict) - Only allows read and create operations, blocks updates and deletions
- **`READ_ONLY`** (Monitoring/Reporting) - Only allows read operations

**Example: Production deployment with safety:**
```yaml
deploy-production:
environment: production
env:
SFCC_SAFETY_LEVEL: NO_DELETE
steps:
- name: Deploy
run: |
b2c code deploy # ✅ Allowed
b2c sandbox start prod # ✅ Allowed
b2c sandbox delete test # ❌ Blocked by safety mode
```
1 change: 1 addition & 0 deletions docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ 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)) |

## .env File

Expand Down
162 changes: 162 additions & 0 deletions docs/guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,167 @@ When adding a new dependency that requires build scripts:

This project uses [NPM trusted publishers](https://docs.npmjs.com/trusted-publishers) for package publication. Instead of storing long-lived npm tokens, packages are published via GitHub Actions using short-lived OIDC tokens that cannot be extracted or reused.

## Operational Security: Safety Mode

The CLI includes a **Safety Mode** feature that prevents accidental or unwanted destructive operations. This is particularly important when:

- Using the CLI in automated environments (CI/CD pipelines)
- Providing the CLI as a tool to AI agents/LLMs
- Working in production environments
- Training new team members
- Running commands from untrusted scripts

### How It Works

Safety Mode uses a **hybrid protection approach**:

1. **HTTP Middleware Layer** (Primary Protection)
- Intercepts ALL HTTP requests before they're sent
- Cannot be bypassed by command-line flags
- Works automatically for all commands
- LLM-proof: controlled via environment variable

2. **Command-Level Checks** (Better UX)
- Provides early, user-friendly error messages
- Catches operations before HTTP requests

### 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 Examples

#### Development (Allow Everything)
```bash
# No restrictions (default)
unset SFCC_SAFETY_LEVEL
# OR
export SFCC_SAFETY_LEVEL=NONE

b2c sandbox delete test-id # ✅ Allowed
```

#### CI/CD (Prevent Deletions)
```bash
# Prevent accidental deletions in automated environments
export SFCC_SAFETY_LEVEL=NO_DELETE

b2c sandbox create --realm test # ✅ Allowed
b2c sandbox delete test-id # ❌ Blocked
```

#### LLM/Agent Tools (Maximum Protection)
```bash
# Prevent AI agents from performing destructive operations
export SFCC_SAFETY_LEVEL=NO_UPDATE

b2c sandbox list # ✅ Allowed
b2c sandbox create --realm test # ✅ Allowed
b2c sandbox delete test-id # ❌ Blocked
b2c sandbox reset test-id # ❌ Blocked
```

#### Monitoring/Reporting (Read-Only)
```bash
# Absolute read-only mode
export SFCC_SAFETY_LEVEL=READ_ONLY

b2c sandbox list # ✅ Allowed
b2c sandbox get test-id # ✅ Allowed
b2c sandbox create test # ❌ Blocked
```

### What Gets Blocked

| Safety Level | DELETE | POST (create) | POST (reset) | PUT/PATCH | GET |
|--------------|--------|---------------|--------------|-----------|-----|
| **NONE** | ✅ | ✅ | ✅ | ✅ | ✅ |
| **NO_DELETE** | ❌ | ✅ | ✅ | ✅ | ✅ |
| **NO_UPDATE** | ❌ | ✅ | ❌ | ✅ | ✅ |
| **READ_ONLY** | ❌ | ❌ | ❌ | ❌ | ✅ |

### Protected Commands

Safety Mode automatically protects ALL destructive commands across all topics:

- **Sandbox**: `delete`, `reset`, `alias delete`
- **Account Manager**: `users delete`, `users reset`, `clients delete`
- **Code**: `delete`
- **MRT**: `project delete`, `env delete`, `env var delete`, `env redirect delete`, `project notification delete`
- **SLAS**: `client delete`
- **eCDN**: All delete operations (certificates, zones, rules, etc.)

### Why Environment Variable?

Environment variables are more secure than command-line flags because:

1. **LLMs Don't Control Them**: When an LLM uses the CLI as a tool, it controls commands and flags but NOT the environment
2. **Session-Level**: Set once for the entire session/container
3. **Audit Trail**: Can be logged and monitored in CI/CD
4. **Cannot Be Bypassed**: Even `--force` flags don't override safety mode

### Error Messages

When an operation is blocked, you'll see clear error messages:

```bash
export SFCC_SAFETY_LEVEL=NO_DELETE
b2c sandbox delete test-id

# Error: Cannot delete sandbox: blocked by safety level NO_DELETE.
#
# Delete operations are blocked in NO_DELETE mode
#
# To allow this operation, unset or change the SFCC_SAFETY_LEVEL environment variable.
```

### Best Practices

#### For CI/CD Pipelines
```yaml
# GitHub Actions example
- name: Deploy to Production
env:
SFCC_SAFETY_LEVEL: NO_DELETE # Prevent accidental deletions
run: |
b2c code deploy
b2c sandbox start production
```

#### For AI Agent Tools
```bash
# Provide CLI to LLMs with safety enabled
export SFCC_SAFETY_LEVEL=NO_UPDATE
# LLMs can now read and create, but cannot delete or reset
```

#### For Production Environments
```bash
# Set in shell profile for production access
echo 'export SFCC_SAFETY_LEVEL=NO_UPDATE' >> ~/.bashrc
```

### Testing Safety Mode

Verify safety mode is working:

```bash
export SFCC_SAFETY_LEVEL=NO_DELETE
b2c sandbox delete fake-id

# Expected: "blocked by safety level NO_DELETE"
# NOT expected: Authentication error or API call
```

For comprehensive testing, see [GitHub Issue #67](https://github.com/SalesforceCommerceCloud/b2c-developer-tooling/issues/67).

## Best Practices

### For Contributors
Expand All @@ -78,3 +239,4 @@ This project uses [NPM trusted publishers](https://docs.npmjs.com/trusted-publis
- Keep the CLI updated to receive security patches
- Review the `pnpm-workspace.yaml` settings if you fork or modify this project
- Consider using similar protections in your own projects
- **Use Safety Mode** when running CLI in automated environments or providing it as a tool to AI agents
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/am/clients/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export default class ClientDelete extends AmCommand<typeof ClientDelete> {
static examples = ['<%= config.bin %> <%= command.id %> <api-client-id>'];

async run(): Promise<void> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete API client');

const apiClientId = this.args['api-client-id'];

this.log(t('commands.client.delete.deleting', 'Deleting API client {{id}}...', {id: apiClientId}));
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/am/users/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export default class UserDelete extends AmCommand<typeof UserDelete> {
};

async run(): Promise<void> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete user');

const {login} = this.args;
const {purge} = this.flags;

Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/am/users/reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export default class UserReset extends AmCommand<typeof UserReset> {
];

async run(): Promise<void> {
// Prevent password reset in safe mode
this.assertDestructiveOperationAllowed('reset user password');

const {login} = this.args;

this.log(t('commands.user.reset.fetching', 'Fetching user {{login}}...', {login}));
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/code/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export default class CodeDelete extends InstanceCommand<typeof CodeDelete> {
};

async run(): Promise<void> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete code version');

this.requireOAuthCredentials();

const codeVersion = this.args.codeVersion;
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/ecdn/certificates/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export default class EcdnCertificatesDelete extends EcdnZoneCommand<typeof EcdnC
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete certificate');

this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/ecdn/logpush/jobs/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export default class EcdnLogpushJobsDelete extends EcdnZoneCommand<typeof EcdnLo
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete Logpush job');

this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/ecdn/mrt-rules/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export default class EcdnMrtRulesDelete extends EcdnZoneCommand<typeof EcdnMrtRu
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete MRT ruleset');

this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export default class EcdnMrtRulesRulesDelete extends EcdnZoneCommand<typeof Ecdn
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete MRT rule');

this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/ecdn/mtls/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export default class EcdnMtlsDelete extends EcdnCommand<typeof EcdnMtlsDelete> {
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete mTLS certificate');

this.requireOAuthCredentials();

const mtlsCertificateId = this.flags['certificate-id'];
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/ecdn/origin-headers/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export default class EcdnOriginHeadersDelete extends EcdnZoneCommand<typeof Ecdn
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete origin header modification');

this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ export default class EcdnPageShieldNotificationsDelete extends EcdnCommand<typeo
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete Page Shield webhook');

this.requireOAuthCredentials();

const webhookId = this.flags['webhook-id'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export default class EcdnPageShieldPoliciesDelete extends EcdnZoneCommand<typeof
};

async run(): Promise<DeleteOutput> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete Page Shield policy');

this.requireOAuthCredentials();

const zoneId = await this.resolveZoneId();
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/mrt/env/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ export default class MrtEnvDelete extends MrtCommand<typeof MrtEnvDelete> {
};

async run(): Promise<{slug: string; project: string}> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete MRT environment');

this.requireMrtCredentials();

const {slug} = this.args;
Expand Down
3 changes: 3 additions & 0 deletions packages/b2c-cli/src/commands/mrt/env/redirect/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ export default class MrtRedirectDelete extends MrtCommand<typeof MrtRedirectDele
};

async run(): Promise<{fromPath: string; deleted: boolean}> {
// Prevent deletion in safe mode
this.assertDestructiveOperationAllowed('delete redirect');

this.requireMrtCredentials();

const {fromPath} = this.args;
Expand Down
Loading
Loading