CFF Explorer

Bridges CFF Explorer's PE analysis engine for natural language-driven binary inspection, import/export analysis, resource extraction, and binary patching on Windows.

filesystem
0Tools
10Findings
2Stars
Mar 22, 2026Last Scanned
3 critical · 5 high · 1 medium · 1 low findings detected

Security Category Deep Dive

Prompt Injection
Prompt & context manipulation attacks
69
Maturity
14
Rules
5
Sub-Categories
1
Gaps
64%
Implemented
56
Tests
1
Stories
PI-DIRDirect Input Injection
100%3 rules
Injection via tool descriptions and parameter fields
GAP-001Prompt Injection Coverage GapMissing detection coverage for emerging prompt injection attack variants not addressed by current rules
PI-INDIndirect / Gateway Injection
100%4 rules
Hidden instructions via external content and tool responses
PI-CTXContext Manipulation
100%2 rules
Context window saturation and prior-approval exploitation
PI-ENCEncoding & Obfuscation
100%3 rules
Payload hiding via invisible chars, base64, schema fields
PI-TPLTemplate & Output Poisoning
50%2 rules1 found
Injection via prompt templates and runtime tool output
Framework Coverage
OWASP MCP Top 1014/14
MITRE ATLAS14/14
CoSAI MCP2/14
OWASP Agentic Top 1012/14
Kill Chain Phases
1Initial Access
1Defense Evasion
1Execution
1Persistence

Findings10

3critical
5high
1medium
1low

Critical3

criticalQ13MCP Bridge Package Supply Chain AttackMCP10-supply-chainAML.T0054
Pattern "(?:mcp|fastmcp|langchain-mcp|llama-index-mcp)(?:>=|~=|==)?(?!\d)" matched in source_code: "MCP" (at position 45)
MCP bridge packages (mcp-remote, mcp-proxy, @modelcontextprotocol/sdk, fastmcp) are high-value supply chain targets — CVE-2025-6514 (CVSS 9.6) in mcp-remote affected 437,000+ installs. Always pin exact versions (no ^ or ~ ranges). Use lockfiles (package-lock.json, pnpm-lock.yaml, uv.lock). Never run `npx mcp-remote` without version pinning. Verify package integrity with `npm audit` or `pip-audit` before deployment. Reference: CVE-2025-6514, OWASP ASI04.
criticalQ11Code Suggestion Poisoning via MCPMCP01-prompt-injectionAML.T0054.001
Pattern "(?:suggest|generate|complete|insert).*(?:code|function|class|import|require)" matched in source_code: "Generate Lua script to walk the PE Import Directory and list all DLLs/function" (at position 7022)
MCP tool outputs flowing into IDE code suggestion contexts must be sanitized. Implement output content policies that: (1) strip hidden Unicode characters (zero-width, RTL override, tag characters), (2) detect embedded instructions targeting AI code assistants, (3) validate code blocks against security patterns before they enter the suggestion pipeline, (4) never include shell commands in tool outputs without explicit [COMMAND] markers visible to the user. Reference: IDEsaster (Dec 2025), arXiv 2509.22040.
criticalJ5Tool Output Poisoning PatternsMCP02-tool-poisoningAML.T0054
Pattern "(error|err|failure|failed).*(?:read|cat|open|access)\s+(?:the\s+)?(?:file|~[/\\]|/etc/|/home/|\.ssh|\.env|\.aws)" matched in source_code: "ERROR=Could not open file" (at position 4277)
Tool responses MUST NOT contain instruction-like content, file read directives, or social engineering phrases. Error messages should be factual and technical — never suggest actions involving sensitive data access. See CyberArk ATPA research for attack demonstration.

High5

highO8Timing-Based Covert ChannelMCP04-data-exfiltrationAML.T0057
Pattern "(?:delay|sleep|timeout|wait)\s*[:=]\s*(?:[^;]*(?:charCodeAt|charAt|bit|byte|\&\s*1|>>|<<|\[i\]))" matched in source_code: "timeout: int = 30) -> dict: """ Write a Lua script to a temporary .cff file and execute it via CFF Explorer. CFF Explorer supports a headless scripting mode: when launched with a .cff script file as its argument, it runs the script silently with no GUI window, prints any output to stdout, and exits. This function captures that output. Args: script_content: Lua script string using CFF Explorer's scripting API. timeout: Maximum seconds to wait for CFF Explorer to finish. Returns: dict with keys: success (bool) - True if returncode == 0 output (str) - Captured stdout error (str) - Captured stderr or exception message """ if not Path(CFF_EXPLORER_PATH).exists(): return { "success": False, "output": "", "error": ( f"CFF Explorer not found at: {CFF_EXPLORER_PATH}\n" f"Set the CFF_EXPLORER_PATH environment variable to the correct path.\n" f"Download CFF Explorer: https://ntcore.com/?page_id=388" ) } # Write the script to a temp file with .cff extension with tempfile.NamedTemporaryFile( suffix=".cff", mode="w", delete=False, encoding="utf-8" ) as f: f.write(script_content) script_path = f.name try: result = subprocess.run( [CFF_EXPLORER_PATH, script_path], capture_output=True, text=True, timeout=timeout ) return { "success": result.returncode == 0, "output": result.stdout.strip(), "error": result.stderr.strip() } except subprocess.TimeoutExpired: return {"success": False, "output": "", "error": f"Script timed out after {timeout}s."} except Exception as e: return {"success": False, "output": "", "error": str(e)} finally: try: os.unlink(script_path) except OSError: pass # ── Lua Script Templates ────────────────────────────────────────────────────── # Important notes about CFF Explorer's modified Lua: # - Standard Lua libraries (os, io) are NOT available # - Use print() for all output — it goes to stdout # - Arrays are 0-based (not standard Lua 1-based) # - Use != instead of ~= for not-equal # - null instead of nil # - C-style @ prefix for raw strings: @"C:\path\file" # - No 'local' keyword needed (and can cause issues inside loops) def script_pe_header_analysis(file_path: str) -> str: """Generate Lua script to extract full PE header information.""" fp = file_path.replace("\\", "\\\\") return f""" -- PE Header Analysis Script -- Reads DOS header, File header, Optional header and section table -- All output via print() captured as stdout pehandle = OpenFile(@"{fp}") if pehandle == null then print("ERROR=Could not open file") return end -- PE type flags is64 = IsPE64(pehandle) isDotNet = IsDotNET(pehandle) print("is_pe64=" .. tostring(is64)) print("is_dotnet=" .. tostring(isDotNet)) -- DOS Header (offset 0 from file start) dosOffset = GetOffset(pehandle, PE_DosHeader) if dosOffset != null then magic = ReadWord(pehandle, dosOffset) print("dos_magic=0x" .. string.format("%04X", magic)) end -- COFF File Header fileHeaderOffset = GetOffset(pehandle, PE_FileHeader) if fileHeaderOffset != null then machine = ReadWord(pehandle, fileHeaderOffset) numSections = ReadWord(pehandle, fileHeaderOffset + 2) timestamp = ReadDword(pehandle, fileHeaderOffset + 4) characteristics = ReadWord(pehandle, fileHeaderOffset + 18) print("machine=0x" .. string.format("%04X", machine)) print("num_sections=" .. numSections) print("timestamp=" .. timestamp) print("characteristics=0x" .. string.format("%04X", characteristics)) end -- Optional Header optOffset = GetOffset(pehandle, PE_OptionalHeader) if optOffset != null then magic2 = ReadWord(pehandle, optOffset) entryPoint = ReadDword(pehandle, optOffset + 0x10) imageBase32 = ReadDword(pehandle, optOffset + 0x18) sizeOfImage = ReadDword(pehandle, optOffset + 0x38) subsystem = ReadWord(pehandle, optOffset + 0x44) print("optional_magic=0x" .. string.format("%04X", magic2)) print("entry_point=0x" .. string.format("%08X", entryPoint)) print("image_base=0x" .. string.format("%08X", imageBase32)) print("size_of_image=0x" .. string.format("%08X", sizeOfImage)) print("subsystem=" .. subsystem) end -- Section Table (each IMAGE_SECTION_HEADER is 40 bytes) nSections = GetNumberOfSections(pehandle) sectOffset = GetOffset(pehandle, PE_SectionHeaders) if sectOffset != null and nSections != null then for i = 0, nSections - 1 do base = sectOffset + (i * 40) secname = ReadString(pehandle, base) vsize = ReadDword(pehandle, base + 8) rva = ReadDword(pehandle, base + 12) rawsize = ReadDword(pehandle, base + 16) rawoff = ReadDword(pehandle, base + 20) chars = ReadDword(pehandle, base + 36) print("section_" .. i .. "=name:" .. (secname or "?") .. ",vsize:0x" .. string.format("%X", vsize) .. ",rva:0x" .. string.format("%X", rva) .. ",rawsize:0x" .. string.format("%X", rawsize) .. ",rawoff:0x" .. string.format("%X", rawoff) .. ",chars:0x" .. string.format("%X", chars)) end end CloseHandle(pehandle) """ def script_list_imports(file_path: str) -> str: """Generate Lua script to walk the PE Import Directory and list all DLLs/functions.""" fp = file_path.replace("\\", "\\\\") return f""" -- Import Directory Listing Script -- Walks IMAGE_IMPORT_DESCRIPTOR array and resolves function names pehandle = OpenFile(@"{fp}") if pehandle == null then print("ERROR: cannot open file") return end itOffset = GetOffset(pehandle, PE_ImportDirectory) if itOffset == null then print("NO_IMPORTS") return end ImportDescriptorSize = 20 nDesc = 0 FirstThunk = ReadDword(pehandle, itOffset + 16) while FirstThunk != 0 do nameRva = ReadDword(pehandle, itOffset + (nDesc * ImportDescriptorSize) + 12) nameOff = RvaToOffset(pehandle, nameRva) modName = ReadString(pehandle, nameOff) print("DLL:" .. (modName or "?")) OFTs = ReadDword(pehandle, itOffset + (nDesc * ImportDescriptorSize)) if OFTs != 0 then Thunks = RvaToOffset(pehandle, OFTs) else FTRva = ReadDword(pehandle, itOffset + (nDesc * ImportDescriptorSize) + 16) Thunks = RvaToOffset(pehandle, FTRva) end bPE64 = IsPE64(pehandle) curOff = Thunks if bPE64 == true then curThunk = ReadQword(pehandle, curOff) else curThunk = ReadDword(pehandle, curOff) end while curThunk != null and curThunk != 0 do isOrd = false if bPE64 == true then isOrd = (curThunk & IMAGE_ORDINAL_FLAG64) == IMAGE_ORDINAL_FLAG64 else isOrd = (curThunk & IMAGE_ORDINAL_FLAG32) == IMAGE_ORDINAL_FLAG32 end if isOrd == true then ordVal = ReadWord(pehandle, curOff) print(" FUNC:ordinal=0x" .. string.format("%04X", ordVal)) else funcOff = RvaToOffset(pehandle, (curThunk & 0xFFFFFFFF)) if funcOff != null then ordVal2 = ReadWord(pehandle, funcOff) fname = ReadString(pehandle, funcOff + 2) print(" FUNC:ordinal=0x" .. string.format("%04X", ordVal2) .. ",name=" .. (fname or "?")) end end if bPE64 == true then curOff = curOff + 8 curThunk = ReadQword(pehandle, curOff) else curOff = curOff + 4 curThunk = ReadDword(pehandle, curOff) end end nDesc = nDesc + 1 FirstThunk = ReadDword(pehandle, itOffset + (nDesc * ImportDescriptorSize) + 16) end CloseHandle(pehandle) """ def script_list_exports(file_path: str) -> str: """Generate Lua script to walk the PE Export Directory and list all exported symbols.""" fp = file_path.replace("\\", "\\\\") return f""" -- Export Directory Listing Script -- Reads IMAGE_EXPORT_DIRECTORY and resolves all named exports pehandle = OpenFile(@"{fp}") if pehandle == null then print("ERROR: cannot open file") return end expOffset = GetOffset(pehandle, PE_ExportDirectory) if expOffset == null then print("NO_EXPORTS") return end nameRva = ReadDword(pehandle, expOffset + 12) base = ReadDword(pehandle, expOffset + 16) numFuncs = ReadDword(pehandle, expOffset + 20) numNames = ReadDword(pehandle, expOffset + 24) namesRva = ReadDword(pehandle, expOffset + 32) ordsRva = ReadDword(pehandle, expOffset + 36) nameOff = RvaToOffset(pehandle, nameRva) dllName = ReadString(pehandle, nameOff) print("DLL_NAME:" .. (dllName or "?")) print("NUM_FUNCTIONS:" .. numFuncs) print("NUM_NAMES:" .. numNames) print("ORDINAL_BASE:" .. base) namesOff = RvaToOffset(pehandle, namesRva) ordsOff = RvaToOffset(pehandle, ordsRva) for i = 0, numNames - 1 do fnRva = ReadDword(pehandle, namesOff + (i * 4)) fnOff = RvaToOffset(pehandle, fnRva) fname = ReadString(pehandle, fnOff) ordVal = ReadWord(pehandle, ordsOff + (i * 2)) print("EXPORT:ordinal=" .. (ordVal + base) .. ",name=" .. (fname or "?")) end CloseHandle(pehandle) """ def script_nop_bytes(file_path: str, offset: int, length: int) -> str: """Generate Lua script to NOP out bytes at a given file offset.""" fp = file_path.replace("\\", "\\\\") return f""" -- NOP Bytes Script -- Overwrites {length} bytes at file offset 0x{offset:X} with NOP instructions pehandle = OpenFile(@"{fp}") if pehandle == null then print("ERROR: cannot open file") return end result = NopBytes(pehandle, {offset}, {length}) if result == true then SaveFile(pehandle) print("SUCCESS: NOPed {length} bytes at offset 0x{offset:X}") else print("ERROR: NopBytes failed at offset 0x{offset:X}") end CloseHandle(pehandle) """ def script_invert_jump(file_path: str, offset: int) -> str: """Generate Lua script to invert a conditional jump at a given file offset.""" fp = file_path.replace("\\", "\\\\") return f""" -- Invert Jump Script -- Inverts the conditional jump at file offset 0x{offset:X} -- e.g. JZ (74) becomes JNZ (75), JE becomes JNE, etc. pehandle = OpenFile(@"{fp}") if pehandle == null then print("ERROR: cannot open file") return end result = InvertJump(pehandle, {offset}) if result == true then SaveFile(pehandle) print("SUCCESS: Inverted jump at offset 0x{offset:X}") else print("ERROR: InvertJump failed - offset 0x{offset:X} may not be a conditional jump") end CloseHandle(pehandle) """ def script_save_resource(file_path: str, res_type: int, res_id: int, out_path: str) -> str: """Generate Lua script to extract a PE resource to disk.""" fp = file_path.replace("\\", "\\\\") op = out_path.replace("\\", "\\\\") return f""" -- Extract Resource Script -- Saves resource (type={res_type}, id={res_id}) to disk pehandle = OpenFile(@"{fp}") if pehandle == null then print("ERROR: cannot open file") return end result = SaveResource(pehandle, @"{op}", {res_type}, {res_id}) if result == true then print("SUCCESS: Resource saved to {out_path}") else print("ERROR: Could not extract resource type={res_type} id={res_id}") end CloseHandle(pehandle) """ # ── Output Parsers ──────────────────────────────────────────────────────────── def parse_pe_header_output(raw: str) -> dict: """Parse key=value lines from PE header script stdout into a structured dict.""" result = {"sections": []} section_map = {} MACHINE_NAMES = { 0x014C: "x86 (I386)", 0x8664: "x64 (AMD64)", 0x0200: "IA64", 0x01C4: "ARM Thumb-2", 0xAA64: "ARM64", } SUBSYSTEM_NAMES = { 1: "Native", 2: "Windows GUI", 3: "Windows CUI (Console)", 5: "OS/2 CUI", 7: "POSIX CUI", 9: "Windows CE GUI", 10: "EFI Application", 14: "Xbox", 16: "Boot Application", } for line in raw.splitlines(): line = line.strip() if "=" not in line: continue key, _, val = line.partition("=") if key.startswith("section_"): idx = int(key.split("_")[1]) parts = dict(p.split(":", 1) for p in val.split(",") if ":" in p) section_map[idx] = parts continue if key == "is_pe64": result["is_pe64"] = val == "true" elif key == "is_dotnet": result["is_dotnet"] = val == "true" elif key == "dos_magic": result["dos_magic"] = val elif key == "machine": m = int(val, 16) result["machine"] = val result["machine_name"] = MACHINE_NAMES.get(m, "Unknown") elif key == "num_sections": result["num_sections"] = int(val) elif key == "timestamp": result["timestamp"] = int(val) elif key == "characteristics": result["characteristics"]= val elif key == "optional_magic": result["optional_magic"] = val elif key == "entry_point": result["entry_point"] = val elif key == "image_base": result["image_base"] = val elif key == "size_of_image": result["size_of_image"] = val elif key == "subsystem": s = int(val) result["subsystem"] = s result["subsystem_name"] = SUBSYSTEM_NAMES.get(s, "Unknown") result["sections"] = [section_map[i] for i in sorted(section_map)] return result def parse_imports_output(raw: str) -> list: """Parse import listing stdout into a structured list of DLL + function dicts.""" imports = [] current_dll = None for line in raw.splitlines(): line = line.strip() if line.startswith("DLL:"): current_dll = {"dll": line[4:], "functions": []} imports.append(current_dll) elif line.startswith("FUNC:") and current_dll: parts = dict(p.split("=", 1) for p in line[5:].split(",") if "=" in p) current_dll["functions"].append(parts) elif line in ("NO_IMPORTS", "ERROR: cannot open file"): return [] return imports def parse_exports_output(raw: str) -> dict: """Parse export listing stdout into a structured dict.""" result = { "dll_name": "", "num_functions": 0, "num_names": 0, "ordinal_base": 0, "exports": [] } for line in raw.splitlines(): line = line.strip() if line.startswith("DLL_NAME:"): result["dll_name"] = line[9:] elif line.startswith("NUM_FUNCTIONS:"): result["num_functions"] = int(line[14:]) elif line.startswith("NUM_NAMES:"): result["num_names"] = int(line[10:]) elif line.startswith("ORDINAL_BASE:"): result["ordinal_base"] = int(line[13:]) elif line.startswith("EXPORT:"): parts = dict(p.split("=", 1) for p in line[7:].split(",") if "=" in p) result["exports"].append(parts) return result # ── MCP Tool Definitions ────────────────────────────────────────────────────── @app.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="analyze_pe_headers", description=( "Analyze the PE (Portable Executable) headers of a file using CFF Explorer. " "Returns DOS header, File header, Optional header, and full section table." ), inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Full Windows path to the PE file (e.g. C:\\\\samples\\\\malware.exe)" } }, "required": ["file_path"] } ), Tool( name="list_imports", description=( "List all imported DLLs and their functions from a PE file using CFF Explorer. " "Useful for identifying suspicious API usage in binaries (e.g. VirtualAlloc, WriteProcessMemory)." ), inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Full Windows path to the PE file" } }, "required": ["file_path"] } ), Tool( name="list_exports", description=( "List all exported functions from a PE file (typically DLLs) using CFF Explorer." ), inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Full Windows path to the PE file" } }, "required": ["file_path"] } ), Tool( name="nop_bytes", description=( "NOP out (neutralize) a specified number of bytes at a given file offset in a PE file. " "Modifies the file in-place using CFF Explorer. Always back up the file first." ), inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Full Windows path to the PE file" }, "offset": { "type": "integer", "description": "File offset (decimal) where NOP patching begins" }, "length": { "type": "integer", "description": "Number of bytes to NOP out" } }, "required": ["file_path", "offset", "length"] } ), Tool( name="invert_jump", description=( "Invert a conditional jump instruction at a given file offset in a PE file using CFF Explorer. " "For example: JZ (jump if zero) becomes JNZ (jump if not zero). " "Modifies the file in-place. Always back up the file first." ), inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Full Windows path to the PE file" }, "offset": { "type": "integer", "description": "File offset (decimal) of the conditional jump instruction" } }, "required": ["file_path", "offset"] } ), Tool( name="extract_resource", description=( "Extract a resource from a PE file to disk using CFF Explorer. " "Common resource type IDs: RT_ICON=3, RT_BITMAP=2, RT_MANIFEST=24, RT_VERSION=16, " "RT_DIALOG=5, RT_STRING=6, RT_MENU=4." ), inputSchema={ "type": "object", "properties": { "file_path": { "type": "string", "description": "Full Windows path to the PE file" }, "resource_type": { "type": "integer", "description": "Resource type ID (e.g. 24 for RT_MANIFEST, 3 for RT_ICON)" }, "resource_id": { "type": "integer", "description": "Resource ID number" }, "output_path": { "type": "string", "description": "Full Windows path where the extracted resource will be saved" } }, "required": ["file_path", "resource_type", "resource_id", "output_path"] } ), ] # ── MCP Tool Handlers ───────────────────────────────────────────────────────── @app.call_tool() async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]: def respond(data: Any) -> list[TextContent]: return [TextContent(type="text", text=json.dumps(data, indent=2))] if name == "analyze_pe_headers": file_path = arguments["file_path"] result = run_cff_script(script_pe_header_analysis(file_path)) if not result["success"] and not result["output"]: return respond({"error": result["error"]}) parsed = parse_pe_header_output(result["output"]) parsed["file"] = file_path if result["error"]: parsed["warnings"] = result["error"] return respond(parsed) elif name == "list_imports": file_path = arguments["file_path"] result = run_cff_script(script_list_imports(file_path)) if not result["success"] and not result["output"]: return respond({"error": result["error"]}) imports = parse_imports_output(result["output"]) return respond({ "file": file_path, "total_dlls": len(imports), "total_functions": sum(len(d["functions"]) for d in imports), "imports": imports }) elif name == "list_exports": file_path = arguments["file_path"] result = run_cff_script(script_list_exports(file_path)) if not result["success"] and not result["output"]: return respond({"error": result["error"]}) exports = parse_exports_output(result["output"]) exports["file"] = file_path return respond(exports) elif name == "nop_bytes": file_path = arguments["file_path"] offset = arguments["offset"] length = arguments["length"] result = run_cff_script(script_nop_bytes(file_path, offset, length)) return respond({ "file": file_path, "operation": "nop_byte" (at position 1361)
Remove all code that calculates sleep/delay durations from application data, secrets, or any variable-length content. Tool response times should be constant or determined only by legitimate processing time. If rate limiting is needed, use fixed intervals not derived from data values. Monitor for anomalous response time patterns that could indicate timing-based exfiltration.
highC15Timing Attack on Secret ComparisonMCP07-insecure-config
Pattern "(?:key|token|secret|password|passwd|auth|credential)\s*[!=]=\s*['"][A-Za-z0-9+/=_\-]{8,}['"]" matched in source_code: "key == "is_dotnet"" (at position 14174)
Use crypto.timingSafeEqual() (Node.js) or hmac.compare_digest() (Python) for ALL secret/token/password comparisons. Standard string equality (===, ==) is vulnerable to timing attacks that allow attackers to determine correct values bit by bit.
highD1Known CVEs in DependenciesMCP08-dependency-vuln
Dependency "mcp@1.0.0" has known CVEs:
Update dependencies to versions that patch known CVEs. Run 'npm audit fix' or 'pip-audit' to identify and resolve vulnerable dependencies.
highK16Unbounded Recursion / Missing Depth LimitsMCP07-insecure-configAML.T0054
Pattern "(invoke|call|execute)[_\s-]?(?:tool|agent|self)(?!.*(?:depth|level|limit|max[_\s-]?(?:depth|recursi|iter|call)|count))" matched in source_code: "call_tool" (at position 22094)
Add explicit depth/recursion limits to all recursive operations. Use iterative approaches where possible. Set maximum depth for directory walking (max_depth=10), tree traversal (max_level=20), and agent re-invocation (max_calls=5). Implement circuit breakers that halt after N iterations. Required by EU AI Act Art. 15 (robustness) and OWASP ASI08.
highQ14Concurrent MCP Server Race ConditionMCP07-insecure-configT1068
Pattern "(?:read|write|modify|delete).*(?:file|path|directory)(?!.*(?:lock|mutex|semaphore|flock|atomic))" matched in source_code: "Write a Lua script to a temporary .cff file" (at position 1401)
MCP servers sharing filesystem or database backends with other servers must implement proper concurrency controls. Use: (1) file locking (flock/lockfile) for filesystem operations, (2) database transactions for all read-modify-write sequences, (3) atomic file operations (O_EXCL, mkdtemp) instead of check-then-create, (4) lstat() to detect symlinks before following (CVE-2025-53109). Never assume exclusive access to shared resources — other MCP servers may be operating concurrently.

Medium1

mediumK17Missing Timeout or Circuit BreakerMCP07-insecure-configAML.T0054
Pattern "(?:exec|execSync|spawn|subprocess\.run|os\.system)\s*\((?!.*(?:timeout|kill|maxBuffer|signal))" matched in source_code: "subprocess.run(" (at position 2754)
Add timeouts to ALL external calls: HTTP requests (30s), database queries (10s), subprocess execution (60s), and MCP tool calls (30s). Implement circuit breakers that open after N consecutive failures (e.g., opossum, cockatiel). Use AbortSignal for cancellable operations. Required by EU AI Act Art. 15 and OWASP ASI08.

Low1

lowF4MCP Spec Non-ComplianceMCP07-insecure-config
Server fails MCP spec compliance checks: required:server_name; required:server_version; required:protocol_version; recommended:tool_descriptions; recommended:parameter_descriptions
Follow the MCP specification for server metadata. Include server name, version, and protocol version. Provide descriptions for all tools and parameters.