NL Protocol Specification v1.0 -- Chapter 04: Pre-Execution Defense
Status: Draft Version: 1.0.0 Date: 2026-02-08
Note: This document is a SPECIFICATION. It defines required behaviors, data formats, and protocols — not specific products or CLI commands. For implementations of this specification, see IMPLEMENTATIONS.md.
1. Introduction
This chapter defines the Pre-Execution Defense layer of the NL Protocol. Every command, tool invocation, or action initiated by an agent MUST pass through a pre-execution interceptor before it reaches any execution environment. The purpose of this layer is to intercept and block actions that would expose, exfiltrate, or compromise secrets -- before they execute.
Pre-Execution Defense is the fourth layer of the NL Protocol's defense-in-depth architecture. While Levels 1 through 3 ensure that agents have verifiable identity, request actions rather than secrets, and execute within isolated environments, Level 4 adds a proactive interception mechanism that prevents dangerous actions from reaching the execution boundary in the first place.
1.1 Scope
This chapter applies to any system that mediates agent actions, regardless of the underlying platform. The interception mechanisms defined here are designed to be implementable across:
- AI coding assistants (Claude Code, Cursor, Copilot, Windsurf, Aider, Codex)
- MCP (Model Context Protocol) servers
- CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI)
- Cloud platforms (AWS, GCP, Azure)
- SaaS and payment platforms (Stripe, Twilio, GitHub)
- Custom agent orchestration frameworks
1.2 Design Philosophy
The Pre-Execution Defense layer operates on two fundamental principles:
Fail closed. If the interceptor is unavailable, experiences an error, or cannot determine whether an action is safe, the action MUST be blocked. An unknown action is a dangerous action.
Educate, do not merely block. When an action is blocked, the system MUST provide the agent with a structured response that explains what was blocked, why it was blocked, and what the safe alternative is. Opaque errors teach nothing; educational responses guide the agent toward NL Protocol-compliant behavior.
2. Command Interception
2.1 Interception Requirement
All agent-initiated actions MUST pass through a Pre-Execution Interceptor before reaching the execution environment. The interceptor MUST be invoked for every action, without exception. There MUST NOT exist any code path that allows an agent action to reach execution without passing through the interceptor.
2.2 Interceptor Position
The interceptor MUST be positioned between the agent's action request and the execution environment:
+-------+ +--------------+ +-------------+ +-----------+
| Agent | ---> | Interceptor | ---> | Authorized | ---> | Executor |
| | | (Level 4) | | Action | | (Level 3) |
+-------+ +--------------+ +-------------+ +-----------+
|
| (blocked)
v
+------------------+
| Educational |
| Response |
+------------------+
The interceptor MUST evaluate the action before any secret resolution occurs. If the action references secrets via placeholders (Chapter 02), the interceptor evaluates the action template containing the placeholder references -- not the resolved secret values.
2.3 Interceptor Evaluation Order
The interceptor MUST evaluate each action against the following checks, in the order specified:
- Standard deny rules (Section 3): If the action matches any standard deny rule, it MUST be blocked.
- Custom deny rules (Section 4): If the action matches any organization-defined deny rule, it MUST be blocked.
- Evasion detection (Section 6): If the action contains patterns that indicate an attempt to evade deny rules, it MUST be blocked.
- Allowlist rules (Section 5): If an allowlist is configured and the action does not match any allowlist entry, the action SHOULD be blocked.
If the action passes all checks, it is forwarded to the execution environment.
If any check results in a block, the interceptor MUST stop evaluation immediately, record the block in the audit log (Chapter 05), and return an educational response (Section 8).
2.4 Interceptor Interface
Implementations MUST expose an interceptor that conforms to the following interface:
InterceptorResult intercept(action: AgentAction): InterceptorResult
AgentAction {
agent: AgentIdentity // The agent requesting the action (Chapter 01)
action_type: string // "exec" | "template" | "inject_stdin" |
// "inject_tempfile" | "tool_call" | "api_call"
command: string // The raw command or tool invocation string
arguments: map<string, any> // Structured arguments (for tool calls and APIs)
target: string // Target resource or endpoint
metadata: map<string, string> // Additional context (platform, session, correlation_id)
}
InterceptorResult {
decision: "allow" | "block"
rule_id: string? // ID of the deny rule that triggered the block
category: string? // Deny rule category
severity: string? // "critical" | "high" | "medium" | "low"
reason: string? // Human-readable explanation
alternative: SafeAlternative? // The safe way to accomplish the goal
reference: string? // URL to protocol documentation
}
3. Standard Deny Rules
3.1 Deny Rule Format
Each deny rule MUST have the following structure:
{
"rule_id": "NL-4-DENY-001",
"category": "direct_secret_access",
"severity": "critical",
"patterns": [
"vault\\s+(get|read|show|reveal|decrypt|fetch)\\s+",
"braincol-vault\\s+get\\s+"
],
"description": "Direct retrieval of secret values via vault CLI",
"safe_alternative": "Use action-based access with {{nl:<reference>}} placeholder syntax (NL Protocol Level 2).",
"applies_to": ["exec", "tool_call"]
}
Fields:
| Field | Type | Required | Description |
|---|---|---|---|
rule_id |
string | MUST | Unique identifier, prefixed with NL-4-DENY- for standard rules. |
category |
string | MUST | One of the categories defined in Section 3.2. |
severity |
string | MUST | One of critical, high, medium, or low. |
patterns |
string[] | MUST | Regular expression patterns to match against the action. Patterns MUST be applied case-insensitively by default. |
description |
string | MUST | Human-readable description of what the rule blocks. |
safe_alternative |
string | MUST | Description of the correct, safe way to accomplish the same goal. |
applies_to |
string[] | SHOULD | Action types this rule applies to. If omitted, the rule applies to all action types. |
3.2 Deny Rule Categories
Every NL Protocol-conformant implementation MUST enforce deny rules in the following seven categories. The specific regular expression patterns within each category MAY vary by platform to accommodate platform-specific CLI tools and APIs, but every category MUST be represented with at least one active rule.
3.2.1 Category 1: Direct Secret Access (NL-4-DENY-001 through NL-4-DENY-009)
Actions that directly retrieve, display, or return the plaintext value of a secret MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-001 | vault\s+(get|read|show|reveal|decrypt|fetch)\s+ |
Generic vault CLI retrieval commands |
| NL-4-DENY-002 | cat\s+\.env |
Reading .env files containing secrets |
| NL-4-DENY-003 | cat\s+.*\.(key|pem|p12|pfx|jks|keystore|crt) |
Reading key and certificate files directly |
| NL-4-DENY-004 | op\s+(read|get|item\s+get)\s+ |
1Password CLI retrieval |
| NL-4-DENY-005 | aws\s+secretsmanager\s+get-secret-value |
AWS Secrets Manager retrieval |
| NL-4-DENY-006 | gcloud\s+secrets\s+versions\s+access |
GCP Secret Manager retrieval |
| NL-4-DENY-007 | az\s+keyvault\s+secret\s+show |
Azure Key Vault retrieval |
| NL-4-DENY-008 | doppler\s+secrets\s+(get|download) |
Doppler CLI retrieval |
| NL-4-DENY-009 | stripe\s+(config|listen)\s+--api-key |
Stripe CLI with inline API key exposure |
Safe alternative: Use action-based access (Chapter 02) with {{nl:<reference>}} placeholder syntax. The secret is resolved inside the isolated execution environment (Chapter 03) and never enters the agent's context.
Example -- blocked vs. safe:
BLOCKED: vault get API_KEY
-> The secret value "sk-live-abc123..." enters the agent context
SAFE: An NL-compliant implementation executes:
curl -H "Authorization: Bearer {{nl:API_KEY}}" https://api.stripe.com/v1/charges
-> The agent never sees the secret; it is injected at execution time
3.2.2 Category 2: Bulk Export (NL-4-DENY-010 through NL-4-DENY-019)
Actions that export, dump, or enumerate multiple secret values at once MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-010 | vault\s+export |
Vault bulk export |
| NL-4-DENY-011 | ^env$|^env\s |
Shell environment variable dump |
| NL-4-DENY-012 | ^printenv$|^printenv\s |
Print all environment variables |
| NL-4-DENY-013 | ^set$|^set\s |
Shell variable dump (includes env vars) |
| NL-4-DENY-014 | doppler\s+secrets(\s+|$) |
Doppler full secrets listing |
| NL-4-DENY-015 | aws\s+secretsmanager\s+batch-get-secret-value |
AWS batch secret retrieval |
| NL-4-DENY-016 | terraform\s+output\s+-json |
Terraform output dump (may contain secrets) |
| NL-4-DENY-017 | kubectl\s+get\s+secret.*-o\s+(json|yaml|jsonpath) |
Kubernetes secret value extraction |
| NL-4-DENY-018 | docker\s+inspect.*--format.*\.Env |
Docker container environment extraction |
| NL-4-DENY-019 | heroku\s+config(\s+|$) |
Heroku config vars dump |
Safe alternative: Use the implementation's secret listing capability to enumerate secret names without their values. Then reference specific secrets via the {{nl:<reference>}} placeholder syntax.
Example -- blocked vs. safe:
BLOCKED: env
-> Dumps ALL environment variables, including any injected secrets
SAFE: An NL-compliant implementation lists secrets by name:
API_KEY, DB_PASSWORD, STRIPE_KEY (values never shown)
3.2.3 Category 3: Internal File Access (NL-4-DENY-020 through NL-4-DENY-029)
Actions that read the internal storage files of a vault, encrypted key stores, or secret manager data directories MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-020 | cat\s+.*vault\.(age|enc|gpg|sealed|db) |
Reading encrypted vault files |
| NL-4-DENY-021 | strings\s+.*\.(key|age|enc|pem|db) |
Extracting strings from encrypted files |
| NL-4-DENY-022 | xxd\s+.*\.(key|age|enc|pem) |
Hex dump of key material |
| NL-4-DENY-023 | sqlite3\s+.*vault |
Direct SQLite access to vault databases |
| NL-4-DENY-024 | cat\s+.*\.vault/ |
Reading vault internal directory structure |
| NL-4-DENY-025 | find\s+.*-name\s+["']?\*?\.(key|pem|p12|age) |
Searching for key files on disk |
| NL-4-DENY-026 | ls\s+(-la?\s+)?.*\.vault/ |
Listing vault internal directory contents |
| NL-4-DENY-027 | cp\s+.*\.(key|pem|age|enc) |
Copying key or encrypted files |
| NL-4-DENY-028 | tar\s+.*\.(key|pem|age|enc|vault) |
Archiving key material or vault files |
| NL-4-DENY-029 | scp\s+.*\.(key|pem|age|enc)\s+ |
Remote copying of key material |
Safe alternative: Interact with the vault exclusively through its CLI, API, or MCP tools. Never access vault storage files directly.
3.2.4 Category 4: Encoding Evasion (NL-4-DENY-030 through NL-4-DENY-039)
Actions that encode, decode, or transform blocked commands to evade pattern matching MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-030 | base64\s+(-d|--decode).*|\s*(sh|bash|zsh|dash) |
Base64 decode piped to shell |
| NL-4-DENY-031 | echo\s+.*|\s*base64\s+(-d|--decode)\s*|\s*(sh|bash) |
Echo encoded payload to decode to shell |
| NL-4-DENY-032 | python[23]?\s+-c\s+.*exec\(.*decode |
Python exec with base64/hex decode |
| NL-4-DENY-033 | node\s+-e\s+.*Buffer\.from\(.*base64 |
Node.js base64 decode and execute |
| NL-4-DENY-034 | printf\s+.*\\\\x[0-9a-fA-F].*|\s*(sh|bash) |
Hex-encoded command piped to shell |
| NL-4-DENY-035 | xxd\s+-r.*|\s*(sh|bash) |
Hex reversal piped to shell |
| NL-4-DENY-036 | perl\s+-e\s+.*pack\s*\( |
Perl pack/unpack evasion |
| NL-4-DENY-037 | ruby\s+-e\s+.*\.unpack |
Ruby unpack evasion |
| NL-4-DENY-038 | openssl\s+(enc|base64)\s+-d.*|\s*(sh|bash) |
OpenSSL decode piped to shell |
| NL-4-DENY-039 | gzip\s+-d.*|\s*(sh|bash) |
Compressed payload decompressed to shell |
Safe alternative: All commands MUST be submitted in plaintext. The interceptor requires commands to be human-readable and inspectable. Encoded or obfuscated commands will always be blocked.
Example -- blocked vs. safe:
BLOCKED: echo 'dmF1bHQgZ2V0IEFQSV9LRVk=' | base64 -d | sh
-> Decodes to "vault get API_KEY" and executes it, bypassing deny rules
SAFE: An NL-compliant implementation executes:
curl -H "Authorization: Bearer {{nl:API_KEY}}" https://api.example.com
-> Command is plaintext, inspectable, and uses placeholder syntax
3.2.5 Category 5: Shell Expansion (NL-4-DENY-040 through NL-4-DENY-049)
Actions that use shell expansion mechanisms to capture or interpolate secret values into the agent's context MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-040 | \$\(\s*vault\s+(get|read|show|reveal)\s+ |
Command substitution with vault get |
| NL-4-DENY-041 | `\s*vault\s+(get|read|show|reveal)\s+ |
Backtick substitution with vault get |
| NL-4-DENY-042 | \$\(\s*op\s+(read|get)\s+ |
Command substitution with 1Password CLI |
| NL-4-DENY-043 | \$\(\s*aws\s+secretsmanager\s+get-secret-value |
Command substitution with AWS Secrets Manager |
| NL-4-DENY-044 | \$\(\s*gcloud\s+secrets\s+versions\s+access |
Command substitution with GCP Secret Manager |
| NL-4-DENY-045 | eval\s+.*vault |
Eval wrapping vault commands |
| NL-4-DENY-046 | source\s+<\(.*vault |
Process substitution with vault commands |
| NL-4-DENY-047 | xargs.*vault\s+(get|read) |
xargs piping to vault retrieval |
| NL-4-DENY-048 | \$\(\s*kubectl\s+get\s+secret |
Command substitution with kubectl secrets |
| NL-4-DENY-049 | \$\(\s*az\s+keyvault\s+secret\s+show |
Command substitution with Azure Key Vault |
Safe alternative: Use the {{nl:<reference>}} placeholder syntax. The NL Protocol runtime resolves placeholders inside an isolated execution environment (Chapter 03) without any shell expansion in the agent's context.
Example -- blocked vs. safe:
BLOCKED: curl -H "Authorization: Bearer $(vault get API_KEY)" https://api.example.com
-> Shell substitution causes the secret value to enter the agent's shell context
SAFE: An NL-compliant implementation executes:
curl -H "Authorization: Bearer {{nl:API_KEY}}" https://api.example.com
-> Placeholder is resolved inside the isolated subprocess; value never enters agent context
3.2.6 Category 6: Environment Dumps (NL-4-DENY-050 through NL-4-DENY-059)
Actions that read process environments -- which may contain secrets injected into execution subprocesses -- MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-050 | cat\s+/proc/.*/environ |
Linux process environment file |
| NL-4-DENY-051 | ps\s+.*eww |
Process listing with full environment |
| NL-4-DENY-052 | tr\s+.*\\\\0.*</proc/.*/environ |
Null-byte-delimited environ parsing |
| NL-4-DENY-053 | cat\s+/proc/self/environ |
Self process environment |
| NL-4-DENY-054 | xargs\s+.*-0.*</proc/.*/environ |
Environ parsing via xargs null delimiter |
| NL-4-DENY-055 | strings\s+/proc/.*/environ |
Extracting strings from process environment |
| NL-4-DENY-056 | python[23]?\s+-c\s+.*os\.environ |
Python os.environ access |
| NL-4-DENY-057 | node\s+-e\s+.*process\.env |
Node.js process.env access |
| NL-4-DENY-058 | ruby\s+-e\s+.*ENV |
Ruby ENV hash access |
| NL-4-DENY-059 | php\s+-r\s+.*getenv\(\) |
PHP environment access |
Safe alternative: Secrets are available only within the isolated execution environment (Chapter 03). The agent's own process environment MUST NOT contain secret values. To use secrets, reference them with {{nl:<reference>}} placeholder syntax.
3.2.7 Category 7: Indirect Execution (NL-4-DENY-060 through NL-4-DENY-069)
Actions that use indirection mechanisms to circumvent the interceptor or construct blocked commands dynamically MUST be blocked.
| Rule ID | Pattern | Description |
|---|---|---|
| NL-4-DENY-060 | eval\s+.*\$ |
Eval with variable expansion |
| NL-4-DENY-061 | bash\s+-c\s+.*vault\s+(get|read|export) |
Subshell wrapping vault retrieval |
| NL-4-DENY-062 | sh\s+-c\s+.*vault\s+(get|read|export) |
Subshell wrapping vault retrieval |
| NL-4-DENY-063 | source\s+.*\.env |
Sourcing .env files into shell context |
| NL-4-DENY-064 | \.\s+.*\.env |
Dot-sourcing .env files |
| NL-4-DENY-065 | crontab\s+ |
Scheduling commands (may persist secret access) |
| NL-4-DENY-066 | at\s+ |
Scheduling one-time commands |
| NL-4-DENY-067 | nohup\s+.*vault |
Background execution of vault commands |
| NL-4-DENY-068 | screen\s+-dmS\s+.*vault |
Detached screen with vault commands |
| NL-4-DENY-069 | tmux\s+.*send-keys.*vault |
tmux send-keys with vault commands |
Note on false positives: Rules NL-4-DENY-060, NL-4-DENY-065, and NL-4-DENY-066 have elevated false positive rates in development contexts. Implementations SHOULD apply these rules contextually: block when the indirect execution wraps a command that would itself be blocked, but allow when the indirect execution is unrelated to secret access. Implementations MAY require these specific rules to be explicitly enabled via configuration.
Safe alternative: Submit all commands directly. Wrapping commands in eval, bash -c, or other indirection layers to avoid the interceptor is a violation of NL Protocol Level 4.
4. Custom Deny Rules
4.1 Requirement
Organizations MUST be able to define additional deny rules beyond the standard set defined in Section 3. Custom rules allow organizations to protect platform-specific secrets, internal tools, proprietary systems, and organization-specific workflows.
4.2 Custom Rule Format
Custom deny rules MUST follow the same format as standard deny rules (Section 3.1) with the following additional fields:
{
"rule_id": "CUSTOM-ORG-001",
"category": "custom",
"severity": "high",
"patterns": ["internal-tool\\s+export-credentials"],
"description": "Blocks credential export from organization-specific internal tool",
"safe_alternative": "Use internal-tool inject-credentials with placeholder syntax.",
"applies_to": ["exec"],
"organization_id": "org_example",
"created_by": "human:admin@example.com",
"created_at": "2026-02-08T10:00:00Z",
"expires_at": "2027-02-08T10:00:00Z"
}
| Field | Type | Required | Description |
|---|---|---|---|
organization_id |
string | MUST | Identifier of the organization that created the rule. |
created_by |
string | MUST | Identity of the administrator who created the rule. MUST be a human identity, not an agent. |
created_at |
string | MUST | ISO 8601 timestamp of rule creation. |
expires_at |
string | MAY | ISO 8601 timestamp after which the rule is no longer enforced. |
4.3 Custom Rule Precedence
Standard deny rules MUST be evaluated before custom deny rules. If both a standard rule and a custom rule match the same action, the standard rule takes precedence for reporting purposes. The action is blocked regardless of which rule matched first.
4.4 Custom Rule Management
Implementations MUST provide mechanisms for authorized administrators to:
- Create custom deny rules.
- List all active deny rules (standard and custom).
- Update custom deny rules. Standard rules MUST NOT be modifiable.
- Delete custom deny rules. Standard rules MUST NOT be deletable.
- Test a custom rule against sample commands without enforcing it (dry-run mode).
Agents MUST NOT be able to create, update, or delete deny rules. Rule management MUST require human administrator privileges.
All custom rule changes (creation, update, deletion) MUST be recorded in the audit log (Chapter 05).
5. Allowlist Mode
5.1 Allowlist as Complementary Control
In addition to deny rules, implementations SHOULD support an allowlist mode. When allowlist mode is enabled, only actions that match an allowlist entry are permitted. All other actions are blocked by default. Allowlist mode provides the strongest security posture because it follows a default-deny approach.
5.2 Allowlist Entry Format
{
"entry_id": "ALLOW-001",
"patterns": ["curl\\s+-H.*\\{\\{nl:.*\\}\\}\\s+https://"],
"description": "Allow curl commands with NL Protocol placeholders targeting HTTPS endpoints",
"applies_to": ["exec"],
"max_secrets": 3,
"allowed_secret_refs": ["api/*", "tokens/*"]
}
| Field | Type | Required | Description |
|---|---|---|---|
entry_id |
string | MUST | Unique identifier for the allowlist entry. |
patterns |
string[] | MUST | Regex patterns that the action must match. |
description |
string | MUST | Human-readable description. |
applies_to |
string[] | SHOULD | Action types this entry applies to. |
max_secrets |
integer | MAY | Maximum number of secret placeholders allowed in a single action. |
allowed_secret_refs |
string[] | MAY | Glob patterns for which secret references are permitted. |
5.3 Interaction with Deny Rules
Deny rules MUST always take precedence over allowlist entries. An action that matches both a deny rule and an allowlist entry MUST be blocked. The evaluation order is:
- Standard deny rules.
- Custom deny rules.
- Evasion detection.
- Allowlist check (if allowlist mode is enabled).
An action MUST pass all four checks to be allowed.
6. Evasion Detection
6.1 Requirement
The interceptor MUST detect and block attempts to evade deny rules through obfuscation, encoding, or indirection. Evasion detection goes beyond static pattern matching by analyzing the structure and intent of commands.
6.2 Evasion Techniques and Countermeasures
6.2.1 Unicode Normalization
Agents MAY attempt to substitute visually similar Unicode characters for ASCII characters to bypass pattern matching (e.g., fullwidth characters vault instead of vault, or Cyrillic characters that resemble Latin letters).
Countermeasure: The interceptor MUST normalize all input to NFC (Unicode Normalization Form C) and strip or replace non-ASCII characters that are visually confusable with ASCII equivalents before applying deny rules. Implementations SHOULD use a confusable character mapping table (such as Unicode Confusables from UTS #39) for this purpose.
6.2.2 Whitespace Manipulation
Agents MAY insert extra whitespace, tabs, newlines, or zero-width characters to break pattern matches (e.g., vault get SECRET).
Countermeasure: The interceptor MUST collapse consecutive whitespace characters into a single space and remove zero-width characters before applying deny rules. Leading and trailing whitespace MUST be trimmed.
6.2.3 Case Variation
Agents MAY use mixed case to bypass case-sensitive patterns (e.g., VaUlT GeT SECRET).
Countermeasure: Deny rule patterns MUST be applied case-insensitively unless the rule explicitly specifies case-sensitive matching.
6.2.4 String Concatenation and Variable Construction
Agents MAY construct blocked commands by concatenating shell variables, using string slicing, or building command names character by character (e.g., cmd="vault"; $cmd get SECRET).
Countermeasure: The interceptor SHOULD perform static analysis of variable assignments and substitutions within the same command context. When variable expansion is detected that could construct a blocked command, the interceptor SHOULD block the action. When variable expansion analysis is not feasible, the interceptor SHOULD block commands that assign variables and immediately use them in command position.
6.2.5 Multi-Stage Payloads (Write-Then-Execute)
Agents MAY write a script containing blocked commands to a file and then execute that file in a separate action (e.g., action 1: echo "vault get SECRET" > /tmp/x.sh; action 2: bash /tmp/x.sh).
Countermeasure: The interceptor MUST track file write operations and correlate them with subsequent execution requests within the same agent session. When a write-then-execute pattern is detected, the content of the written file SHOULD be subject to the same deny rules as direct commands. Implementations SHOULD maintain a session-scoped watchlist of files written by the agent and intercept execution of those files.
6.2.6 Alias and Function Definition
Agents MAY define shell aliases or functions that wrap blocked commands (e.g., alias v=vault; v get SECRET or function g() { vault get "$1"; }; g SECRET).
Countermeasure: The interceptor SHOULD detect alias and function definitions that reference blocked command patterns. When detected, both the definition and any subsequent invocations of the alias or function MUST be blocked.
6.3 Evasion Scoring
Each detected evasion attempt MUST be recorded in the audit log (Chapter 05) with category evasion_attempt. Evasion attempts SHOULD increment the agent's threat score (Chapter 06) by at least +10. Repeated evasion attempts from the same agent SHOULD trigger enhanced monitoring or automatic suspension depending on the implementation's threat response policy.
7. Fail-Closed Design
7.1 Requirement
If the interceptor fails, becomes unavailable, or encounters any error during evaluation, the action MUST be blocked. The system MUST NOT fall back to allowing unfiltered actions under any failure condition.
7.2 Failure Modes
The following failure modes MUST result in the action being blocked:
| Failure Mode | Required Behavior |
|---|---|
| Interceptor process crash | Block all pending and new actions; restart interceptor before resuming. |
| Deny rule file parsing error | Block all actions; alert administrator. Use last-known-good rules if available. |
| Pattern matching timeout | Block the action that caused the timeout; log the event. |
| Network error (if rules are loaded remotely) | Block all actions; use last-known-good cached rules if available and cache has not expired. |
| Resource exhaustion (memory, CPU) | Block all actions; alert administrator; shed load. |
| Unknown or unrecognized action type | Block the action; log the unknown type for investigation. |
| Configuration corruption | Block all actions; alert administrator. |
7.3 Health Monitoring
Implementations SHOULD expose a health check endpoint or signal for the interceptor. Monitoring systems SHOULD query this health check at a regular interval (RECOMMENDED: every 10 seconds). If the interceptor becomes unhealthy, all agent actions MUST be queued or rejected until the interceptor recovers.
The health check MUST verify:
- The interceptor process is running.
- Deny rules are loaded and parseable.
- The last successful interception occurred within a configurable recency window (RECOMMENDED: 60 seconds).
7.4 Graceful Degradation with Cached Rules
When the interceptor uses cached deny rules during a network failure:
- The implementation MUST log that cached rules are in use with a
warningseverity. - A maximum cache duration MUST be enforced (RECOMMENDED: 1 hour).
- If the cache expires without successful reconnection to the rule source, ALL actions MUST be blocked until connectivity is restored.
8. Educational Response
8.1 Response Requirement
When an action is blocked, the interceptor MUST return an Educational Response. The response MUST provide the agent with sufficient information to understand the block and correct its behavior. Responses MUST NOT be opaque error codes, generic failure messages, or empty rejections.
The educational response serves a dual purpose: it trains the AI agent to use safe patterns, and it provides a human-readable audit trail of why an action was blocked.
8.2 Educational Response Schema
{
"status": "BLOCKED",
"rule_id": "NL-4-DENY-001",
"category": "direct_secret_access",
"severity": "critical",
"blocked_action": "vault get API_KEY",
"reason": "Direct secret retrieval would expose the secret value in the agent's LLM context window. Once in context, the secret can be memorized, replicated, or exfiltrated through the model.",
"risk": "The secret value would become part of the agent's conversation history and could appear in logs, model outputs, or be sent to unintended recipients.",
"safe_alternative": {
"description": "Use action-based access (NL Protocol Level 2) with placeholder syntax to reference the secret without retrieving its value.",
"example": "Submit an Action Request with command: curl -H \"Authorization: Bearer {{nl:API_KEY}}\" https://api.example.com",
"documentation": "https://nlprotocol.org/spec/v1.0/02-action-based-access"
},
"protocol_reference": "https://nlprotocol.org/spec/v1.0/04-pre-execution-defense#direct-secret-access",
"agent_guidance": "You do not need the secret's value to use it. Reference it with {{nl:API_KEY}} and the NL Protocol runtime will inject it into your command at execution time."
}
8.3 Required Fields
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | MUST | Always "BLOCKED". |
rule_id |
string | MUST | The ID of the deny rule that triggered the block. |
category |
string | MUST | The deny rule category. |
severity |
string | MUST | The severity level: critical, high, medium, or low. |
blocked_action |
string | MUST | The action that was blocked. Secret values MUST NOT appear here; only references or command templates. |
reason |
string | MUST | A clear, specific explanation of why the action is dangerous in the context of AI agent security. |
risk |
string | SHOULD | A description of the concrete risk if the action were allowed to execute. |
safe_alternative |
object | MUST | An object containing description (string, MUST), example (string, MUST), and documentation (string, SHOULD). |
protocol_reference |
string | SHOULD | URL to the relevant section of the NL Protocol specification. |
agent_guidance |
string | SHOULD | A concise, agent-friendly instruction on how to proceed correctly. |
8.4 Additional Examples
8.4.1 Blocked Bulk Export
{
"status": "BLOCKED",
"rule_id": "NL-4-DENY-011",
"category": "bulk_export",
"severity": "critical",
"blocked_action": "env",
"reason": "The 'env' command dumps all environment variables to stdout. In execution environments where secrets are injected as environment variables, this would expose all secret values simultaneously.",
"risk": "Every secret currently in the environment would enter the agent's context at once, maximizing the blast radius of any subsequent exfiltration.",
"safe_alternative": {
"description": "Use the implementation's secret listing capability to enumerate secret names without values, or reference specific secrets via placeholder syntax.",
"example": "Use the list_secrets tool or equivalent to enumerate available secret names without values.",
"documentation": "https://nlprotocol.org/spec/v1.0/02-action-based-access#placeholder-syntax"
},
"agent_guidance": "To see which secrets are available, use the secret listing capability. To use a specific secret, reference it with {{nl:SECRET_NAME}} in your command."
}
8.4.2 Blocked Encoding Evasion
{
"status": "BLOCKED",
"rule_id": "NL-4-DENY-030",
"category": "encoding_evasion",
"severity": "critical",
"blocked_action": "echo 'dmF1bHQgZ2V0IEFQSV9LRVk=' | base64 -d | sh",
"reason": "Base64-encoded commands piped to a shell are a known evasion technique. The decoded content matches a blocked pattern. Encoded commands bypass pattern matching and cannot be inspected by the interceptor.",
"risk": "The decoded command would execute without interception, potentially exposing secret values to the agent's context.",
"safe_alternative": {
"description": "Submit all commands in plaintext. The interceptor requires commands to be human-readable.",
"example": "Submit an Action Request with command: curl -H \"Authorization: Bearer {{nl:API_KEY}}\" https://api.example.com",
"documentation": "https://nlprotocol.org/spec/v1.0/04-pre-execution-defense#encoding-evasion"
},
"agent_guidance": "Do not encode commands. Use {{nl:SECRET_NAME}} placeholder syntax to reference secrets safely and submit commands in plaintext."
}
8.4.3 Blocked Kubernetes Secret Extraction
{
"status": "BLOCKED",
"rule_id": "NL-4-DENY-017",
"category": "bulk_export",
"severity": "critical",
"blocked_action": "kubectl get secret my-secret -o json",
"reason": "Extracting Kubernetes secrets with JSON output returns base64-encoded secret values in the 'data' field. These values can be trivially decoded and would enter the agent's context.",
"risk": "All key-value pairs in the Kubernetes secret would be exposed in base64 format, which is equivalent to plaintext.",
"safe_alternative": {
"description": "Use the NL Protocol Kubernetes integration to inject secrets directly into pod specifications without retrieving their values.",
"example": "Submit an Action Request with command: kubectl apply -f deployment.yaml (where deployment.yaml uses {{nl:db/password}} placeholders)",
"documentation": "https://nlprotocol.org/spec/v1.0/04-pre-execution-defense#bulk-export"
},
"agent_guidance": "Do not extract Kubernetes secret values. Reference them with {{nl:k8s/SECRET_NAME}} in your manifests."
}
9. Opaque Proxy Pattern for MCP and API Servers
9.1 Purpose
When agents interact with secret managers through MCP (Model Context Protocol) servers, REST APIs, or GraphQL endpoints, the server itself becomes a critical trust boundary. A poorly designed MCP server can expose secret values through its tool names, input schemas, or return types -- even if the agent did not explicitly request those values. This section defines the Opaque Proxy Pattern to prevent such exposure at the API design level.
9.2 Tool Naming Constraints
MCP servers and API endpoints that manage secrets MUST NOT expose tools or endpoints whose names suggest that they return secret values. The following name patterns MUST NOT be used:
| Prohibited Pattern | Reason |
|---|---|
get_value, getValue |
Implies returning a secret's value |
reveal, reveal_secret |
Implies making a secret visible |
decrypt, decrypt_secret |
Implies returning decrypted plaintext |
raw, raw_value, get_raw |
Implies unprocessed secret data |
fetch_secret, read_secret |
Implies reading the secret's content |
export, dump, dump_secrets |
Implies bulk data extraction |
plaintext, cleartext |
Implies unencrypted content |
show_secret, display_secret |
Implies visual display of the value |
Tools that manage secrets SHOULD use action-oriented names:
| Recommended Pattern | Purpose |
|---|---|
inject_secret |
Inject a secret into an execution context |
list_secrets |
List secret names and metadata (not values) |
search_secrets |
Search for secrets by name, category, or tag |
create_secret |
Create a new secret |
rotate_secret |
Rotate a secret's value |
delete_secret |
Delete a secret |
verify_access |
Check whether the agent has access to a specific secret |
9.3 Return Type Constraints
The return type schema of any tool or endpoint related to secrets MUST NOT include fields that could contain secret values. Implementations MUST validate return schemas at server startup and at tool registration time.
Non-compliant return schema (MUST NOT be used):
{
"name": "get_secret",
"returns": {
"type": "object",
"properties": {
"name": { "type": "string" },
"value": { "type": "string" },
"category": { "type": "string" }
}
}
}
Compliant return schema:
{
"name": "inject_secret",
"returns": {
"type": "object",
"properties": {
"status": { "type": "string", "enum": ["success", "error", "blocked"] },
"stdout": { "type": "string", "description": "Sanitized command output" },
"stderr": { "type": "string", "description": "Sanitized error output" },
"exit_code": { "type": "integer" }
}
}
}
9.4 Pre-Authentication Resources
MCP servers SHOULD expose documentation resources that do not require authentication. These resources educate agents about the NL Protocol's constraints and safe usage patterns before any authenticated interaction occurs. Exposing documentation before authentication reduces the rate of blocked actions because the agent understands the rules before it starts working.
Pre-authentication resources SHOULD include:
- Protocol overview: What the NL Protocol is and its core security model ("use secrets without seeing them").
- Usage guide: How to use the
{{nl:<reference>}}placeholder syntax and action-based access. - Available tools: What authenticated tools are available and what they do (without exposing any data).
- Deny rules summary: What actions are blocked, why, and what the safe alternatives are.
Example MCP resource URIs:
nl-provider://docs/overview -> NL Protocol overview
nl-provider://docs/usage -> Placeholder syntax and injection guide
nl-provider://docs/tools -> Available MCP tools reference
nl-provider://docs/never-leak-protocol -> Security rules and deny patterns
nl-provider://docs/api-reference -> API reference
9.5 API Endpoint Constraints
REST APIs, GraphQL endpoints, and other programmatic interfaces that manage secrets MUST follow the same constraints as MCP tools:
- No endpoint MUST return secret values in response bodies.
- Endpoint paths MUST NOT suggest value retrieval (e.g.,
/secrets/{id}/valueis prohibited;/secrets/{id}/metadatais acceptable). - Response schemas MUST be validated to ensure no field contains or could contain a secret value.
- API documentation MUST clearly indicate that all endpoints return masked data only.
- List endpoints MUST return secret names, categories, and metadata -- never values.
10. Platform Integration Points
10.1 Purpose
The NL Protocol is designed to be implementable across any platform that mediates agent actions. This section defines how the Pre-Execution Defense layer integrates with specific platform categories. Each integration point specifies where interception occurs, how deny rules are loaded, and how bypass prevention is achieved.
10.2 Universal Integration Requirements
Regardless of platform, every integration MUST satisfy:
- Interception point: Define where in the platform's action pipeline the interceptor is invoked.
- Deny rule loading: Define how standard and custom deny rules are loaded and updated.
- Response delivery: Define how educational responses are delivered back to the agent.
- Bypass prevention: Ensure no code path from agent to execution bypasses the interceptor.
- Audit integration: Every interception decision MUST be loggable per Chapter 05.
10.3 AI Coding Assistants
10.3.1 Claude Code
- Interception point: PreToolUse hooks defined in
.claude/hooks.json. - Deny rule loading: The hook script loads deny rules from a configuration file or evaluates them inline.
- Response delivery: The hook script outputs the educational response to stdout. Exit code
2signals a block; exit code0signals allow. - Bypass prevention: The
.claude/settings.local.jsonfile MUST include deny patterns in theblockedCommandsarray as a secondary enforcement layer.
Example hook configuration:
{
"hooks": {
"PreToolUse": [
{
"type": "command",
"command": "vault-guard.sh \"$NL_TOOL_NAME\" \"$NL_TOOL_INPUT\"",
"blocking": true
}
]
}
}
10.3.2 Cursor
- Interception point:
.cursorrulesfile with deny patterns as agent instructions, plus.cursor/mcp.jsonfor MCP-level enforcement. - Deny rule loading: Deny patterns are embedded as natural language instructions in the
.cursorrulesfile; MCP server enforces programmatically. - Response delivery: The
.cursorrulesinstructions tell the agent to use safe alternatives; the MCP server returns structured error responses. - Bypass prevention: MCP server-side enforcement provides the programmatic enforcement layer.
10.3.3 Generic MCP Clients
- Interception point: MCP server-side tool validation at request processing time.
- Deny rule loading: The MCP server enforces deny rules internally before executing any tool.
- Response delivery: The tool returns an error result containing the educational response as structured JSON.
- Bypass prevention: The MCP server is the sole execution path; the agent has no direct access to the underlying system.
10.4 CI/CD Pipelines
10.4.1 GitHub Actions
- Interception point: A composite action or reusable workflow step that validates commands before execution.
- Deny rule loading: Rules loaded from a configuration file in the repository or from a shared organization-level configuration.
- Response delivery: The action fails with an annotation containing the educational response.
- Bypass prevention: The validation step MUST be enforced via required workflows or branch protection rules that cannot be overridden by repository contributors.
Example workflow step:
- name: NL Protocol Pre-Execution Check
uses: nlprotocol/guard-action@v1
with:
command: ${{ steps.agent.outputs.command }}
deny_rules: standard+custom
fail_on_block: true
10.4.2 GitLab CI
- Interception point: A
before_scriptblock or CI/CD component. - Deny rule loading: Rules loaded from CI/CD variables or an external configuration endpoint.
- Response delivery: Pipeline fails with a descriptive error in the job log.
- Bypass prevention: The
before_scriptMUST be defined in a shared CI template (usinginclude) that project-level.gitlab-ci.ymlfiles cannot override.
10.4.3 Jenkins
- Interception point: A shared library step invoked before each agent-generated command.
- Deny rule loading: Rules loaded from Jenkins credentials store or a configuration management system.
- Response delivery: The shared library step fails the build with a descriptive error message.
- Bypass prevention: The shared library MUST be configured as a globally trusted library.
10.5 Cloud Platforms
10.5.1 AWS
- Interception point: Lambda authorizer on API Gateway, IAM policy conditions, or AWS Config rules.
- Deny rule loading: Rules stored in AWS Systems Manager Parameter Store or S3.
- Response delivery: API Gateway returns the educational response as a structured JSON error body.
- Bypass prevention: IAM policies MUST deny direct
secretsmanager:GetSecretValueandssm:GetParameter(for SecureString) to agent IAM roles. Agents MUST access secrets only through an NL Protocol-compliant intermediary Lambda or service.
10.5.2 GCP
- Interception point: Cloud Functions or Cloud Run service acting as a proxy before Secret Manager.
- Deny rule loading: Rules stored in non-secret configuration in Firestore, Cloud Storage, or environment configuration.
- Response delivery: The proxy returns the educational response as a structured JSON error.
- Bypass prevention: IAM bindings MUST deny
secretmanager.versions.accessto agent service accounts. Agents access secrets only through the proxy.
10.5.3 Azure
- Interception point: Azure API Management policy, Azure Functions proxy, or Managed Identity configuration.
- Deny rule loading: Rules stored in Azure App Configuration or Blob Storage.
- Response delivery: The proxy returns the educational response as a structured JSON error.
- Bypass prevention: Azure RBAC MUST deny Key Vault
getoperations to agent managed identities directly. Agents MUST use the NL Protocol proxy.
10.6 SaaS Platforms
10.6.1 Stripe
- Interception point: A proxy service that mediates all agent interactions with the Stripe API.
- Deny rule loading: Rules embedded in the proxy configuration.
- Response delivery: The proxy returns the educational response when a blocked pattern is detected.
- Bypass prevention: Restricted API keys with minimal scope MUST be used. Agents MUST NOT have access to unrestricted Stripe secret keys. The proxy is the only component with API key access.
10.6.2 GitHub (as a Platform)
- Interception point: GitHub App permissions, fine-grained PAT scope limits, and webhook-based validation.
- Deny rule loading: Rules stored in the GitHub App's server-side configuration.
- Response delivery: Check run annotations or commit status descriptions with the educational response.
- Bypass prevention: Fine-grained personal access tokens or GitHub App installation tokens with minimal scopes. Agents MUST NOT have access to classic PATs with broad scope.
10.7 Custom Platforms (Webhook-Based Interception)
10.7.1 Webhook Pattern
For platforms not covered in Sections 10.3 through 10.6, implementations MUST support a webhook-based interception pattern:
- The agent's execution environment sends a pre-execution webhook request to an NL Protocol interceptor service.
- The interceptor evaluates the action against standard and custom deny rules.
- The interceptor responds with
alloworblock, including the educational response if blocked. - The execution environment proceeds or halts based on the interceptor's response.
Webhook request format:
{
"webhook_version": "1.0",
"agent": {
"agent_id": "nl://example.com/deploy-bot/1.0.0",
"organization_id": "org_example",
"session_id": "session_abc123"
},
"action": {
"type": "exec",
"command": "vault get API_KEY",
"timestamp": "2026-02-08T10:30:00.000Z"
},
"platform": "custom-orchestrator",
"request_id": "req-7f3a2b1c",
"nonce": "nonce-unique-value"
}
Webhook response format:
{
"decision": "block",
"request_id": "req-7f3a2b1c",
"response": {
"status": "BLOCKED",
"rule_id": "NL-4-DENY-001",
"category": "direct_secret_access",
"reason": "Direct secret retrieval is not permitted under NL Protocol Level 4.",
"safe_alternative": {
"description": "Use action-based access with {{nl:API_KEY}} placeholder syntax.",
"example": "Submit an Action Request with command: curl -H \"Auth: Bearer {{nl:API_KEY}}\" https://api.example.com"
}
}
}
10.7.2 Webhook Security Requirements
Webhook-based interception MUST use the following security measures:
- TLS: All webhook communication MUST use HTTPS with TLS 1.2 or higher.
- Authentication: Webhook requests MUST be authenticated using HMAC-SHA256 signatures, mutual TLS, or OAuth 2.0 bearer tokens.
- Timeout: Webhook responses MUST be received within a configurable timeout (RECOMMENDED: 5 seconds). If the timeout is exceeded, the action MUST be blocked (fail-closed per Section 7).
- Replay prevention: Webhook requests MUST include a timestamp and nonce. The interceptor MUST reject requests with timestamps older than 5 minutes or with previously seen nonces.
- Idempotency: The interceptor MUST return the same decision for the same
request_idif the request is retried.
11. Defense Bypass Prevention
11.1 Requirement
The interceptor MUST be implemented such that no agent-accessible mechanism can bypass, disable, or circumvent it. Bypass prevention is a fundamental architectural requirement, not an optional feature.
11.2 Bypass Vectors and Mitigations
| Bypass Vector | Required Mitigation |
|---|---|
| Agent modifies interceptor configuration files | Configuration files MUST be read-only to the agent's process. File integrity monitoring SHOULD detect and alert on changes. |
| Agent terminates the interceptor process | The interceptor MUST run as a separate, privileged process that the agent cannot signal or terminate. Alternatively, the interceptor MUST be embedded in the execution pipeline such that its absence blocks all actions (fail-closed). |
| Agent uses an alternative execution path | All execution paths MUST route through the interceptor. Implementations MUST audit their architecture to confirm no alternative paths exist. |
| Agent writes a script and executes it later | File write operations followed by execution MUST be correlated and analyzed (Section 6.2.5). Scheduled task creation (cron, at) MUST be blocked by default. |
| Agent requests another agent to execute a blocked command | Cross-agent action requests MUST also pass through the receiving agent's interceptor (Chapter 07). There is no "trusted agent" exemption. |
| Agent modifies deny rules at runtime | Deny rule creation, modification, and deletion MUST require human administrator privileges. These privileges MUST NOT be delegatable to agents. |
| Agent manipulates the interceptor's input before evaluation | The interceptor MUST receive the raw, unmodified action as submitted by the agent. No intermediate layer MUST be able to sanitize or transform the action before interception evaluation. |
11.3 Architectural Guarantee
Implementations MUST ensure that the interceptor is in the mandatory execution path:
- The interceptor MUST NOT be an optional middleware that can be configured off.
- The interceptor MUST NOT be bypassable by calling underlying system functions directly.
- The interceptor MUST be initialized and healthy before any agent action is processed.
- If the interceptor is not initialized, has failed, or has been tampered with, zero agent actions MUST be processed.
12. Security Considerations
Performance. The interceptor adds latency to every agent action. Implementations SHOULD optimize pattern matching using compiled regular expressions, trie-based matchers, or Aho-Corasick multi-pattern matching to minimize overhead. The RECOMMENDED maximum latency added by the interceptor is 10 milliseconds per action.
False positives. Some deny rules -- particularly in the indirect execution category (Section 3.2.7) -- may block legitimate actions. Implementations SHOULD provide mechanisms for administrators to review blocked actions and adjust custom rules. Agents MUST NOT be given the ability to override or dismiss blocks.
Rule updates. When deny rules are updated (standard rules via protocol version update, custom rules via administrator action), the interceptor MUST reload rules without requiring a process restart. Rule reloading MUST be atomic: the old rule set is enforced until the new rule set is fully loaded and validated.
Logging. Every interception decision MUST be logged for audit purposes (Chapter 05). Block decisions MUST always be logged. Allow decisions SHOULD be logged at a configurable verbosity level.
Denial of service. An agent could flood the interceptor with rapid requests to degrade its performance or cause it to fail (triggering fail-closed blocking of all actions). Implementations SHOULD apply per-agent rate limiting independently of the interceptor to mitigate this vector.
Rule completeness. The standard deny rules defined in this chapter represent a baseline. No deny rule set is exhaustive. Organizations SHOULD continuously evaluate their custom rule sets based on observed blocked actions, security incidents, and new attack techniques.
13. Conformance Requirements
An implementation conforms to NL Protocol Level 4 if it satisfies all MUST-level requirements. Full conformance includes satisfying all MUST and SHOULD requirements.
| Requirement ID | Description | Level |
|---|---|---|
| NL-4.1 | All agent actions pass through a pre-execution interceptor (Section 2.1) | MUST |
| NL-4.2 | Standard deny rules are enforced across all seven categories (Section 3.2) | MUST |
| NL-4.3 | Organizations can define, manage, and enforce custom deny rules (Section 4) | MUST |
| NL-4.4 | Encoding and evasion attempts are detected and blocked (Section 6) | MUST |
| NL-4.5 | Fail-closed behavior on any interceptor failure (Section 7) | MUST |
| NL-4.6 | Educational responses returned for every blocked action (Section 8) | MUST |
| NL-4.7 | No bypass path exists from agent to execution without interception (Section 11) | MUST |
| NL-4.8 | MCP/API servers follow the Opaque Proxy Pattern (Section 9) | MUST |
| NL-4.9 | Allowlist mode is supported (Section 5) | SHOULD |
| NL-4.10 | Pre-authentication documentation resources are exposed (Section 9.4) | SHOULD |
| NL-4.11 | At least one platform integration is implemented (Section 10) | SHOULD |
| NL-4.12 | Webhook-based interception is supported for custom platforms (Section 10.7) | SHOULD |
| NL-4.13 | Interceptor health monitoring is implemented (Section 7.3) | SHOULD |
| NL-4.14 | Variable expansion analysis is performed for evasion detection (Section 6.2.4) | MAY |
| NL-4.15 | Write-then-execute pattern detection is implemented (Section 6.2.5) | MAY |