Mostly safe โ a couple of notes worth reading.
Scanned 5/5/2026, 8:22:55 AMยทCached resultยทFast Scanยท45 rulesยทHow we decide โ
AIVSS Score
Low
Severity Breakdown
0
critical
1
high
258
medium
18
low
MCP Server Information
Findings
This package carries a B-grade security rating with a safety score of 77/100 and presents one high-severity finding alongside 258 medium-severity issues, primarily related to verbose error handling that could expose sensitive information. The main concern is a server configuration vulnerability, though the lack of critical findings and predominance of medium-severity readiness issues suggest the risks are manageable with proper deployment practices. Installation is acceptable for most use cases, but you should review the high-severity finding and consider implementing error handling safeguards before production deployment.
AWS API MCP File Access Restriction Bypass
Scan Details
Want deeper analysis?
Fast scan found 21 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.
21 of 21 findings
21 findings
MCP tool returns a hyperlink whose URL interpolates a runtime variable. Two render surfaces: markdown `[text](URL)` and HTML `<a href="URL">`. When the host renders the link, the user may click โ exfiltration, phishing, or click-tracking. Unlike MCP-220 (markdown image, auto-fetched at render time), hyperlinks fire on user click โ but the same threat model applies: never put untrusted data in the destination URL. Hard-code the URL or validate against a per-tool allow-list before interpolation.
Evidence
| 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
Remediation
Hard-code the URL, or validate against a per-tool allow-list before interpolation. If user input must shape the link, keep the destination URL static and put the variable in the visible link text only.
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
| 64 | result = r.srem(key, member) |
| 65 | return f"Successfully removed {result} member from set '{key}'" |
| 66 | except ValkeyError as e: |
| 67 | return f"Error removing from set '{key}': {str(e)}" |
| 68 | |
| 69 | |
| 70 | @mcp.tool() |
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 | result = r.hlen(key) |
| 217 | return str(result) |
| 218 | except ValkeyError as e: |
| 219 | return f"Error getting hash length from '{key}': {str(e)}" |
| 220 | |
| 221 | |
| 222 | @mcp.tool() |
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
| 682 | error_msg = f'Error searching for open places: {str(e)}' |
| 683 | logger.error(error_msg) |
| 684 | await ctx.error(error_msg) |
| 685 | return {'error': str(e)} |
| 686 | |
| 687 | |
| 688 | @mcp.tool() |
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
| 161 | return results |
| 162 | |
| 163 | except Exception as e: |
| 164 | return {'error': str(e), 'query': query, 'index_id': kendra_index_id} |
| 165 | |
| 166 | |
| 167 | def main(): |
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
| 175 | return response |
| 176 | except Exception as e: |
| 177 | logger.error(f"Error executing ECS API operation {api_operation}: {e}") |
| 178 | return {"error": str(e), "status": "failed"} |
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
| 160 | result = r.zremrangebylex(key, min_lex, max_lex) |
| 161 | return f"Successfully removed {result} member(s) by lex range from sorted set '{key}'" |
| 162 | except ValkeyError as e: |
| 163 | return f"Error removing by lex range from sorted set '{key}': {str(e)}" |
| 164 | |
| 165 | |
| 166 | @mcp.tool() |
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
| 57 | await ctx.warning( |
| 58 | f"Resource Explorer not set up in {region}. Using alternative method." |
| 59 | ) |
| 60 | return {"region": region, "services": [], "error": str(e)} |
| 61 | else: |
| 62 | raise 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
| 199 | result = r.sismember(key, member) |
| 200 | return str(result).lower() |
| 201 | except ValkeyError as e: |
| 202 | return f"Error checking set membership in '{key}': {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
| 923 | return f'Security Error: {str(e)}' |
| 924 | except Exception as e: |
| 925 | logger.error(f'Analysis failed with exception: {str(e)}') |
| 926 | return f'Analysis failed: {str(e)}' |
| 927 | |
| 928 | |
| 929 | def _load_next_steps_prompt(filename: str, **kwargs) -> 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
| 194 | if processed_configs: |
| 195 | modify_request['LogDeliveryConfigurations'] = processed_configs |
| 196 | except ValueError as e: |
| 197 | return {'error': str(e)} |
| 198 | |
| 199 | return modify_request |
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 | f"Unexpected error in get_service_level_objective for '{slo_id}': {str(e)}", |
| 273 | exc_info=True, |
| 274 | ) |
| 275 | return f'Error: {str(e)}' |
| 276 | |
| 277 | |
| 278 | async def list_slos( |
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
| 317 | return f"No new entries for consumer '{consumer_name}' in group '{group_name}'" |
| 318 | return str(result) |
| 319 | except ValkeyError as e: |
| 320 | return f'Error reading from group: {str(e)}' |
| 321 | |
| 322 | |
| 323 | @mcp.tool() |
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
| 276 | return f"List '{key}' is empty" |
| 277 | return str(result) |
| 278 | except ValkeyError as e: |
| 279 | return f"Error popping from right of list '{key}': {str(e)}" |
| 280 | |
| 281 | |
| 282 | @mcp.tool() |
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
| 462 | MemcachedConnectionManager.close_connection() |
| 463 | return 'Successfully closed connection' |
| 464 | except MemcacheError as e: |
| 465 | return f'Error closing connection: {str(e)}' |
| 466 | |
| 467 | |
| 468 | @mcp.tool() |
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
| 98 | } |
| 99 | |
| 100 | except Exception as e: |
| 101 | return {'error': str(e), 'region': region or os.environ.get('AWS_REGION', 'us-east-1')} |
| 102 | |
| 103 | |
| 104 | @mcp.tool(name='KendraQueryTool') |
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
| 2181 | 'expected': '< 80%', |
| 2182 | '_key': f'cw_mem_high|{d["id"]}', |
| 2183 | } |
| 2184 | ) |
| 2185 | except (ValueError, TypeError): |
| 2186 | pass |
| 2187 | if '๐ด' in d['status_check']: |
| 2188 | findings.append( |
| 2189 | { |
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
| 594 | $PORT = $match.Matches[0].Groups[1].Value |
| 595 | break |
| 596 | } |
| 597 | } catch {} |
| 598 | } |
| 599 | |
| 600 | if ($PORT) { |
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
| 117 | finally: |
| 118 | if mock_file_path and os.path.exists(mock_file_path): |
| 119 | try: |
| 120 | os.unlink(mock_file_path) |
| 121 | except OSError: |
| 122 | pass |
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
| 499 | if isinstance(cost, str) and '$' in cost: |
| 500 | try: |
| 501 | cost_value = float(cost.replace('$', '').replace(',', '')) |
| 502 | total_cost += cost_value |
| 503 | except ValueError: |
| 504 | pass |
| 505 | |
| 506 | if total_cost > 0: |
| 507 | monthly_cost = f'${total_cost:.2f}' |
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 | sample_{{ entity_name.lower() }}.{{ param }} |
| 80 | {%- if not loop.last %}, {% endif -%} |
| 81 | {%- endfor %}) |
| 82 | print(f" ๐๏ธ Deleted leftover {{ entity_name.lower() }} (if existed)") |
| 83 | except Exception: |
| 84 | pass # Ignore errors - item might not exist |
| 85 | {%- endfor %} |
| 86 | {%- endfor %} |
| 87 | print("โ
Pre-test cleanup completed\n") |
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.
KendraQueryTool
read_documentation