Back to skill

Security audit

Google Docs to Feishu

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Google Docs to Feishu sync tool, but its implementation could let a crafted document run local commands and it stores OAuth tokens on disk without hardening.

Install only after the publisher fixes the shell command construction, preferably by using execFile/spawn with argument arrays or a direct Feishu API/SDK. Treat the Google credential and token files as secrets, restrict them to owner-only access, and consider removing the unused profile/email OAuth scopes. Review documents before syncing because their content will be copied from Google into Feishu.

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)

T09 · Insecure Skill Coding Practices

Error
Location
google-docs-to-feishu.js:438
Finding

OS Command Injection Through Untrusted Document and CLI Values

Content
View full analysis

Vulnerability Details

File Location: google-docs-to-feishu.js, lines 438–440 and 517–519
Vulnerability Type: OS command injection
Risk Level: High

Vulnerable Code

js
const createCmd = `
  openclaw tools call feishu_doc '{"action":"create","title":"${title}"${folderToken ? `,"folder_token":"${folderToken}"` : ''}${ownerOpenId ? `,"owner_open_id":"${ownerOpenId}"` : ''}}'
`;

try {
  const result = execSync(createCmd, { encoding: 'utf8' });
  return JSON.parse(result);
} catch (e) {
  return createFeishuDocByAPI(title, folderToken, ownerOpenId);
}

A second vulnerable command is used when writing the document:

js
const writeCmd = `openclaw tools call feishu_doc '{"action":"write","doc_token":"${feishuResult.doc_token}","content":${JSON.stringify(markdown)}}'`;
execSync(writeCmd, { encoding: 'utf8' });

Technical Analysis

The code constructs shell command strings by directly interpolating values from several untrusted or externally controlled sources:

  • title originates from the Google document.
  • markdown originates from the Google document body.
  • folderToken and ownerOpenId are command-line arguments.
  • doc_token is returned by an external tool.

These strings are passed to execSync(), which invokes a shell. The JSON argument is enclosed in shell single quotes, but interpolated values are not escaped for the shell. A single quote in a document title, document content, folder token, or owner identifier can terminate the quoted argument. Shell metacharacters following it can then introduce an arbitrary command.

JSON.stringify(markdown) only performs JSON serialization. It does not make the result safe for insertion into a shell command, because JSON escaping and shell escaping follow different rules.

Attack Path

  1. An attacker creates or modifies a Google document accessible to the victim.
  2. The attacker places a shell-breaking payload in ...[truncated 1431 chars]
Remediation
View remediation

Remediation Suggestions

  • Eliminate shell command construction for tool invocation.

  • Use execFileSync() or spawnSync() with an argument array so that the operating system does not interpret document values as shell syntax.

  • Construct the complete tool payload as an object and serialize it once:

    js
    const payload = {
      action: 'create',
      title
    };
    
    if (folderToken) payload.folder_token = folderToken;
    if (ownerOpenId) payload.owner_open_id = ownerOpenId;
    
    const result = execFileSync(
      'openclaw',
      ['tools', 'call', 'feishu_doc', JSON.stringify(payload)],
      { encoding: 'utf8', shell: false }
    );
    
  • Apply the same approach to the write operation:

    js
    const payload = {
      action: 'write',
      doc_token: feishuResult.doc_token,
      content: markdown
    };
    
    execFileSync(
      'openclaw',
      ['tools', 'call', 'feishu_doc', JSON.stringify(payload)],
      { encoding: 'utf8', shell: false }
    );
    
  • Prefer a direct, structured OpenClaw API or SDK instead of invoking a CLI.

  • Validate folder tokens, owner identifiers, and returned document tokens against their expected character sets and length limits.

  • Treat validation as defense in depth rather than as a substitute for removing shell interpretation.

  • Add regression tests containing single quotes, command substitutions, newlines, semicolons, and other shell metacharacters in document titles and content.

T09 · Insecure Skill Coding Practices

Warning
Location
google-docs-to-feishu.js:65
Finding

OAuth Access and Refresh Tokens Stored Without Explicit Restrictive Permissions

Content
View full analysis

Vulnerability Details

File Location: google-docs-to-feishu.js, lines 37–40 and 65–68
Vulnerability Type: Insecure storage of sensitive credentials
Risk Level: Medium

Vulnerable Code

js
function ensureConfigDir() {
  if (!fs.existsSync(CONFIG_DIR)) {
    fs.mkdirSync(CONFIG_DIR, { recursive: true });
  }
}
js
function saveToken(token) {
  ensureConfigDir();
  fs.writeFileSync(TOKEN_PATH, JSON.stringify(token, null, 2));
}

Technical Analysis

The Skill stores OAuth token data in:

text
~/.config/google-docs-to-feishu/token.json

The token object can include an access token and a long-lived refresh token. However, neither the configuration directory nor the token file is created with an explicit owner-only permission mode.

Consequently, access depends on the process umask and any permissions already present on the path. On a system with a permissive umask or an improperly configured existing directory, other local users may be able to read the credential file. Existing path ownership and file type are also not verified before reading or overwriting the file.

Storing OAuth credentials in a dedicated user configuration directory is necessary for the declared automatic token-refresh functionality. The risk arises from failing to enforce security controls appropriate for reusable bearer credentials.

Attack Path

  1. The victim runs the authorization flow.
  2. saveToken() writes the Google OAuth token to token.json using permissions derived from the environment's umask.
  3. On a permissively configured multi-user system, the resulting directory or file is readable by another local account.
  4. The local attacker reads the access token and refresh token.
  5. The attacker submits the stolen token to the relevant Google APIs or uses the refresh token to obtain new access tokens.
  6. The attacker accesses data authorized by the token until authorization is ...[truncated 630 chars]
Remediation
View remediation

Remediation Suggestions

  • Create the configuration directory with owner-only permissions:

    js
    fs.mkdirSync(CONFIG_DIR, {
      recursive: true,
      mode: 0o700
    });
    fs.chmodSync(CONFIG_DIR, 0o700);
    
  • Create token files with mode 0600:

    js
    fs.writeFileSync(
      TOKEN_PATH,
      JSON.stringify(token, null, 2),
      { encoding: 'utf8', mode: 0o600 }
    );
    fs.chmodSync(TOKEN_PATH, 0o600);
    
  • Write to a securely created temporary file in the same directory and atomically rename it to avoid partial writes.

  • Before reading or writing, verify that the directory and token path are owned by the expected user and are not symbolic links or unexpected file types.

  • Correct permissions on existing files rather than securing only newly created files.

  • Consider using the operating system's credential store or keychain for refresh tokens.

  • Update SKILL.md to instruct users to protect the directory with mode 0700 and credential files with mode 0600.

  • Avoid logging token values or including them in error output.

T05 · Unauthorized Access and Privilege Escalation

Note
Location
google-docs-to-feishu.js:27
Finding

OAuth Authorization Requests Unnecessary Profile and Email Scopes

Content
View full analysis

Vulnerability Details

File Location: google-docs-to-feishu.js, lines 27–31
Vulnerability Type: Excessive OAuth permissions
Risk Level: Low

Vulnerable Code

js
const SCOPES = [
  'https://www.googleapis.com/auth/documents.readonly',
  'https://www.googleapis.com/auth/userinfo.profile',
  'https://www.googleapis.com/auth/userinfo.email'
];

Technical Analysis

The declared functionality only needs to retrieve a selected Google document for conversion. The implementation uses the Google Docs API but does not use the authenticated user's profile or email address.

The userinfo.profile and userinfo.email scopes therefore exceed the minimum privileges required by the Skill. This conflicts with the documentation in SKILL.md, which describes the Google authorization requirement as read-only document access.

Although these identity scopes do not provide write access to Google documents, requesting unused permissions unnecessarily expands the data available to the application and increases the consequences of token theft.

Attack Path

  1. The victim begins the authorization flow.
  2. The generated Google OAuth URL requests document-read, profile, and email scopes.
  3. The victim grants all requested permissions.
  4. The resulting token is stored locally.
  5. If the token is exposed through insecure file permissions, command injection, or another compromise, an attacker can use all granted scopes.
  6. The attacker obtains identity information that was not needed for document synchronization.

Impact Assessment

The excessive scopes expose the authenticated user's profile and email information in addition to the necessary read-only document authority. They do not grant document modification or administrative system privileges.

The principal impact is unnecessary privacy exposure and an expanded blast radius if the OAuth token is compromised. Removing these scopes would preserve the ...[truncated 71 chars]

Remediation
View remediation

Remediation Suggestions

  • Request only the scope required to retrieve Google document content:

    js
    const SCOPES = [
      'https://www.googleapis.com/auth/documents.readonly'
    ];
    
  • Remove userinfo.profile and userinfo.email unless a documented feature is introduced that strictly requires them.

  • Revoke or reauthorize previously issued tokens so existing grants no longer retain the unnecessary scopes.

  • Keep SKILL.md synchronized with the actual OAuth consent request.

  • Add an automated test or review check that compares configured scopes against APIs and user information actually consumed by the implementation.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

The skill instructs users to place OAuth client credentials in a predictable local path, and the workflow also relies on an access token stored alongside it. In a skill with detected env/shell capabilities, predictable credential locations materially increase the risk that secrets are read, copied, or mishandled by the agent or by other local processes.

Content

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

md
1. 启用 **Google Docs API**
2. 创建 **OAuth 2.0 Client ID**(桌面应用类型)
3. 下载 JSON 文件,保存为 `~/.config/google-docs-to-feishu/credentials.json`

### 2. 授权

Credential Access

High
Category
Privilege Escalation
Confidence
94% confidence
Finding

The documentation explicitly enumerates both the OAuth client file and the generated token file in a fixed directory. Exposing exact secret filenames and locations is dangerous in context because the skill appears capable of local execution, making credential discovery and theft easier if the agent, plugins, or host are over-privileged.

Content

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

text
~/.config/google-docs-to-feishu/
├── credentials.json    # OAuth 客户端信息
├── token.json          # 访问令牌(自动生成)

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 · google-docs-to-feishu.js (reported line 21)May include surrounding context.

js
// ============ 配置 ============
const CONFIG_DIR = path.join(process.env.HOME, '.config', 'google-docs-to-feishu');
const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json');
const TOKEN_PATH = path.join(CONFIG_DIR, 'token.json');

// Google OAuth 配置

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
73% confidence
Finding

The skill documentation indicates capabilities that likely require shell and environment access, but it does not declare any explicit tool scope or permission boundaries. This creates a least-privilege problem: an agent executing the skill may gain broader access than users or reviewers can easily see, increasing the chance of unintended command execution or access to local secrets.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill invokes shell commands via execSync for browser launching and Feishu operations, and some command components include untrusted values such as document titles and optional tokens. Because these values are interpolated into shell command strings without robust escaping, an attacker controlling document metadata could potentially trigger command injection or unintended command execution in the user's environment.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

OAuth tokens are stored persistently on disk in a predictable location without any visible permission hardening or user warning. If the host is multi-user, compromised, or backed up insecurely, these tokens could be stolen and used to access the user's Google data without re-consent.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The file header and function comments state that the tool creates and writes Feishu documents, but the fallback implementation in createFeishuDocByAPI merely logs a message and returns an object containing the title. This means the documented behavior does not match the actual code path when the OpenClaw command path fails.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill transfers Google Docs content to Feishu, which is the core function, but it does so without an explicit consent checkpoint or warning about cross-service data movement. This can expose sensitive internal documents to another platform, potentially violating user expectations, policy, or data residency constraints.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

Natural-language strings, comments, usage, and user-facing output are presented only in Chinese, with no indication that another language can be selected. This can violate a language/locale policy when the skill imposes a language without opt-in or documented justification.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The manifest says the skill synchronizes or copies Google Docs to Feishu, but does not mention hosting a local listener. Running a local server on localhost:8765 is an additional capability beyond the user-facing purpose, even if used to complete OAuth.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
google-docs-to-feishu.js:444

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
google-docs-to-feishu.js:55