Security — v0.3.0 Scope
Attack surface overview
The 3+1 MCP design has a smaller attack surface than a full-tool MCP server (one write operation instead of nine), but three vectors require mitigation before release.
Attack surfaces:
├── Skill file injection ← Agent reads tampered instructions from disk
├── Init write safety ← Symlinks or overwrites during project setup
├── CLI argument injection ← Shell metacharacters in agent-supplied values
├── Contract spoofing ← False guarantees from forked/modified server (future)
├── Status information leakage ← Sensitive feature names in resource responses (low risk)
└── Resource DoS ← Repeated parsing of large feature sets (low risk)
Risk priority matrix
| Risk |
Severity |
Likelihood |
v0.3.0 action |
| Skill file injection |
High |
Medium |
Mitigate |
| Init path traversal / symlink |
High |
Low |
Mitigate |
| CLI argument injection |
High |
Low |
Verify & tighten |
| Contract spoofing |
Medium |
Low |
Document; defer signed contracts |
| Status information leakage |
Low |
Medium |
Document guidance only |
| Resource DoS |
Low |
Low |
Cache if needed; defer |
1. Skill file integrity verification
Important This has been implemented in previous issue
Can skip from scope of this issue. Verify the below works as expected.
2. Init write safety
Risk: specleft_init is the only MCP tool and the only write operation exposed through the protocol. Path traversal or symlink attacks could write files to unintended locations.
v0.3.0 deliverables:
A. No user-supplied paths in MCP tool schema
The init tool schema accepts only example, blank, and dry_run booleans. All output paths are hardcoded relative to the current working directory. No dir or output parameter is exposed through MCP. This is already the case in the current design — explicitly preserve this constraint.
B. Symlink detection before all writes
Before creating any directory or file, check that the target path is not a symlink:
import os
def safe_write_path(path: str) -> str:
"""Validate path is safe to write to. Raises SecurityError if not."""
resolved = os.path.realpath(path)
cwd = os.path.realpath(os.getcwd())
# Must resolve within project directory
if not resolved.startswith(cwd):
raise SecurityError(f"Path resolves outside project: {path} -> {resolved}")
# Must not be a symlink
if os.path.islink(path):
raise SecurityError(f"Refusing to write through symlink: {path}")
# Check parent directories for symlinks
parent = os.path.dirname(path)
while parent and parent != cwd:
if os.path.islink(parent):
raise SecurityError(f"Parent directory is a symlink: {parent}")
parent = os.path.dirname(parent)
return resolved
C. Warn on existing modified files, don't overwrite
If .specleft/SKILL.md exists and its checksum doesn't match the expected template for the installed version:
{
"success": true,
"warnings": [
"Existing SKILL.md has been modified from template (checksum mismatch). Use --force to regenerate."
],
"skill_file_regenerated": false
}
Only --force overwrites a modified skill file. Unmodified skill files (checksum matches previous version's template) are silently upgraded.
3. CLI argument validation
Risk: Agents construct shell commands from the skill file, potentially passing unsanitised user input as CLI arguments. Shell metacharacters in feature IDs or titles could enable command injection.
v0.3.0 deliverables:
A. Strict validation at the Click parameter level
All ID parameters (--id, --feature, --story) validated against:
import re
import click
def validate_id(ctx, param, value):
"""Enforce kebab-case alphanumeric IDs. Reject shell metacharacters."""
if value is None:
return value
if not re.match(r'^[a-z0-9]+(-[a-z0-9]+)*$', value):
raise click.BadParameter(
f"Must be kebab-case alphanumeric (got: {value!r}). "
"Characters allowed: a-z, 0-9, hyphens."
)
return value
All text parameters (--title, --description, --name) validated against:
SHELL_METACHARACTERS = set('$`|;&><(){}[]!\\\'\"')
def validate_text(ctx, param, value):
"""Reject shell metacharacters in text inputs."""
if value is None:
return value
dangerous = SHELL_METACHARACTERS.intersection(value)
if dangerous:
raise click.BadParameter(
f"Contains disallowed characters: {dangerous}. "
"Avoid shell metacharacters in text inputs."
)
return value
B. Audit all existing Click parameters
Review every CLI command for parameters that accept freeform text. Ensure validation is applied at the parameter decorator level, not deferred to business logic. This prevents any code path from receiving unsanitised input.
C. Skill file safety note
Include in the generated skill file:
## Safety
- All --id values must be kebab-case alphanumeric (a-z, 0-9, hyphens)
- All text inputs reject shell metacharacters ($, `, |, ;, &, etc.)
- Never pass unsanitised user input directly as CLI arguments
- All commands are single invocations — no pipes, chaining, or redirects
- Exit codes: 0=success, 1=error, 2=cancelled
- All commands deterministic and safe to retry
4. Contract payload updates
The contract resource payload expands to include security guarantees:
{
"contract_version": "1.1",
"specleft_version": "0.3.0",
"guarantees": {
"dry_run_never_writes": true,
"no_writes_without_confirmation": true,
"existing_files_never_overwritten": true,
"skeletons_skipped_by_default": true,
"skipped_never_fail": true,
"deterministic_for_same_inputs": true,
"safe_for_retries": true,
"exit_codes": { "success": 0, "error": 1, "cancelled": 2 },
"skill_file_integrity_check": true,
"skill_file_commands_are_simple": true,
"cli_rejects_shell_metacharacters": true,
"init_refuses_symlinks": true,
"no_network_access": true,
"no_telemetry": true
}
}
Target size: ~100 tokens (up from ~80). Marginal increase for significant trust signal improvement.
5. Deferred security work (post v0.3.0)
| Item |
Description |
Target |
| Signed contracts |
Cryptographically sign contract payload during build. Agents verify against SpecLeft public key. |
Enforcement tier |
| Signed skill files |
Embed signature in skill file header. Verification without separate checksum file. |
v0.4.0 |
| Status redaction |
--redact flag on status output for teams with strict information controls. |
On demand |
| Resource caching |
Cache status resource responses with short TTL for large projects. |
On demand |
| Feature name guidance |
Document in guide resource: avoid sensitive information in feature/scenario names. |
v0.3.0 docs |
Competitive security positioning
Most MCP servers ship with zero security story. SpecLeft v0.3.0 will have:
- Agent Contract with machine-verifiable guarantees (unique in the spec-driven tooling space)
- Skill file integrity verification with checksums (no competitor has instruction file verification)
- Strict input validation rejecting shell metacharacters (defense-in-depth for agent-executed commands)
- Symlink-safe writes with path traversal protection
- No network access, no telemetry (verifiable by design — the MCP server is stdio-only)
Security — v0.3.0 Scope
Attack surface overview
The 3+1 MCP design has a smaller attack surface than a full-tool MCP server (one write operation instead of nine), but three vectors require mitigation before release.
Risk priority matrix
1. Skill file integrity verification
Important This has been implemented in previous issue
Can skip from scope of this issue. Verify the below works as expected.
2. Init write safety
Risk:
specleft_initis the only MCP tool and the only write operation exposed through the protocol. Path traversal or symlink attacks could write files to unintended locations.v0.3.0 deliverables:
A. No user-supplied paths in MCP tool schema
The init tool schema accepts only
example,blank, anddry_runbooleans. All output paths are hardcoded relative to the current working directory. Nodiroroutputparameter is exposed through MCP. This is already the case in the current design — explicitly preserve this constraint.B. Symlink detection before all writes
Before creating any directory or file, check that the target path is not a symlink:
C. Warn on existing modified files, don't overwrite
If
.specleft/SKILL.mdexists and its checksum doesn't match the expected template for the installed version:{ "success": true, "warnings": [ "Existing SKILL.md has been modified from template (checksum mismatch). Use --force to regenerate." ], "skill_file_regenerated": false }Only
--forceoverwrites a modified skill file. Unmodified skill files (checksum matches previous version's template) are silently upgraded.3. CLI argument validation
Risk: Agents construct shell commands from the skill file, potentially passing unsanitised user input as CLI arguments. Shell metacharacters in feature IDs or titles could enable command injection.
v0.3.0 deliverables:
A. Strict validation at the Click parameter level
All ID parameters (
--id,--feature,--story) validated against:All text parameters (
--title,--description,--name) validated against:B. Audit all existing Click parameters
Review every CLI command for parameters that accept freeform text. Ensure validation is applied at the parameter decorator level, not deferred to business logic. This prevents any code path from receiving unsanitised input.
C. Skill file safety note
Include in the generated skill file:
4. Contract payload updates
The contract resource payload expands to include security guarantees:
{ "contract_version": "1.1", "specleft_version": "0.3.0", "guarantees": { "dry_run_never_writes": true, "no_writes_without_confirmation": true, "existing_files_never_overwritten": true, "skeletons_skipped_by_default": true, "skipped_never_fail": true, "deterministic_for_same_inputs": true, "safe_for_retries": true, "exit_codes": { "success": 0, "error": 1, "cancelled": 2 }, "skill_file_integrity_check": true, "skill_file_commands_are_simple": true, "cli_rejects_shell_metacharacters": true, "init_refuses_symlinks": true, "no_network_access": true, "no_telemetry": true } }Target size: ~100 tokens (up from ~80). Marginal increase for significant trust signal improvement.
5. Deferred security work (post v0.3.0)
--redactflag on status output for teams with strict information controls.Competitive security positioning
Most MCP servers ship with zero security story. SpecLeft v0.3.0 will have: