Back to skill

Security audit

Prompt Lint

Security checks for vulnerabilities and agentic risk

Overview

The prompt linter itself is small and mostly purpose-aligned, but its install docs fetch executable code from a mutable GitHub URL and its bundled verifier can execute untrusted Python files.

Review before installing. Prefer the bundled prompt_lint.py over the curl command, or only download from a pinned commit with a verified hash. Do not rely on this version as a CI-blocking safety gate unless the exit-code behavior is fixed. Avoid running ci/verify_product.py on untrusted submissions unless it is isolated from secrets, network access, and writable sensitive files.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T03 · Remote Payload Retrieval and Execution

Error
Location
SKILL.md:31
Finding

Mutable Remote Python Payload Downloaded and Subsequently Executed

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:31-41; duplicated in README.md:33-43
Vulnerability Type: Mutable remote payload retrieval and execution
Risk Level: High

Vulnerable Code

SKILL.md:31-41:

bash
## Install

# Requires Python 3.8+. No pip install needed.
curl -O https://raw.githubusercontent.com/itsPremkumar/prompt-lint/main/prompt_lint.py
# Or copy the file anywhere — it's self-contained.

## Quick start

python prompt_lint.py self-test     # prove it works end-to-end
python prompt_lint.py check --help   # check subcommand

The same installation and execution instructions appear in README.md:33-43.

Technical Analysis

The installation procedure retrieves executable Python code from the mutable main branch of a personal GitHub repository. The quick-start instructions then execute a file with the downloaded name.

The downloaded content is not constrained by an immutable commit identifier, cryptographic checksum, or digital signature. Consequently, the effective code executed by users can change after the reviewed Skill package has been published and audited. This creates a remote payload channel independent of the bundled prompt_lint.py.

Remote retrieval is unnecessary for the declared prompt-linting functionality because a self-contained copy of prompt_lint.py is already included in the package. The behavior therefore exceeds the minimum access needed to run the Skill locally.

Attack Path

  1. An attacker compromises the maintainer account, repository, or publishing workflow, or an authorized maintainer replaces the file on the main branch.
  2. The attacker modifies prompt_lint.py at the documented raw GitHub URL.
  3. A user follows the installation instructions and downloads the modified file.
  4. The downloaded file is saved as prompt_lint.py without integrity verification.
  5. The user follows the quick-start instructions and executes the attacker ...[truncated 691 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the remote download command and direct users to execute the reviewed, bundled prompt_lint.py.
  • If remote distribution is genuinely required, use a release artifact or raw URL pinned to an immutable commit hash rather than main.
  • Publish an expected SHA-256 digest through a separate trusted channel and require verification before execution.
  • Prefer signed release artifacts and document signature verification.
  • Ensure installation does not overwrite an existing local file without explicit user confirmation.
  • Update both SKILL.md and README.md so they provide identical, integrity-preserving installation instructions.
  • Configure CI to detect mutable executable-download instructions in documentation.

T09 · Insecure Skill Coding Practices

Error
Location
ci/verify_product.py:40
Finding

Verification Harness Executes Every Root-Level Python File During Test Discovery

Content
View full analysis

Vulnerability Details

File Location: ci/verify_product.py:40-42 and ci/verify_product.py:80-81
Vulnerability Type: Unsafe execution of untrusted project files
Risk Level: High

Vulnerable Code

ci/verify_product.py:40-42:

python
def has_self_test(fp):
    r = subprocess.run([sys.executable, fp, "self-test"], capture_output=True, text=True, timeout=30)
    return r.returncode == 0 and "PASS" in r.stdout

ci/verify_product.py:80-81:

python
if any(has_self_test(fp) for fp in files):
    st_ok, st_note = True, "self-test subcommand"

The files collection is populated from every root-level Python file:

python
files = [os.path.join(folder, f) for f in os.listdir(folder) if f.endswith(".py")]

Technical Analysis

The verifier attempts to discover a self-test by launching every root-level .py file with the argument self-test. Python executes module-level statements before a script processes or rejects command-line arguments. A file therefore does not need to implement a self-test to have arbitrary top-level code executed.

The preceding compilation check only validates Python syntax and does not establish that a file is safe. The hardcoded-secret regex is similarly unrelated to runtime safety. The subprocess receives the verifier's inherited environment and is not isolated through filesystem, credential, process, or network restrictions. A 30-second timeout limits duration but does not prevent immediate malicious actions.

This is especially dangerous when the verifier runs against pull requests or other attacker-influenced product folders.

Attack Path

  1. An attacker adds or modifies any root-level file ending in .py.
  2. The file contains syntactically valid malicious module-level code.
  3. CI or a maintainer runs ci/verify_product.py against that folder.
  4. The verifier enumerates the attacker-controlled file.
  5. has_self_test() invokes the ...[truncated 921 chars]
Remediation
View remediation

Remediation Suggestions

  • Do not discover tests by executing every Python file.
  • Require an explicit, reviewed test command in a trusted manifest or invoke a fixed, known test entry point.
  • If static discovery is needed, parse source files with Python's ast module without importing or executing them.
  • Run tests from untrusted contributions in an isolated container or disposable virtual machine.
  • Remove secrets and privileged credentials from the test environment.
  • Disable network access unless it is explicitly required.
  • Mount source files read-only where practical and provide only a disposable writable directory.
  • Use a low-privilege account and enforce process, time, memory, and filesystem limits.
  • Do not run attacker-controlled pull-request code in a privileged workflow context.

T09 · Insecure Skill Coding Practices

Warning
Location
prompt_lint.py:64
Finding

Prompt Checks Always Exit Successfully Despite Detected Security Findings

Content
View full analysis

Vulnerability Details

File Location: prompt_lint.py:64-65; conflicting claims in SKILL.md:66-67 and README.md:68-69
Vulnerability Type: Fail-open CI security control
Risk Level: Medium

Vulnerable Code

prompt_lint.py:64-65:

python
if a.cmd == "self-test": return self_test()
if a.cmd == "check": check(a.file, a.json); return 0

The documentation represents the command as a CI gate with a nonzero failure result:

markdown
**Q: CI?**  
A: Yes — --json, non-zero on failure.

Technical Analysis

The check() function identifies issues and computes a score, but the check command discards the result for exit-status purposes and unconditionally returns 0. This includes cases where the linter reports a forbidden or jailbreak-style phrase.

CI systems generally determine whether a step passed from its process exit status. As implemented, the command only reports findings in output; it cannot enforce the documented security gate. This creates a fail-open condition and a false expectation that unsafe prompts will stop a build.

Attack Path

  1. A project configures CI to run python prompt_lint.py check prompt.md, relying on the documented nonzero failure behavior.
  2. A prompt containing forbidden phrases or multiple quality issues is committed.
  3. The linter detects and prints the issues.
  4. The check branch returns 0 regardless of the findings.
  5. The CI step is marked successful and downstream publication or deployment continues.

Impact Assessment

This flaw does not directly provide code execution or additional system privileges. Its impact is the bypass of the intended prompt-safety and quality gate. Prompts containing recognized injection language can proceed through CI, potentially reaching production despite the linter reporting them as unsafe.

The practical scope is limited to workflows that rely on the process exit code rather than independently pa ...[truncated 36 chars]

Remediation
View remediation

Remediation Suggestions

  • Define an explicit failure policy, such as failing on any forbidden phrase or when the score is below a configurable threshold.
  • Return a nonzero exit status when that policy is violated.
  • Preserve successful exit behavior only for clean or policy-compliant input.
  • Add command-line options for a threshold if different CI environments require different strictness.
  • Add automated tests asserting both output and exit status for safe and unsafe prompts.
  • Update the documentation if the tool is intended only to report findings rather than enforce them.
  • Consider emitting distinct exit codes for usage errors, file-reading failures, and lint-policy failures.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
70% confidence
Finding

Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Content

Scanner excerpt · prompt_lint.py (reported line 6)May include surrounding context.

python
prompt-lint.py - lint a prompt / SKILL.md for quality heuristics.

Flags weak prompts: missing role/goal, vague verbs, no output format, too long,
forbidden phrases (e.g. "always obey", jailbreak-style). Helps you ship crisp agent
instructions. Zero deps.

Usage:

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
70% confidence
Finding

Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.

Content

Scanner excerpt · prompt_lint.py (reported line 20)May include surrounding context.

python
import sys

VAGUE = re.compile(r'\b(help|do your best|somehow|maybe|as needed|appropriately)\b', re.I)
FORBID = re.compile(r'(?i)(ignore (all|previous) (instructions|rules)|jailbreak|always obey|do whatever)', re.I)
ROLE = re.compile(r'(?i)(you are|act as|your role|as an? .*?agent)', re.I)
GOAL = re.compile(r'(?i)(goal|objective|task is|your job)', re.I)
OUTFMT = re.compile(r'(?i)(output|format|return|respond with|json|markdown)', re.I)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
98% confidence
Finding

This code executes arbitrary Python files from the target product folder as part of verification by invoking <python> <file> self-test. In a CI or reviewer environment, a malicious product can place hostile code in any .py file and have it run automatically during verification, leading to arbitrary code execution, credential theft, filesystem tampering, or CI runner compromise.

Content

Scanner excerpt · ci/verify_product.py (reported line 37)May include surrounding context.

python
def has_self_test(fp):
    r = subprocess.run([sys.executable, fp, "self-test"], capture_output=True, text=True, timeout=30)
    return r.returncode == 0 and "PASS" in r.stdout

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · ci/verify_product.py (reported line 67)May include surrounding context.

python
all_ok &= axis(False, "frontmatter (no SKILL.md)")
        all_ok &= axis(False, "docs (no SKILL.md)")
    # 3 compiles
    comp_ok = all(subprocess.run([sys.executable, "-m", "py_compile", fp],
                                capture_output=True).returncode == 0 for fp in files)
    all_ok &= axis(comp_ok, "compiles (py_compile all .py)")
    # 4 self-test (any of 3 valid forms)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
99% confidence
Finding

This code automatically executes any test_*.py or *_test.py file found in the target folder. An attacker can submit a product with a malicious test file that exfiltrates secrets, alters the environment, or compromises the CI runner when verification is performed.

Content

Scanner excerpt · ci/verify_product.py (reported line 79)May include surrounding context.

python
tests = [os.path.join(folder, f) for f in os.listdir(folder)
                 if (f.startswith("test_") or f.endswith("_test.py")) and f.endswith(".py")]
        if tests:
            st_ok = all(subprocess.run([sys.executable, t], capture_output=True, text=True,
                                      timeout=30).returncode == 0 for t in tests)
            st_note = f"{len(tests)} test_*.py"
        elif re.search(r"(?im)^test\s*:", skill_txt):

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · ci/verify_product.py (reported line 92)May include surrounding context.

python
# 7 deploy-ready
    ci = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ci_check.py")
    if os.path.isfile(ci) and os.path.isfile(skill):
        r = subprocess.run([sys.executable, ci], cwd=folder, capture_output=True, text=True)
        all_ok &= axis(r.returncode == 0, "deploy-ready (ci/ci_check.py PASS)")
    else:
        all_ok &= axis(True, "deploy-ready (skipped)")

Static analysis

No suspicious patterns detected.