blockedPhrases or allowedCharacters, the corresponding checks run during installation. Omit or comment out each property to disable it. If a blocked phrase is found, installation stops immediately and reports which skill and phrase caused the block. When allowedCharacters is present, character sanitization runs: the baseline (printable ASCII + standard whitespace) is always preserved, and the value specifies extra Unicode ranges to keep on top of that.
+
**Flags**:
- `-e, --env [file]`: Load variables from a `.env` file instead of using the web UI
- Without filename: Uses `.env` in the project directory
@@ -130,6 +142,11 @@ providers:
options:
toolExposure: expose-all # or 'on-demand'
+ # Optional security (blocked phrases, character sanitization)
+ # security:
+ # blockedPhrases: []
+ # # Or load from file: blockedPhrases: { file: "./blocked-phrases.txt" }
+ # allowedCharacters: "" # "" = baseline only (strips non-ASCII); "[\\u00A0-\\uFFFF]" = allow all Unicode
skills:
- id: skill-id
@@ -279,6 +296,53 @@ servers:
**Variable Substitution**: Use `${VarName}` for credentials. CAPA will prompt for these securely via a web UI.
+### Security Options
+
+Under `options.security`, you can enforce safety during skill installation:
+
+#### Blocked Phrases
+Block installation if any skill file (SKILL.md or additional files) contains a forbidden phrase. Omit or comment out to disable. Configure either inline or via file:
+
+**Inline phrases:**
+```yaml
+options:
+ security:
+ blockedPhrases:
+ - "some-dangerous-command"
+```
+
+**Phrases from file (one phrase per line):**
+```yaml
+options:
+ security:
+ blockedPhrases:
+ file: "./blocked-phrases.txt"
+```
+
+The file path is relative to the capabilities file directory. Empty lines are ignored.
+
+#### Character Sanitization
+Replace disallowed characters with spaces during installation. Omit or comment out to disable. Useful to restrict skills to safe character sets.
+
+```yaml
+options:
+ security:
+ allowedCharacters: "" # baseline only: strips non-ASCII Unicode (emoji, etc.)
+ # allowedCharacters: "[\\u00A0-\\uFFFF]" # allow all printable Unicode including emoji
+```
+
+**How it works:** A hardcoded baseline—tab, LF, CR, and all printable ASCII (U+0020–U+007E)—is **always preserved** no matter what. Characters like `-`, `:`, `"`, `'`, `\n`, and every keyboard symbol are in the baseline and will never be stripped. The `allowedCharacters` field extends the baseline by specifying **additional** Unicode ranges to keep. Characters outside both the baseline and the extra allowance are replaced with a space.
+
+Only text files (`.md`, `.txt`, `.ts`, `.js`, `.json`, `.yaml`, etc.) are sanitized; other files are copied as-is. Omit or comment out `allowedCharacters` to disable sanitization entirely.
+
+#### Blocked Phrase Detection
+When `capa install` detects a blocked phrase, it **stops immediately** and reports:
+- Which skill (or skill in plugin) contains it
+- The file path
+- The forbidden phrase
+
+No further skills are installed until you remove the phrase from the skill or update your security configuration.
+
### Tools Section
Define tools that skills can use:
@@ -735,6 +799,12 @@ cat .cursor/mcp.json
- Test server command manually outside CAPA
- Check if port 5912 is available
+### Installation Blocked: Forbidden Phrase Detected
+When you see a red "Installation blocked" message during `capa install`:
+- A skill (or skill in a plugin) contains a phrase from your `options.security.blockedPhrases` list
+- The message shows the skill ID, file path, and the forbidden phrase
+- **Resolution**: Remove the phrase from the skill's files, or remove/comment out blockedPhrases (or change the restriction) in your capabilities file, then run capa install again
+
### Tool Not Found Errors
- Verify tool ID matches between skill `requires` and tools section
- Check that server ID in tool definition uses `@` prefix (e.g., `@server-id`)
diff --git a/src/cli/commands/install.ts b/src/cli/commands/install.ts
index 7a9b5d5..298ebd6 100644
--- a/src/cli/commands/install.ts
+++ b/src/cli/commands/install.ts
@@ -18,6 +18,17 @@ import { registerMCPServer } from '../utils/mcp-client-manager';
import { parseEnvFile } from '../../shared/env-parser';
import { extractAllVariables } from '../../shared/variable-resolver';
import { resolvePlugins } from './plugin-install';
+import {
+ loadBlockedPhrases,
+ checkBlockedPhrases,
+ sanitizeContent,
+ getAllowedCharacters,
+ isTextFile,
+ isBlockedPhrasesEnabled,
+ isCharacterSanitizationEnabled,
+ BlockedPhraseError,
+ reportBlockedPhraseAndExit,
+} from '../../shared/skill-security';
const execAsync = promisify(exec);
@@ -373,7 +384,8 @@ export async function installCommand(envFile?: string | boolean): Promise