High risk. Don't ship without significant remediation.
Scanned 5/16/2026, 12:32:35 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 and tool poisoning risks means this package could be compromised to execute unintended actions or return malicious results. You should remediate these high-severity issues before deployment, particularly the prompt injection vectors and server configuration gaps.
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 without verifying that the caller owns or has permission to modify the target function. Add an authorization check function that validates the caller's credentials against an access control list before calling safe_post(); for example, add `if not authorize_user(get_caller_identity(), old_name, 'rename'): raise PermissionError(...)` before the safe_post call. This ensures only authorized users can rename functions they own or have explicit permission to modify. Verify the fix by attempting to rename a function as an unauthorized user and confirming the request is rejected with a 403 Forbidden or PermissionError.
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 posts it to the decompile endpoint with only the name as a filter, allowing any caller to retrieve decompiled code for any function without ownership or access control verification. Add an authorization check that validates the caller has permission to view the target function before calling safe_post(); for example, insert `if not authorize_user(get_caller_identity(), name, 'read'): raise PermissionError(...)` before the return statement. This restricts decompilation access to authorized users only. Test by calling decompile_function with a function name as an unauthorized user and verify the request is denied.
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 parameter and renames the data label at that address by posting to renameData with only the address as a filter, with no ownership or access control check. Add an authorization check before the safe_post call: `if not authorize_user(get_caller_identity(), address, 'rename'): raise PermissionError(...)` to verify the caller has permission to modify data at that address. This prevents unauthorized users from renaming data labels they do not own or have permission to modify. Verify by attempting to rename data at an address as an unauthorized user and confirming the request is rejected.
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 to the Ghidra server, which is a network side effect that modifies state. Update the docstring to explicitly document the side effect: change the docstring to 'Rename a function by its current name to a new user-defined name. **WARNING: This tool modifies the Ghidra server state via a POST request.**' This ensures callers are aware the operation is not read-only and may have external consequences. Verify by checking that the updated docstring appears in the tool's metadata when calling tools/list.
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 to the Ghidra server, which is a network side effect that modifies state. Update the docstring to explicitly document the side effect: change the docstring to 'Rename a data label at the specified address. **WARNING: This tool modifies the Ghidra server state via a POST request.**' This ensures callers understand the operation modifies external state. Verify by checking that the updated docstring appears in the tool's metadata when calling tools/list.
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 decompiled C code from the external Ghidra server and returns it verbatim without any provenance markers or indication that the content is untrusted, enabling indirect prompt injection if the decompiled code contains malicious instructions. Wrap the return value with a provenance marker; for example, change `return safe_post("decompile", name)` to `return f"[EXTERNAL: Ghidra decompile output]\n{safe_post('decompile', name)}"` to clearly mark the content as untrusted external data. This prevents the LLM from treating the decompiled code as trusted system output. Verify by decompiling a function and confirming the response includes the provenance marker 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 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; for example, change `return safe_get("segments", {...})` to prepend `"[EXTERNAL: Ghidra segments]"` to each item in the returned list or wrap the entire response. This clearly marks the data as untrusted external content. Verify by calling list_segments and confirming each segment entry includes or is prefixed with the provenance marker.
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 function names from the external Ghidra server and returns them verbatim without provenance markers, enabling indirect prompt injection if function names contain malicious instructions. Wrap the return value with a provenance marker; for example, modify the return to prepend `"[EXTERNAL: Ghidra methods]"` to the response or wrap each method name with a marker. This ensures the LLM treats the data as untrusted external input. Verify by calling list_methods and confirming the response includes provenance markers.
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 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; for example, modify the return statement to prepend `"[EXTERNAL: Ghidra imports]"` to the response or wrap each import entry. This clearly marks the data as untrusted external content. Verify by calling list_imports and confirming the response includes provenance markers.
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 namespace/class names from the external Ghidra server and returns them verbatim without provenance markers, enabling indirect prompt injection if class names contain malicious instructions. Wrap the return value with a provenance marker; for example, modify the return to prepend `"[EXTERNAL: Ghidra classes]"` to the response or wrap each class name. This ensures the LLM treats the data as untrusted external input. Verify by calling list_classes and confirming the response includes provenance markers.
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 eliminates the network attack surface entirely and requires direct process communication. This prevents remote exploitation via DNS rebinding. Verify 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 to localhost without validating the Host header, allowing DNS-rebinding attacks to bypass same-origin checks even with authentication. Add Host header validation by implementing a middleware that checks `req.headers.host` against an allow-list of expected hostnames (e.g., `['localhost', '127.0.0.1', '[::1]']`) and rejects requests with mismatched Host headers. Alternatively, upgrade to mcp SDK โฅ1.24.0 and enable the built-in `hostHeaderValidation()` middleware. This prevents DNS-rebinding attacks by ensuring requests originate from expected hosts. Verify by making a request with a spoofed Host header and confirming it is rejected.
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 specifies requests==2.32.3, which has 2 known CVEs (GHSA-9hjg-9r4m-mvj7, GHSA-gc5v-m9x4-r6x2) rated MEDIUM severity. Upgrade requests to a patched version by changing `requests==2.32.3` to `requests>=2.33.0` (or the latest stable version) in requirements.txt. This applies security patches that fix the known vulnerabilities. Verify by running `pip install -r requirements.txt` and confirming the installed version is โฅ2.33.0 using `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 specifies mcp==1.5.0, which has 3 known CVEs (GHSA-3qhf-m339-9g5v, GHSA-9h52-p55h-vw2f, GHSA-j975-95f5-7wqh) rated HIGH severity. Upgrade mcp to a patched version by changing `mcp==1.5.0` to `mcp>=1.6.0` (or the latest stable version that addresses these CVEs) in requirements.txt. This applies critical security patches. Verify by running `pip install -r requirements.txt` and confirming the installed version is โฅ1.6.0 using `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 to the caller (e.g., `return [f"Request failed: {str(e)}"]`), leaking internal paths, library versions, and query structure useful for reconnaissance. Replace the exception detail with a generic error message: change `return [f"Request failed: {str(e)}"]` to `return ["Request failed: Unable to fetch data"]` and log the full exception internally using `logging.error(str(e))`. This hides sensitive implementation details from callers. Verify by triggering an exception (e.g., network timeout) and confirming the response contains only a 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 to the caller (e.g., `return f"Request failed: {str(e)}"`), leaking internal paths, library versions, and query structure. Replace the exception detail with a generic error message: change `return f"Request failed: {str(e)}"` to `return "Request failed: Unable to complete operation"` and log the full exception internally using `logging.error(str(e))`. This hides sensitive implementation details. Verify by triggering an exception and confirming the response contains only a generic message without the 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) 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 authentication mechanism protects the server. Add an explicit authentication field to the README or manifest file; for example, add a section: `## Authentication: This server currently uses [network-layer auth / host-level auth / API key validation]. See [implementation details].` This documents the real security mechanism so reviewers can audit it. Verify by confirming the README includes a clear statement of the authentication method used.
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 a version or hash field to each tool's metadata and declare the `notifications/tools/list_changed` capability in the server initialization; for example, add `server.declare_capability('notifications/tools/list_changed')` and include a tool version in the response. This allows clients to detect when tool definitions have been modified. Verify by modifying a tool definition and confirming clients receive a notification or detect the change via the updated hash.
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 silently rewritten by a compromised maintainer, allowing malicious code injection into the CI pipeline. Pin the action to a specific 40-character commit SHA: change `uses: actions/checkout@v4` to `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1`. This ensures the exact version is used and prevents tag rewriting attacks. Verify by confirming the workflow file contains the full SHA and the comment documents the version.
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 silently rewritten, allowing malicious code injection. Pin the action to a specific 40-character commit SHA: change `uses: actions/setup-java@v4` to `uses: actions/setup-java@6c51739f1f7f172d52be7d631ca0d57748ab3810 # v4.x.x`. This ensures the exact version is used and prevents tag rewriting attacks. Verify by confirming the workflow file contains the full SHA.
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 silently rewritten, allowing malicious code injection. Pin the action to a specific 40-character commit SHA: change `uses: actions/upload-artifact@v4` to `uses: actions/upload-artifact@65462800fd760344d3fbb3e7f9ce0f3857592998 # v4.x.x`. This ensures the exact version is used and prevents tag rewriting attacks. Verify by confirming the workflow file contains the full SHA.
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 the `global` keyword to mutate module-level state (e.g., `global ghidra_server_url`), which in a multi-tenant MCP server creates a cross-request data path where one request's modifications affect other requests. Remove the global keyword and pass the ghidra_server_url as a parameter through the function call chain or store it in a per-request context object; for example, create a request context class and pass it to each tool handler instead of using module-level globals. This ensures each request has isolated state. Verify by running multiple concurrent requests and confirming that changes to ghidra_server_url in one request do not affect other requests.
LLM consensus
decompile_function
list_classes
rename_function
list_methods
+2 more โ click to filter
rename_data
list_segments