Mostly safe ā a couple of notes worth reading.
Scanned 7/24/2026, 10:26:10 PMĀ·Cached resultĀ·Fast ScanĀ·48 rulesĀ·How we decide ā
AIVSS Score
Low
Severity Breakdown
0
critical
0
high
80
medium
6
low
MCP Server Information
Findings
This package has a B security grade with a safety score of 79/100 and carries moderate risk due to 80 medium-severity findings, primarily related to verbose error handling (69 instances) that could leak sensitive information. The main concerns are verbose errors, readiness issues (6 findings), and resource exhaustion vulnerabilities (5 findings), though no critical or high-severity flaws were detected. Installation is acceptable with awareness of these moderate issues, particularly around error message handling and resource management.
No known CVEs found for this package or its dependencies.
Scan Details
Want deeper analysis?
Fast scan found 86 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
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 86 findings
86 findings
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. " |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
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": |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
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, |
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
| 451 | ) |
| 452 | return message |
| 453 | except Exception as e: |
| 454 | return f"Failed to delete footnote: {str(e)}" |
| 455 | |
| 456 | |
| 457 | # ============================================================================ |
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
| 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, |
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
| 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, |
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 | "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: |
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
| 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, |
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
| 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)}" |
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
| 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. |
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
| 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, |
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
| 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, |
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
| 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]: |
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
| 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", |
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
| 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]]: |
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
| 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: |
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
| 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.""" |
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 | 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: |
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
| 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.
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
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.