Skip to main content
ClaudeWave
Skill349 estrellas del repoactualizado today

guardrail-patterns

The guardrail-patterns skill documents the structure and implementation of destructive shell command blocking in opencode-swarm's `checkDestructiveCommand()` function across ~3900 lines. Load this before modifying `src/hooks/guardrails.ts`, any guardrail test file, adding new guardrail sections, or implementing regex patterns for command blocking, as it details the normalization pipeline, wrapper detection layers, segmentation logic, and 22+ validation sections that prevent dangerous shell operations.

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/zaxbysauce/opencode-swarm /tmp/guardrail-patterns && cp -r /tmp/guardrail-patterns/.opencode/skills/generated/guardrail-patterns ~/.claude/skills/guardrail-patterns
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# Skill: Guardrail Patterns for opencode-swarm

**Source knowledge:** `098926ef`, `2c1e4689`, `54c33fa4`, `4f51d11a`

Load this skill **before** modifying `src/hooks/guardrails.ts` — adding, removing, or changing guardrail blocks in `checkDestructiveCommand()`. It documents the pattern structure, known bypass surfaces, regex anti-patterns, and test conventions used across 41 guardrail test files and the ~3900-line guardrails.ts file.

## When to load this skill

Load before any change to:
- `src/hooks/guardrails.ts` — especially `checkDestructiveCommand()` or `dcNormalizeCommand()`
- `tests/unit/hooks/guardrails*.test.ts` — any guardrail test file
- Adding a new section (currently Sections 1–22) to `checkDestructiveCommand()`
- Adding regex patterns for shell command blocking

## Architecture overview

### `checkDestructiveCommand()` — the shell command guard

Located at `src/hooks/guardrails.ts` (line 1304). This is the **only** function that blocks destructive shell commands. It is invoked by the `toolBefore` hook in `guardrails.ts` before every `bash` or `shell` tool call.

**Pipeline (in order):**
1. `dcNormalizeCommand()` (line ~627) — NFKC normalization + evasion collapse: collapses `""` (doubled double-quotes) and `''` (doubled single-quotes). Single-quote splice like `m'v'` remains OPEN.
2. `dcStripOneWrapper()` (line ~664) — detects and strips individual shell wrappers: `bash`, `sh`, `zsh`, `dash`, `fish`, `pwsh`, `powershell`, `cmd` (with `-c`/`-Command`), `sudo`, `nohup`, `time`, `nice`, `env VAR=val`, `call` (batch), `Invoke-Command -ScriptBlock`, `& { }` script blocks, `wsl`, `iex`
3. `dcUnwrapWrappers()` (line ~737) — loops `dcStripOneWrapper` until no more wrappers remain (max depth 10)
4. `dcSplitSegments()` (line ~753) — splits compound commands on `&&`, `;`, `|`, newlines
5. Per-segment loop — each segment evaluated against 22+ guardrail sections
6. `dcValidateTargets()` — runtime `lstat`-ancestor walk on destructive targets

### Key normalization functions

| Function | Line | What it normalizes |
|---|---|---|
| `dcNormalizeCommand` | ~627 | NFKC, caret escapes (`^`), backtick escapes, collapsed `""` and `''` |
| `dcStripOneWrapper` | ~664 | Detects/strips a single shell wrapper (bash/sh/zsh/pwsh/cmd/powershell/wsl etc) |
| `dcUnwrapWrappers` | ~737 | Loops `dcStripOneWrapper` until no more wrappers (max depth 10) |
| `dcSplitSegments` | ~753 | Splits on `&&`, `;`, `|`, `\n` for per-segment checking |

**Known wrapper unwrapping limitation:** `sh -c` and `bash -c` with **single-quoted** inner commands (`sh -c 'mv ...'`) are NOT unwrapped because `dcStripOneWrapper` uses `"?` (optional double-quote). Only double-quoted inner commands are properly stripped.

## Adding a new guardrail block

### Step 1 — Determine the pattern placement

Inside `checkDestructiveCommand()`, the per-segment `for` loop evaluates each segment against sections 1–22. A new section should be added after the last existing section and before the closing `}` of the `for` loop (currently after Section 22 at approximately line 1733).

### Step 2 — Choose the regex pattern structure

There are three patterns used in the codebase:

**Pattern A — Simple inline regex (single condition):**
```typescript
// Good for: single-command blocking with no complex extraction
if (/^blockedcommand\b.*\.swarm[\x5c/\s]?/i.test(seg)) {
  throw new Error(`BLOCKED: "blockedcommand" targeting .swarm/ detected — ...`);
}
```

**Pattern B — Multi-condition (flag check + path check):**
```typescript
// Good for: archive tools with flags + .swarm/ path (prevents argument-order bypass)
if (
  /^toolname\b.*--dangerous-flag\b/i.test(seg) &&
  /\.swarm(?:[\x5c/\s]|$)/i.test(seg)
) {
```
This is **recommended** because it handles both `tool --flag .swarm/path` and `tool .swarm/path --flag` argument orders.

**Pattern C — Argument extraction + stripped check:**
```typescript
// Good for: commands where you need argument isolation (e.g., `mv` with arg capture)
if (/^\\?command\s/i.test(seg)) {
  const match = seg.match(/^\\?command\s+(.+)$/i);
  if (match) {
    const argsStr = match[1].replace(/["']/g, '');
    if (/\.swarm(?:[\x5c/\s]|$)/.test(argsStr)) {
      throw new Error(`BLOCKED: ...`);
    }
  }
}
```

### Step 3 — Handle all platform variants

POSIX, Windows cmd.exe, and PowerShell often use different commands for the same operation. All three must be covered:

```typescript
// POSIX section
if (/^\\?posix-cmd\s/i.test(seg) && /\.swarm/i.test(strippedArgs)) { ... }

// Windows cmd section (case-insensitive, optional .exe)
if (/^\\?(?:cmd-cmd|cmd-cmd-alias)(?:\.exe)?\s/i.test(seg) && /\.swarm/i.test(argsStr)) { ... }

// PowerShell section (case-insensitive, all aliases)
if (/^\\?(?:PowerShell-Cmdlet|alias1|alias2)\b.*\.swarm/i.test(seg)) { ... }
```

### Step 4 — Handle the `.swarm` path separator

Always use `\x5c` (backslash) for cross-platform path matching — `\` alone is the regex escape character.

```typescript
// Correct: matches both / and \
/\.swarm[\x5c/]/

// More complete: also matches .swarm followed by whitespace or end-of-string
// (catches whole-directory targeting like `mv .swarm /tmp/`)
/\.swarm(?:[\x5c/\s]|$)/
```

### Step 5 — Handle backslash-prefixed command evasion

Commands prefixed with `\` (e.g., `\mv`) bypass simple `^command\s` anchors. Always add `\\?`:

```typescript
// Correct: catches both mv and \mv
if (/^\\?mv\s/i.test(seg)) { ... }

// Correct for rm (uses \b instead of \s)
if (/^\\?rm\b/i.test(seg)) { ... }
```

## Known bypass surfaces (must document in adversarial tests)

These are documented bypass vectors that the current regex-based approach cannot fully close. Every new guardrail section should include adversarial tests for these patterns:

| Evasion | Example | Status | Mitigation |
|---|---|---|---|
| Backslash prefix | `\mv .swarm/file` | **CLOSED** | Add `^\\?` to command anchor |
| Quote splicing | `m'v' .swarm/file` | **OPEN** | Requires NFKC normalization change |
| Quoted