Back to skill

Security audit

github-workflow-integration

Security checks for vulnerabilities and agentic risk

Overview

This GitHub helper is mostly coherent and disclosed, but it can change remote repositories with a PAT without a technical confirmation step.

Install only if you are comfortable giving the agent a GitHub PAT that can affect repositories. Prefer a least-privilege token, run write commands with --dry-run first, and verify repo, branch, file path, and commit or PR details before allowing any real write.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gh_workflow.py:90
Finding

Repository write operation does not enforce the documented confirmation gate

Content
View full analysis

Vulnerability Details

File Location: scripts/gh_workflow.py, lines 90–163
Vulnerability Type: Unenforced authorization and confirmation gate for remote repository mutation
Risk Level: Medium

Vulnerable Code

python
def cmd_push_file(a):
    with open(a.path, "rb") as f:
        content_b64 = base64.b64encode(f.read()).decode("ascii")
    remote_path = a.repo_path or os.path.basename(a.path)
    # 1. blob
    blob = call("POST", "/repos/%s/git/blobs" % a.repo,
                {"content": content_b64, "encoding": "base64"}, a.dry_run)
    if a.dry_run:
        print("would push %s -> %s:%s (branch=%s)" % (a.path, a.repo, remote_path, a.branch))
        return
    blob_sha = blob["sha"]
    # 2. current branch tip
    ref = call("GET", "/repos/%s/git/refs/heads/%s" % (a.repo, a.branch))
    commit_sha = ref["object"]["sha"]
    # 3. current tree
    cur = call("GET", "/repos/%s/git/commits/%s" % (a.repo, commit_sha))
    tree_sha = cur["tree"]["sha"]
    # 4. new tree
    new_tree = call("POST", "/repos/%s/git/trees" % a.repo,
                    {"base_tree": tree_sha,
                     "tree": [{"path": remote_path, "mode": "100644",
                               "type": "blob", "sha": blob_sha}]})
    # 5. new commit
    new_commit = call("POST", "/repos/%s/git/commits" % a.repo,
                      {"message": a.message, "tree": new_tree["sha"], "parents": [commit_sha]})
    # 6. move ref
    call("PATCH", "/repos/%s/git/refs/heads/%s" % (a.repo, a.branch),
         {"sha": new_commit["sha"], "force": False})
    print("pushed %s -> %s:%s  commit=%s" % (a.path, a.repo, remote_path, new_commit["sha"][:8]))
python
    pf = sub.add_parser("push-file")
    pf.add_argument("--repo", required=True)
    pf.add_argument("--path", required=True, help="local file to upload")
    pf.add_argument("--repo-path", help="remote path (default: local basename)")
...[truncated 2773 chars]
Remediation
View remediation

Remediation Suggestions

  1. Make push-file dry-run by default and require an explicit apply option for the mutating phase.
  2. Require a write-specific authorization flag such as --confirm-write rather than treating the absence of --dry-run as authorization.
  3. Bind confirmation to the exact repository, branch, remote path, local path, and commit message. For example, print a normalized operation summary and require a confirmation token derived from that summary.
  4. For interactive use, prompt the user to approve the normalized target immediately before the first POST request. Provide a separate explicit non-interactive confirmation mechanism for CI environments.
  5. Apply the gate before uploading the blob so that no remote Git objects are created before authorization.
  6. Preserve force=False and consider adding branch or repository allowlists where the Skill is deployed in an automated Agent environment.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (6)

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 · README.md (reported line 12)May include surrounding context.

EN: Pure-stdlib wrapper over the GitHub REST API — open/list PRs, query commit status, push a single file via Git Data API. Token read from GITHUB_TOKEN env at call time, never stored or echoed. PAT auth, no OAuth flow; --dry-run to preview. rc=0 ok / rc=1 API error / rc=2 usage or missing token.

Setup / 前置

Mint a classic PAT on github.com → Settings → Developer settings → Personal access tokens, tick repo, then:

bash
export GITHUB_TOKEN=ghp_xxx

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 80)May include surrounding context.

EN: Pure-stdlib wrapper over the GitHub REST API — open/list PRs, query commit status, push a single file via Git Data API. Token read from GITHUB_TOKEN env at call time, never stored or echoed. PAT auth, no OAuth flow; --dry-run to preview. rc=0 ok / rc=1 API error / rc=2 usage or missing token.

Setup / 前置

Mint a classic PAT on github.com → Settings → Developer settings → Personal access tokens, tick repo, then:

bash
export GITHUB_TOKEN=ghp_xxx

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 134)May include surrounding context.

EN: Pure-stdlib wrapper over the GitHub REST API — open/list PRs, query commit status, push a single file via Git Data API. Token read from GITHUB_TOKEN env at call time, never stored or echoed. PAT auth, no OAuth flow; --dry-run to preview. rc=0 ok / rc=1 API error / rc=2 usage or missing token.

Setup / 前置

Mint a classic PAT on github.com → Settings → Developer settings → Personal access tokens, tick repo, then:

bash
export GITHUB_TOKEN=ghp_xxx

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 79)May include surrounding context.

md
The official `gh` CLI is heavy, `git push` fails on 401/proxy/timeout, and wrapping the GitHub REST API yourself means copy-pasting `urllib` boilerplate every time. This skill is the thin, stdlib-only wrapper you can drop into any Python 3.8+ environment: open a PR, list open PRs, read a commit's combined status, or push a single file via the Git Data API (blob→tree→commit→ref) — without installing git, gh, or any third-party package.

### Authentication boundary (read this)
- **This skill uses a Personal Access Token (PAT), NOT OAuth.** There is no redirect flow, no `client_id`, no token exchange.
- The user mints the PAT themselves: GitHub → Settings → Developer settings → Personal access tokens (classic) → tick `repo` scope → copy.
- The token is read from the environment variable `GITHUB_TOKEN` **at call time**. It is never written to disk, never logged, never echoed in output.
- If `GITHUB_TOKEN` is unset, the tool exits rc=2 with minting instructions — it does **not** open a browser, does **not** pretend to be authenticated, and does **not** ship a half-working integration.

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 133)May include surrounding context.

md
The official `gh` CLI is heavy, `git push` fails on 401/proxy/timeout, and wrapping the GitHub REST API yourself means copy-pasting `urllib` boilerplate every time. This skill is the thin, stdlib-only wrapper you can drop into any Python 3.8+ environment: open a PR, list open PRs, read a commit's combined status, or push a single file via the Git Data API (blob→tree→commit→ref) — without installing git, gh, or any third-party package.

### Authentication boundary (read this)
- **This skill uses a Personal Access Token (PAT), NOT OAuth.** There is no redirect flow, no `client_id`, no token exchange.
- The user mints the PAT themselves: GitHub → Settings → Developer settings → Personal access tokens (classic) → tick `repo` scope → copy.
- The token is read from the environment variable `GITHUB_TOKEN` **at call time**. It is never written to disk, never logged, never echoed in output.
- If `GITHUB_TOKEN` is unset, the tool exits rc=2 with minting instructions — it does **not** open a browser, does **not** pretend to be authenticated, and does **not** ship a half-working integration.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill index explicitly advertises GitHub operations that use a Personal Access Token from environment variables and can create PRs or push files to remote repositories, but it provides no warning about credential handling, required scopes, or the fact that it can modify remote state. In an AI-readable entrypoint file, this omission is risky because an agent may invoke the skill with high-trust credentials and perform unintended repository actions without adequate user awareness or confirmation.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.