Back to skill

Security audit

ClawRank

Security checks for vulnerabilities and agentic risk

Overview

The skill is broadly coherent for a leaderboard uploader, but auto-setup can send your GitHub CLI token to ClawRank and it automatically gathers local and GitHub activity with limited user control.

Review this skill before installing. Only use it if you are comfortable sending aggregate OpenClaw usage, GitHub username, and automatic GitHub activity metrics to ClawRank. Avoid auto-setup unless you trust ClawRank with your GitHub CLI token, prefer a manually generated ClawRank token, do not use untrusted endpoint overrides, and be deliberate before enabling the recurring daily job.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/ingest.py:976
Finding

Raw GitHub Access Token Disclosed to a Third-Party Service During Automatic Setup

Content
View full analysis
str | None: """Get the current GitHub auth token from gh CLI.""" try: result = subprocess.run( ["gh", "auth", "token"], capture_output=True, text=True, timeout=5, ) token = result.stdout.strip() if token and result.returncode == 0: return token except (FileNotFoundError, subprocess.TimeoutExpired, OSError): pass return None CLI_AUTH_PATH = "/api/auth/cli" def run_setup(endpoint: str, verbose: bool = False) -> str | None: """ Auto-setup: exchange a GitHub token for a ClawRank API token. Returns the raw cr_live_ token on success, or None on failure. """ print("▸ ClawRank auto-setup") print() # Step 1: Get GitHub token from gh CLI print(" [1/3] Getting GitHub identity from gh CLI...") gh_token = _get_gh_token() if not gh_token: print(" ✗ Could not get GitHub token. Make sure gh CLI is installed and authenticated.", file=sys.stderr) print(" Run: gh auth login", file=sys.stderr) return None if verbose: print(f" Got GitHub token ({gh_token[:8]}...)") # Step 2: Exchange for ClawRank API token print(" [2/3] Registering with ClawRank...") url = f"{endpoint}{CLI_AUTH_PATH}" body = json.dumps({"githubToken": gh_token, "label": "auto-setup"}).encode("utf-8") req = urllib.request.Request( url, data=body, headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: data = json.loads(resp.read().decode("utf-8")) ``` Automatic invocation occurs when the ClawRank token is absent: ``` ...[truncated 2956 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/ingest.py:52
Finding

Credential-Bearing Requests Can Be Redirected to Arbitrary or Plaintext Endpoints

Content
View full analysis
str: if override: return override.rstrip("/") return os.environ.get("CLAWRANK_ENDPOINT", DEFAULT_ENDPOINT).rstrip("/") ``` The selected endpoint receives the ClawRank bearer token: ```python def submit(endpoint: str, token: str, submission: dict) -> dict: """POST a DailyFactSubmission to the ClawRank API. Returns response JSON.""" url = f"{endpoint}{SUBMIT_PATH}" body = json.dumps(submission).encode("utf-8") req = urllib.request.Request( url, data=body, headers={ "Content-Type": "application/json", "Authorization": f"Bearer {token}", }, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: return json.loads(resp.read().decode("utf-8")) ``` The same endpoint receives the raw GitHub token during setup: ```python url = f"{endpoint}{CLI_AUTH_PATH}" body = json.dumps({"githubToken": gh_token, "label": "auto-setup"}).encode("utf-8") req = urllib.request.Request( url, data=body, headers={"Content-Type": "application/json"}, method="POST", ) try: with urllib.request.urlopen(req, timeout=30) as resp: data = json.loads(resp.read().decode("utf-8")) ``` ### Technical Analysis The destination can be overridden through the `--endpoint` argument or `CLAWRANK_ENDPOINT` environment variable. The code performs no URL scheme validation, exact-host validation, port restriction, or destination allowlisting. As a result, the script accepts attacker-controlled URLs and permits plaintext HTTP. Two distinct credentials can be exposed: - The raw GitHub token sent in the setup request body. ...[truncated 1747 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/ingest.py:228
Finding

Automatic Enumeration and Submission of Private GitHub Activity Exceeds Core Skill Requirements

Content
View full analysis
list[dict]: """ Discover repos accessible to the authenticated user that have recent pushes. Returns list of {owner, name, full_name, pushed_at}. """ repos = _gh_api("/user/repos?sort=pushed&per_page=100&type=all", paginate=True) if not isinstance(repos, list): return [] # Filter to repos with a push in the last 90 days cutoff = datetime.now(timezone.utc) - timedelta(days=90) results = [] for repo in repos: pushed_at = repo.get("pushed_at", "") try: pushed_at_dt = datetime.fromisoformat(pushed_at.replace("Z", "+00:00")) except (AttributeError, ValueError): continue if pushed_at_dt >= cutoff: owner = repo.get("owner", {}).get("login", "") name = repo.get("name", "") if owner and name: results.append({ "owner": owner, "name": name, "full_name": f"{owner}/{name}", "pushed_at": pushed_at, }) ``` Collection runs automatically whenever the GitHub CLI is authenticated: ```python git_metrics: dict[str, dict] | None = None last_git_sync_date = state.get("lastGitSyncDate") if _gh_available() and gh_username: print(" [git-metrics] gh CLI detected — collecting GitHub commit & PR metrics...") try: git_metrics = collect_github_metrics( login=gh_username, last_submission_date=last_git_sync_date, verbose=args.verbose, ) if git_metrics: total_git_commits = sum(v ...[truncated 2736 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (18)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill invokes a Python script that can read local agent data, write configuration files, use network access, and potentially register cron jobs, yet the manifest declares no explicit tool scope or permissions. That creates an over-privileged execution surface where an agent may run sensitive operations without clear user-visible boundaries or policy enforcement.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger text uses broad phrases like submit, sync, report, upload stats, get ranked, and setup automated ingestion, which can cause the skill to activate in ordinary conversation without sufficiently specific user intent. In this context, accidental activation is risky because execution may transmit local usage data and GitHub-derived activity to an external service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill presents a one-command quick start and convenience messaging before clearly warning that it will scan local session transcripts, derive usage metrics, collect GitHub activity, and send the results to a third-party public leaderboard. Users may consent to 'get ranked' without understanding the scope of local data processing, account linkage, persistence of API tokens, and public exposure of activity-derived metadata.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill instructs the agent to scan local transcripts and recently active repositories, aggregate metrics, and submit them to a public leaderboard while framing the process as routine productivity reporting. This is dangerous because local transcripts and repository activity can reveal sensitive behavioral, project, and operational metadata, and the public leaderboard context materially increases privacy and confidentiality risk.

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

python
def _get_gh_username() -> str | None:
    """Extract GitHub username from `gh auth status` output."""
    try:
        result = subprocess.run(
            ["gh", "auth", "status"],
            capture_output=True, text=True, timeout=5,
        )

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

python
def _get_git_first_name() -> str | None:
    """Get first name from git config. Returns None if not set or looks like an email."""
    try:
        result = subprocess.run(
            ["git", "config", "--global", "user.name"],
            capture_output=True, text=True, timeout=5,
        )

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

python
Returns empty dict if the CLI is unavailable or fails.
    """
    try:
        result = subprocess.run(
            ["openclaw", "agents", "list", "--json"],
            capture_output=True, text=True, timeout=10,
        )

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill claims to report local OpenClaw token usage, but it also enumerates accessible GitHub repositories and uploads commit counts, PR activity, and line-change metrics. This is a material expansion of collected telemetry beyond the stated purpose, increasing privacy and metadata-exfiltration risk for users who may not expect repository activity profiling.

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

python
def _gh_available() -> bool:
    """Check if gh CLI is installed and authenticated."""
    try:
        result = subprocess.run(
            ["gh", "auth", "status"],
            capture_output=True, text=True, timeout=10,
        )

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

python
if paginate:
        cmd.append("--paginate")
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        if result.returncode != 0:
            return None
        text = result.stdout.strip()

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

python
def _openclaw_available() -> bool:
    """Check if openclaw CLI is installed."""
    try:
        result = subprocess.run(
            ["openclaw", "--version"],
            capture_output=True, text=True, timeout=5,
        )

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

python
def _find_cron_job_id() -> str | None:
    """Return the job ID of the clawrank-ingest cron job, or None if not found."""
    try:
        result = subprocess.run(
            ["openclaw", "cron", "list", "--json"],
            capture_output=True, text=True, timeout=15,
        )

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

python
]

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
        if result.returncode == 0:
            # Parse job ID from JSON response for the removal hint
            job_id = None

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

python
def _get_gh_token() -> str | None:
    """Get the current GitHub auth token from gh CLI."""
    try:
        result = subprocess.run(
            ["gh", "auth", "token"],
            capture_output=True, text=True, timeout=5,
        )

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The auto-setup flow obtains a GitHub authentication token, sends it to a remote service for exchange, and persists the returned API token into local OpenClaw configuration. This goes well beyond passive usage-stat submission and materially changes trust boundaries by handing a powerful credential to an external endpoint and modifying local auth config automatically.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

At this point the script packages the user's GitHub auth token and transmits it to the remote ClawRank API, but the operation site does not present a strong warning or confirmation that a GitHub credential is leaving the machine. If the remote service is compromised, malicious, or misconfigured, the token could be abused to access repositories and account data permitted by that credential.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The setup flow silently writes a new API token into ~/.openclaw/openclaw.json without an explicit confirmation step. Persisting credentials automatically can surprise users, increase the blast radius if local files are exposed, and make later automated submissions occur under a token the user did not knowingly store.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

When --recurring is used, the script creates a persistent daily cron-style OpenClaw job that continues submitting data in the future. Even though this is gated by a flag, the operation modifies persistent automation and should be treated as a sensitive state change requiring explicit disclosure of schedule, command, and ongoing data transmission.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.