Mostly safe ā a couple of notes worth reading.
Scanned 7/24/2026, 10:59:06 PMĀ·Cached resultĀ·Deep ScanĀ·91 rulesĀ·How we decide ā
AIVSS Score
Low
Severity Breakdown
0
critical
2
high
88
medium
6
low
MCP Server Information
Findings
This package presents moderate security concerns with a B grade and 67/100 safety score, driven primarily by 69 verbose error instances that could leak sensitive information and 88 total medium-severity issues. The two high-severity findings include ANSI escape injection vulnerabilities that could enable terminal-based attacks, alongside server configuration gaps and resource exhaustion risks that warrant remediation before production deployment.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 ā 20 of 96 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnāt shipped yet.
Showing 1ā30 of 96 findings
96 findings
MCP server binds an HTTP transport to localhost and registers tools, but no authentication is enforced on requests. The official MCP security best practices warn that this is reachable via DNS-rebinding attacks ā a malicious web page can hit `http://127.0.0.1:<port>` from inside the user's browser and invoke tools as the user. Pick one fix: 1. Switch to stdio transport (`mcp.run(transport="stdio")`). 2. Require an `Authorization` / `Bearer` / `api_key` check on every request. 3. Bind
Evidence
| 1 | """ |
| 2 | Main entry point for the Word Document MCP Server. |
| 3 | Acts as the central controller for the MCP server that handles Word document operations. |
| 4 | Supports multiple transports: stdio, sse, and streamable-http using standalone FastMCP. |
| 5 | """ |
| 6 | |
| 7 | import os |
| 8 | import sys |
| 9 | from dotenv import load_dotenv |
| 10 | |
| 11 | # Load environment variables from .env file |
| 12 | print("Loading configuration from .env file...") |
| 13 | load_dotenv() |
| 14 | # Set required environment variable for FastMCP 2.8.1+ |
| 15 | os.environ.setdefault('FASTMCP_LOG_LEVEL', 'INFO |
RemediationAI
The problem is that the MCP server exposes an HTTP endpoint on localhost without any authentication mechanism, allowing DNS-rebinding attacks where a malicious webpage can invoke tools as the user. Switch the transport from HTTP to stdio by changing the server initialization to use `mcp.run(transport="stdio")` in word_document_server/main.py, which eliminates network exposure entirely. This fix removes the attack surface because stdio transport communicates only through standard input/output streams, making it impossible for remote web pages to reach the server. Verify the fix by running the server and confirming it reads from stdin and writes to stdout instead of binding to a network port.
LLM consensus
MCP server binds an HTTP transport to localhost / 127.0.0.1 / [::1] and registers tools, but does not validate the request `Host` header. Even with auth, this is exploitable via DNS rebinding ā a malicious web page can make the user's browser resolve `evil.com` to `127.0.0.1`, bypassing same-origin checks. Fix: enable `hostHeaderValidation()` middleware (TS SDK ā„1.24.0), or check `req.headers.host` against an allow-list of expected hostnames. Co-fires with MCP-268 (no auth) when both gaps are p
Evidence
| 1 | """ |
| 2 | Main entry point for the Word Document MCP Server. |
| 3 | Acts as the central controller for the MCP server that handles Word document operations. |
| 4 | Supports multiple transports: stdio, sse, and streamable-http using standalone FastMCP. |
| 5 | """ |
| 6 | |
| 7 | import os |
| 8 | import sys |
| 9 | from dotenv import load_dotenv |
| 10 | |
| 11 | # Load environment variables from .env file |
| 12 | print("Loading configuration from .env file...") |
| 13 | load_dotenv() |
| 14 | # Set required environment variable for FastMCP 2.8.1+ |
| 15 | os.environ.setdefault('FASTMCP_LOG_LEVEL', 'INFO |
RemediationAI
The problem is that even with authentication, the HTTP server does not validate the `Host` header, allowing attackers to use DNS rebinding to make the user's browser resolve a malicious domain to 127.0.0.1 and bypass same-origin protections. Add host header validation middleware by implementing a check in word_document_server/main.py that validates `req.headers.host` against an allowlist of expected hostnames (e.g., `localhost`, `127.0.0.1`, `[::1]`), or use the FastMCP `hostHeaderValidation()` middleware if available in your SDK version. This fix prevents DNS rebinding because requests with forged Host headers will be rejected before reaching the tool handlers. Verify by making a request with a spoofed Host header and confirming it returns a 400 Bad Request error.
LLM consensus
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 50 | print(f" Result: {result}") |
| 51 | |
| 52 | # Test 4: Body text in Times New Roman |
| 53 | print("Test 4: Adding body text in Times New Roman 14pt...") |
| 54 | result = await add_paragraph( |
| 55 | test_doc, |
| 56 | "This is body text that should be in Times New Roman at 14pt. " |
RemediationAI
The problem is that user-controlled output from `result` is printed directly to the terminal without sanitizing ANSI escape sequences, allowing malicious input to inject cursor-control codes that rewrite earlier output or hide commands. Replace the direct print with a sanitized version by importing `shlex` or a library like `bleach` and calling `print(f" Result: {shlex.quote(str(result))}")` or a custom sanitization function that strips ANSI codes in test_formatting.py. This fix prevents injection because escape sequences are either escaped or removed before terminal output. Verify by passing a string containing ANSI codes (e.g., `"\x1b[2J"`) as result and confirming the terminal is not cleared.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 372 | else: # stdio |
| 373 | print(f"\nš» STDIO Transport Configuration:") |
| 374 | print(f" Server runs locally with standard input/output") |
| 375 | |
| 376 | # Provide instructions for adding configuration to Claude Desktop configuration file |
| 377 | if platform.system() == "Windows": |
RemediationAI
The problem is that user-controlled configuration values are printed directly to the terminal without sanitizing ANSI escape sequences, allowing injection of cursor-control codes. Replace the direct print statements in setup_mcp.py with sanitized output by using `print(f"\nš» STDIO Transport Configuration:"); print(f" {shlex.quote(str(server_info))}")` or a custom function that strips ANSI codes from all user-controlled values. This fix prevents terminal injection attacks because escape sequences are neutralized before display. Verify by injecting an ANSI code into a configuration value and confirming it appears as literal text rather than executing a terminal command.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 544 | else: |
| 545 | return f"Failed to merge cells vertically. Check that indices are valid." |
| 546 | except Exception as e: |
| 547 | return f"Failed to merge cells vertically: {str(e)}" |
| 548 | |
| 549 | |
| 550 | async def set_table_cell_alignment(filename: str, table_index: int, row_index: int, col_index: int, |
RemediationAI
The problem is that the exception message in word_document_server/tools/format_tools.py returns `str(e)` which exposes internal error details, library versions, and code structure to callers. Replace `return f"Failed to merge cells vertically: {str(e)}"` with `return "Failed to merge cells vertically. Please check that indices are valid and try again."` to return only a generic user-facing message without exception details. This fix eliminates information disclosure because attackers no longer see stack traces or internal implementation details. Verify by triggering an error condition and confirming the response contains no Python traceback, file paths, or library names.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 451 | ) |
| 452 | return message |
| 453 | except Exception as e: |
| 454 | return f"Failed to delete footnote: {str(e)}" |
| 455 | |
| 456 | |
| 457 | # ============================================================================ |
RemediationAI
The problem is that the exception message in word_document_server/tools/footnote_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to delete footnote: {str(e)}"` with `return "Failed to delete footnote. Please verify the footnote index and document structure."` to return only a generic message without exception details. This fix prevents information leakage because the actual error is logged internally but not exposed to the caller. Verify by triggering a footnote deletion error and confirming the response contains no exception details or traceback information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 288 | else: |
| 289 | return f"Failed to apply cell shading." |
| 290 | except Exception as e: |
| 291 | return f"Failed to apply cell shading: {str(e)}" |
| 292 | |
| 293 | |
| 294 | async def apply_table_alternating_rows(filename: str, table_index: int, |
RemediationAI
The problem is that the exception message in word_document_server/tools/format_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to apply cell shading: {str(e)}"` with `return "Failed to apply cell shading. Please verify the table and cell indices."` to return only a generic message without exception details. This fix prevents information disclosure because internal error details are hidden from the caller. Verify by triggering a cell shading error and confirming the response contains no exception details or library information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 819 | else: |
| 820 | return f"Failed to set column widths." |
| 821 | except Exception as e: |
| 822 | return f"Failed to set column widths: {str(e)}" |
| 823 | |
| 824 | |
| 825 | async def set_table_width(filename: str, table_index: int, width: float, |
RemediationAI
The problem is that the exception message in word_document_server/tools/format_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to set column widths: {str(e)}"` with `return "Failed to set column widths. Please verify the table index and width values."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a column width error and confirming the response contains no exception details or traceback.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 35 | "table_count": len(doc.tables) |
| 36 | } |
| 37 | except Exception as e: |
| 38 | return {"error": f"Failed to get document properties: {str(e)}"} |
| 39 | |
| 40 | |
| 41 | def extract_document_text(doc_path: str) -> str: |
RemediationAI
The problem is that the exception message in word_document_server/utils/document_utils.py returns `str(e)` which exposes internal error details to callers. Replace `return {"error": f"Failed to get document properties: {str(e)}"}` with `return {"error": "Failed to retrieve document properties. Please verify the document path and format."}` to return only a generic message without exception details. This fix prevents information disclosure because internal errors are hidden from the caller. Verify by passing an invalid document path and confirming the error response contains no exception details or file system information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 743 | else: |
| 744 | return f"Failed to set column width. Check that indices are valid." |
| 745 | except Exception as e: |
| 746 | return f"Failed to set column width: {str(e)}" |
| 747 | |
| 748 | |
| 749 | async def set_table_column_widths(filename: str, table_index: int, widths: list, |
RemediationAI
The problem is that the exception message in word_document_server/tools/format_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to set column width: {str(e)}"` with `return "Failed to set column width. Please verify the table index and width value."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a column width error and confirming the response contains no exception details or library information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 75 | os.remove(metadata_path) |
| 76 | return True, "Protection removed successfully" |
| 77 | except Exception as e: |
| 78 | return False, f"Error removing protection: {str(e)}" |
RemediationAI
The problem is that the exception message in word_document_server/core/unprotect.py returns `str(e)` which exposes internal error details to callers. Replace `return False, f"Error removing protection: {str(e)}"` with `return False, "Error removing protection. Please verify the document and password."` to return only a generic message without exception details. This fix prevents information disclosure because internal errors are hidden from the caller. Verify by triggering a protection removal error and confirming the response contains no exception details or traceback information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 168 | else: |
| 169 | return f"Failed to add digital signature to document {filename}" |
| 170 | except Exception as e: |
| 171 | return f"Failed to add digital signature: {str(e)}" |
| 172 | |
| 173 | async def verify_document(filename: str, password: Optional[str] = None) -> str: |
| 174 | """Verify document protection and/or digital signature. |
RemediationAI
The problem is that the exception message in word_document_server/tools/protection_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to add digital signature: {str(e)}"` with `return "Failed to add digital signature. Please verify the document and certificate."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a signature error and confirming the response contains no exception details or library information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 107 | doc.save(filename) |
| 108 | return f"Heading '{text}' (level {level}) added to {filename}" |
| 109 | except Exception as e: |
| 110 | return f"Failed to add heading: {str(e)}" |
| 111 | |
| 112 | |
| 113 | async def add_paragraph(filename: str, text: str, style: Optional[str] = None, |
RemediationAI
The problem is that the exception message in word_document_server/tools/content_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to add heading: {str(e)}"` with `return "Failed to add heading. Please verify the document path and heading parameters."` to return only a generic message without exception details. This fix prevents information disclosure because internal errors are hidden from the caller. Verify by triggering a heading addition error and confirming the response contains no exception details or traceback.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 382 | else: |
| 383 | return f"Failed to apply header highlighting." |
| 384 | except Exception as e: |
| 385 | return f"Failed to apply header highlighting: {str(e)}" |
| 386 | |
| 387 | |
| 388 | async def merge_table_cells(filename: str, table_index: int, start_row: int, start_col: int, |
RemediationAI
The problem is that the exception message in word_document_server/tools/format_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to apply header highlighting: {str(e)}"` with `return "Failed to apply header highlighting. Please verify the table and header indices."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a header highlighting error and confirming the response contains no exception details or library information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 59 | return "\n".join(text) |
| 60 | except Exception as e: |
| 61 | return f"Failed to extract text: {str(e)}" |
| 62 | |
| 63 | |
| 64 | def get_document_structure(doc_path: str) -> Dict[str, Any]: |
RemediationAI
The problem is that the exception message in word_document_server/utils/document_utils.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to extract text: {str(e)}"` with `return "Failed to extract text. Please verify the document path and format."` to return only a generic message without exception details. This fix prevents information disclosure because internal errors are hidden from the caller. Verify by passing an invalid document path and confirming the error response contains no exception details or file system information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 353 | ) |
| 354 | return message |
| 355 | except Exception as e: |
| 356 | return f"Failed to add footnote: {str(e)}" |
| 357 | |
| 358 | |
| 359 | async def customize_footnote_style(filename: str, numbering_format: str = "1, 2, 3", |
RemediationAI
The problem is that the exception message in word_document_server/tools/footnote_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to add footnote: {str(e)}"` with `return "Failed to add footnote. Please verify the document and footnote parameters."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a footnote addition error and confirming the response contains no exception details or traceback information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 40 | except IOError as e: |
| 41 | return False, f"File {filepath} is not writeable: {str(e)}" |
| 42 | except Exception as e: |
| 43 | return False, f"Unknown error checking file permissions: {str(e)}" |
| 44 | |
| 45 | |
| 46 | def create_document_copy(source_path: str, dest_path: Optional[str] = None) -> Tuple[bool, str, Optional[str]]: |
RemediationAI
The problem is that the exception messages in word_document_server/utils/file_utils.py return `str(e)` which exposes internal error details to callers. Replace `return False, f"File {filepath} is not writeable: {str(e)}"` and `return False, f"Unknown error checking file permissions: {str(e)}"` with generic messages like `return False, "File is not accessible. Please verify permissions."` to hide exception details. This fix prevents information disclosure because internal errors and file paths are not exposed to the caller. Verify by triggering permission errors and confirming the response contains no exception details or file system information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 124 | return True, f"Document is protected with {protection_type} protection" |
| 125 | |
| 126 | except Exception as e: |
| 127 | return False, f"Error verifying protection: {str(e)}" |
| 128 | |
| 129 | |
| 130 | def is_section_editable(doc_path: str, section_name: str) -> bool: |
RemediationAI
The problem is that the exception message in word_document_server/core/protection.py returns `str(e)` which exposes internal error details to callers. Replace `return False, f"Error verifying protection: {str(e)}"` with `return False, "Error verifying protection. Please check the document and try again."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a protection verification error and confirming the response contains no exception details or library information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 458 | else: |
| 459 | return f"No occurrences of '{find_text}' found." |
| 460 | except Exception as e: |
| 461 | return f"Failed to search and replace: {str(e)}" |
| 462 | |
| 463 | async def insert_header_near_text_tool(filename: str, target_text: str = None, header_title: str = "", position: str = 'after', header_style: str = 'Heading 1', target_paragraph_index: int = None) -> str: |
| 464 | """Insert a header (with specified style) before or after the target paragraph. Specify by text or paragraph index.""" |
RemediationAI
The problem is that the exception message in word_document_server/tools/content_tools.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to search and replace: {str(e)}"` with `return "Failed to search and replace. Please verify the search text and document."` to return only a generic message without exception details. This fix prevents information disclosure because internal errors are hidden from the caller. Verify by triggering a search and replace error and confirming the response contains no exception details or traceback.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 237 | else: |
| 238 | return f"Header '{header_title}' (style: {header_style}) inserted {position} the target paragraph." |
| 239 | except Exception as e: |
| 240 | return f"Failed to insert header: {str(e)}" |
| 241 | |
| 242 | |
| 243 | def insert_line_or_paragraph_near_text(doc_path: str, target_text: str = None, line_text: str = "", position: str = 'after', line_style: str = None, target_paragraph_index: int = None) -> str: |
RemediationAI
The problem is that the exception message in word_document_server/utils/document_utils.py returns `str(e)` which exposes internal error details to callers. Replace `return f"Failed to insert header: {str(e)}"` with `return "Failed to insert header. Please verify the target text and header parameters."` to return only a generic message without exception details. This fix prevents information leakage because internal errors are not exposed to the caller. Verify by triggering a header insertion error and confirming the response contains no exception details or library information.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 117 | else: |
| 118 | return f"Failed to protect document {filename} with restricted editing" |
| 119 | except Exception as e: |
| 120 | return f"Failed to add restricted editing: {str(e)}" |
| 121 | |
| 122 | async def add_digital_signature(filename: str, signer_name: str, reason: Optional[str] = None) -> str: |
| 123 | """Add a digital signature to a Word document. |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 412 | return f"Footnote style and numbering customized in {filename}" |
| 413 | except Exception as e: |
| 414 | return f"Failed to customize footnote style: {str(e)}" |
| 415 | |
| 416 | |
| 417 | async def delete_footnote_from_document(filename: str, footnote_id: Optional[int] = None, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 274 | ) |
| 275 | return message |
| 276 | except Exception as e: |
| 277 | return f"Failed to add footnote: {str(e)}" |
| 278 | |
| 279 | |
| 280 | async def add_footnote_before_text(filename: str, search_text: str, footnote_text: str, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 270 | outfile.write(encrypted_data) |
| 271 | return f"Failed to decrypt document {filename}: {str(e)}. Encrypted file restored." |
| 272 | else: |
| 273 | return f"Failed to decrypt document {filename}: {str(e)}. Could not restore encrypted file." |
| 274 | except Exception as restore_e: |
| 275 | return f"Failed to decrypt document {filename}: {str(e)}. Also failed to restore encrypted file: {str(restore_e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 76 | else: |
| 77 | return f"Failed to encrypt document {filename}: {str(e)}. Could not restore original file." |
| 78 | except Exception as restore_e: |
| 79 | return f"Failed to encrypt document {filename}: {str(e)}. Also failed to restore original file: {str(restore_e)}" |
| 80 | |
| 81 | |
| 82 | async def add_restricted_editing(filename: str, password: str, editable_sections: List[str]) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 72 | if 'original_data' in locals(): |
| 73 | with open(filename, "wb") as outfile: |
| 74 | outfile.write(original_data) |
| 75 | return f"Failed to encrypt document {filename}: {str(e)}. Original file restored." |
| 76 | else: |
| 77 | return f"Failed to encrypt document {filename}: {str(e)}. Could not restore original file." |
| 78 | except Exception as restore_e: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 311 | ) |
| 312 | return message |
| 313 | except Exception as e: |
| 314 | return f"Failed to add footnote: {str(e)}" |
| 315 | |
| 316 | |
| 317 | async def add_footnote_enhanced(filename: str, paragraph_index: int, footnote_text: str, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 109 | return structure |
| 110 | except Exception as e: |
| 111 | return {"error": f"Failed to get document structure: {str(e)}"} |
| 112 | |
| 113 | |
| 114 | def find_paragraph_by_text(doc, text, partial_match=False): |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 493 | else: |
| 494 | return f"Failed to merge cells horizontally. Check that indices are valid." |
| 495 | except Exception as e: |
| 496 | return f"Failed to merge cells horizontally: {str(e)}" |
| 497 | |
| 498 | |
| 499 | async def merge_table_cells_vertical(filename: str, table_index: int, col_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 62 | properties = get_document_properties(filename) |
| 63 | return json.dumps(properties, indent=2) |
| 64 | except Exception as e: |
| 65 | return f"Failed to get document info: {str(e)}" |
| 66 | |
| 67 | |
| 68 | async def get_document_text(filename: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 1019 | else: |
| 1020 | return f"Failed to format cell text. Check that indices are valid." |
| 1021 | except Exception as e: |
| 1022 | return f"Failed to format cell text: {str(e)}" |
| 1023 | |
| 1024 | |
| 1025 | async def set_table_cell_padding(filename: str, table_index: int, row_index: int, col_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 67 | shutil.copy2(source_path, dest_path) |
| 68 | return True, f"Document copied to {dest_path}", dest_path |
| 69 | except Exception as e: |
| 70 | return False, f"Failed to copy document: {str(e)}", None |
| 71 | |
| 72 | |
| 73 | def ensure_docx_extension(filename: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 268 | if 'encrypted_data' in locals(): |
| 269 | with open(filename, "wb") as outfile: |
| 270 | outfile.write(encrypted_data) |
| 271 | return f"Failed to decrypt document {filename}: {str(e)}. Encrypted file restored." |
| 272 | else: |
| 273 | return f"Failed to decrypt document {filename}: {str(e)}. Could not restore encrypted file." |
| 274 | except Exception as restore_e: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 300 | doc.save(filename) |
| 301 | return f"Page break added to {filename}." |
| 302 | except Exception as e: |
| 303 | return f"Failed to add page break: {str(e)}" |
| 304 | |
| 305 | |
| 306 | async def add_table_of_contents(filename: str, title: str = "Table of Contents", max_level: int = 3) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 44 | return f"Document {filename} created successfully" |
| 45 | except Exception as e: |
| 46 | return f"Failed to create document: {str(e)}" |
| 47 | |
| 48 | |
| 49 | async def get_document_info(filename: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 389 | return f"Table of contents with {len(headings)} entries added to {filename}" |
| 390 | except Exception as e: |
| 391 | return f"Failed to add table of contents: {str(e)}" |
| 392 | |
| 393 | |
| 394 | async def delete_paragraph(filename: str, paragraph_index: int) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 486 | temp_file = working_file + '.tmp' |
| 487 | if os.path.exists(temp_file): |
| 488 | os.remove(temp_file) |
| 489 | return False, f"Error adding footnote: {str(e)}", None |
| 490 | |
| 491 | |
| 492 | def delete_footnote_robust( |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 239 | return True, f"Document signature is valid. Signed by {signature_info.get('signer')} on {signature_info.get('timestamp')}" |
| 240 | |
| 241 | except Exception as e: |
| 242 | return False, f"Error verifying signature: {str(e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 442 | else: |
| 443 | return f"Failed to merge cells. Check that indices are valid." |
| 444 | except Exception as e: |
| 445 | return f"Failed to merge cells: {str(e)}" |
| 446 | |
| 447 | |
| 448 | async def merge_table_cells_horizontal(filename: str, table_index: int, row_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 162 | return results |
| 163 | except Exception as e: |
| 164 | return {"error": f"Failed to search for text: {str(e)}"} |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 169 | doc.save(filename) |
| 170 | return f"Paragraph added to {filename}" |
| 171 | except Exception as e: |
| 172 | return f"Failed to add paragraph: {str(e)}" |
| 173 | |
| 174 | |
| 175 | async def add_table(filename: str, rows: int, cols: int, data: Optional[List[List[str]]] = None) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 188 | with docx_zip.open('word/document.xml') as xml_file: |
| 189 | return xml_file.read().decode('utf-8') |
| 190 | except Exception as e: |
| 191 | return f"Failed to extract XML: {str(e)}" |
| 192 | |
| 193 | |
| 194 | def insert_header_near_text(doc_path: str, target_text: str = None, header_title: str = "", position: str = 'after', header_style: str = 'Heading 1', target_paragraph_index: int = None) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 220 | return message |
| 221 | except Exception as e: |
| 222 | return f"Failed to verify document: {str(e)}" |
| 223 | |
| 224 | async def unprotect_document(filename: str, password: str) -> str: |
| 225 | """Remove password protection from a Word document. |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 206 | target_doc.save(target_filename) |
| 207 | return f"Successfully merged {len(source_filenames)} documents into {target_filename}" |
| 208 | except Exception as e: |
| 209 | return f"Failed to merge documents: {str(e)}" |
| 210 | |
| 211 | |
| 212 | async def get_document_xml_tool(filename: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 111 | return result |
| 112 | except Exception as e: |
| 113 | return f"Failed to list documents: {str(e)}" |
| 114 | |
| 115 | |
| 116 | async def copy_document(source_filename: str, destination_filename: Optional[str] = None) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 732 | return is_valid, message, report |
| 733 | |
| 734 | except Exception as e: |
| 735 | return False, f"Error validating document: {str(e)}", report |
| 736 | |
| 737 | |
| 738 | # ============================================================================ |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 60 | result = find_text(filename, text_to_find, match_case, whole_word) |
| 61 | return json.dumps(result, indent=2) |
| 62 | except Exception as e: |
| 63 | return f"Failed to search for text: {str(e)}" |
| 64 | |
| 65 | |
| 66 | async def convert_to_pdf(filename: str, output_filename: Optional[str] = None) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 74 | outfile.write(original_data) |
| 75 | return f"Failed to encrypt document {filename}: {str(e)}. Original file restored." |
| 76 | else: |
| 77 | return f"Failed to encrypt document {filename}: {str(e)}. Could not restore original file." |
| 78 | except Exception as restore_e: |
| 79 | return f"Failed to encrypt document {filename}: {str(e)}. Also failed to restore original file: {str(restore_e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 605 | else: |
| 606 | return f"Failed to set cell alignment. Check that indices are valid." |
| 607 | except Exception as e: |
| 608 | return f"Failed to set cell alignment: {str(e)}" |
| 609 | |
| 610 | |
| 611 | async def set_table_alignment_all(filename: str, table_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 1109 | else: |
| 1110 | return f"Failed to set cell padding. Check that indices are valid." |
| 1111 | except Exception as e: |
| 1112 | return f"Failed to set cell padding: {str(e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 36 | "is_heading": paragraph.style.name.startswith("Heading") if paragraph.style else False |
| 37 | } |
| 38 | except Exception as e: |
| 39 | return {"error": f"Failed to get paragraph text: {str(e)}"} |
| 40 | |
| 41 | |
| 42 | def find_text(doc_path: str, text_to_find: str, match_case: bool = True, whole_word: bool = False) -> Dict[str, Any]: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 335 | else: |
| 336 | return f"Failed to apply alternating row shading." |
| 337 | except Exception as e: |
| 338 | return f"Failed to apply alternating row shading: {str(e)}" |
| 339 | |
| 340 | |
| 341 | async def highlight_table_header(filename: str, table_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 425 | doc.save(filename) |
| 426 | return f"Paragraph at index {paragraph_index} deleted successfully." |
| 427 | except Exception as e: |
| 428 | return f"Failed to delete paragraph: {str(e)}" |
| 429 | |
| 430 | |
| 431 | async def search_and_replace(filename: str, find_text: str, replace_text: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 934 | else: |
| 935 | return f"Failed to set table auto-fit." |
| 936 | except Exception as e: |
| 937 | return f"Failed to set table auto-fit: {str(e)}" |
| 938 | |
| 939 | |
| 940 | async def format_table_cell_text(filename: str, table_index: int, row_index: int, col_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 237 | return f"Converted {len(footnote_references)} footnotes to endnotes in {filename}" |
| 238 | except Exception as e: |
| 239 | return f"Failed to convert footnotes to endnotes: {str(e)}" |
| 240 | |
| 241 | |
| 242 | async def add_footnote_after_text(filename: str, search_text: str, footnote_text: str, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 38 | pass |
| 39 | return True, "" |
| 40 | except IOError as e: |
| 41 | return False, f"File {filepath} is not writeable: {str(e)}" |
| 42 | except Exception as e: |
| 43 | return False, f"Unknown error checking file permissions: {str(e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 414 | else: |
| 415 | return f"{list_type.capitalize()} list with {len(new_paras)} items inserted {position} the target paragraph." |
| 416 | except Exception as e: |
| 417 | return f"Failed to insert numbered list: {str(e)}" |
| 418 | |
| 419 | |
| 420 | def is_toc_paragraph(para): |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 662 | else: |
| 663 | return f"Failed to set table alignment." |
| 664 | except Exception as e: |
| 665 | return f"Failed to set table alignment: {str(e)}" |
| 666 | |
| 667 | |
| 668 | async def set_table_column_width(filename: str, table_index: int, col_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 292 | else: |
| 293 | return f"Line/paragraph inserted {position} the target paragraph with style '{style}'." |
| 294 | except Exception as e: |
| 295 | return f"Failed to insert line/paragraph: {str(e)}" |
| 296 | |
| 297 | |
| 298 | def add_bullet_numbering(paragraph, num_id=1, level=0): |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 153 | doc.save(filename) |
| 154 | return f"Endnote added to paragraph {paragraph_index} in {filename}" |
| 155 | except Exception as e: |
| 156 | return f"Failed to add endnote: {str(e)}" |
| 157 | |
| 158 | |
| 159 | async def convert_footnotes_to_endnotes_in_document(filename: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 890 | else: |
| 891 | return f"Failed to set table width." |
| 892 | except Exception as e: |
| 893 | return f"Failed to set table width: {str(e)}" |
| 894 | |
| 895 | |
| 896 | async def auto_fit_table_columns(filename: str, table_index: int) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 607 | return True, message, details |
| 608 | |
| 609 | except Exception as e: |
| 610 | return False, f"Error deleting footnote: {str(e)}", None |
| 611 | |
| 612 | |
| 613 | def validate_document_footnotes(filename: str) -> Tuple[bool, str, Dict[str, Any]]: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 186 | doc.save(filename) |
| 187 | return f"Style '{style_name}' created successfully." |
| 188 | except Exception as e: |
| 189 | return f"Failed to create style: {str(e)}" |
| 190 | |
| 191 | |
| 192 | async def format_table(filename: str, table_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 129 | doc.save(filename) |
| 130 | return f"Text '{target_text}' formatted successfully in paragraph {paragraph_index}." |
| 131 | except Exception as e: |
| 132 | return f"Failed to format text: {str(e)}" |
| 133 | |
| 134 | |
| 135 | async def create_custom_style(filename: str, style_name: str, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 93 | doc.save(filename) |
| 94 | return f"Footnote added to paragraph {paragraph_index} in {filename} (simplified approach)" |
| 95 | except Exception as e: |
| 96 | return f"Failed to add footnote: {str(e)}" |
| 97 | |
| 98 | |
| 99 | async def add_endnote_to_document(filename: str, paragraph_index: int, endnote_text: str) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 216 | else: |
| 217 | return f"Document signature is valid. Signed by {signature_info.get('signer')} on {signature_info.get('timestamp')}" |
| 218 | except Exception as e: |
| 219 | return f"Error verifying signature: {str(e)}" |
| 220 | |
| 221 | return message |
| 222 | except Exception as e: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 272 | else: |
| 273 | return f"Failed to decrypt document {filename}: {str(e)}. Could not restore encrypted file." |
| 274 | except Exception as restore_e: |
| 275 | return f"Failed to decrypt document {filename}: {str(e)}. Also failed to restore encrypted file: {str(restore_e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 35 | result = get_paragraph_text(filename, paragraph_index) |
| 36 | return json.dumps(result, indent=2) |
| 37 | except Exception as e: |
| 38 | return f"Failed to get paragraph text: {str(e)}" |
| 39 | |
| 40 | |
| 41 | async def find_text_in_document(filename: str, text_to_find: str, match_case: bool = True, whole_word: bool = False) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 69 | except ImportError: |
| 70 | return False, "Missing msoffcrypto package required for encryption/decryption" |
| 71 | except Exception as e: |
| 72 | return False, f"Error decrypting document: {str(e)}" |
| 73 | |
| 74 | # Remove the protection metadata file |
| 75 | os.remove(metadata_path) |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 181 | return f"PDF conversion not supported on {system} platform" |
| 182 | |
| 183 | except Exception as e: |
| 184 | return f"Failed to convert document to PDF: {str(e)}" |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 216 | doc.save(filename) |
| 217 | return f"Table ({rows}x{cols}) added to {filename}" |
| 218 | except Exception as e: |
| 219 | return f"Failed to add table: {str(e)}" |
| 220 | |
| 221 | |
| 222 | async def add_picture(filename: str, image_path: str, width: Optional[float] = None) -> str: |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 111 | convert(filename, output_filename) |
| 112 | return f"Document successfully converted to PDF: {output_filename}" |
| 113 | except (ImportError, Exception) as e: |
| 114 | return f"Failed to convert document to PDF: {str(e)}\nNote: docx2pdf requires Microsoft Word to be installed." |
| 115 | |
| 116 | elif system in ["Linux", "Darwin"]: # Linux or macOS |
| 117 | errors = [] |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Full exception detail or stack trace returned to the caller. Leaking tracebacks exposes internal paths, library versions, and query structure ā useful recon for attackers.
Evidence
| 230 | else: |
| 231 | return f"Failed to format table at index {table_index}." |
| 232 | except Exception as e: |
| 233 | return f"Failed to format table: {str(e)}" |
| 234 | |
| 235 | |
| 236 | async def set_table_cell_shading(filename: str, table_index: int, row_index: int, |
Remediation
Log the full exception server-side with a correlation ID; return only {"error_id": id, "message": "internal error"} to the caller. Never enable Flask debug mode in production.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 153 | # Also install dependencies from requirements.txt if it exists |
| 154 | requirements_path = os.path.join(base_path, 'requirements.txt') |
| 155 | if os.path.exists(requirements_path): |
| 156 | subprocess.run([pip_path, 'install', '-r', requirements_path], check=True) |
| 157 | |
| 158 | print("Requirements installed successfully!") |
| 159 | except subprocess.CalledProcessError as e: |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 128 | # Create virtual environment |
| 129 | try: |
| 130 | subprocess.run([sys.executable, '-m', 'venv', venv_path], check=True) |
| 131 | print("Virtual environment created successfully!") |
| 132 | except subprocess.CalledProcessError as e: |
| 133 | print(f"Error creating virtual environment: {e}") |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 148 | # Install FastMCP package (standalone library) |
| 149 | subprocess.run([pip_path, 'install', 'fastmcp'], check=True) |
| 150 | # Install python-docx package |
| 151 | subprocess.run([pip_path, 'install', 'python-docx'], check=True) |
| 152 | |
| 153 | # Also install dependencies from requirements.txt if it exists |
| 154 | requirements_path = os.path.join(base_path, 'requirements.txt') |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 334 | """ |
| 335 | print("\nInstalling word-document-server from PyPI...") |
| 336 | try: |
| 337 | subprocess.run([sys.executable, "-m", "pip", "install", "word-mcp-server"], check=True) |
| 338 | print("word-mcp-server successfully installed from PyPI!") |
| 339 | return True |
| 340 | except subprocess.CalledProcessError: |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 146 | print("\nInstalling requirements...") |
| 147 | try: |
| 148 | # Install FastMCP package (standalone library) |
| 149 | subprocess.run([pip_path, 'install', 'fastmcp'], check=True) |
| 150 | # Install python-docx package |
| 151 | subprocess.run([pip_path, 'install', 'python-docx'], check=True) |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Dockerfile never sets a non-root `USER` directive, so the CMD runs as root by default. Any RCE or library-level vulnerability exploited inside this container gets full privileges (MCP Top-10 R3). Add `USER <non-root>` before CMD / ENTRYPOINT in the final stage ā e.g. `USER 1000`, `USER nobody`, or `USER nonroot` on distroless.
Evidence
| 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config |
| 2 | # syntax=docker/dockerfile:1 |
| 3 | |
| 4 | # Use official Python runtime |
| 5 | FROM python:3.11-slim |
| 6 | |
| 7 | # Set working directory |
| 8 | WORKDIR /app |
| 9 | |
| 10 | # Install build dependencies |
| 11 | RUN apt-get update \ |
| 12 | && apt-get install -y --no-install-recommends build-essential \ |
| 13 | && rm -rf /var/lib/apt/lists/* |
| 14 | |
| 15 | # Copy project files |
| 16 | COPY . /app |
| 17 | |
| 18 | # Install Python dependencies |
| 19 | RUN pip install --no-cache-dir . |
| 20 | |
| 21 | # Default command |
| 22 | ENTRYPOINT ["word_mcp_ |
Remediation
Create and switch to a non-root user before the CMD / ENTRYPOINT: RUN adduser --system --uid 1000 app USER 1000 Or reuse the base image's shipped non-root account (e.g. `USER nobody`, `USER nonroot` on distroless). Multi-stage builds only need the USER directive in the final stage.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal ā confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Import necessary Python standard libraries |
| 2 | import os |
| 3 | import json |
| 4 | import subprocess |
| 5 | import sys |
| 6 | import shutil |
| 7 | import platform |
| 8 | |
| 9 | def check_prerequisites(): |
| 10 | """ |
| 11 | Check if necessary prerequisites are installed |
| 12 | |
| 13 | Returns: |
| 14 | tuple: (python_ok, uv_installed, uvx_installed, word_server_installed) |
| 15 | """ |
| 16 | # Check Python version |
| 17 | python_version = sys.version_info |
| 18 | python_ok = python_version.major >= 3 and python_version.minor >= 8 |
| 19 | |
| 20 | # |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal ā confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Office-Word-MCP-Server |
| 2 | |
| 3 | [](https://smithery.ai/server/@GongRzhe/Office-Word-MCP-Server) |
| 4 | |
| 5 | A Model Context Protocol (MCP) server for creating, reading, and manipulating Microsoft Word documents. This server enables AI assistants to work with Word documents through a standardized interface, providing rich document editing capabilities. |
| 6 | |
| 7 | <a href="https://glama.ai/mcp/servers/@GongRzhe/Office-Word-MCP-Server"> |
| 8 | <img width |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal ā confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "mcpServers": { |
| 3 | "word-document-server": { |
| 4 | "command": "/Users/gongzhe/GitRepos/Office-Word-MCP-Server/.venv/bin/python", |
| 5 | "args": [ |
| 6 | "/Users/gongzhe/GitRepos/Office-Word-MCP-Server/word_mcp_server.py" |
| 7 | ], |
| 8 | "env": { |
| 9 | "PYTHONPATH": "/Users/gongzhe/GitRepos/Office-Word-MCP-Server", |
| 10 | "MCP_TRANSPORT": "stdio" |
| 11 | } |
| 12 | } |
| 13 | } |
| 14 | } |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable ā a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 11 | - uses: actions/checkout@v4 |
| 12 | |
| 13 | - name: Set up Python |
| 14 | uses: actions/setup-python@v5 |
| 15 | with: |
| 16 | python-version: "3.11" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version ā Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable ā a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 8 | publish: |
| 9 | runs-on: ubuntu-latest |
| 10 | steps: |
| 11 | - uses: actions/checkout@v4 |
| 12 | |
| 13 | - name: Set up Python |
| 14 | uses: actions/setup-python@v5 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version ā Dependabot can do this automatically with `version-update-strategy: inc
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines ā without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | # Import necessary Python standard libraries |
| 2 | import os |
| 3 | import json |
| 4 | import subprocess |
| 5 | import sys |
| 6 | import shutil |
| 7 | import platform |
| 8 | |
| 9 | def check_prerequisites(): |
| 10 | """ |
| 11 | Check if necessary prerequisites are installed |
| 12 | |
| 13 | Returns: |
| 14 | tuple: (python_ok, uv_installed, uvx_installed, word_server_installed) |
| 15 | """ |
| 16 | # Check Python version |
| 17 | python_version = sys.version_info |
| 18 | python_ok = python_version.major >= 3 and python_version.minor >= 8 |
| 19 | |
| 20 | # |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` ā fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines ā without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | """ |
| 2 | Unprotect document functionality for the Word Document Server. |
| 3 | |
| 4 | This module handles removing document protection. |
| 5 | """ |
| 6 | import os |
| 7 | import json |
| 8 | import hashlib |
| 9 | import tempfile |
| 10 | import shutil |
| 11 | from typing import Tuple, Optional |
| 12 | |
| 13 | def remove_protection_info(filename: str, password: Optional[str] = None) -> Tuple[bool, str]: |
| 14 | """ |
| 15 | Remove protection information from a document and decrypt it if necessary. |
| 16 | |
| 17 | Args: |
| 18 | filename: Path to the Word document |
| 19 | password: Password to veri |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` ā fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines ā without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | """ |
| 2 | Consolidated footnote functionality for Word documents. |
| 3 | This module combines all footnote implementations with proper namespace handling and Word compliance. |
| 4 | """ |
| 5 | |
| 6 | import os |
| 7 | import zipfile |
| 8 | import tempfile |
| 9 | from typing import Optional, Tuple, Dict, Any, List |
| 10 | from lxml import etree |
| 11 | from docx import Document |
| 12 | from docx.oxml.ns import qn |
| 13 | |
| 14 | # Namespace definitions |
| 15 | W_NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main' |
| 16 | R_NS = 'http://schemas.openxmlformats.org/officeDocument/2006/relati |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` ā fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines ā without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | """ |
| 2 | Protection tools for Word Document Server. |
| 3 | |
| 4 | These tools handle document protection features such as |
| 5 | password protection, restricted editing, and digital signatures. |
| 6 | """ |
| 7 | import os |
| 8 | import hashlib |
| 9 | import datetime |
| 10 | import io |
| 11 | from typing import List, Optional, Dict, Any |
| 12 | from docx import Document |
| 13 | import msoffcrypto |
| 14 | |
| 15 | from word_document_server.utils.file_utils import check_file_writeable, ensure_docx_extension |
| 16 | |
| 17 | |
| 18 | |
| 19 | from word_document_server.core.protection import ( |
| 20 | add_protection_info, |
| 21 | verify |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` ā fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines ā without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | """ |
| 2 | Document protection functionality for Word Document Server. |
| 3 | """ |
| 4 | import os |
| 5 | import json |
| 6 | import hashlib |
| 7 | import datetime |
| 8 | from typing import Dict, List, Tuple, Optional, Any |
| 9 | |
| 10 | |
| 11 | def add_protection_info(doc_path: str, protection_type: str, password_hash: str, |
| 12 | sections: Optional[List[str]] = None, |
| 13 | signature_info: Optional[Dict[str, Any]] = None, |
| 14 | raw_password: Optional[str] = None) -> bool: |
| 15 | """ |
| 16 | Add document protection in |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` ā fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
MCP tool file registers a tool, performs a destructive sink (fs.unlink / shutil.rmtree / DROP TABLE / DELETE FROM / TRUNCATE / UPDATE ... SET / HTTP DELETE|PUT|PATCH / subprocess / exec / spawn), and emits no audit event anywhere in the file. Without an audit event, an investigator cannot answer "who deleted record X on day Y?" ā the irreversible action leaves no trail. Closes the OWASP MCP Top 10:2025 MCP08 (Lack of Audit and Telemetry) gap. Distinct from MCP-201 (no confirmation) and MCP-283
Evidence
| 1 | # Import necessary Python standard libraries |
| 2 | import os |
| 3 | import json |
| 4 | import subprocess |
| 5 | import sys |
| 6 | import shutil |
| 7 | import platform |
| 8 | |
| 9 | def check_prerequisites(): |
| 10 | """ |
| 11 | Check if necessary prerequisites are installed |
| 12 | |
| 13 | Returns: |
| 14 | tuple: (python_ok, uv_installed, uvx_installed, word_server_installed) |
| 15 | """ |
| 16 | # Check Python version |
| 17 | python_version = sys.version_info |
| 18 | python_ok = python_version.major >= 3 and python_version.minor >= 8 |
| 19 | |
| 20 | # |
Remediation
Add a structured audit-event emit immediately after every destructive sink. Minimum schema: actor, action, target, outcome, request_id. Python: from acme.audit import audit_log @mcp.tool() def delete_record(token: str, record_id: str) -> dict: actor = verify_token(token) db.execute("DELETE FROM records WHERE id = %s", (record_id,)) audit_log( actor=actor.sub, action="delete_record", target=record_id, outcome=
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 103 | # Apply style if available |
| 104 | if footnote_style: |
| 105 | try: |
| 106 | ref['paragraph'].style = footnote_style |
| 107 | except: |
| 108 | pass |
| 109 | count += 1 |
| 110 | return count |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 124 | fn_id = fn.get(f'{{{W_NS}}}id') |
| 125 | if fn_id: |
| 126 | try: |
| 127 | used_ids.add(int(fn_id)) |
| 128 | except ValueError: |
| 129 | pass |
| 130 | |
| 131 | # Start from 2 to avoid reserved IDs |
| 132 | candidate_id = 2 |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 370 | # Copy style if possible |
| 371 | try: |
| 372 | if paragraph.style: |
| 373 | p.style = paragraph.style.name |
| 374 | except: |
| 375 | pass |
| 376 | |
| 377 | # Copy tables |
| 378 | for table in doc.tables: |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 128 | except: |
| 129 | # Fall back to default grid style |
| 130 | try: |
| 131 | new_table.style = 'Table Grid' |
| 132 | except: |
| 133 | pass |
| 134 | |
| 135 | # Copy cell contents |
| 136 | for i, row in enumerate(source_table.rows): |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 249 | r = int(text_color[0:2], 16) |
| 250 | g = int(text_color[2:4], 16) |
| 251 | b = int(text_color[4:6], 16) |
| 252 | run.font.color.rgb = RGBColor(r, g, b) |
| 253 | except: |
| 254 | pass # Skip if color format is invalid |
| 255 | return True |
| 256 | except Exception as e: |
| 257 | print(f"Error highlighting header row: {e}") |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 182 | # Try to match the style if possible |
| 183 | try: |
| 184 | if paragraph.style and paragraph.style.name in target_doc.styles: |
| 185 | new_paragraph.style = target_doc.styles[paragraph.style.name] |
| 186 | except: |
| 187 | pass |
| 188 | |
| 189 | # Copy run formatting |
| 190 | for i, run in enumerate(paragraph.runs): |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.