Back to skill

Security audit

Repo Guardian

Security checks for vulnerabilities and agentic risk

Overview

Repo Guardian is a disclosed GitHub PR automation skill, but it can auto-merge pull requests by default using model decisions on untrusted PR content.

Install only if you are comfortable with unattended repository write actions. Use a fine-grained token limited to one repository, set GUARDIAN_AUTO_MERGE=false or pass --no-merge unless a human approval process exists, configure two genuinely separate reviewers, and rely on GitHub branch protections and CI rather than model consensus alone.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
scripts/guardian.sh:72
Finding

Untrusted pull request content can influence model-authorized merge decisions

Content
View full analysis
/dev/null) DIFF_LINES=$(echo "$DIFF" | wc -l) echo "[guardian] Diff: $DIFF_LINES lines" # Truncate large diffs for context window if [[ $DIFF_LINES -gt 500 ]]; then DIFF=$(echo "$DIFF" | head -500) DIFF="$DIFF\n\n[... truncated, $DIFF_LINES total lines ...]" fi # Fetch PR files list FILES=$(curl -s -H "$AUTH" -H "$ACCEPT" "$API/repos/$REPO/pulls/$PR_NUM/files" 2>/dev/null | \ python3 -c "import sys,json; [print(f['filename']) for f in json.load(sys.stdin)]" 2>/dev/null || echo "unknown") REVIEW_PROMPT="You are reviewing PR #$PR_NUM on $REPO. Title: $PR_TITLE Branch: $PR_BRANCH Files changed: $FILES Diff: \`\`\`diff $DIFF \`\`\` Review this PR against these criteria: 1. CORRECTNESS — Does the code do what the PR title/description claims? 2. SECURITY — Any vulnerabilities, secret exposure, injection risks? 3. TESTS — Are changes tested? Could they break existing tests? 4. SCOPE — Does the PR stay within its stated purpose? 5. QUALITY — Code style, error handling, edge cases, naming Return your verdict as JSON: { \"verdict\": \"APPROVE\" or \"REQUEST_CHANGES\", \"summary\": \"One-line summary of your assessment\", \"findings\": [ {\"severity\": \"critical|major|minor\", \"file\": \"path\", \"line\": 0, \"issue\": \"description\", \"fix\": \"suggestion\"} ], \"confidence\": \"high\" or \"medium\" or \"low\" } If the PR is clean and correct, use verdict APPROVE with an empty findings array. If you find issues, use REQUEST_CHANGES with specific findings. Only use severity critical for security vulnerabilities or data loss risks. Return ONLY the JSON object, no other text." OPUS_RESULT=$( ...[truncated 3391 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/guardian.sh:12
Finding

Auto-merge is enabled by default contrary to the documented explicit opt-in policy

Content
View full analysis
/dev/null MERGE_BODY=$(python3 -c " import json, sys title = sys.argv[1] pr_num = sys.argv[2] print(json.dumps({'merge_method': 'squash', 'commit_title': f'{title} (#{pr_num})'})) " "$PR_TITLE" "$PR_NUM") curl -s -X PUT -H "$AUTH" -H "$ACCEPT" \ "$API/repos/$REPO/pulls/$PR_NUM/merge" \ -d "$MERGE_BODY" > /dev/null echo "[guardian] ✅ PR #$PR_NUM merged (squash)" fi ``` ### Technical Analysis Bash parameter expansion `${GUARDIAN_AUTO_MERGE:-true}` assigns `true` when the environment variable is unset or empty. Therefore, auto-merge is active during a normal invocation unless the operator explicitly disables it. This behavior directly contradicts the documented claim that auto-merge is disabled by default and requires explicit opt-in. Operators following the documentation may supply a write-capable GitHub token while reasonably exp ...[truncated 1341 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/guardian.sh:17
Finding

Dual-review consensus does not guarantee independent agents or models

Content
View full analysis
/dev/null | \ python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('text','{}'))" 2>/dev/null || echo '{"verdict":"ERROR","summary":"dispatch failed"}') fi echo "[guardian] Dispatching GPT review..." if [[ "$DRY_RUN" == "true" ]]; then echo "[guardian] DRY RUN — would dispatch GPT review" GPT_RESULT='{"verdict":"APPROVE","summary":"dry run","findings":[],"confidence":"high"}' else GPT_RESULT=$(openclaw agent --agent "$REVIEWER_B_AGENT" --local --message "$REVIEW_PROMPT" --json 2>/dev/null | \ python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('text','{}'))" 2>/dev/null || echo '{"verdict":"ERROR","summary":"dispatch failed"}') # Fallback to Sonnet if GPT failed GPT_VERDICT=$(echo "$GPT_RESULT" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','ERROR'))" 2>/dev/null || echo "ERROR") if [[ "$GPT_VERDICT" == "ERROR" ]]; then echo "[guardian] GPT failed, falling back to Sonnet..." GPT_RESULT=$(openclaw agent --agent "$REVIEWER_B_AGENT" --local --message "$REVIEW_PROMPT" --json 2>/dev/null | \ python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('text','{}'))" 2>/dev/null || echo '{"verdict":"ERROR","summary":"fallback failed"}') fi fi ``` ### Technical Analysis `R ...[truncated 1770 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

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

md
## Requirements

**Required environment variables:**
- `GH_TOKEN` — A GitHub Personal Access Token. **Must be set explicitly** (the script will not fall back to `gh auth token` to avoid inadvertent scope leakage). Use a fine-grained PAT scoped to the specific target repository with read/write permissions for pull requests and issues.

**Required binaries (must be on PATH):**
- `openclaw` — OpenClaw CLI (dispatches review prompts to configured model agents)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

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

Content

Scanner excerpt · scripts/guardian.sh (reported line 43)May include surrounding context.

sh
# Ensure GH_TOKEN (must be explicitly set — no fallback to gh auth to avoid scope leakage)
if [[ -z "${GH_TOKEN:-}" ]]; then
  echo "[guardian] ERROR: GH_TOKEN environment variable is required." >&2
  echo "[guardian] Set it in your .env or shell: export GH_TOKEN=ghp_..." >&2
  echo "[guardian] Recommended: use a fine-grained PAT scoped to the target repo only." >&2
  exit 1
fi

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · scripts/guardian.sh (reported line 55)May include surrounding context.

sh
# ─── PR REVIEW ───────────────────────────────────────────────────────

echo "[guardian] Fetching open PRs..."
PRS=$(curl -s -H "$AUTH" -H "$ACCEPT" "$API/repos/$REPO/pulls?state=open&per_page=$MAX_PRS" 2>/dev/null)
PR_COUNT=$(echo "$PRS" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")

echo "[guardian] Found $PR_COUNT open PR(s)"

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · scripts/guardian.sh (reported line 177)May include surrounding context.

sh
body += '*Auto-merging on dual consensus.*'
print(json.dumps({'body': body}))
" "$OPUS_SUMMARY" "$GPT_SUMMARY")
        curl -s -X POST -H "$AUTH" -H "$ACCEPT" \
          "$API/repos/$REPO/issues/$PR_NUM/comments" \
          -d "$COMMENT_BODY" > /dev/null

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · scripts/guardian.sh (reported line 241)May include surrounding context.

sh
" "$OPUS_VERDICT" "$OPUS_SUMMARY" "$GPT_VERDICT" "$GPT_SUMMARY" 2>/dev/null) || \
        COMMENT_BODY='{"body": "Repo Guardian reviewed this PR. Changes requested by one or both models. See logs for details."}'
        
        curl -s -X POST -H "$AUTH" -H "$ACCEPT" \
          "$API/repos/$REPO/issues/$PR_NUM/comments" \
          -d "$COMMENT_BODY" > /dev/null

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding

The skill requires environment access and shell-capable execution (openclaw, python3, curl) but does not declare an explicit tool permission boundary such as permissions or allowed-tools. In a security-sensitive automation skill that can review, comment on, label, and potentially merge PRs, the absence of an explicit scope increases the risk of overbroad execution or unintended tool use by the hosting agent.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

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

md
- **Implementing issue fixes end-to-end** — fetching issues, spawning coding agents, writing code, and opening PRs belongs to the **gh-issues** skill
- **General GitHub CLI operations** — listing PRs, commenting, checking CI, or ad-hoc `gh` queries belong to the **github** skill
- **Code authoring or refactoring** — Repo Guardian reviews and gates merges; it does not write new code

## What It Does

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The header describes the script as providing both dual-model PR review and issue triage, and the manifest also includes repository triage on a cron schedule. In the issue section, the code merely fetches and counts open issues, then prints that auto-fix processing is 'not yet implemented in v1' rather than actually triaging or acting on repository state.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/guardian.sh (reported line 177)May include surrounding context.

sh
body += '*Auto-merging on dual consensus.*'
print(json.dumps({'body': body}))
" "$OPUS_SUMMARY" "$GPT_SUMMARY")
        curl -s -X POST -H "$AUTH" -H "$ACCEPT" \
          "$API/repos/$REPO/issues/$PR_NUM/comments" \
          -d "$COMMENT_BODY" > /dev/null

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/guardian.sh (reported line 241)May include surrounding context.

sh
" "$OPUS_VERDICT" "$OPUS_SUMMARY" "$GPT_VERDICT" "$GPT_SUMMARY" 2>/dev/null) || \
        COMMENT_BODY='{"body": "Repo Guardian reviewed this PR. Changes requested by one or both models. See logs for details."}'
        
        curl -s -X POST -H "$AUTH" -H "$ACCEPT" \
          "$API/repos/$REPO/issues/$PR_NUM/comments" \
          -d "$COMMENT_BODY" > /dev/null

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The comment explicitly states 'Fallback to Sonnet if GPT failed', implying a change of model or agent. However, the fallback invocation uses the same openclaw command with the same "$REVIEWER_B_AGENT" value, so the documented intent contradicts the actual behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.