Back to skill

Security audit

GitHub Token

Security checks for vulnerabilities and agentic risk

Overview

The skill is for GitHub PAT workflows, but it handles tokens and pushes repository data in ways that could expose credentials or unintended files.

Review this skill before installing. Only use narrowly scoped fine-grained PATs, avoid storing tokens in workspace files such as TOOLS.md, do not pass tokens on command lines, verify the Git remote hostname before any push, and inspect git status/diff manually because the push command stages everything.

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/gh.py:126
Finding

GitHub PAT Can Be Transmitted to an Attacker-Controlled Host

Content
View full analysis

Vulnerability Details

File Location: scripts/gh.py, lines 126-153
Vulnerability Type: Improper remote-host validation leading to credential disclosure
Risk Level: High

Vulnerable Code

python
# Check if token already in URL
token_in_url = "@github.com" in remote_url

if token and "github.com" in remote_url and not token_in_url:
    # Inject token into remote URL
    auth_url = remote_url.replace("https://", f"https://{token}@")
    use_url = True
else:
    # Token already in URL or not needed
    use_url = False

# Stage all changes
print("📦 Staging changes...")
subprocess.run(["git", "add", "-A"])

# Commit
print(f"💾 Committing: {message}")
result = subprocess.run(["git", "commit", "-m", message], capture_output=True, text=True)
if result.returncode != 0 and "nothing to commit" in result.stdout + result.stderr:
    print("ℹ️  Nothing to commit")
    return

# Push
print(f"🚀 Pushing to {branch}...")
if use_url:
    result = subprocess.run(
        ["git", "push", "-u", auth_url, branch],
        capture_output=True, text=True
    )

Technical Analysis

The remote is classified as GitHub by testing whether the unparsed URL contains the substring github.com. This does not verify that the URL's actual hostname is github.com.

For example, an attacker-controlled remote such as:

text
https://attacker.example/github.com/owner/repo.git

passes the substring check. Token injection then produces:

text
https://GITHUB_PAT@attacker.example/github.com/owner/repo.git

When git push processes that URL, it connects to attacker.example and can disclose the PAT as URL user information. The use of an argument array prevents shell injection, but it does not prevent this credential-forwarding vulnerability.

Attack Path

  1. An attacker or untrusted repository configuration changes the local origin URL to a URL whose path contains ` ...[truncated 1121 chars]
Remediation
View remediation

Remediation Suggestions

  1. Parse the remote with urllib.parse.urlsplit rather than performing substring checks.
  2. Require the normalized hostname to equal github.com exactly.
  3. Reject unexpected schemes, user information, ports, and malformed URLs.
  4. Handle SSH remotes separately and do not inject HTTP credentials into them.
  5. Do not embed the PAT in a URL. Use a protected Git credential helper or temporary GIT_ASKPASS implementation.
  6. Clear temporary credential-related environment variables immediately after the Git operation.
  7. Add tests covering deceptive remotes such as:
    • https://attacker.example/github.com/repo
    • https://github.com.attacker.example/repo
    • https://attacker@github.com.example/repo
  8. Rotate any token that may already have been used with an untrusted remote.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gh.py:80
Finding

Plaintext PAT Exposure Through Clone URLs, Process Arguments, and Documented Storage

Content
View full analysis

Vulnerability Details

File Location: scripts/gh.py, lines 80-85; additional affected locations: scripts/gh.py line 270 and SKILL.md lines 14-15, 20-22, and 53-55
Vulnerability Type: Insecure handling and persistence of authentication credentials
Risk Level: High

Vulnerable Code

python
# Use token in URL for auth
url = f"https://{token}@github.com/{repo}.git"
dest = repo.split("/")[-1]

print(f"📥 Cloning {repo}...")
result = subprocess.run(["git", "clone", url, dest], capture_output=True, text=True)

The command-line interface also explicitly accepts the secret as an argument:

python
parser.add_argument("--token", "-t", help="GitHub PAT (or set GITHUB_TOKEN)")

The documentation recommends plaintext storage:

markdown
Store in TOOLS.md or pass via `--token`.
markdown
Agent stores token in TOOLS.md under `### GitHub` section. Never expose in logs or messages.

Technical Analysis

The clone operation embeds the PAT directly in the repository URL and passes that URL as a subprocess argument. This creates several credential-exposure channels:

  • Command-line arguments can be visible to local process-inspection facilities while the command runs.
  • Passing --token can place the PAT in shell history and command logs.
  • Git commonly records the clone URL as the origin remote in .git/config, potentially persisting the PAT in plaintext.
  • Storing the token in TOOLS.md makes it available to any process, user, backup system, or agent session capable of reading the workspace.
  • Diagnostic tooling or wrappers around Git may record the full argument list.

HTTPS protects the credential while it is transmitted to the legitimate GitHub host, but it does not address local disclosure or persistence. Supplying a PAT is necessary for authenticated operations, but exposing it in URLs and ordinary files is not necessary and violates the documented least-priv ...[truncated 1131 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the --token option to prevent secrets from entering shell history and process arguments.
  2. Remove the recommendation to store PATs in TOOLS.md or any ordinary workspace file.
  3. Obtain credentials from an operating-system credential manager, Git credential helper, or interactive no-echo prompt.
  4. Use a temporary, permission-restricted GIT_ASKPASS mechanism so the PAT is not embedded in the remote URL.
  5. Configure the saved origin as a credential-free URL such as https://github.com/owner/repo.git.
  6. Ensure temporary credential files have owner-only permissions and are securely deleted immediately after use.
  7. Redact credentials from errors, diagnostics, and subprocess output.
  8. Detect and remove credential-bearing Git remotes from existing clones.
  9. Rotate PATs previously passed through command-line arguments or stored in repository configuration and workspace files.
  10. Recommend fine-grained PATs restricted to only the required repositories and permissions.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gh.py:135
Finding

Push Command Indiscriminately Stages and Uploads All Workspace Changes

Content
View full analysis

Vulnerability Details

File Location: scripts/gh.py, lines 135-142
Vulnerability Type: Overly broad file selection causing unintended sensitive-data publication
Risk Level: Medium

Vulnerable Code

python
# Stage all changes
print("📦 Staging changes...")
subprocess.run(["git", "add", "-A"])

# Commit
print(f"💾 Committing: {message}")
result = subprocess.run(["git", "commit", "-m", message], capture_output=True, text=True)
if result.returncode != 0 and "nothing to commit" in result.stdout + result.stderr:
    print("ℹ️  Nothing to commit")
    return

Technical Analysis

git add -A stages every tracked modification, deletion, and untracked file in the repository. The implementation neither asks the user to select files nor displays the staged diff before committing and pushing.

Consequently, unrelated or sensitive files can be included in the commit. Examples include .env files, private keys, access tokens, local configuration, generated archives, debugging output, and proprietary files that were not intended for publication.

The declared push functionality requires transmitting selected repository changes, but it does not require staging every available change. The implementation therefore exceeds the minimum data scope necessary for the operation.

Attack Path

  1. A sensitive or unrelated file exists within the repository working tree and is not excluded by .gitignore.
  2. The user invokes the Skill's push command to publish an intended change.
  3. git add -A silently stages the sensitive file along with the intended files.
  4. The command creates a commit containing all staged content.
  5. The commit is pushed to the configured remote repository.
  6. Repository collaborators, automated systems, or the public can retrieve the sensitive content.
  7. Removing the file in a later commit does not remove it from existing Git history.

Impact Assessment

This issu ...[truncated 623 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require users to specify the exact files or paths to stage.
  2. Display git status --short and the staged diff before committing.
  3. Require explicit confirmation before committing and pushing.
  4. Refuse common secret-bearing files by default, including .env, private keys, credential files, and token stores.
  5. Run secret detection against staged content before committing.
  6. Respect and encourage a restrictive .gitignore, while recognizing that ignore rules alone are not a complete security control.
  7. Separate staging, committing, and pushing into distinct commands so users can review each operation.
  8. Check and handle the return status of git add and git commit before attempting a push.
  9. If sensitive content has already been pushed, revoke exposed credentials and rewrite repository history where appropriate.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

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

md
---
name: github-pat
description: Interact with GitHub using Personal Access Tokens. Secure, user-controlled access - no OAuth, no full account access. Clone, push, branch, PR, issues. Use when user wants to work with GitHub repos.
---

# GitHub PAT

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

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

md
---
name: github-pat
description: Interact with GitHub using Personal Access Tokens. Secure, user-controlled access - no OAuth, no full account access. Clone, push, branch, PR, issues. Use when user wants to work with GitHub repos.
---

# GitHub PAT

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

Recommending storage of a PAT in TOOLS.md encourages plaintext credential persistence in a documentation file that may be copied, logged, indexed, committed, or exposed to other tools and agents. This is especially dangerous because the skill is designed for source-control workflows, where accidental inclusion in a repository is a realistic failure mode.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

Recommending storage of a PAT in TOOLS.md encourages plaintext credential persistence in a documentation file that may be copied, logged, indexed, committed, or exposed to other tools and agents. This is especially dangerous because the skill is designed for source-control workflows, where accidental inclusion in a repository is a realistic failure mode.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill exposes shell, network, and environment-backed capabilities but does not declare any explicit tool scope or permission boundaries. In an agent setting, this increases the chance the skill is invoked with broader execution power than intended, making misuse, unintended network access, or unsafe command execution harder to constrain.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The description is broad enough to match many generic GitHub-related requests, which can cause the agent to invoke this credential-handling skill in situations where it is unnecessary or inappropriate. Because the skill deals with PATs and repository operations, over-broad routing raises the risk of accidental credential use, writes to repositories, or exposure during unrelated tasks.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
96% confidence
Finding

The clone command embeds the GitHub Personal Access Token directly into the repository URL passed to git. This can expose the token via process listings, git error output, shell history wrappers, or credential leakage into local git configuration and logs, making credential theft plausible on multi-user or monitored systems.

Content

Scanner excerpt · scripts/gh.py (reported line 76)May include surrounding context.

python
dest = repo.split("/")[-1]
    
    print(f"📥 Cloning {repo}...")
    result = subprocess.run(["git", "clone", url, dest], capture_output=True, text=True)
    
    if result.returncode == 0:
        print(f"✅ Cloned to ./{dest}")

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 · scripts/gh.py (reported line 89)May include surrounding context.

python
branch = args.branch
    
    # Check if we're in a git repo
    result = subprocess.run(["git", "rev-parse", "--git-dir"], capture_output=True)
    if result.returncode != 0:
        print("Error: Not in a git repository", file=sys.stderr)
        sys.exit(1)

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 · scripts/gh.py (reported line 95)May include surrounding context.

python
sys.exit(1)
    
    print(f"🌿 Creating branch: {branch}")
    result = subprocess.run(["git", "checkout", "-b", branch], capture_output=True, text=True)
    
    if result.returncode == 0:
        print(f"✅ Switched to new branch '{branch}'")

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 · scripts/gh.py (reported line 101)May include surrounding context.

python
print(f"✅ Switched to new branch '{branch}'")
    else:
        # Maybe branch exists, try switching
        result = subprocess.run(["git", "checkout", branch], capture_output=True, text=True)
        if result.returncode == 0:
            print(f"✅ Switched to existing branch '{branch}'")
        else:

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 · scripts/gh.py (reported line 114)May include surrounding context.

python
message = args.message
    
    # Get current branch
    result = subprocess.run(["git", "branch", "--show-current"], capture_output=True, text=True)
    branch = result.stdout.strip() or "main"
    
    # Get remote URL

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 · scripts/gh.py (reported line 200)May include surrounding context.

python
message = args.message
    
    # Get current branch
    result = subprocess.run(["git", "branch", "--show-current"], capture_output=True, text=True)
    branch = result.stdout.strip() or "main"
    
    # Get remote URL

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 · scripts/gh.py (reported line 118)May include surrounding context.

python
branch = result.stdout.strip() or "main"
    
    # Get remote URL
    result = subprocess.run(["git", "remote", "get-url", "origin"], capture_output=True, text=True)
    remote_url = result.stdout.strip()
    
    # Check if token already in URL

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 · scripts/gh.py (reported line 184)May include surrounding context.

python
branch = result.stdout.strip() or "main"
    
    # Get remote URL
    result = subprocess.run(["git", "remote", "get-url", "origin"], capture_output=True, text=True)
    remote_url = result.stdout.strip()
    
    # Check if token already in URL

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 · scripts/gh.py (reported line 226)May include surrounding context.

python
branch = result.stdout.strip() or "main"
    
    # Get remote URL
    result = subprocess.run(["git", "remote", "get-url", "origin"], capture_output=True, text=True)
    remote_url = result.stdout.strip()
    
    # Check if token already in URL

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The push workflow automatically stages all tracked and untracked changes with 'git add -A', commits them, and transmits them without showing a diff or requiring explicit user confirmation. In an agent skill context, this materially increases the chance of accidentally exfiltrating secrets, local configuration, generated artifacts, or unrelated work to a remote repository.

Content

No source excerpt is available for this finding.

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 · scripts/gh.py (reported line 134)May include surrounding context.

python
# Stage all changes
    print("📦 Staging changes...")
    subprocess.run(["git", "add", "-A"])
    
    # Commit
    print(f"💾 Committing: {message}")

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 · scripts/gh.py (reported line 138)May include surrounding context.

python
# Commit
    print(f"💾 Committing: {message}")
    result = subprocess.run(["git", "commit", "-m", message], capture_output=True, text=True)
    if result.returncode != 0 and "nothing to commit" in result.stdout + result.stderr:
        print("ℹ️  Nothing to commit")
        return

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

The push command injects the PAT into the remote URL and passes that authenticated URL directly to git push. This risks exposing the token in process arguments, diagnostic output, audit logs, and possibly repository configuration or credential traces, enabling compromise of GitHub access.

Content

Scanner excerpt · scripts/gh.py (reported line 146)May include surrounding context.

python
# Push
    print(f"🚀 Pushing to {branch}...")
    if use_url:
        result = subprocess.run(
            ["git", "push", "-u", auth_url, branch],
            capture_output=True, text=True
        )

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 · scripts/gh.py (reported line 151)May include surrounding context.

python
capture_output=True, text=True
        )
    else:
        result = subprocess.run(
            ["git", "push", "-u", "origin", branch],
            capture_output=True, text=True
        )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

This fallback push path again places the PAT in the authenticated URL passed to git. As with the earlier push case, credentials may leak through process inspection, logs, crash output, or other local observability channels, exposing GitHub account access tied to the PAT.

Content

Scanner excerpt · scripts/gh.py (reported line 161)May include surrounding context.

python
else:
        # Try setting upstream
        if use_url:
            result = subprocess.run(
                ["git", "push", "--set-upstream", auth_url, branch],
                capture_output=True, text=True
            )

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 · scripts/gh.py (reported line 166)May include surrounding context.

python
capture_output=True, text=True
            )
        else:
            result = subprocess.run(
                ["git", "push", "--set-upstream", "origin", branch],
                capture_output=True, text=True
            )

Excessive Permissions

Low
Category
Privilege Escalation
Confidence
75% confidence
Finding

Skill requests more permissions than appear necessary for its stated functionality. Review if elevated access is justified.

Content

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

md
## Security Model

- **User controls access** via PAT scopes
- **No OAuth** - no "allow full access" prompts
- **Least privilege** - user creates PAT with minimal needed scopes
- **Fine-grained PATs** supported for specific repo access

Static analysis

No suspicious patterns detected.