Use with caution. Address findings before production.
Scanned 5/3/2026, 7:19:53 PMยทCached resultยทFast Scanยท45 rulesยทHow we decide โ
AIVSS Score
Medium
Severity Breakdown
0
critical
7
high
122
medium
14
low
MCP Server Information
Findings
This package has a security grade of C and a safety score of 60/100, indicating a moderate level of risk. It contains seven high-severity vulnerabilities, including issues related to server configuration and verbose errors, which could expose sensitive information or lead to potential exploitation. Given the number of medium and low severity findings, careful consideration and remediation of these issues are advisable before installation.
No known CVEs found for this package or its dependencies.
Scan Details
Want deeper analysis?
Fast scan found 27 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.
27 of 27 findings
27 findings
MCP tool with a destructive-sounding name is registered without the `destructiveHint: true` annotation. Clients rely on this hint to gate user confirmation, so the model may invoke the tool silently. Add `{ annotations: { destructiveHint: true } }` (TS) or `destructive_hint=True` on the tool decorator (Python).
Evidence
| 1 | /** |
| 2 | * Component management tools for KiCAD MCP server |
| 3 | */ |
| 4 | |
| 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 6 | import { z } from "zod"; |
| 7 | import { logger } from "../logger.js"; |
| 8 | |
| 9 | // Command function type for KiCAD script calls |
| 10 | type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>; |
| 11 | |
| 12 | /** |
| 13 | * Register component management tools with the MCP server |
| 14 | * |
| 15 | * @param server MCP server instance |
| 16 | * @param callKicadScript Function to call KiCAD script command |
Remediation
Missing annotation: add `{ annotations: { destructiveHint: true } }` as the 4th argument to `server.tool(...)` (TS) or pass `destructive_hint=True` to the `@mcp.tool(...)` decorator (Python). readOnly-lying: remove the `readOnlyHint: true` / `read_only_hint=True` assertion, or refactor the handler so it no longer calls the destructive sink. Do not suppress with a waiver โ clients rely on these hints for user-facing confirmation.
MCP tool with a destructive-sounding name is registered without the `destructiveHint: true` annotation. Clients rely on this hint to gate user confirmation, so the model may invoke the tool silently. Add `{ annotations: { destructiveHint: true } }` (TS) or `destructive_hint=True` on the tool decorator (Python).
Evidence
| 1 | /** |
| 2 | * Routing tools for KiCAD MCP server |
| 3 | */ |
| 4 | |
| 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 6 | import { z } from "zod"; |
| 7 | |
| 8 | export function registerRoutingTools(server: McpServer, callKicadScript: Function) { |
| 9 | // Add net tool |
| 10 | server.tool( |
| 11 | "add_net", |
| 12 | "Create a new net on the PCB", |
| 13 | { |
| 14 | name: z.string().describe("Net name"), |
| 15 | netClass: z.string().optional().describe("Net class name"), |
| 16 | }, |
| 17 | async (args: { name: string; netClass?: string }) => { |
| 18 | c |
Remediation
Missing annotation: add `{ annotations: { destructiveHint: true } }` as the 4th argument to `server.tool(...)` (TS) or pass `destructive_hint=True` to the `@mcp.tool(...)` decorator (Python). readOnly-lying: remove the `readOnlyHint: true` / `read_only_hint=True` assertion, or refactor the handler so it no longer calls the destructive sink. Do not suppress with a waiver โ clients rely on these hints for user-facing confirmation.
MCP tool with a destructive-sounding name is registered without the `destructiveHint: true` annotation. Clients rely on this hint to gate user confirmation, so the model may invoke the tool silently. Add `{ annotations: { destructiveHint: true } }` (TS) or `destructive_hint=True` on the tool decorator (Python).
Evidence
| 1 | /** |
| 2 | * Schematic tools for KiCAD MCP server |
| 3 | */ |
| 4 | |
| 5 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 6 | import { z } from "zod"; |
| 7 | |
| 8 | export function registerSchematicTools(server: McpServer, callKicadScript: Function) { |
| 9 | // Create schematic tool |
| 10 | server.tool( |
| 11 | "create_schematic", |
| 12 | "Create a new schematic", |
| 13 | { |
| 14 | name: z.string().describe("Schematic name"), |
| 15 | path: z.string().optional().describe("Optional path"), |
| 16 | }, |
| 17 | async (args: { name: string; path?: string } |
Remediation
Missing annotation: add `{ annotations: { destructiveHint: true } }` as the 4th argument to `server.tool(...)` (TS) or pass `destructive_hint=True` to the `@mcp.tool(...)` decorator (Python). readOnly-lying: remove the `readOnlyHint: true` / `read_only_hint=True` assertion, or refactor the handler so it no longer calls the destructive sink. Do not suppress with a waiver โ clients rely on these hints for user-facing confirmation.
MCP tool with a destructive-sounding name is registered without the `destructiveHint: true` annotation. Clients rely on this hint to gate user confirmation, so the model may invoke the tool silently. Add `{ annotations: { destructiveHint: true } }` (TS) or `destructive_hint=True` on the tool decorator (Python).
Evidence
| 1 | /** |
| 2 | * Symbol creator tools for KiCAD MCP server |
| 3 | * |
| 4 | * create_symbol โ add a new symbol to a .kicad_sym library |
| 5 | * delete_symbol โ remove a symbol from a library |
| 6 | * list_symbols_in_library โ list all symbols in a .kicad_sym file |
| 7 | * register_symbol_library โ add library to sym-lib-table |
| 8 | */ |
| 9 | |
| 10 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 11 | import { z } from "zod"; |
| 12 | |
| 13 | const PinSchema = z.object({ |
| 14 | name: z.string().describe("Pin name, e.g. 'VCC', ' |
Remediation
Missing annotation: add `{ annotations: { destructiveHint: true } }` as the 4th argument to `server.tool(...)` (TS) or pass `destructive_hint=True` to the `@mcp.tool(...)` decorator (Python). readOnly-lying: remove the `readOnlyHint: true` / `read_only_hint=True` assertion, or refactor the handler so it no longer calls the destructive sink. Do not suppress with a waiver โ clients rely on these hints for user-facing confirmation.
XML parser configured without entity expansion disabled. XXE (XML external entity) attacks can read local files, exfiltrate data, and cause SSRF when the parser resolves external entities.
Evidence
| 4098 | import xml.etree.ElementTree as ET |
| 4099 | |
| 4100 | tree = ET.parse(svg_output) |
| 4101 | root = tree.getroot() |
| 4102 | |
| 4103 | # KiCad schematic SVGs use mm as viewBox units directly |
Remediation
Use defusedxml in Python (drop-in replacement for stdlib XML modules). In Java, set XMLConstants.FEATURE_SECURE_PROCESSING=true and disable external-DTD features on the factory before parsing.
XML parser configured without entity expansion disabled. XXE (XML external entity) attacks can read local files, exfiltrate data, and cause SSRF when the parser resolves external entities.
Evidence
| 540 | try: |
| 541 | # --- 1. Parse SVG --- |
| 542 | tree = ET.parse(svg_path) |
| 543 | root = tree.getroot() |
| 544 | |
| 545 | # Determine SVG viewport |
Remediation
Use defusedxml in Python (drop-in replacement for stdlib XML modules). In Java, set XMLConstants.FEATURE_SECURE_PROCESSING=true and disable external-DTD features on the factory before parsing.
XML parser configured without entity expansion disabled. XXE (XML external entity) attacks can read local files, exfiltrate data, and cause SSRF when the parser resolves external entities.
Evidence
| 3750 | "message": f"kicad-cli failed (exit {result.returncode}): {result.stderr.strip()}", |
| 3751 | } |
| 3752 | |
| 3753 | tree = ET.parse(tmp_path) |
| 3754 | root = tree.getroot() |
| 3755 | |
| 3756 | components = [] |
Remediation
Use defusedxml in Python (drop-in replacement for stdlib XML modules). In Java, set XMLConstants.FEATURE_SECURE_PROCESSING=true and disable external-DTD features on the factory before parsing.
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
| 152 | PYTHONPATH_VALUE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["purelib"])' "$DETECT_JSON")" |
| 153 | PCBNEW_OK="$(python3 -c 'import json,sys; print("true" if json.loads(sys.argv[1])["pcbnew_ok"] else "false")' "$DETECT_JSON")" |
| 154 | PCBNEW_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["pcbnew_version"] or "")' "$DETECT_JSON")" |
| 155 | PCBNEW_ERROR="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["pcbnew_error"] or "")' "$DETECT_JSON")" |
| 156 | |
| 157 | if [[ "$PCBNEW_OK" != "true |
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
| 481 | }, 30000); // 30 seconds timeout |
| 482 | |
| 483 | // Write the request to the Python process |
| 484 | console.error(`Sending request: ${requestStr}`); |
| 485 | this.pythonProcess?.stdin?.write(requestStr + "\n"); |
| 486 | } catch (error) { |
| 487 | console.error(`Error processing request: ${error}`); |
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
| 463 | // Set a timeout |
| 464 | const timeout = setTimeout(() => { |
| 465 | console.error(`Command timeout: ${request.command}`); |
| 466 | |
| 467 | // Clear listeners |
| 468 | if (this.pythonProcess?.stdout) { |
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
| 484 | console.error(`Sending request: ${requestStr}`); |
| 485 | this.pythonProcess?.stdin?.write(requestStr + "\n"); |
| 486 | } catch (error) { |
| 487 | console.error(`Error processing request: ${error}`); |
| 488 | |
| 489 | // Reset processing flag |
| 490 | this.processingRequest = false; |
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
| 150 | PYTHON_EXE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_executable"])' "$DETECT_JSON")" |
| 151 | PYTHON_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_version"])' "$DETECT_JSON")" |
| 152 | PYTHONPATH_VALUE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["purelib"])' "$DETECT_JSON")" |
| 153 | PCBNEW_OK="$(python3 -c 'import json,sys; print("true" if json.loads(sys.argv[1])["pcbnew_ok"] else "false")' "$DETECT_JSON")" |
| 154 | PCBNEW_VERSION="$(python3 -c 'import |
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
| 149 | PYTHON_EXE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_executable"])' "$DETECT_JSON")" |
| 150 | PYTHON_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_version"])' "$DETECT_JSON")" |
| 151 | PYTHONPATH_VALUE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["purelib"])' "$DETECT_JSON")" |
| 152 | PCBNEW_OK="$(python3 -c 'import json,sys; print("true" if json.loads(sys.argv[1])["pcbnew_ok"] else "false")' "$DETECT_JSON")" |
| 153 | PCBNEW_VERSION="$(python3 -c 'import |
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
| 151 | PYTHON_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_version"])' "$DETECT_JSON")" |
| 152 | PYTHONPATH_VALUE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["purelib"])' "$DETECT_JSON")" |
| 153 | PCBNEW_OK="$(python3 -c 'import json,sys; print("true" if json.loads(sys.argv[1])["pcbnew_ok"] else "false")' "$DETECT_JSON")" |
| 154 | PCBNEW_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["pcbnew_version"] or "")' "$DETECT_JSON")" |
| 155 | PCBNEW_ERROR="$(python3 -c 'im |
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
| 419 | const result = JSON.parse(responseData); |
| 420 | |
| 421 | // If we get here, we have a valid JSON response |
| 422 | console.error( |
| 423 | `Completed KiCAD command: ${request.command} with result: ${JSON.stringify(result)}`, |
| 424 | ); |
| 425 | |
| 426 | // Reset processing flag |
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
| 393 | const { request, resolve, reject } = this.requestQueue.shift()!; |
| 394 | |
| 395 | try { |
| 396 | console.error(`Processing KiCAD command: ${request.command}`); |
| 397 | |
| 398 | // Format the command and parameters as JSON |
| 399 | const requestStr = JSON.stringify(request); |
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
| 148 | )" |
| 149 | |
| 150 | PYTHON_EXE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_executable"])' "$DETECT_JSON")" |
| 151 | PYTHON_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_version"])' "$DETECT_JSON")" |
| 152 | PYTHONPATH_VALUE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["purelib"])' "$DETECT_JSON")" |
| 153 | PCBNEW_OK="$(python3 -c 'import json,sys; print("true" if json.loads(sys.argv[1])["pcbnew_ok"] else "false")' "$DETECT_JSON")" |
| 154 | PCBNEW_VERSION="$(python3 -c 'imp |
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
| 519 | usage = response.usage |
| 520 | if hasattr(usage, "cache_creation_input_tokens"): |
| 521 | print( |
| 522 | f" Tokens โ input: {usage.input_tokens}, " |
| 523 | f"cache_write: {usage.cache_creation_input_tokens}, " |
| 524 | f"cache_read: {usage.cache_read_input_tokens}, " |
| 525 | f"output: {usage.output_tokens}" |
| 526 | ) |
| 527 | |
| 528 | new_annotations = _parse_response(response.content[0].text) |
| 529 | result = dict(existing) |
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
| 147 | PY |
| 148 | )" |
| 149 | |
| 150 | PYTHON_EXE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_executable"])' "$DETECT_JSON")" |
| 151 | PYTHON_VERSION="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["python_version"])' "$DETECT_JSON")" |
| 152 | PYTHONPATH_VALUE="$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["purelib"])' "$DETECT_JSON")" |
| 153 | PCBNEW_OK="$(python3 -c 'import json,sys; print("true" if json.loads(sys.argv[1])["pcbnew_ok"] else "false")' "$DETECT_JSON")" |
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
| 4208 | import traceback |
| 4209 | |
| 4210 | logger.error(traceback.format_exc()) |
| 4211 | return {"success": False, "message": str(e)} |
| 4212 | |
| 4213 | def _handle_find_wires_crossing_symbols(self, params: Dict[str, Any]) -> Dict[str, Any]: |
| 4214 | """Find wires that cross over component symbol bodies""" |
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
| 4806 | } |
| 4807 | except Exception as e: |
| 4808 | logger.error(f"IPC get_board_info error: {e}") |
| 4809 | return {"success": False, "message": str(e)} |
| 4810 | |
| 4811 | def _ipc_place_component(self, params: Dict[str, Any]) -> Dict[str, Any]: |
| 4812 | """IPC handler for place_component - places component with real-time UI update""" |
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
| 4259 | import traceback |
| 4260 | |
| 4261 | logger.error(traceback.format_exc()) |
| 4262 | return {"success": False, "message": str(e)} |
| 4263 | |
| 4264 | def _handle_list_floating_labels(self, params: Dict[str, Any]) -> Dict[str, Any]: |
| 4265 | """List net labels that are not connected to any component pin""" |
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.
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
| 505 | timeout=10, |
| 506 | ) |
| 507 | java_version = (proc.stderr or proc.stdout).strip().split("\n")[0] |
| 508 | java_21_ok = _java_version_ok(java_exe) |
| 509 | except Exception: |
| 510 | pass |
| 511 | |
| 512 | # Check Docker/Podman |
| 513 | docker_exe = _find_docker() |
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
| 3868 | for _pin_num, (px, py) in all_pins.items(): |
| 3869 | k = snap(px, py) |
| 3870 | point_net[k] = net_name |
| 3871 | all_net_names.add(net_name) |
| 3872 | except Exception: |
| 3873 | pass |
| 3874 | |
| 3875 | # โโ 2. Build wire adjacency and BFS-propagate net names โโโโโโโโโโ |
| 3876 | wire_segments = [] |
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
| 3778 | finally: |
| 3779 | try: |
| 3780 | os.unlink(tmp_path) |
| 3781 | except OSError: |
| 3782 | pass |
| 3783 | |
| 3784 | except FileNotFoundError: |
| 3785 | return {"success": False, "message": "kicad-cli not found in PATH"} |
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
| 220 | try: |
| 221 | backend = IPCBackend() |
| 222 | if backend.connect(): |
| 223 | return backend |
| 224 | except ImportError: |
| 225 | pass |
| 226 | |
| 227 | # Fall back to SWIG |
| 228 | return SWIGBackend() |
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
| 79 | if "version" in line: |
| 80 | ver = line.split('"')[1] if '"' in line else "" |
| 81 | major = int(ver.split(".")[0]) |
| 82 | return major >= 21 |
| 83 | except Exception: |
| 84 | pass |
| 85 | return False |
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.
place_component
add_net
create_schematic
create_symbol