T09 · Insecure Skill Coding Practices
- Location
scripts/create_pr.py:10- Finding
Shell Command Injection and Guardrail Bypass in Pull Request Creation
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is meant to fix GitHub issues, but its safety wrappers are under-enforced and some scripts can run unsafe shell commands while using your GitHub credentials.
Use this only in a disposable checkout with a tightly scoped GitHub account or token. Do not give it access to private repositories or broad local files until command execution is changed to structured argv calls, PR creation is forced through verified approvals, and repo/branch/path scope checks are enforced at execution time.
scripts/create_pr.py:10Shell Command Injection and Guardrail Bypass in Pull Request Creation
scripts/sandbox.py:130Caller-Asserted Approval Allows Execution of Non-Allowlisted Commands
scripts/sandbox.py:34Protected Branch, Repository, and Path Scope Checks Are Not Enforced During Command Execution
The declared description promises a broad autonomous GitHub issue resolution agent with end-to-end workflow support from discovery through fix and PR submission, plus guardrails. The supplied code chunk does something much narrower: it reads one specified issue and related metadata from the public GitHub API and emits JSON. It does not discover issues across a repo, modify code, generate fixes, interact with git or GitHub pull requests beyond reading linked references, or implement any guardrails. This is a material description-versus-behavior mismatch in primary purpose and capabilities.
The declared description promises a full autonomous GitHub issue resolution workflow, including discovering issues, analyzing them, fixing bugs, and safely submitting PRs with guardrails. The supplied code only performs one limited step: creating/checking out a branch, pushing it to origin, and creating a pull request with gh. It accepts an issue number argument but does not use it. There is no logic for finding issues, inspecting repository state beyond current branch, modifying code, validating fixes, constraining scope, or enforcing safety policies. While PR creation could be a supporting component of such an agent, the code chunk itself materially underimplements the declared purpose and lacks the stated guardrails, so this is a description-behavior mismatch.
The declared description promises a much broader autonomous agent that can discover, analyze, fix issues, and submit PRs with explicit guardrails. The supplied code chunk only reads public GitHub issue data via the GitHub API, checks timelines for linked PRs, scores issue candidates, and prints ranked results. This is related to issue discovery, but it does not implement issue fixing, code changes, PR creation, repository write access, or meaningful guardrail logic. The primary behavior is a narrow issue-fetching and ranking utility, which materially underdelivers relative to the declared end-to-end resolver agent.
The declared description promises an autonomous GitHub issue resolver covering discovery, analysis, fixing issues, and PR submission with guardrails. The supplied code only performs issue fetching, heuristic analysis, ranking, and presentation of recommendations. While issue discovery/analysis is consistent with part of the description, the primary declared capability—actually resolving issues—is absent. No code changes, repository writes, branch/PR operations, or explicit safety guardrails are present in this chunk. Therefore the description materially overstates the implemented behavior.
Referenced artifact was not completely inspected
1. Load `guardrails.json` config
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
},
"paths": {
"denied": [
".env",
".env.*",
"*.pem",
"*.key",
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
},
"paths": {
"denied": [
".env",
".env.*",
"*.pem",
"*.key",
Allowing broad commands like git push and git push -u origin creates parameter-abuse risk because the guardrail lists only command prefixes, not tightly validated argument structures. If enforcement is naive, an agent could push to an unintended remote, ref, or branch, undermining the intended approval workflow and repository-scope restrictions.
"git add",
"git commit",
"git push",
"git push -u origin",
"git diff",
"git status",
"git branch",
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).
"git push --mirror",
"git push --delete",
"git rebase",
"git reset --hard",
"git reset --merge",
"git clean",
"git filter-branch",
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
| `timeoutMinutes` | 15 | Auto-abort stuck operations |
| `draftPRByDefault` | true | Always open as draft PR |
| `noForceEver` | true | `--force` push permanently blocked |
| `noSelfModify` | true | Cannot edit its own skill/plugin files |
| `autoRollbackOnTestFail` | true | Revert changes if tests fail |
## Scripts
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
| `timeoutMinutes` | 15 | Auto-abort stuck operations |
| `draftPRByDefault` | true | Always open as draft PR |
| `noForceEver` | true | `--force` push permanently blocked |
| `noSelfModify` | true | Cannot edit its own skill/plugin files |
| `autoRollbackOnTestFail` | true | Revert changes if tests fail |
## Scripts
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
| `timeoutMinutes` | 15 | Auto-abort stuck operations |
| `draftPRByDefault` | true | Always open as draft PR |
| `noForceEver` | true | `--force` push permanently blocked |
| `noSelfModify` | true | Cannot edit its own skill/plugin files |
| `autoRollbackOnTestFail` | true | Revert changes if tests fail |
## Scripts
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
| `timeoutMinutes` | 15 | Auto-abort stuck operations |
| `draftPRByDefault` | true | Always open as draft PR |
| `noForceEver` | true | `--force` push permanently blocked |
| `noSelfModify` | true | Cannot edit its own skill/plugin files |
| `autoRollbackOnTestFail` | true | Revert changes if tests fail |
## Scripts
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).
python3 guardrails.py branch main
# Check if command is allowed
python3 guardrails.py command "git push --force"
# Check file path
python3 guardrails.py path ".env.production"
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
python3 guardrails.py command "git push --force"
# Check file path
python3 guardrails.py path ".env.production"
# Full validation
python3 guardrails.py validate write_code owner=facebook repo=react path=src/App.tsx
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
python3 sandbox.py run_approved git push origin fix-issue-42
# Check if file is safe
python3 sandbox.py check_file .env.local
# Get status
python3 sandbox.py status
This is a true tool-parameter-abuse issue because the script builds shell commands from external inputs and executes them through a shell. In the context of an autonomous GitHub issue resolver with access to local git state and GitHub authentication, command injection is especially dangerous: it can run arbitrary local commands, tamper with repositories, steal tokens, or alter remote state.
def run_cmd(cmd, check=True):
"""Run shell command and return output."""
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if check and result.returncode != 0:
print(f"Command failed: {cmd}", file=sys.stderr)
print(f"Error: {result.stderr}", file=sys.stderr)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
if "\x00" in normalized:
return {"allowed": False, "reason": "Path contains null byte (injection attempt)"}
# ── STRIP WHITESPACE for matching (catch ".env " with trailing space) ──
stripped = normalized.strip()
stripped_nfkc = normalized_nfkc.strip()
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
results["blocked_by"] = "path"
return results
# Self-modify check
self_check = self.check_self_modify(kwargs["path"])
results["checks"].append({"check": "self_modify", "result": self_check})
if not self_check["allowed"]:
Passing unstructured command text into subprocess.run with shell=True is a classic tool-parameter-abuse primitive. In this skill, the agent is explicitly designed to operate on repositories and issues autonomously, so attacker-influenced content could be transformed into shell commands, leading to arbitrary command execution, data exfiltration, repository sabotage, or lateral movement if the runtime has credentials.
# Step 5: Execute
work_dir = cwd or self._working_dir
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
The pre-approved execution path still accepts a raw shell command and executes it with shell=True, which preserves the same abuse surface while potentially encouraging over-trust because it is marked approved. If approval decisions can be socially engineered or based on a non-canonical command string, an attacker may smuggle dangerous shell behavior into the approved command.
# Execute
work_dir = cwd or self._working_dir
try:
result = subprocess.run(
command, shell=True, capture_output=True, text=True,
cwd=work_dir,
timeout=self.guardrails.behavior.get("timeoutMinutes", 15) * 60
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
def check_file_safe(self, file_path: str) -> dict:
"""Check if a file is safe to read/modify."""
path_check = self.guardrails.check_path(file_path)
self_check = self.guardrails.check_self_modify(file_path)
if not path_check["allowed"]:
self.audit.log_guardrail_block("file_access", path_check["reason"],
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
def check_file_safe(self, file_path: str) -> dict:
"""Check if a file is safe to read/modify."""
path_check = self.guardrails.check_path(file_path)
self_check = self.guardrails.check_self_modify(file_path)
if not path_check["allowed"]:
self.audit.log_guardrail_block("file_access", path_check["reason"],
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
def check_file_safe(self, file_path: str) -> dict:
"""Check if a file is safe to read/modify."""
path_check = self.guardrails.check_path(file_path)
self_check = self.guardrails.check_self_modify(file_path)
if not path_check["allowed"]:
self.audit.log_guardrail_block("file_access", path_check["reason"],
Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.
def check_file_safe(self, file_path: str) -> dict:
"""Check if a file is safe to read/modify."""
path_check = self.guardrails.check_path(file_path)
self_check = self.guardrails.check_self_modify(file_path)
if not path_check["allowed"]:
self.audit.log_guardrail_block("file_access", path_check["reason"],
No suspicious patterns detected.