Back to skill

Security audit

AWS Patrol

Security checks for vulnerabilities and agentic risk

Overview

This AWS reporting skill is purpose-aligned, but it handles sensitive cloud data and uses an unsafe report-rendering workflow that needs manual review before installation.

Install only with a least-privilege read-only AWS profile, a dedicated output directory with restrictive permissions, and reviewed chat destinations. Avoid the documented http.server plus --no-sandbox screenshot command until the report generator escapes all data, serves only a dedicated report directory on loopback, and runs Chromium with appropriate isolation.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gen-report.py:14
Finding
Unescaped Report Data Allows HTML and Content Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gen-report.py`, lines 14-111 **Additional Affected Component**: `assets/report-template.html` **Vulnerability Type**: Unescaped HTML generation **Risk Level**: High ### Complete Code Snippet ```python data = json.loads(sys.argv[1]) if len(sys.argv) > 1 else {} def r(key, default='—'): return str(data.get(key, default)) replacements = { '{{DATE}}': r('date', '2026-04-08'), '{{WEEKDAY}}': r('weekday', '周三'), '{{TIME}}': r('time', '09:00'), '{{EC2_COUNT}}': r('ec2Count', '86'), '{{RDS_COUNT}}': r('rdsCount', '34'), '{{ELB_COUNT}}': r('elbCount', '28'), '{{COST_TOTAL}}': r('costTotal', '70,005'), '{{COST_DAILY}}': r('costDaily', '2,334'), '{{UNATTACHED_VOL}}': r('unattachedVol', '19'), '{{UNUSED_EIP}}': r('unusedEip', '1'), '{{LOW_CPU}}': r('lowCpu', '53'), '{{OLD_SNAP}}': r('oldSnap', '1'), '{{SP_UTIL_PCT}}': r('spUtilPct', '100'), '{{SP_COV_PCT}}': r('spCovPct', '51.5'), '{{RDS_RI_PCT}}': r('rdsRiPct', '34.6'), '{{EC_RI_PCT}}': r('ecRiPct', '18.4'), '{{NO_MFA}}': r('noMfa', '31/35'), '{{UNENC_EBS}}': r('unencEbs', '201/205'), '{{OPEN_SG}}': r('openSg', '17'), '{{OLD_KEYS}}': r('oldKeys', '13'), '{{S3_RISK}}': r('s3Risk', '4/19'), } for k, v in replacements.items(): html = html.replace(k, v) high_cpu = data.get('highCpu', [ {'name': 'kafka-prod-server-02', 'cpu': 50.6, 'level': 'orange'}, ]) cpu_html = '' for item in high_cpu: level = item.get('level', 'yellow') cpu_html += f'<div class="highlight-row"><span class="dot {level}"></span>{item["name"]}<span style="margin-left:auto;font-weight:600;color:{"#f87171" if level=="red" else "#fb923c" if level=="orange" else "#fbbf24"}">{item["cpu"]}%</span></div>' html = html.replace('{{HIGH_CPU_ITEMS}}', cpu_html) sp_details = data.get( 'spRiDetails', 'SP: $27.06/h 承诺 (3个活跃) · RDS RI: 31个实例 · Redis RI: 60个节点' ) html = html.replace('{{SP_RI_DE ...[truncated 3945 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace manual string concatenation and substitution with an auto-escaping template engine such as Jinja2. 2. Escape every text value with `html.escape(value, quote=True)` when a template engine cannot be used. 3. Apply validation according to output context: - Parse counts, costs, CPU values, and percentages as numeric types. - Constrain percentages to an expected range such as 0–100. - Allow-list CSS class values such as `red`, `orange`, `yellow`, `green`, and `blue`. - Reject unexpected object types and missing required fields. 4. Do not construct markup using attacker-influenced f-strings. 5. Add a restrictive Content Security Policy, for example by default-denying external content and disallowing scripts, frames, objects, and network connections. 6. Disable JavaScript in Puppeteer if the static report does not require it. 7. Treat all AWS metadata, tags, names, notification text, and generated summaries as untrusted input. 8. Add regression tests containing HTML metacharacters and active-markup payloads to verify that they are rendered as text. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:76
Finding
Chromium Security Sandbox Is Explicitly Disabled During Report Rendering<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, line 76 **Vulnerability Type**: Unsafe browser execution configuration **Risk Level**: High ### Complete Code Snippet ```bash node -e "const p=require('puppeteer');(async()=>{const b=await p.launch({headless:'new',args:['--no-sandbox']});const pg=await b.newPage();await pg.setViewport({width:520,height:800,deviceScaleFactor:2});await pg.goto('http://localhost:18923/daily-report.html',{waitUntil:'networkidle0'});await pg.screenshot({path:'daily-report.png',fullPage:true});await b.close()})()" ``` ### Technical Analysis The documented Puppeteer command passes Chromium the `--no-sandbox` option. Chromium's process sandbox is a major defense-in-depth boundary intended to limit the operating-system access available to compromised renderer processes. Disabling the sandbox is not inherently necessary for screenshot generation. It is particularly hazardous in this project because the rendered document is built using unescaped data. Consequently, a report may contain attacker-controlled markup or external resources. HTML injection alone does not automatically create arbitrary operating-system code execution. However, if malicious content triggers a Chromium vulnerability, disabling the sandbox can eliminate a containment layer that would otherwise restrict the renderer. ### Attack Path 1. An attacker introduces malicious content into a field included in the generated report. 2. `gen-report.py` writes the content into `daily-report.html` without safe escaping. 3. The documented Puppeteer command opens the report with `--no-sandbox`. 4. The malicious content loads or exercises browser functionality. 5. If the content exploits a Chromium renderer vulnerability, the compromised process runs without normal Chromium sandbox containment. 6. The exploit may access resources available to the user account running Puppeteer, subject to operating-system permissions and any external container controls. ### Im ...[truncated 733 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--no-sandbox` argument and run Chromium with its standard sandbox enabled. 2. Run screenshot generation as a dedicated, unprivileged operating-system user. 3. If the hosting environment cannot support Chromium's sandbox, isolate rendering in a disposable container or virtual machine with: - No AWS credentials. - A read-only filesystem except for a dedicated output path. - No access to host namespaces or sensitive sockets. - Restricted outbound networking. - CPU, memory, process, and execution-time limits. 4. Disable JavaScript for the report page if it is not required: ```javascript await pg.setJavaScriptEnabled(false); ``` 5. Block unnecessary requests through Puppeteer request interception and allow only the local report document. 6. Fix the report HTML injection issue before rendering data originating from cloud resources or other users. 7. Keep Chromium and Puppeteer patched and use compatible, supported versions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:73
Finding
Report Workflow Exposes the Working Directory Through a Network-Bound HTTP Server<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 73-80 **Vulnerability Type**: Unrestricted local file serving and sensitive-data exposure **Risk Level**: Medium ### Complete Code Snippet ```bash # Start HTTP server python3 -m http.server 18923 & # Screenshot node -e "const p=require('puppeteer');(async()=>{const b=await p.launch({headless:'new',args:['--no-sandbox']});const pg=await b.newPage();await pg.setViewport({width:520,height:800,deviceScaleFactor:2});await pg.goto('http://localhost:18923/daily-report.html',{waitUntil:'networkidle0'});await pg.screenshot({path:'daily-report.png',fullPage:true});await b.close()})()" # Stop server kill %1 ``` ### Technical Analysis Python's `http.server` serves the current working directory and, unless explicitly restricted, binds to all available network interfaces. The documented workflow does not specify either a dedicated document root or a loopback-only bind address. The collection scripts write sensitive AWS reports into `AWS_PATROL_OUTPUT`, which defaults to the current directory. These files can contain: - Resource identifiers and names. - Private IP addresses and load-balancer DNS names. - CloudWatch alarm details. - IAM usernames and access-key identifiers. - Security findings. - Cost and reservation information. - AWS Health and SMS registration details. If the HTTP server is launched from that directory, those files and any other files in the working directory may be retrievable while the server is active. The `kill %1` cleanup also depends on interactive shell job numbering and may not execute if an earlier command fails or the workflow is interrupted. ### Attack Path 1. The patrol scripts create `aws-patrol-detail.json`, `aws-security-cost.json`, and `daily-report.html` in the current or configured output directory. 2. The operator follows the documented command and starts `python3 -m http.server 18923`. 3. The server listens on network interfaces and serves the entire current ...[truncated 1242 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Avoid starting an HTTP server where possible. Use one of the following safer approaches: - Open a local `file://` URL in Puppeteer. - Read the generated HTML and use `page.setContent()`. 2. If HTTP is required, bind it explicitly to loopback: ```bash python3 -m http.server --bind 127.0.0.1 18923 --directory /path/to/dedicated-report-root ``` 3. Create a dedicated temporary directory containing only the HTML and static assets needed for rendering. 4. Do not place raw AWS JSON reports in the served directory. 5. Set restrictive directory and file permissions, such as owner-only access. 6. Use robust lifecycle management rather than shell job numbering: - Capture the process ID. - Register an `EXIT`, `INT`, and `TERM` trap. - Stop the exact captured process during cleanup. 7. Prefer a randomized ephemeral port and verify that no existing process is using it. 8. Restrict the host firewall so the report-rendering port cannot be reached from external interfaces. 9. Remove or redact sensitive fields from reports that do not require resource-level detail. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
This second mismatch likewise indicates that the skill's security and cost-audit claims are broader than its real behavior. When a skill is used for AWS patrol or compliance-style review, such gaps can directly lead to missed misconfigurations and uninvestigated incidents because operators assume checks were completed.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
This second mismatch likewise indicates that the skill's security and cost-audit claims are broader than its real behavior. When a skill is used for AWS patrol or compliance-style review, such gaps can directly lead to missed misconfigurations and uninvestigated incidents because operators assume checks were completed.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The markdown describes fully automated monitoring that collects AWS data, generates reports, takes screenshots, and delivers results to chat, but it does not include any warning about transmitting potentially sensitive infrastructure, security, and cost information. For a skill handling cloud-environment data and chat delivery, the description should disclose privacy and operational implications to the user.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises access to AWS account data and output generation but does not declare any explicit tool scope or permission boundaries. In an agent environment, missing scope constraints can let the skill access environment secrets and write files more broadly than users expect, increasing the chance of unintended credential exposure or unsafe file operations.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The manifest description says to use the skill for 'daily/periodic AWS monitoring, cost optimization checks, security audits, or automated reporting on AWS infrastructure health,' which is a wide set of common admin tasks. It does not provide explicit trigger phrases, exclusions, or narrower activation context, increasing the risk of unintended invocation for ordinary AWS operations discussions.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The template sets the document language to Chinese via `lang="zh"`, and the visible report content is also entirely in Chinese. This imposes a specific language/locale without any indication of user opt-in or a documented region-specific constraint, which matches the natural-language locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
Multiple default strings are fixed in Chinese, including weekday text and report content, which effectively forces a specific language/locale in generated output. The file does not offer user opt-in or configurable locale selection, and no justification is documented for restricting output to Chinese.

Tainted flow: 'out_path' from os.environ.get (line 8, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
actions_html += '</div>'
html = html.replace('{{ACTION_ITEMS}}', actions_html)

with open(out_path, 'w') as f:
    f.write(html)
print(f"Written to {out_path}")
Confidence
95% confidence
Finding
The script derives the output directory from the AWS_PATROL_OUTPUT environment variable and writes the generated HTML report to that path without validation or restriction. If an attacker can influence the environment in which this skill runs, they can redirect writes to arbitrary filesystem locations, causing unintended file overwrite, data clobbering, or placement of attacker-controlled HTML in sensitive or served directories.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The script accesses AWS profile configuration via environment variables to authenticate and then performs broad account-wide API queries. While its purpose is analysis, there is no explicit warning in code comments, docstrings, or user-facing output that it will use local AWS credentials/profile context to inspect account resources.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The comment indicates a straightforward rightsizing step based on existing patrol data, but the implementation references OUT_DIR prior to assigning it, causing the file read to fail and the logic to be skipped by the broad exception handler. This means the advertised rightsizing behavior is not actually performed as documented.

Tainted flow: 'out' from os.environ.get (line 419, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
OUT_DIR = os.environ.get('AWS_PATROL_OUTPUT', os.getcwd())
out = os.path.join(OUT_DIR, 'aws-security-cost.json')
with open(out, 'w') as f:
    json.dump(result, f, indent=2, default=str)

print(f"\nDone! Written to {out}", file=sys.stderr)
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The module docstring and the implemented code only collect EC2, RDS, CloudWatch alarm, ELB, Health event, and SMS registration data. The manifest claims broader security posture and cost-analysis coverage, but this file contains no IAM, S3, EBS encryption, security group, Savings Plans, or Reserved Instance logic, creating a clear description-to-behavior mismatch.

Tainted flow: 'out' from os.environ.get (line 209, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
OUT_DIR = os.environ.get('AWS_PATROL_OUTPUT', os.getcwd())
out = os.path.join(OUT_DIR, 'aws-patrol-detail.json')
with open(out, 'w') as f:
    json.dump(result, f, indent=2, default=str)

print(f"\nDone! {result['summary']}", file=sys.stderr)
Confidence
88% confidence
Finding
The output path is derived from an environment variable and then used directly in a file write. If an attacker can influence the process environment, they can redirect the report to an unintended location, causing overwrite of arbitrary writable files or writing sensitive AWS inventory data into an unsafe path; the skill context increases concern because the JSON contains detailed cloud asset and health information.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The example schedule is presented only in Chinese, which implicitly imposes a specific language/locale in user-facing instructions without offering alternatives or documenting that the skill is region-specific. This can violate language/locale policy expectations when no opt-in or justification is provided.

Natural-Language Policy Violations

Low
Confidence
98% confidence
Finding
The scheduling example is written entirely in Chinese, which imposes a specific language on users despite the rest of the skill being in English. There is no opt-in, alternative English phrasing, or justification that this skill is intended only for a Chinese-language environment.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This code performs a file write to `daily-report.html`, which is a safety-relevant operation under the rule, but the only user-facing disclosure is an after-the-fact success print. The module docstring says it generates a report, yet there is no explicit pre-write warning or comment near the write about overwriting or creating output in the configured directory.

Description-Behavior Mismatch

Low
Confidence
76% confidence
Finding
The manifest description emphasizes infrastructure patrol including security posture for IAM MFA, security groups, EBS encryption, and S3, so much of this file is aligned. However, the code additionally collects broader IAM/account summary details and S3 ACL/public-access-block exposure checks that go beyond the specifically described metrics-oriented wording for this component.

Missing User Warnings

Low
Confidence
77% confidence
Finding
The script persists collected security and cost findings to aws-security-cost.json, but the only user-facing notice appears after the write completes. Because this output may contain sensitive infrastructure metadata, users are not warned ahead of time that a local file will be created in the working directory or configured output directory.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The script persists a detailed AWS infrastructure inventory to local disk without any explicit warning or controls around storage location, retention, or permissions. In this skill context, the report can contain instance identifiers, private IPs, load balancer DNS names, alarm state, and health-event metadata, so unanticipated persistence can expose sensitive operational data to other local users or downstream tooling.

Static analysis

No suspicious patterns detected.