Back to skill

Security audit

Neckr0ik Security Fixer

Security checks for vulnerabilities and agentic risk

Overview

This security-fixer skill is not clearly malicious, but its auto-fix mode has unsafe file-modification behavior that can corrupt code or write outside the intended target.

Review this skill carefully before installing. Use it only on a clean, backed-up repository, prefer --dry-run, inspect every diff manually, and avoid --auto until the path containment, manual-review bypass, and source-replacement issues are fixed. The .env guidance itself looks purpose-aligned, but the file-writing behavior is too broad for unattended use.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/fixer.py:74
Finding
Unrestricted Scanner-Supplied File Access and Modification<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fixer.py:74-77`, `scripts/fixer.py:255-273` **Vulnerability Type**: Unvalidated path usage leading to arbitrary file read and write **Risk Level**: High ### Vulnerable Code ```python file_path = Path(vuln.file) try: content = file_path.read_text(encoding='utf-8') ``` ```python def _apply_fix(self, fix: Fix) -> None: """Apply a single fix to a file.""" # Create backup if enabled if self.backup: backup_path = fix.file_path.with_suffix(fix.file_path.suffix + '.bak') shutil.copy2(fix.file_path, backup_path) # Read file content = fix.file_path.read_text(encoding='utf-8') lines = content.split('\n') # Replace lines new_lines = lines[:fix.line_start-1] new_lines.extend(fix.fixed_code.split('\n')) new_lines.extend(lines[fix.line_end:]) # Write back fix.file_path.write_text('\n'.join(new_lines), encoding='utf-8') ``` ### Technical Analysis The path contained in `vuln.file` is accepted directly from the external scanner result and converted to a `Path` without validation. The implementation does not resolve the path against `self.skill_path`, verify that it remains under the requested skill directory, reject absolute paths, or detect symlinks that escape the directory. Consequently, a malicious or compromised `audit` implementation can submit an absolute path such as `/home/user/.bashrc` or a traversal path such as `../../sensitive-file`. The fixer will read that file during fix generation and may subsequently create a backup and overwrite it during fix application. The required `audit` module is not included in this project, so its trustworthiness and validation behavior cannot be established from the audited source. ### Attack Path 1. An attacker influences the target being scanned or compromises/spoofs the imported `audit` module. 2. The scanner returns a vulnerability whose `file` field points outside the inte ...[truncated 881 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Resolve the configured skill root once with `skill_root = self.skill_path.resolve(strict=True)`. - Resolve every scanner-supplied path before reading or writing it. - Require each candidate path to be contained by the skill root using `Path.relative_to()` or `Path.is_relative_to()`. - Reject absolute scanner paths unless they resolve inside the approved root. - Detect and reject symlink-based escapes. Revalidate containment immediately before each write to reduce time-of-check/time-of-use exposure. - Validate scanner output against a strict schema, including path type, line bounds, vulnerability identifier, and expected source text. - Open files defensively and avoid following symlinks where the operating system supports that behavior. - Apply least privilege by running the fixer under an account that cannot modify unrelated sensitive files. Example containment check: ```python skill_root = self.skill_path.resolve(strict=True) candidate = Path(vuln.file) if not candidate.is_absolute(): candidate = skill_root / candidate candidate = candidate.resolve(strict=True) try: candidate.relative_to(skill_root) except ValueError: raise ValueError(f"Scanner path escapes skill root: {candidate}") ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fixer.py:228
Finding
Automatic Mode Applies Fixes Explicitly Marked as Unsafe for Automatic Application<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fixer.py:228-249` **Vulnerability Type**: Safety-control bypass in automatic remediation **Risk Level**: High ### Vulnerable Code ```python def apply_fixes(self, auto: bool = False) -> List[Fix]: """Apply all fixes to files.""" applied = [] for fix in self.fixes: if fix.manual_review and not auto: print(f"[REVIEW] {fix.file_path}:{fix.vulnerability.line} - {fix.vulnerability.name}") print(f" {fix.notes}") continue if not fix.auto_fixable and not auto: print(f"[MANUAL] {fix.file_path}:{fix.vulnerability.line} - {fix.vulnerability.name}") print(f" Requires manual fix") continue # Apply fix if self.dry_run: print(f"[DRY-RUN] Would fix: {fix.file_path}:{fix.vulnerability.line}") ``` ### Technical Analysis Both safety conditions are dependent on `not auto`. When `--auto` is enabled, a fix is applied even if `manual_review` is true or `auto_fixable` is false. This behavior defeats the distinction between trusted automatic transformations and review-only placeholders. It also contradicts the documented claim that complex issues are flagged for manual review. Non-fix transformations, including comments generated for `eval`, `exec`, or dependency findings, can therefore be written directly into source files. ### Attack Path 1. The scanner reports a vulnerability for which the fixer generates a review-only or non-auto-fixable replacement. 2. The resulting `Fix` has `manual_review=True` or `auto_fixable=False`. 3. The user invokes the documented `fix <path> --auto` command. 4. Because `auto` is true, both rejection conditions evaluate to false. 5. `_apply_fix()` writes the unsafe placeholder or incomplete transformation into the target file. 6. The modified application may become nonfunctional or lose security-relevant behavior. ### I ...[truncated 513 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never apply fixes marked for manual review or marked non-auto-fixable, regardless of `--auto`. - Define `--auto` as automatic approval only for transformations that have independently passed eligibility and validation checks. - Use an unconditional eligibility gate: ```python if fix.manual_review or not fix.auto_fixable: print(f"[MANUAL] {fix.file_path}:{fix.vulnerability.line}") continue ``` - Represent review status with a single enum rather than two potentially inconsistent booleans, for example `AUTO_SAFE`, `REVIEW_REQUIRED`, and `UNSUPPORTED`. - Validate transformed source before writing it, including syntax parsing and confirmation that the expected vulnerable construct was removed. - Require explicit interactive confirmation for review-only changes; do not provide a global flag that bypasses this restriction. - Add tests proving that `--auto` cannot apply unsupported, manual-review, `eval`/`exec`, or dependency placeholder changes. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/fixer.py:82
Finding
Fix Application Replaces and Deletes Unrelated Surrounding Source Lines<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fixer.py:82-85`, `scripts/fixer.py:263-273` **Vulnerability Type**: Incorrect source replacement range causing code corruption **Risk Level**: High ### Vulnerable Code ```python # Get original code line_start = max(1, vuln.line - 2) line_end = min(len(lines), vuln.line + 2) original_code = '\n'.join(lines[line_start-1:line_end]) ``` ```python # Read file content = fix.file_path.read_text(encoding='utf-8') lines = content.split('\n') # Replace lines new_lines = lines[:fix.line_start-1] new_lines.extend(fix.fixed_code.split('\n')) new_lines.extend(lines[fix.line_end:]) # Write back fix.file_path.write_text('\n'.join(new_lines), encoding='utf-8') ``` ### Technical Analysis The implementation expands a single reported vulnerability line into a five-line context window: two lines before the finding, the finding itself, and two lines after it. That context range is then stored as `line_start` and `line_end`. During application, the entire context window is removed and replaced with `fixed_code`. However, the fix generators generally transform only the reported line and do not preserve the four neighboring lines. The surrounding lines are therefore silently deleted. The code also does not verify that the source file remains unchanged between scan and write. Concurrent edits or earlier fixes can shift line numbers, causing subsequent fixes to replace unrelated code. ### Attack Path 1. A scanner finding identifies one vulnerable source line. 2. `_generate_fix()` expands the replacement boundaries to include two neighboring lines on each side. 3. The generator creates replacement code for only the identified construct. 4. `_apply_fix()` removes the complete five-line range. 5. Unrelated statements, validation checks, access-control checks, exception handling, or control-flow boundaries in the neighboring lines are deleted. 6. The corrupted file is written and reported as fixed. A malicious scanner ...[truncated 621 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store display context separately from the exact replacement span. - Replace only the scanner-reported line or an explicitly reported start/end range. - Require scanner findings to include the expected original source text or a cryptographic digest. - Before writing, verify that the current text at the replacement span exactly matches the text that was scanned. - Recalculate offsets after each fix, or group fixes by file and apply them from the highest line number to the lowest. - Reject overlapping fixes unless they are merged and validated explicitly. - Parse Python files into an abstract syntax tree or use a concrete syntax tree library for syntax-aware transformations. - Parse the resulting file before committing it and use an atomic temporary-file replacement only after validation succeeds. - Preserve a backup, generate a diff, and require review when a transformation changes lines outside the exact vulnerable construct. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/fixer.py:174
Finding
Generated Security Fixes Are Incomplete and Can Leave Vulnerabilities Unresolved<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fixer.py:174-219`; `references/fix-templates.md:98-103` **Vulnerability Type**: Unsafe security transformation and misleading remediation **Risk Level**: Medium ### Vulnerable Code The shell transformation only changes the `shell` flag: ```python # subprocess with shell=True if 'shell=True' in line: fixed = line.replace('shell=True', 'shell=False') return fixed, True ``` The prompt-injection transformation discards the original vulnerable statement and creates a generic variable that may never be used: ```python def _fix_prompt_injection(self, vuln: Vulnerability, lines: List[str]) -> Tuple[str, bool]: """Fix prompt injection vulnerabilities.""" line = lines[vuln.line - 1] if vuln.line <= len(lines) else "" indent = len(line) - len(line.lstrip()) fixed = f'''{line[:indent]}import re {line[:indent]}def sanitize_for_prompt(text: str) -> str: {line[:indent]} return re.sub(r'[<>\\{{\\}}\\[\\]\\\\]', '', text[:1000]) {line[:indent]} {line[:indent]}# Sanitize user input before use in prompt {line[:indent]}sanitized_input = sanitize_for_prompt(user_input)''' return fixed, True ``` The path-containment transformation uses a string-prefix comparison: ```python fixed = f'''{line[:indent]}from pathlib import Path {line[:indent]}import re {line[:indent]} {line[:indent]}def safe_path(base_dir: str, user_input: str) -> Path: {line[:indent]} safe_name = re.sub(r'[^\\w.-]', '_', Path(user_input).name) {line[:indent]} full_path = (Path(base_dir) / safe_name).resolve() {line[:indent]} if not str(full_path).startswith(str(Path(base_dir).resolve())): {line[:indent]} raise ValueError("Path traversal detected") {line[:indent]} return full_path''' ``` The documentation also recommends shell quoting while invoking a process without a shell: ```python import subprocess import shlex safe_input = shlex.quote(user_input) subprocess.run(["echo", safe ...[truncated 2732 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Replace text-based transformations with syntax-aware transformations and reject cases that cannot be transformed unambiguously. - For subprocess calls: - Parse the intended executable and arguments into an explicit list. - Keep `shell=False`. - Pass user input as a distinct argument without `shlex.quote()`. - Do not split arbitrary command strings with basic whitespace splitting. - Require manual review when shell operators, redirections, pipelines, substitutions, or environment expansion are present. - For prompt handling: - Keep system instructions and untrusted user content in separate structured message roles. - Treat user content as untrusted data rather than attempting to make it safe through punctuation removal. - Add application-specific allowlists, output validation, capability restrictions, and human approval for sensitive tool actions. - Ensure the transformed value is actually used by the original call site. - For path validation: - Resolve the base and candidate paths. - Use `candidate.relative_to(base)` or `candidate.is_relative_to(base)` instead of string-prefix checks. - Establish an explicit policy on whether nested paths are permitted. - Revalidate after resolution and before opening the file. - Parse and test generated Python before writing it. - Change the status to manual review whenever semantic equivalence and complete remediation cannot be proven. - Correct the unsafe examples in `references/fix-templates.md` so documentation does not encourage broken security patterns. ]]>
Vulnerability Patterns
  • 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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (11)

Credential Access

High
Category
Privilege Escalation
Content
```
# Environment Variables
# Copy to .env and fill in your values

OPENAI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```
# Environment Variables
# Copy to .env and fill in your values

OPENAI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```
# Environment Variables
# Copy to .env and fill in your values

OPENAI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```
# Environment Variables
# Copy to .env and fill in your values

OPENAI_API_KEY=your-key-here
ANTHROPIC_API_KEY=your-key-here
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
**Before:**
```python
subprocess.run(f"ls {directory}", shell=True, capture_output=True)
```

**After:**
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).

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def _load_from_env(self) -> None:
        """Load configuration from environment variables."""
        for key, value in os.environ.items():
            if key.startswith(self.prefix):
                config_key = key[len(self.prefix):].lower()
                self._config[config_key] = value
Confidence
70% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Credential Access

High
Category
Privilege Escalation
Content
if gitignore_path.exists():
            gitignore_content = gitignore_path.read_text()
        
        if '.env' not in gitignore_content:
            gitignore_content += "\n# Environment variables\n.env\n.env.local\n"
            
            if not self.dry_run:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill describes capabilities to read and write files, access environment variables, and invoke shell commands, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an authorization gap where a consumer may not understand the skill's operational reach, increasing the risk of unsafe execution or over-privileged use.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest says this skill auto-remediates hardcoded secrets, shell injection risks, prompt injection, and path traversal issues. This file also includes a separate 'Code Execution' section with eval/exec remediation templates, which expands the documented remediation scope beyond the listed vulnerability classes.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The fixer writes modified source files in place once invoked, and can do so automatically with `--auto`, but it does not present an explicit overwrite warning or require confirmation before changing project files. This creates a real safety risk because users may unintentionally alter code, and if the skill is pointed at an unexpected path it could overwrite files beyond what the user intended.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The tool creates or updates `.env.example` and `.gitignore` as part of normal execution without an explicit warning that project metadata files will be written. While not credential theft by itself, silent modification of repository files can surprise users, break workflows, or be abused to persist changes in a project under the guise of security remediation.

Static analysis

Detected: suspicious.dynamic_code_execution, suspicious.exposed_secret_literal

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/fixer.py:180

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
references/fix-templates.md:11

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:55