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

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 (lines 59-80). This function aggregates deny arrays from up to three configuration sources in precedence order:

The entry point checkDenyPolicy() in src/server.ts (lines 312-332) invokes evaluateCommandDenyOnly() (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 (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) protects the Read tool by loading tool-specific patterns via readToolDenyPatterns("Read") (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

// 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 /"
// Detecting embedded commands in JavaScript
const js = `console.log(\`whoami\`)`;
const result = checkNonShellDenyPolicy(js, 'javascript', 'execute');
// → Error result if deny pattern matches "whoami"
// 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.
  • 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 for project-local overrides, .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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →