Back to skill

Security audit

Python

Security checks for vulnerabilities and agentic risk

Overview

This Python help skill is documentation-only, and the scanner hits are safety examples rather than hidden or executable behavior.

This skill is reasonable to install if you want Python guidance. It can cause the agent to suggest commands, dependency installs, file edits, and preference storage, so review generated commands and any remembered preferences before applying them.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (13)

Hidden Instructions

High
Category
Prompt Injection
Confidence
60% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · files.md (reported line 9)May include surrounding context.

md
- `open(path)` uses the platform's default encoding, not UTF-8: cp1252 on Windows, ASCII under `LANG=C` in a lean container. The same code reads a file on your laptop and raises `UnicodeDecodeError: ... byte 0x93 in position 12` in CI. Write `encoding="utf-8"` on every text `open`, `read_text`, `write_text`, and `subprocess` call (Core Rule 8).
- Find the ones you missed: `python -X warn_default_encoding` (`python >=3.10`) emits an `EncodingWarning` at each site. `PYTHONUTF8=1` / `python -X utf8` forces UTF-8 process-wide as a stopgap for code you cannot edit; PEP 686 makes that the default in `python >=3.15`.
- Files exported from Excel start with a BOM: read them with `encoding="utf-8-sig"` (strips it) — otherwise the first column name is `"id"` and every dict lookup on `"id"` fails.
- `errors="replace"` belongs only at a display boundary. Applying it to data silently substitutes `�` and destroys the bytes you needed.
- Bytes in, bytes out: read binary (`"rb"`) when the content is not text. Round-tripping unknown bytes through `str` corrupts them unless you use `errors="surrogateescape"`, which is also how paths with invalid encodings survive.
- Unicode equality is not byte equality: macOS filesystems hand back NFD (`e` + combining accent) where Linux gives NFC. Two visually identical names compare unequal. Normalize before comparing: `unicodedata.normalize("NFC", s)`.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · files.md (reported line 18)May include surrounding context.

md
- `pathlib` by default: `Path("data") / "in.csv"`, `p.with_suffix(".json")`, `p.read_text(encoding="utf-8")`, `p.stem`, `p.parent`, `p.exists()`.
- Anchor relative paths to the code, not the caller's cwd: `HERE = Path(__file__).resolve().parent`. A script that works from the project root and fails from anywhere else is this bug.
- Joining with an absolute second element DISCARDS the first: `Path("/srv/data") / "/etc/passwd"` is `/etc/passwd`, and `os.path.join` does the same. Any user-supplied path component needs the containment check in `security.md`.
- `.resolve()` follows symlinks and normalizes `..`; `.absolute()` does neither. Compare and store resolved paths, or the same file has two names.
- `Path.glob("*.csv")` is one level, `rglob` recurses; neither matches dotfiles. For large trees `os.scandir` avoids a second `stat()` per entry and is several times faster than `os.walk`+`os.path.isdir`.
- Package data files are NOT filesystem paths: `importlib.resources.files("mypkg") / "schema.json"` works when the package is a zip or an installed wheel (`packaging.md`).

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Confidence
90% confidence
Finding

Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Content

Scanner excerpt · http.md (reported line 45)May include surrounding context.

md
- The library-level retry: `HTTPAdapter(max_retries=Retry(total=5, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504], respect_retry_after_header=True))`. By default `Retry` only replays idempotent methods — adding POST to `allowed_methods` is a decision about duplicate side effects, not a configuration detail.
- Redirects follow automatically. A 301/302 on a POST is re-issued as a GET by nearly every client (matching browsers, not the RFC); 307/308 preserve the method and body. If your POST "succeeds" but nothing was created, print `resp.history`.
- `requests` strips the `Authorization` header when a redirect crosses to a different host, and keeps it on the same host. Do not rely on either: resolve the final URL yourself when the credential matters.
- `allow_redirects=False` plus an explicit hop check is the only safe form when the URL came from a user — one redirect to `169.254.169.254` turns a fetch into credential theft (`security.md`).

## Streaming And Large Bodies

Cloud Metadata Access

High
Category
Server-Side Request Forgery
Confidence
90% confidence
Finding

Code accesses a cloud instance metadata endpoint (e.g. 169.254.169.254). A single request can return temporary IAM credentials, making this a high-value SSRF target for credential theft.

Content

Scanner excerpt · security.md (reported line 48)May include surrounding context.

md
- The library-level retry: `HTTPAdapter(max_retries=Retry(total=5, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504], respect_retry_after_header=True))`. By default `Retry` only replays idempotent methods — adding POST to `allowed_methods` is a decision about duplicate side effects, not a configuration detail.
- Redirects follow automatically. A 301/302 on a POST is re-issued as a GET by nearly every client (matching browsers, not the RFC); 307/308 preserve the method and body. If your POST "succeeds" but nothing was created, print `resp.history`.
- `requests` strips the `Authorization` header when a redirect crosses to a different host, and keeps it on the same host. Do not rely on either: resolve the final URL yourself when the credential matters.
- `allow_redirects=False` plus an explicit hop check is the only safe form when the URL came from a user — one redirect to `169.254.169.254` turns a fetch into credential theft (`security.md`).

## Streaming And Large Bodies

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · subprocess.md (reported line 21)May include surrounding context.

md
## shell=True

- Only when you genuinely need shell features (pipes, globs, `&&`, redirection) and the whole command line is a literal you wrote.
- With any interpolated value it is command injection: `subprocess.run(f"grep {pattern} file", shell=True)` where `pattern` is `x; rm -rf ~` runs both. If you must build a shell string, every interpolated value goes through `shlex.quote`.
- On POSIX, `shell=True` runs `/bin/sh`, not bash — bashisms (`[[`, arrays, `pipefail`) fail with a syntax error. Call `["bash", "-c", script]` explicitly when you need bash.
- Prefer doing it in Python: a pipe is two `Popen`s or, more often, `run(...)` plus a Python transformation; a glob is `Path.glob`.
- Never `os.system`: no argument control, and its return value is a wait status (exit code × 256 on POSIX), not an exit code.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · subprocess.md (reported line 21)May include surrounding context.

md
## shell=True

- Only when you genuinely need shell features (pipes, globs, `&&`, redirection) and the whole command line is a literal you wrote.
- With any interpolated value it is command injection: `subprocess.run(f"grep {pattern} file", shell=True)` where `pattern` is `x; rm -rf ~` runs both. If you must build a shell string, every interpolated value goes through `shlex.quote`.
- On POSIX, `shell=True` runs `/bin/sh`, not bash — bashisms (`[[`, arrays, `pipefail`) fail with a syntax error. Call `["bash", "-c", script]` explicitly when you need bash.
- Prefer doing it in Python: a pipe is two `Popen`s or, more often, `run(...)` plus a Python transformation; a glob is `Path.glob`.
- Never `os.system`: no argument control, and its return value is a wait status (exit code × 256 on POSIX), not an exit code.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · subprocess.md (reported line 21)May include surrounding context.

md
## shell=True

- Only when you genuinely need shell features (pipes, globs, `&&`, redirection) and the whole command line is a literal you wrote.
- With any interpolated value it is command injection: `subprocess.run(f"grep {pattern} file", shell=True)` where `pattern` is `x; rm -rf ~` runs both. If you must build a shell string, every interpolated value goes through `shlex.quote`.
- On POSIX, `shell=True` runs `/bin/sh`, not bash — bashisms (`[[`, arrays, `pipefail`) fail with a syntax error. Call `["bash", "-c", script]` explicitly when you need bash.
- Prefer doing it in Python: a pipe is two `Popen`s or, more often, `run(...)` plus a Python transformation; a glob is `Path.glob`.
- Never `os.system`: no argument control, and its return value is a wait status (exit code × 256 on POSIX), not an exit code.

Chaining Abuse

High
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Content

Scanner excerpt · subprocess.md (reported line 21)May include surrounding context.

md
## shell=True

- Only when you genuinely need shell features (pipes, globs, `&&`, redirection) and the whole command line is a literal you wrote.
- With any interpolated value it is command injection: `subprocess.run(f"grep {pattern} file", shell=True)` where `pattern` is `x; rm -rf ~` runs both. If you must build a shell string, every interpolated value goes through `shlex.quote`.
- On POSIX, `shell=True` runs `/bin/sh`, not bash — bashisms (`[[`, arrays, `pipefail`) fail with a syntax error. Call `["bash", "-c", script]` explicitly when you need bash.
- Prefer doing it in Python: a pipe is two `Popen`s or, more often, `run(...)` plus a Python transformation; a glob is `Path.glob`.
- Never `os.system`: no argument control, and its return value is a wait status (exit code × 256 on POSIX), not an exit code.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · SKILL.md (reported line 159)May include surrounding context.

md
## TLS and Network

- `verify=False` in requests, or `ssl._create_unverified_context()`, disables authentication of the peer entirely — a corporate proxy error is fixed by installing the CA (`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE`, or `certifi`), not by turning off verification.
- `ssl.create_default_context()` is the correct starting point (verification and hostname checking on). `ssl.wrap_socket` was removed in `python >=3.12`.
- Set a timeout on every outbound call or a slow server becomes your outage (`errors.md`).
- Cloud metadata endpoints (169.254.169.254) are reachable from most runtimes: an SSRF in a service becomes credential theft. Validate outbound hosts.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · http.md (reported line 58)May include surrounding context.

md
## TLS and Network

- `verify=False` in requests, or `ssl._create_unverified_context()`, disables authentication of the peer entirely — a corporate proxy error is fixed by installing the CA (`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE`, or `certifi`), not by turning off verification.
- `ssl.create_default_context()` is the correct starting point (verification and hostname checking on). `ssl.wrap_socket` was removed in `python >=3.12`.
- Set a timeout on every outbound call or a slow server becomes your outage (`errors.md`).
- Cloud metadata endpoints (169.254.169.254) are reachable from most runtimes: an SSRF in a service becomes credential theft. Validate outbound hosts.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · security.md (reported line 45)May include surrounding context.

md
## TLS and Network

- `verify=False` in requests, or `ssl._create_unverified_context()`, disables authentication of the peer entirely — a corporate proxy error is fixed by installing the CA (`REQUESTS_CA_BUNDLE`, `SSL_CERT_FILE`, or `certifi`), not by turning off verification.
- `ssl.create_default_context()` is the correct starting point (verification and hostname checking on). `ssl.wrap_socket` was removed in `python >=3.12`.
- Set a timeout on every outbound call or a slow server becomes your outage (`errors.md`).
- Cloud metadata endpoints (169.254.169.254) are reachable from most runtimes: an SSRF in a service becomes credential theft. Validate outbound hosts.

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
70% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · security.md (reported line 52)May include surrounding context.

md
## Temp Files and Filesystem

- `tempfile.mkstemp`/`NamedTemporaryFile`, never a hand-built `/tmp/app-<pid>` path — predictable names in a world-writable directory are a symlink race.
- Create secrets with restrictive permissions from the start: `os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)`. Chmod after writing leaves a window.
- `shutil.rmtree` follows into directories you may not intend; `os.walk(followlinks=False)` is the default for a reason — keep it.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This is a markdown file, so SQP-2 applies to omissions in the skill description. The document explains how to run external commands, including shell usage and process killing, but does not explicitly warn users that following this guidance can execute commands that affect the local system, files, or processes.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.