High risk. Don't ship without significant remediation.
Scanned 5/18/2026, 5:29:46 PMยทCached resultยทDeep Scanยท91 rulesยทHow we decide โ
AIVSS Score
High
Severity Breakdown
0
critical
12
high
10
medium
0
low
MCP Server Information
Findings
This package carries a D security grade with 12 high-severity findings, primarily centered on prompt injection vulnerabilities (8 instances) and server configuration issues (7 instances) that could allow attackers to manipulate tool behavior or exploit misconfigurations. The combination of prompt injection risks and tool poisoning weaknesses means this package could be compromised to execute unintended actions or exfiltrate data, making it unsuitable for production use without significant hardening.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 โ 22 of 22 findings. Click any finding to read.
Dependencies
requests (2)
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.
22 of 22 findings
22 findings
rename_function accepts old_name and new_name identifiers and renames a function by old name alone, with no ownership or access control check.
Evidence
| 72 | return safe_get("classes", {"offset": offset, "limit": limit}) |
| 73 | |
| 74 | @mcp.tool() |
| 75 | def decompile_function(name: str) -> str: |
| 76 | """ |
| 77 | Decompile a specific function by name and return the decompiled C code. |
| 78 | """ |
| 79 | return safe_post("decompile", name) |
| 80 | |
| 81 | @mcp.tool() |
| 82 | def rename_function(old_name: str, new_name: str) -> str: |
RemediationAI
The rename_function tool accepts old_name and new_name parameters and directly posts them to the renameFunction endpoint with no authorization check, allowing any caller to rename any function in the Ghidra database. Add an access control layer by implementing a function ownership registry and checking that the caller has permission before calling safe_post(). Wrap the safe_post() call with a guard function like `check_function_ownership(old_name, caller_id)` that validates the caller owns or has write access to the target function. Verify the fix by attempting to rename a function as an unprivileged user and confirming the request is rejected with a 403 Forbidden response.
LLM consensus
decompile_function accepts a function name identifier and returns decompiled code by posting to 'decompile' endpoint with only the name as filter, with no ownership or access control check.
Evidence
| 65 | return safe_get("methods", {"offset": offset, "limit": limit}) |
| 66 | |
| 67 | @mcp.tool() |
| 68 | def list_classes(offset: int = 0, limit: int = 100) -> list: |
| 69 | """ |
| 70 | List all namespace/class names in the program with pagination. |
| 71 | """ |
| 72 | return safe_get("classes", {"offset": offset, "limit": limit}) |
| 73 | |
| 74 | @mcp.tool() |
| 75 | def decompile_function(name: str) -> str: |
RemediationAI
The decompile_function tool accepts a function name and returns decompiled C code by posting to the decompile endpoint with only the name as filter, with no ownership or access control check. Add an access control layer by implementing a function read-permission check before decompilation; wrap the safe_post("decompile", name) call with a guard function like `check_function_read_access(name, caller_id)` that validates the caller has permission to view the function's decompiled code. This prevents unauthorized callers from extracting sensitive decompiled logic. Verify by attempting to decompile a restricted function as an unprivileged user and confirming the request is rejected.
LLM consensus
rename_data accepts an address identifier and renames a data label at that address with only the address as filter, with no ownership or access control check.
Evidence
| 79 | return safe_post("decompile", name) |
| 80 | |
| 81 | @mcp.tool() |
| 82 | def rename_function(old_name: str, new_name: str) -> str: |
| 83 | """ |
| 84 | Rename a function by its current name to a new user-defined name. |
| 85 | """ |
| 86 | return safe_post("renameFunction", {"oldName": old_name, "newName": new_name}) |
| 87 | |
| 88 | @mcp.tool() |
| 89 | def rename_data(address: str, new_name: str) -> str: |
RemediationAI
The rename_data tool accepts an address identifier and renames a data label at that address with only the address as filter, with no ownership or access control check. Add an access control layer by implementing a data-label ownership registry and checking permissions before calling safe_post("renameData", ...); wrap the call with `check_data_ownership(address, caller_id)` to validate the caller has write access to the target data label. This prevents unauthorized renaming of critical data symbols. Verify by attempting to rename a data label at a restricted address as an unprivileged user and confirming rejection.
LLM consensus
Tool 'rename_function' performs NETWORK side effect (POST request to Ghidra server) that is not disclosed in description.
Evidence
| 70 | List all namespace/class names in the program with pagination. |
| 71 | """ |
| 72 | return safe_get("classes", {"offset": offset, "limit": limit}) |
| 73 | |
| 74 | @mcp.tool() |
| 75 | def decompile_function(name: str) -> str: |
| 76 | """ |
| 77 | Decompile a specific function by name and return the decompiled C code. |
| 78 | """ |
| 79 | return safe_post("decompile", name) |
RemediationAI
The rename_function tool description states 'Rename a function by its current name to a new user-defined name' but does not disclose that it performs a POST request (network side effect) to the Ghidra server, which modifies state. Update the docstring to explicitly state: 'Rename a function by its current name to a new user-defined name. **This tool modifies the Ghidra database via a POST request to the server.**' Adding this disclosure ensures callers understand the tool has side effects and is not read-only. Verify the updated docstring appears in the tool's metadata by calling the MCP tools/list endpoint and confirming the description includes the side-effect warning.
LLM consensus
Tool 'rename_data' performs NETWORK side effect (POST request to Ghidra server) that is not disclosed in description.
Evidence
| 75 | def decompile_function(name: str) -> str: |
| 76 | """ |
| 77 | Decompile a specific function by name and return the decompiled C code. |
| 78 | """ |
| 79 | return safe_post("decompile", name) |
| 80 | |
| 81 | @mcp.tool() |
| 82 | def rename_function(old_name: str, new_name: str) -> str: |
| 83 | """ |
| 84 | Rename a function by its current name to a new user-defined name. |
| 85 | """ |
RemediationAI
The rename_data tool description states 'Rename a data label at the specified address' but does not disclose that it performs a POST request (network side effect) to the Ghidra server, which modifies state. Update the docstring to explicitly state: 'Rename a data label at the specified address. **This tool modifies the Ghidra database via a POST request to the server.**' Adding this disclosure ensures callers understand the tool has side effects and is not read-only. Verify the updated docstring appears in the tool's metadata by calling the MCP tools/list endpoint and confirming the description includes the side-effect warning.
LLM consensus
Tool 'decompile_function' fetches untrusted decompiled C code from external Ghidra server and returns it verbatim without provenance markers, enabling indirect prompt injection.
Evidence
| 65 | return safe_get("methods", {"offset": offset, "limit": limit}) |
| 66 | |
| 67 | @mcp.tool() |
| 68 | def list_classes(offset: int = 0, limit: int = 100) -> list: |
| 69 | """ |
| 70 | List all namespace/class names in the program with pagination. |
| 71 | """ |
| 72 | return safe_get("classes", {"offset": offset, "limit": limit}) |
| 73 | |
| 74 | @mcp.tool() |
| 75 | def decompile_function(name: str) -> str: |
RemediationAI
The decompile_function tool fetches untrusted decompiled C code from the external Ghidra server and returns it verbatim without provenance markers, enabling indirect prompt injection if the decompiled code contains malicious instructions. Wrap the return value with a provenance marker by changing `return safe_post("decompile", name)` to `return f"[Ghidra decompile output for {name}]\n{safe_post('decompile', name)}"` to clearly tag the external origin. This signals to the LLM that the content is untrusted and sourced from an external tool. Verify by inspecting the decompile_function output and confirming it includes the provenance prefix.
LLM consensus
Tool 'list_segments' fetches untrusted memory segment information from external Ghidra server and returns it verbatim without provenance markers, enabling indirect prompt injection.
Evidence
| 86 | return safe_post("renameFunction", {"oldName": old_name, "newName": new_name}) |
| 87 | |
| 88 | @mcp.tool() |
| 89 | def rename_data(address: str, new_name: str) -> str: |
| 90 | """ |
| 91 | Rename a data label at the specified address. |
| 92 | """ |
| 93 | return safe_post("renameData", {"address": address, "newName": new_name}) |
| 94 | |
| 95 | @mcp.tool() |
| 96 | def list_segments(offset: int = 0, limit: int = 100) -> list: |
RemediationAI
The list_segments tool fetches untrusted memory segment information from the external Ghidra server and returns it verbatim without provenance markers, enabling indirect prompt injection. Wrap the return value with a provenance marker by changing `return safe_get("segments", ...)` to `return [f"[Ghidra segment data] {item}" for item in safe_get("segments", ...)]` to clearly tag each segment as external and untrusted. This signals to the LLM that the content originates from an external tool. Verify by inspecting list_segments output and confirming each item includes the provenance prefix.
LLM consensus
Tool 'list_methods' fetches untrusted decompiled function names from external Ghidra server and returns them verbatim without provenance markers, enabling indirect prompt injection.
Evidence
| 51 | response = requests.post(url, data=data.encode("utf-8"), timeout=5) |
| 52 | response.encoding = 'utf-8' |
| 53 | if response.ok: |
| 54 | return response.text.strip() |
| 55 | else: |
| 56 | return f"Error {response.status_code}: {response.text.strip()}" |
| 57 | except Exception as e: |
| 58 | return f"Request failed: {str(e)}" |
| 59 | |
| 60 | @mcp.tool() |
| 61 | def list_methods(offset: int = 0, limit: int = 100) -> list: |
RemediationAI
The list_methods tool fetches untrusted decompiled function names from the external Ghidra server and returns them verbatim without provenance markers, enabling indirect prompt injection. Wrap the return value with a provenance marker by changing `return safe_get("methods", ...)` to `return [f"[Ghidra method] {item}" for item in safe_get("methods", ...)]` to clearly tag each method name as external and untrusted. This signals to the LLM that the content originates from an external tool. Verify by inspecting list_methods output and confirming each item includes the provenance prefix.
LLM consensus
Tool 'list_imports' fetches untrusted imported symbol information from external Ghidra server and returns it verbatim without provenance markers, enabling indirect prompt injection.
Evidence
| 93 | return safe_post("renameData", {"address": address, "newName": new_name}) |
| 94 | |
| 95 | @mcp.tool() |
| 96 | def list_segments(offset: int = 0, limit: int = 100) -> list: |
| 97 | """ |
| 98 | List all memory segments in the program with pagination. |
| 99 | """ |
| 100 | return safe_get("segments", {"offset": offset, "limit": limit}) |
| 101 | |
| 102 | @mcp.tool() |
| 103 | def list_imports(offset: int = 0, limit: int = 100) -> list: |
RemediationAI
The list_imports tool fetches untrusted imported symbol information from the external Ghidra server and returns it verbatim without provenance markers, enabling indirect prompt injection. Wrap the return value with a provenance marker by changing `return safe_get("imports", ...)` to `return [f"[Ghidra import] {item}" for item in safe_get("imports", ...)]` to clearly tag each import as external and untrusted. This signals to the LLM that the content originates from an external tool. Verify by inspecting list_imports output and confirming each item includes the provenance prefix.
LLM consensus
Tool 'list_classes' fetches untrusted namespace/class names from external Ghidra server and returns them verbatim without provenance markers, enabling indirect prompt injection.
Evidence
| 58 | return f"Request failed: {str(e)}" |
| 59 | |
| 60 | @mcp.tool() |
| 61 | def list_methods(offset: int = 0, limit: int = 100) -> list: |
| 62 | """ |
| 63 | List all function names in the program with pagination. |
| 64 | """ |
| 65 | return safe_get("methods", {"offset": offset, "limit": limit}) |
| 66 | |
| 67 | @mcp.tool() |
| 68 | def list_classes(offset: int = 0, limit: int = 100) -> list: |
RemediationAI
The list_classes tool fetches untrusted namespace/class names from the external Ghidra server and returns them verbatim without provenance markers, enabling indirect prompt injection. Wrap the return value with a provenance marker by changing `return safe_get("classes", ...)` to `return [f"[Ghidra class] {item}" for item in safe_get("classes", ...)]` to clearly tag each class name as external and untrusted. This signals to the LLM that the content originates from an external tool. Verify by inspecting list_classes output and confirming each item includes the provenance prefix.
LLM consensus
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 | # /// script |
| 2 | # requires-python = ">=3.10" |
| 3 | # dependencies = [ |
| 4 | # "requests>=2,<3", |
| 5 | # "mcp>=1.2.0,<2", |
| 6 | # ] |
| 7 | # /// |
| 8 | |
| 9 | import sys |
| 10 | import requests |
| 11 | import argparse |
| 12 | import logging |
| 13 | from urllib.parse import urljoin |
| 14 | |
| 15 | from mcp.server.fastmcp import FastMCP |
| 16 | |
| 17 | DEFAULT_GHIDRA_SERVER = "http://127.0.0.1:8080/" |
| 18 | |
| 19 | logger = logging.getLogger(__name__) |
| 20 | |
| 21 | mcp = FastMCP("ghidra-mcp") |
| 22 | |
| 23 | # Initialize ghidra_server_url with default value |
| 24 | ghidra_server_url = DEFAULT_GHIDRA_SERVER |
| 25 | |
| 26 | def safe_get(endpoint: str, params: dic |
RemediationAI
The MCP server binds an HTTP transport to localhost without authentication, making it vulnerable to DNS-rebinding attacks where a malicious web page can invoke tools from the user's browser. Switch to stdio transport by changing the server startup from `mcp.run(transport="http", port=...)` to `mcp.run(transport="stdio")`, which uses standard input/output instead of HTTP and eliminates network-based attack surface. Stdio transport is immune to DNS rebinding because it has no network listener. Verify the fix by confirming the server no longer listens on any TCP port and can only be invoked via stdin/stdout.
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 | # /// script |
| 2 | # requires-python = ">=3.10" |
| 3 | # dependencies = [ |
| 4 | # "requests>=2,<3", |
| 5 | # "mcp>=1.2.0,<2", |
| 6 | # ] |
| 7 | # /// |
| 8 | |
| 9 | import sys |
| 10 | import requests |
| 11 | import argparse |
| 12 | import logging |
| 13 | from urllib.parse import urljoin |
| 14 | |
| 15 | from mcp.server.fastmcp import FastMCP |
| 16 | |
| 17 | DEFAULT_GHIDRA_SERVER = "http://127.0.0.1:8080/" |
| 18 | |
| 19 | logger = logging.getLogger(__name__) |
| 20 | |
| 21 | mcp = FastMCP("ghidra-mcp") |
| 22 | |
| 23 | # Initialize ghidra_server_url with default value |
| 24 | ghidra_server_url = DEFAULT_GHIDRA_SERVER |
| 25 | |
| 26 | def safe_get(endpoint: str, params: dic |
RemediationAI
The MCP server binds an HTTP transport to localhost without validating the request Host header, making it exploitable via DNS rebinding even with authentication. Add Host header validation by installing the `mcp` package โฅ1.24.0 and adding a middleware check in the FastMCP initialization: `app.add_middleware(HostHeaderValidation, allowed_hosts=["127.0.0.1", "localhost", "[::1]"])` or manually check `request.headers.host` against an allowlist before processing each request. This prevents malicious DNS rebinding attacks by rejecting requests with unexpected Host headers. Verify by making a request with a spoofed Host header and confirming it is rejected with a 400 Bad Request.
LLM consensus
requests==2.32.3 has 2 known CVEs [MEDIUM]: GHSA-9hjg-9r4m-mvj7, GHSA-gc5v-m9x4-r6x2. Upgrade to a patched version.
RemediationAI
The requirements.txt pins requests==2.32.3, which has 2 known CVEs (GHSA-9hjg-9r4m-mvj7, GHSA-gc5v-m9x4-r6x2) at MEDIUM severity. Upgrade to a patched version by changing `requests==2.32.3` to `requests>=2.32.4` (or the latest stable version) in requirements.txt. Newer versions include security patches that mitigate the known vulnerabilities. Verify the fix by running `pip install -r requirements.txt` and confirming the installed version is โฅ2.32.4 via `pip show requests`.
mcp==1.5.0 has 3 known CVEs [HIGH]: GHSA-3qhf-m339-9g5v, GHSA-9h52-p55h-vw2f, GHSA-j975-95f5-7wqh. Upgrade to a patched version.
RemediationAI
The requirements.txt pins mcp==1.5.0, which has 3 known CVEs (GHSA-3qhf-m339-9g5v, GHSA-9h52-p55h-vw2f, GHSA-j975-95f5-7wqh) at HIGH severity. Upgrade to a patched version by changing `mcp==1.5.0` to `mcp>=1.6.0` (or the latest stable version) in requirements.txt. Newer versions include security patches that mitigate the known vulnerabilities. Verify the fix by running `pip install -r requirements.txt` and confirming the installed version is โฅ1.6.0 via `pip show mcp`.
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 | else: |
| 41 | return [f"Error {response.status_code}: {response.text.strip()}"] |
| 42 | except Exception as e: |
| 43 | return [f"Request failed: {str(e)}"] |
| 44 | |
| 45 | def safe_post(endpoint: str, data: dict | str) -> str: |
| 46 | try: |
RemediationAI
The safe_get function returns full exception details via `return [f"Request failed: {str(e)}"]`, leaking internal paths, library versions, and query structure to attackers. Replace the exception handler with a generic error message by changing `except Exception as e: return [f"Request failed: {str(e)}"]` to `except Exception as e: logging.error(f"Request failed: {e}"); return ["Request failed: Unable to fetch data"]`. This logs the full error for debugging but returns a generic message to the caller. Verify by triggering an error condition (e.g., network timeout) and confirming the response contains only the generic message, not the full traceback.
LLM consensus
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
| 55 | else: |
| 56 | return f"Error {response.status_code}: {response.text.strip()}" |
| 57 | except Exception as e: |
| 58 | return f"Request failed: {str(e)}" |
| 59 | |
| 60 | @mcp.tool() |
| 61 | def list_methods(offset: int = 0, limit: int = 100) -> list: |
RemediationAI
The safe_post function returns full exception details via `return f"Request failed: {str(e)}"`, leaking internal paths, library versions, and query structure to attackers. Replace the exception handler with a generic error message by changing `except Exception as e: return f"Request failed: {str(e)}"` to `except Exception as e: logging.error(f"Request failed: {e}"); return "Request failed: Unable to process request".` This logs the full error for debugging but returns a generic message to the caller. Verify by triggering an error condition (e.g., network timeout) and confirming the response contains only the generic message, not the full traceback.
LLM consensus
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 | [](https://www.apache.org/licenses/LICENSE-2.0) |
| 2 | [](https://github.com/LaurieWired/GhidraMCP/releases) |
| 3 | [](https://github.com/LaurieWired/GhidraMCP/stargazers) |
| 4 | [](https://github.com/Lauri |
RemediationAI
The MCP manifest (README.md or mcp.json) declares tools but does not include an authentication field (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken), making it unclear what security mechanism protects the server. Add an explicit authentication field to the manifest by including `"authentication": "none (localhost HTTP only)"` or `"authentication": "bearer_token"` depending on the actual mechanism used. This signals to reviewers and auditors what security model is in place. Verify by reviewing the manifest and confirming the authentication field is present and accurately describes the security mechanism.
ghidra-mcp server uses FastMCP with @mcp.tool() decorators that can be dynamically modified at runtime, but the tools/list response does not include per-tool content-bound integrity fields (version, etag, digest, sha256, hash) to detect tool definition changes, and no notifications/tools/list_changed capability is declared to signal mutations.
Evidence
| 1 | # /// script |
| 2 | # requires-python = ">=3.10" |
| 3 | # dependencies = [ |
| 4 | # "requests>=2,<3", |
RemediationAI
The FastMCP server uses @mcp.tool() decorators that can be dynamically modified at runtime, but the tools/list response does not include per-tool integrity fields (version, etag, digest, sha256) to detect tool definition changes, and no tools/list_changed notification capability is declared. Add integrity fields by extending the tool metadata to include a hash of the tool definition: modify the tool registration to include `"sha256": hashlib.sha256(json.dumps(tool_def).encode()).hexdigest()` in the tools/list response, and declare the `notifications/tools/list_changed` capability in the server initialization. This allows clients to detect unauthorized tool mutations. Verify by computing the hash of a tool definition and confirming it matches the value in tools/list.
LLM consensus
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
| 25 | steps: |
| 26 | - uses: actions/checkout@v4 |
| 27 | - name: Set up JDK 21 |
| 28 | uses: actions/setup-java@v4 |
| 29 | with: |
| 30 | java-version: '21' |
| 31 | distribution: 'temurin' |
RemediationAI
The GitHub Actions workflow uses `actions/checkout@v4`, which is a mutable tag that can be rewritten by maintainers or compromised accounts, allowing malicious code injection into the CI pipeline. Pin the action to a specific commit SHA by changing `uses: actions/checkout@v4` to `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents silent substitution of malicious code. Verify by inspecting the workflow file and confirming all `uses:` references are pinned to 40-character commit SHAs.
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
| 54 | cp bridge_mcp_ghidra.py release/ |
| 55 | |
| 56 | - name: Upload artifact |
| 57 | uses: actions/upload-artifact@v4 |
| 58 | with: |
| 59 | name: GhidraMCP-artifact |
| 60 | path: | |
RemediationAI
The GitHub Actions workflow uses `actions/setup-java@v4`, which is a mutable tag that can be rewritten by maintainers or compromised accounts, allowing malicious code injection into the CI pipeline. Pin the action to a specific commit SHA by changing `uses: actions/setup-java@v4` to `uses: actions/setup-java@6c51739f1f7f172d52be7d631ca0c53051b3cfeea # v4.0.0`. This ensures the exact version is used and prevents silent substitution of malicious code. Verify by inspecting the workflow file and confirming all `uses:` references are pinned to 40-character commit SHAs.
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
| 23 | Framework/Gui/lib/Gui.jar |
| 24 | |
| 25 | steps: |
| 26 | - uses: actions/checkout@v4 |
| 27 | - name: Set up JDK 21 |
| 28 | uses: actions/setup-java@v4 |
| 29 | with: |
RemediationAI
The GitHub Actions workflow uses `actions/upload-artifact@v4`, which is a mutable tag that can be rewritten by maintainers or compromised accounts, allowing malicious code injection into the CI pipeline. Pin the action to a specific commit SHA by changing `uses: actions/upload-artifact@v4` to `uses: actions/upload-artifact@26f52261f24fa3f7c6737eed93afd3656efdb3ce3 # v4.3.0`. This ensures the exact version is used and prevents silent substitution of malicious code. Verify by inspecting the workflow file and confirming all `uses:` references are pinned to 40-character commit SHAs.
MCP server file uses Python's `global` keyword. `global` mutates module-level state from inside a function โ in a multi-tenant MCP server, this is almost always a cross-request data path. Closes the OWASP MCP Top 10:2025 MCP10 (Context Injection & Over-Sharing) gap. Fix: thread state through function arguments or a per-request context object. Module-level mutable singletons have no place in a tool handler.
Evidence
| 300 | args = parser.parse_args() |
| 301 | |
| 302 | # Use the global variable to ensure it's properly updated |
| 303 | global ghidra_server_url |
| 304 | if args.ghidra_server: |
| 305 | ghidra_server_url = args.ghidra_server |
RemediationAI
The bridge_mcp_ghidra.py file uses Python's `global` keyword to mutate module-level state (ghidra_server_url) from inside the main function, creating a cross-request data path vulnerability in multi-tenant MCP servers where state persists across requests. Remove the `global` statement and thread the ghidra_server_url through function arguments instead: change `global ghidra_server_url; ghidra_server_url = args.ghidra_server` to passing ghidra_server_url as a parameter to each tool function, or store it in a per-request context object. This ensures each request has isolated state and prevents data leakage between callers. Verify by running multiple concurrent requests with different ghidra_server values and confirming each request uses its own server URL without cross-contamination.
LLM consensus
decompile_function
list_classes
rename_function
list_methods
+2 more โ click to filter
rename_data
list_segments