# Server-Side Deny Firewall in mksglu/context-mode: Security Policies and Enforcement

> Discover how the server-side deny firewall in mksglu/context-mode enforces bash command blocking, shell-escape prevention, and file-read restriction using JSON deny patterns for robust security.

- Repository: [Mert Köseoğlu/context-mode](https://github.com/mksglu/context-mode)
- Tags: security
- Published: 2026-04-24

---

**The server-side deny firewall enforces three security policies—bash command blocking, embedded shell-escape prevention, and file-read restriction—by evaluating tool requests against JSON-defined deny patterns before execution.**

The server-side deny firewall in `mksglu/context-mode` provides a runtime security layer that blocks unsafe operations when no interactive UI is available to prompt the user. This lightweight, policy-driven guard runs entirely on the server side, loading deny rules from cascading JSON configuration files to prevent unauthorized shell execution and file access.

## Three-Stage Security Enforcement Model

The firewall operates through three distinct validation stages, each targeting a specific attack vector for non-interactive server requests.

### Bash Command Deny

When tools attempt to execute shell commands, the firewall validates every segment against deny patterns loaded via `readBashPolicies()` from [`src/security.ts`](https://github.com/mksglu/context-mode/blob/main/src/security.ts) (lines 59-80). This function aggregates deny arrays from up to three configuration sources in precedence order:

- Project-local: [`.claude/settings.local.json`](https://github.com/mksglu/context-mode/blob/main/.claude/settings.local.json)
- Project-shared: [`.claude/settings.json`](https://github.com/mksglu/context-mode/blob/main/.claude/settings.json)  
- Global user: `~/.claude/settings.json`

The entry point `checkDenyPolicy()` in [`src/server.ts`](https://github.com/mksglu/context-mode/blob/main/src/server.ts) (lines 312-332) invokes `evaluateCommandDenyOnly()` ([src/security.ts#L1008-L1021](https://github.com/mksglu/context-mode/blob/main/src/security.ts#L1008-L1021)) to test commands against compiled patterns. If any segment matches, the function returns `{decision:"deny"}` and the tool receives an error `ToolResult` with the message: "Command blocked by security policy: matches deny pattern …".

### Embedded Shell-Escape Deny

The firewall detects shell commands embedded within non-shell code through `checkNonShellDenyPolicy()` in [`src/server.ts`](https://github.com/mksglu/context-mode/blob/main/src/server.ts) (lines 335-362). The `extractShellCommands()` function parses code to identify back-tick escapes (such as `` `cmd` `` in JavaScript or Python) before passing extracted commands to `evaluateCommandDenyOnly()`. Each extracted command undergoes the same pattern matching as standalone bash commands, producing identical error results upon detection.

### File-Read Deny

For file system access, `checkFilePathDenyPolicy()` ([src/server.ts#L365-L384](https://github.com/mksglu/context-mode/blob/main/src/server.ts#L365-L384)) protects the `Read` tool by loading tool-specific patterns via `readToolDenyPatterns("Read")` ([src/security.ts#L93-L118](https://github.com/mksglu/context-mode/blob/main/src/security.ts#L93-L118)). The `evaluateFilePath()` function normalizes paths and matches them against compiled deny globs, returning "File access blocked by security policy: path matches Read deny pattern …" when violations occur.

## Policy Loading and Precedence

**The firewall only enforces deny rules**—`allow` and `ask` entries are ignored because the server cannot prompt the user interactively. Policy aggregation follows strict precedence: project-local settings override project-shared settings, which in turn override global user settings.

All deny arrays from these three sources merge into a single evaluation set used across all security stages.

## Command Parsing and Bypass Prevention

To prevent circumvention through command chaining, the implementation uses `splitChainedCommands()` within `evaluateCommandDenyOnly()` to segment commands on control operators (`&&`, `||`, `;`, `|`). This ensures that dangerous sequences like `echo ok && sudo rm -rf /` cannot bypass security patterns by hiding malicious segments behind benign ones.

## Fail-Open Safety Mechanism

All security checks wrap execution in `try … catch` blocks. If policy files cannot be read or parsing fails, the firewall **fails open**—allowing the request to proceed—because the surrounding hook layer serves as the primary enforcement point. This prevents service interruptions from malformed JSON or missing configuration files while maintaining defense in depth.

## Code Examples

```typescript
// Blocking a disallowed shell command
const result = checkDenyPolicy('rm -rf /', 'execute');
// → result.isError === true
//   result.content[0].text:
//   "Command blocked by security policy: matches deny pattern rm -rf /"

```

```typescript
// Detecting embedded commands in JavaScript
const js = `console.log(\`whoami\`)`;
const result = checkNonShellDenyPolicy(js, 'javascript', 'execute');
// → Error result if deny pattern matches "whoami"

```

```typescript
// Preventing unauthorized file reads
const result = checkFilePathDenyPolicy('/etc/passwd', 'read');
// → Error result if deny glob like "/etc/**" is configured

```

## Summary

- **Three enforcement stages**: Bash commands, embedded shell escapes, and file reads each have dedicated validation paths in [`src/server.ts`](https://github.com/mksglu/context-mode/blob/main/src/server.ts).
- **Policy sources**: Deny rules load from project-local, project-shared, and global JSON files via `readBashPolicies()` and `readToolDenyPatterns()`.
- **Command segmentation**: `splitChainedCommands()` breaks chained commands on `&&`, `||`, `;`, and `|` to prevent bypass attempts.
- **Deny-only mode**: The server-side implementation ignores `allow` and `ask` policies, enforcing only explicit deny patterns.
- **Graceful degradation**: Policy parsing errors fail open, deferring to upstream security controls rather than crashing the runtime.

## Frequently Asked Questions

### What happens when a deny pattern matches?

When a pattern matches, the evaluation function immediately returns a denial decision object, and the calling check function generates an error `ToolResult`. The tool receives a descriptive error message indicating which pattern blocked the operation, and execution halts before the system processes the command or file access.

### How does the firewall handle chained commands?

The `evaluateCommandDenyOnly()` function calls `splitChainedCommands()` to break complex commands on control operators (`&&`, `||`, `;`, `|`). It evaluates each segment independently against deny patterns, preventing attackers from hiding malicious commands behind benign ones in chained sequences.

### Why does the firewall ignore allow and ask policies?

The server-side context lacks interactive UI capabilities to prompt users for approval. Since `ask` policies require user confirmation and `allow` policies define permitted paths rather than blocked ones, the deny-only approach ensures deterministic automated enforcement without hanging on input requests.

### Where are deny rules configured?

Administrators define deny patterns in three potential locations: [`.claude/settings.local.json`](https://github.com/mksglu/context-mode/blob/main/.claude/settings.local.json) for project-local overrides, [`.claude/settings.json`](https://github.com/mksglu/context-mode/blob/main/.claude/settings.json) for shared project settings, and `~/.claude/settings.json` for global user preferences. The `readBashPolicies()` and `readToolDenyPatterns()` functions load and merge these files according to precedence rules.