Back to skill

Security audit

Github Collab

Security checks for vulnerabilities and agentic risk

Overview

This GitHub collaboration skill is mostly purpose-aligned, but it needs review because several implementation paths can expose credentials or execute shell commands from project data.

Review this skill before installing. It should not be used with real GitHub, database, or messaging credentials until command execution is changed to argument-array APIs with strict repository-name validation, saved/exported configuration redacts secrets, passwords are hashed and not returned, and any automatic subagent or scheduler behavior is explicitly limited to intended repositories and channels.

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

T09 · Insecure Skill Coding Practices

Error
Location
src/scripts/progress-report.js:9
Finding
Shell Command Injection Through Repository Name in Progress Report## Vulnerability Details **File Location**: `src/scripts/progress-report.js:9-13` **Vulnerability Type**: OS command injection through shell interpolation **Risk Level**: High ### Vulnerable Code ```javascript function generateProgressReport(repoName) { try { const issues = execSync(`gh issue list --repo ${repoName} --limit 50`, { encoding: 'utf8' }); const commits = execSync(`gh pr list --repo ${repoName} --limit 20`, { encoding: 'utf8' }); ``` ### Technical Analysis The exported `generateProgressReport` function places `repoName` directly into command strings passed to `child_process.execSync`. This API invokes a shell, so shell metacharacters in the repository name are interpreted as command syntax rather than as part of a single GitHub CLI argument. There is no validation that the value has the expected `owner/repository` format and no escaping or argument separation. Any application component that invokes this exported function with user-controlled repository data can consequently provide additional shell commands. ### Attack Path 1. An attacker supplies a repository name through an integration, API, agent task, or other caller of `generateProgressReport`. 2. The value contains shell syntax, for example: ```text valid/repo; touch /tmp/progress-report-injection; # ``` 3. The function constructs a command equivalent to: ```sh gh issue list --repo valid/repo; touch /tmp/progress-report-injection; # --limit 50 ``` 4. `execSync` executes both the intended GitHub CLI invocation and the injected command. 5. More consequential payloads could read application credentials, alter files, execute downloaded programs, or invoke other tools available to the Node.js process. ### Impact Assessment Successful exploitation provides arbitrary command execution with the operating-system privileges of the Node.js process. The attacker can access files and environment variables availa ...[truncated 378 chars]
Remediation
## Remediation Suggestions - Replace shell-based `execSync` with `execFileSync` or `spawnSync` and pass every argument separately: ```javascript const { execFileSync } = require('child_process'); function validateRepository(repoName) { if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repoName)) { throw new Error('Invalid GitHub repository identifier'); } return repoName; } const repository = validateRepository(repoName); const issues = execFileSync( 'gh', ['issue', 'list', '--repo', repository, '--limit', '50'], { encoding: 'utf8', shell: false } ); ``` - Apply a strict allowlist for GitHub owner and repository syntax and enforce a reasonable maximum length. - Do not attempt to solve this solely by adding quotes; argument-array execution with `shell: false` is safer. - Run the process under a dedicated, minimally privileged account. - Restrict GitHub tokens to the minimum repository and operation scopes. - Add regression tests containing semicolons, command substitutions, quotes, newlines, pipes, and redirection characters, verifying that all are rejected or treated as literal data.

T09 · Insecure Skill Coding Practices

Error
Location
src/scripts/scheduler.js:51
Finding
Stored Command Injection Through Scheduler Project Names## Vulnerability Details **File Location**: `src/scripts/scheduler.js:51-62` **Vulnerability Type**: Stored OS command injection **Risk Level**: High ### Vulnerable Code ```javascript function dailyProgressReport() { console.log('📊 生成每日进度报告...'); const projects = readJSON(CONFIG.projectsFile) || {}; const report = []; Object.entries(projects).forEach(([name, project]) => { const fullRepoName = `${getCurrentUser()}/${name}`; try { const issues = execSync(`gh issue list --repo ${fullRepoName} --limit 10`, { encoding: 'utf8' }); const commits = execSync(`gh pr list --repo ${fullRepoName} --limit 5`, { encoding: 'utf8' }); ``` ### Technical Analysis Project names are loaded from `src/data/projects.json`, concatenated into a repository identifier, and interpolated into shell commands. JSON parsing does not make the resulting strings safe for shell execution. This creates a stored command-injection condition: malicious shell syntax can remain dormant in the projects data file until `dailyProgressReport` runs. The vulnerable report is executed by both the default/report path and the scheduler's `daily-report` operation. The scheduler does not install operating-system persistence by itself; nevertheless, repeated invocation by an external scheduler or agent automation can repeatedly trigger the stored payload. ### Attack Path 1. An attacker gains the ability to create, import, synchronize, or otherwise influence a key in `src/data/projects.json`. 2. The project key is set to a value containing shell syntax, such as: ```text demo; touch /tmp/scheduler-injection; # ``` 3. An operator or automation runs: ```sh node src/scripts/scheduler.js report ``` Alternatively, the configured `daily-report` task reaches its execution time. 4. `dailyProgressReport` concatenates the stored key into `fullRepoName`. 5. `execSync` passes the resulting strin ...[truncated 729 chars]
Remediation
## Remediation Suggestions - Use `execFileSync` or `spawnSync` with an argument array and disable shell processing: ```javascript const repository = `${getCurrentUser()}/${validateProjectName(name)}`; const issues = execFileSync( 'gh', ['issue', 'list', '--repo', repository, '--limit', '10'], { encoding: 'utf8', shell: false } ); ``` - Validate stored project names both when records are created and again immediately before use. A suitable allowlist should only permit GitHub repository-name characters. - Treat `projects.json` as untrusted persisted input, even when it was originally produced by another local component. - Store application data outside the source tree with permissions limited to the scheduler account. - Reject malformed existing records and log a safe record identifier rather than executing them. - Add tests for stored values containing newlines, semicolons, command substitutions, pipes, quotes, and redirection operators.

T09 · Insecure Skill Coding Practices

Error
Location
src/db/database.js:24
Finding
User Passwords Are Stored and Returned in Plaintext## Vulnerability Details **File Location**: `src/db/database.js:24-45` **Vulnerability Type**: Plaintext credential storage and disclosure **Risk Level**: High ### Vulnerable Code ```javascript async createUser(userData) { const { username, email, password, role, status } = userData; // 验证输入 if (!username || username.trim() === '') { throw new Error('用户名不能为空'); } if (!email || !this.isValidEmail(email)) { throw new Error('无效的邮箱地址'); } const now = new Date().toISOString(); const result = await this.db.prepare(` INSERT INTO users (username, email, password, role, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) `).run(username, email, password, role, status, now, now); const user = await this.getUser(result.lastInsertRowid); return user; } async getUsers() { const rows = await this.db.prepare('SELECT * FROM users').all(); return rows; } async getUser(id) { const row = await this.db.prepare('SELECT * FROM users WHERE id = ?').get(id); return row || null; } ``` Password changes are also written without hashing: ```javascript async updateUserPassword(id, newPassword) { return this.updateUser(id, { password: newPassword }); } ``` ### Technical Analysis `createUser` writes the supplied password directly to the database. `updateUserPassword` likewise forwards the new plaintext password to the generic update routine. No password hashing, salt generation, key derivation, or encryption is applied. In addition, user retrieval uses `SELECT *`, causing the stored password field to be returned from `createUser`, `getUser`, and `getUsers`. This expands exposure beyond direct database compromise to any caller, serializer, logger, or response handler that receives these user objects. Parameterized SQL prevents SQL injection but does not protect the confidentiality of stored password values. ### Attack Path 1. A user regis ...[truncated 890 chars]
Remediation
## Remediation Suggestions - Hash passwords before storage using Argon2id, scrypt, or bcrypt with a current work factor and a unique automatically generated salt. - Hash new passwords in both user-creation and password-change paths; do not rely on callers to pre-hash values. - Name the column `password_hash` to make its intended content explicit. - Replace `SELECT *` with an explicit projection that excludes the password hash: ```sql SELECT id, username, email, role, status, created_at, updated_at FROM users ``` - Ensure user objects returned by creation, lookup, and listing methods never include credential material. - Implement password verification with a constant-time library function such as `argon2.verify`. - Enforce password length and compromised-password controls, but do not silently truncate passwords. - Migrate existing records by requiring a password reset, since plaintext records should not remain in the database. - Protect database files and backups with restrictive permissions and avoid placing them in source-controlled directories.

T09 · Insecure Skill Coding Practices

Error
Location
src/core/config-manager.js:136
Finding
Environment Tokens Can Be Persisted to an Unprotected Plaintext Configuration File## Vulnerability Details **File Location**: `src/core/config-manager.js:136-157` and `src/core/config-manager.js:248-251` **Vulnerability Type**: Plaintext secret persistence **Risk Level**: High ### Vulnerable Code ```javascript // 从环境变量加载 this.config.github = { token: process.env.GITHUB_TOKEN || '', owner: process.env.GITHUB_OWNER || 'default-owner' }; this.config.agents = { dev_count: parseInt(process.env.DEV_AGENT_COUNT) || 2, test_count: parseInt(process.env.TEST_AGENT_COUNT) || 1, review_count: parseInt(process.env.REVIEW_AGENT_COUNT) || 1 }; this.config.logging = { level: process.env.LOG_LEVEL || 'info', file: process.env.LOG_FILE || 'github-collab.log' }; // 默认 QQ 配置(不依赖数据库) this.config.qq = { enabled: process.env.QQ_ENABLED === 'true', token: process.env.QQ_TOKEN || '', defaultTarget: process.env.QQ_TARGET || '', agentAddresses: {}, dbConfigured: false }; ``` The complete in-memory object, including the token fields, is later serialized: ```javascript save() { const configPath = path.join(__dirname, '.github-collab-config.json'); fs.writeFileSync(configPath, JSON.stringify(this.config, null, 2)); console.log('[Config] Configuration saved to:', configPath); } ``` ### Technical Analysis The configuration loader places `GITHUB_TOKEN` and `QQ_TOKEN` into a general-purpose configuration object. The public `save` method serializes that entire object to `.github-collab-config.json` without redaction, encryption, or an explicit restrictive file mode. This converts ephemeral environment secrets into persistent plaintext data inside the source directory. The file can survive process termination and may be included in source archives, backups, container layers, debugging bundles, or accidental commits. The configuration file is also loaded with a top-level spread over the environment-derived configuration, so persis ...[truncated 1174 chars]
Remediation
## Remediation Suggestions - Keep secrets out of the serializable general configuration object. Resolve them from environment variables or a secret manager only when needed. - Maintain a schema that explicitly marks secret fields and excludes them from `save`, `getAll`, logging, backup, and export operations. - Serialize an allowlisted public configuration object rather than serializing `this.config` wholesale. - If persistent secret storage is unavoidable, use an operating-system credential store or managed secret service rather than a source-tree JSON file. - Create configuration files with restrictive permissions, such as mode `0o600`, and place them outside the repository. - Add `.github-collab-config.json` and similar generated secret-bearing files to `.gitignore`. - Rotate any token that may already have been written by this method and purge it from repository history and build artifacts. - Add automated secret scanning and tests verifying that saved or exported configuration never contains values from `GITHUB_TOKEN`, `QQ_TOKEN`, or database password variables.
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (177)

Credential Access

High
Category
Privilege Escalation
Content
# 环境变量配置示例
# 复制此文件为 .env 并修改相应值

# 应用配置
PORT=3000
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Concealed Executable Artifact

High
Category
Supply Chain
Confidence
100% confidence
Finding
An executable nested in a document or hidden/disguised artifact can evade ordinary extension-based review while still being available to the skill at runtime.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
## 🔧 环境变量配置

### .env 文件

```bash
# 数据库配置
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### 2. 配置环境变量
```bash
cp .env.example .env
# 编辑 .env 文件
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### 2. 配置环境变量
```bash
cp .env.example .env
# 编辑 .env 文件
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Ae1

High
Category
analysis-evasion
Content
- `main-controller.js` - 主控制器(传统版本)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `openclaw-message.js` - 消息处理
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `openclaw-tools.js` - OpenClaw 原生工具封装
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `openclaw-agent-orchestrator.js` - Agent 调度器
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `agent-health-manager.js` - Agent 健康监控
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `performance-monitor.js` - 性能监控
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `task-cli.js` - 任务管理 CLI
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `task-cli.js` - 任务管理 CLI
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `task-cli.js` - 任务管理 CLI
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `task-cli.js` - 任务管理 CLI
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `task-cli.js` - 任务管理 CLI
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `task-cli.js` - 任务管理 CLI
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/scripts/main.js:28

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/scripts/progress-report.js:12

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/scripts/scheduler.js:45

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
src/tests/test-all.js:26