Back to skill

Security audit

Gh Standup

Security checks for vulnerabilities and agentic risk

Overview

This GitHub standup skill is coherent, but it should be reviewed because it can use authenticated GitHub access and post unescaped repository content into a standup message.

Review before installing. Use this only for repos or orgs whose activity you are comfortable summarizing through the authenticated GitHub CLI, prefer --repo over --org for tighter scope, and inspect the generated standup text before posting because repository contributors can influence PR titles and commit messages that appear in the report.

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/standup.py:106
Finding

Untrusted GitHub Content Is Forwarded into Agent-Facing Output Without Neutralization

Content
View full analysis

Vulnerability Details

File Location: scripts/standup.py:106-115, with output sinks at scripts/standup.py:177-185 and the posting workflow at SKILL.md:29-33
Vulnerability Type: Indirect prompt injection and unsafe rendering of untrusted remote content
Risk Level: Medium

Vulnerable Code

python
def format_pr(pr: dict) -> str:
    num = pr.get("number", "?")
    title = pr.get("title", "")
    url = pr.get("html_url", "")
    return f"  • PR #{num}: {title}\n    {url}"


def format_commit(c: dict) -> str:
    sha = c.get("sha", "")[:7]
    msg = c.get("commit", {}).get("message", "").split("\n")[0]
    return f"  • `{sha}` {msg}"

The formatted values are subsequently written directly to standard output:

python
if all_prs:
    print(f"### ✅ Merged PRs ({len(all_prs)})\n")
    for pr in all_prs:
        repo_label = f"[{pr['_repo']}] " if len(repos) > 1 else ""
        print(f"{repo_label}{format_pr(pr)}")
    print()

if standalone_commits:
    print(f"### 🔧 Direct Commits ({len(standalone_commits)})\n")
    for c in standalone_commits:
        repo_label = f"[{c['_repo']}] " if len(repos) > 1 else ""
        print(f"{repo_label}{format_commit(c)}")
    print()

The documented workflow directs the Agent to post this output:

markdown
## Workflow

1. Run `scripts/standup.py` with the user's repo/org and standup days
2. The script calculates the most recent past standup day and uses it as `since`
3. Output: merged PRs + direct commits (PR commits de-duped) since that date
4. Post the output as the standup message

Technical Analysis

Pull-request titles, commit subjects, repository names, author names, and GitHub-provided URLs are remote data that may be controlled by repository contributors. The script interpolates these fields directly into Markdown-formatted output without escaping, validation, or an explicit trust-boundary marker.

...[truncated 2180 chars]

Remediation
View remediation

Remediation Suggestions

  1. Treat every value returned by GitHub as untrusted data, including PR titles, commit messages, repository names, author names, and URLs.
  2. Return structured JSON rather than prose when output is consumed by an AI Agent. Keep remote fields in dedicated data properties and render them only through trusted presentation logic.
  3. Add an explicit instruction at the Agent boundary stating that report fields are untrusted records and that instructions contained in those fields must never be followed.
  4. Escape Markdown metacharacters in titles, commit subjects, author names, and repository labels. Validate URLs against the expected HTTPS GitHub origin before rendering them as links.
  5. Remove or replace control characters, bidirectional text controls, and other invisible formatting characters that could disguise content.
  6. Apply reasonable length limits to all remote text fields to reduce context flooding and output manipulation.
  7. Delimit generated records clearly, for example by placing escaped content in quoted or code-formatted fields while ensuring that delimiters inside the data cannot terminate the boundary.
  8. Require user confirmation before posting generated content to an external channel, especially when it contains links, mentions, or instruction-like phrases.
  9. Add tests using hostile PR titles and commit subjects to verify that Markdown injection, control characters, and instruction-like text remain inert data.
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill invokes a Python script and therefore clearly relies on shell/code execution, but it does not declare any tool scope such as allowed-tools or permissions. That omission weakens containment and reviewability because an agent may execute shell-capable actions without an explicit, minimal authorization boundary, increasing the chance of unintended command execution or data access.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The description includes broad triggers such as 'work summary', 'weekly update', and 'what did I do since [day]', which can cause the skill to activate in contexts broader than a narrowly defined GitHub standup request. In this skill's context, over-triggering is more concerning because activation leads to shell execution and GitHub data retrieval, potentially causing unnecessary access to repository or organization activity.

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/standup.py (reported line 36)May include surrounding context.

python
def gh_api_simple(endpoint: str) -> list | dict:
    """Single-page gh api call (no --paginate)."""
    result = subprocess.run(["gh", "api", endpoint], capture_output=True, text=True)
    if result.returncode != 0:
        return []
    try:

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/standup.py (reported line 52)May include surrounding context.

python
qs = "&".join(f"{k}={v}" for k, v in params.items())
        url = f"{endpoint}?{qs}"
    cmd = ["gh", "api", "--paginate", url]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"[gh error] {result.stderr.strip()}", file=sys.stderr)
        return []

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/standup.py (reported line 74)May include surrounding context.

python
def get_author_login(author_arg: str) -> str:
    if author_arg != "@me":
        return author_arg
    result = subprocess.run(["gh", "api", "user", "--jq", ".login"],
                            capture_output=True, text=True)
    return result.stdout.strip() or "@me"

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code invokes the GitHub CLI to query remote API endpoints and retrieve repository, PR, commit, and user-account data. While the script's top-level docstring states its purpose, there is no runtime warning, confirmation, or explicit disclosure that it will make network requests and collect activity data from GitHub APIs.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.