Back to skill

Security audit

Lark Calendar & Tasks

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a real Lark calendar/task integration, but it uses broad employee-directory access, automatic fixed attendee inclusion, undocumented messaging helpers, destructive actions, and brittle secret loading that should be reviewed before installation.

Review this before installing in a real Lark tenant. Use a dedicated Lark app with the narrowest scopes, ensure the secrets file contains only this app's credentials, remove or restrict generic messaging helpers, minimize employee fields returned or cached, decide whether the forced Boyang attendee rule is acceptable, and require explicit confirmation for event/task deletion and membership changes.

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

Warning
Location
lib/employees.mjs:48
Finding

Organization-Wide Employee PII Collection Exceeds Functional Requirements

Content
View full analysis

Vulnerability Details

File Location: lib/employees.mjs:48-77, 218-240
Vulnerability Type: Excessive directory access and sensitive-data retention
Risk Level: Medium

Vulnerable Code

javascript
async function fetchEmployees() {
  const employees = new Map();
  let pageToken = '';

  try {
    do {
      const params = {
        department_id: '0', // Root department = all employees
        page_size: 50,
        user_id_type: 'user_id'
      };
      if (pageToken) params.page_token = pageToken;

      const result = await larkApi('GET', '/contact/v3/users', { params });

      for (const user of (result.items || [])) {
        employees.set(user.user_id, {
          user_id: user.user_id,
          name: user.name,
          en_name: user.en_name,
          nickname: user.nickname,
          email: user.email,
          mobile: user.mobile,
          department_ids: user.department_ids,
          open_id: user.open_id
        });
      }

      pageToken = result.has_more ? result.page_token : '';
    } while (pageToken);

    return employees;
javascript
export async function listEmployees() {
  const employees = await getEmployeeCache();
  return Array.from(employees.values());
}

export async function searchEmployees(query) {
  const employees = await getEmployeeCache();
  const results = [];
  const q = query.toLowerCase();

  for (const emp of employees.values()) {
    const searchFields = [emp.name, emp.en_name, emp.nickname, emp.email].filter(Boolean);
    if (searchFields.some(f => f.toLowerCase().includes(q))) {
      results.push(emp);
    }
  }

  return results;
}

Technical Analysis

The declared name-resolution feature requires only a limited mapping between employee names and Lark user IDs. Instead, the implementation queries the root department, paginates through the organization-wide directory, and re ...[truncated 1883 chars]

Remediation
View remediation

Remediation Suggestions

  1. Retain only fields required for identity resolution:
    javascript
    employees.set(user.user_id, {
      user_id: user.user_id,
      name: user.name,
      en_name: user.en_name,
      nickname: user.nickname
    });
    
  2. Remove email, mobile, department_ids, and open_id unless a separately documented feature strictly requires them.
  3. Avoid retrieving the entire root department for each cache refresh. Prefer a narrow server-side search endpoint or resolve only explicitly requested identities.
  4. Remove or restrict listEmployees() and searchEmployees() if organization-wide enumeration is not part of the declared interface.
  5. Configure the Lark application with the narrowest contact scope capable of resolving attendee and assignee names.
  6. Document the exact directory data accessed, its retention period, and which exported interfaces can return it.
  7. Add tests that assert sensitive fields are neither retained nor returned by name-resolution functions.

T09 · Insecure Skill Coding Practices

Warning
Location
lib/lark-api.mjs:10
Finding

Hard-Coded Parent Traversal Loads a Shared Secrets File Outside the Project

Content
View full analysis

Vulnerability Details

File Location: lib/lark-api.mjs:10-15
Vulnerability Type: Overbroad and brittle secrets loading
Risk Level: Medium

Vulnerable Code

javascript
// Load secrets
const __dirname = dirname(fileURLToPath(import.meta.url));
config({ path: join(__dirname, '../../../../.secrets.env') });

const APP_ID = process.env.FEISHU_APP_ID;
const APP_SECRET = process.env.FEISHU_APP_SECRET;

Technical Analysis

The secrets path is derived by traversing four parent directories from lib/lark-api.mjs. It is not constrained to the Skill root and depends on the deployment directory depth. In the audited directory structure, this traversal resolves outside the project and may target a workspace-wide or system-level .secrets.env file.

Importing lark-api.mjs causes dotenv to parse the entire selected file even though the Skill requires only FEISHU_APP_ID and FEISHU_APP_SECRET. Any unrelated entries loaded from that shared file become available through process.env to all code running in the same process.

The Lark app secret is subsequently sent to the fixed official endpoint:

javascript
const response = await fetch(`${BASE_URL}/auth/v3/tenant_access_token/internal`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    app_id: APP_ID,
    app_secret: APP_SECRET
  })
});

Sending those two credentials to Lark's official token endpoint is necessary for the declared functionality. The vulnerability is the unnecessary loading of a potentially shared secrets file, not that authentication request.

Attack Path

  1. The Skill is installed or moved to a directory whose four-level parent traversal reaches a shared .secrets.env.
  2. A legitimate script imports lib/lark-api.mjs.
  3. The module automatically parses the selected file during import.
  4. Variables from the file are introduced into the process environm ...[truncated 885 chars]
Remediation
View remediation

Remediation Suggestions

  1. Prefer credentials supplied by the host environment and remove automatic file loading from the library:
    javascript
    const APP_ID = process.env.FEISHU_APP_ID;
    const APP_SECRET = process.env.FEISHU_APP_SECRET;
    
  2. If file-based configuration is required, accept an explicit path from the trusted launcher rather than traversing parent directories.
  3. Resolve and validate the configured path against an approved Skill or workspace directory before reading it.
  4. Use a dedicated secrets file containing only the Lark credentials required by this Skill.
  5. Validate credentials before the first request and return a non-sensitive error:
    javascript
    if (!APP_ID || !APP_SECRET) {
      throw new Error('Missing required Lark credentials');
    }
    
  6. Run the Skill in a dedicated process so unrelated components cannot inspect its environment.
  7. Ensure secret files have restrictive filesystem permissions and are excluded from source control, logs, and diagnostic output.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/create-event.mjs:70
Finding

Missing Await Causes Name-Based Calendar and Task Operations to Fail

Content
View full analysis

Vulnerability Details

File Location: scripts/create-event.mjs:70-76; scripts/create-task.mjs:61-67; scripts/manage-attendees.mjs:59-68, 82-87; scripts/manage-task-members.mjs:56-65, 78-83
Vulnerability Type: Asynchronous control-flow error causing denial of functionality
Risk Level: Low

Vulnerable Code

javascript
if (values.attendees) {
  const names = values.attendees.split(',').map(s => s.trim()).filter(Boolean);
  const { resolved, unresolved } = resolveNames(names);
  attendeeIds = [...new Set([...attendeeIds, ...resolved])];
  if (unresolved.length > 0) {
    console.warn(`Warning: Could not resolve names: ${unresolved.join(', ')}`);
  }
}

The same defective pattern appears in the other affected scripts:

javascript
const { resolved, unresolved } = resolveNames(names);

However, resolveNames() is explicitly asynchronous:

javascript
export async function resolveNames(names) {
  const resolved = [];
  const unresolved = [];

  for (const name of names) {
    const userId = await resolveNameToId(name.trim());
    if (userId) {
      if (!resolved.includes(userId)) {
        resolved.push(userId);
      }
    } else {
      unresolved.push(name);
    }
  }

  return { resolved, unresolved };
}

Technical Analysis

An async function returns a Promise. The CLI scripts attempt to destructure resolved and unresolved directly from that Promise instead of awaiting its result. Consequently, both values are undefined.

Subsequent operations such as spreading resolved, reading unresolved.length, or calling resolved.push() cause runtime exceptions. This makes the documented name-based attendee, assignee, and member-management interfaces unreliable.

This is not command injection and does not grant additional privileges. It is a denial-of-functionality issue in the Skill's documented execution paths.

Attack Path

...[truncated 1050 chars]

Remediation
View remediation

Remediation Suggestions

  1. Move all asynchronous resolution logic inside each script's asynchronous main() function.
  2. Await every call to resolveNames():
    javascript
    const { resolved, unresolved } = await resolveNames(names);
    
  3. Apply the correction in:
    • scripts/create-event.mjs
    • scripts/create-task.mjs
    • scripts/manage-attendees.mjs
    • scripts/manage-task-members.mjs
  4. Add integration tests for every documented name-based option, including resolved names, unknown names, empty input, and mixed direct IDs and names.
  5. Add linting rules such as no-floating-promises or equivalent static analysis for unhandled asynchronous calls.
  6. Validate the resolved value before performing array operations and produce a controlled error rather than allowing an uncaught type failure.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (31)

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code hard-codes a specific user ID to always be added as an attendee, regardless of the user's request. This creates an unauthorized data-sharing and surveillance channel because calendar details may be silently disclosed to that user for every event where this helper is used.

Content

No source excerpt is available for this finding.

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

md
// Load secrets
const __dirname = dirname(fileURLToPath(import.meta.url));
config({ path: join(__dirname, '../../../../.secrets.env') });

const APP_ID = process.env.FEISHU_APP_ID;
const APP_SECRET = process.env.FEISHU_APP_SECRET;

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 · lib/lark-api.mjs (reported line 12)May include surrounding context.

js
// Load secrets
const __dirname = dirname(fileURLToPath(import.meta.url));
config({ path: join(__dirname, '../../../../.secrets.env') });

const APP_ID = process.env.FEISHU_APP_ID;
const APP_SECRET = process.env.FEISHU_APP_SECRET;

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 · lib/lark-api.mjs (reported line 24)May include surrounding context.

js
let tokenExpiry = 0;

/**
 * Get or refresh access token
 */
async function getAccessToken() {
  // Return cached token if still valid (with 5 min buffer)

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 · lib/lark-api.mjs (reported line 44)May include surrounding context.

js
let tokenExpiry = 0;

/**
 * Get or refresh access token
 */
async function getAccessToken() {
  // Return cached token if still valid (with 5 min buffer)

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The file exposes messaging primitives (replyMessage and sendMessage) even though the declared skill scope is calendar/task management with directory lookup. This expands the skill's authority beyond user-expected behavior and could be abused to send arbitrary messages or social-engineering content through the organization's Lark tenant.

Content

No source excerpt is available for this finding.

Exfiltration Commands

High
Category
Prompt Injection
Confidence
95% confidence
Finding

The presence of a generic message-sending command creates a direct outbound communication channel that can be repurposed for data exfiltration, spam, or impersonation within Lark. Given the skill's stated purpose is calendar/task management, this capability is contextually suspicious and materially increases abuse potential.

Content

Scanner excerpt · lib/lark-api.mjs (reported line 123)May include surrounding context.

js
}

/**
 * Send message to a chat
 * @param {string} receiveId - Chat ID or user ID
 * @param {string} receiveIdType - 'chat_id' | 'user_id' | 'open_id'
 * @param {object} content - Message content

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The function is labeled and documented as completing a task, but it performs no completion action and just calls updateTask with no fields. This can cause integrity and workflow errors where users or higher-level agents believe a task was completed when it was not, potentially leading to missed work, duplicate actions, or unsafe business automation decisions.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill documents access to environment-based secrets and performs external calendar/task operations, but it does not declare any explicit tool scope or allowed-tools boundary. In an agent setting, this weakens least-privilege controls and makes it easier for the skill to access sensitive runtime capabilities beyond what users may expect.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill includes delete operations for calendar events and tasks without prominent user-facing warnings or confirmation guidance. In practice, this can lead to accidental or socially engineered destructive actions that remove business records, meetings, or assignments with no clear safety checkpoint.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill embeds a plain-text employee directory containing internal user IDs, aliases, and role mappings such as leadership, HR, and R&D. This materially aids internal reconnaissance, targeted phishing, and privilege targeting by giving an attacker a curated map of identities and organizational structure.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The createEvent API contract and comments state that attendees come from options.attendeeIds, but the implementation silently adds an extra attendee. This mismatch is dangerous because it conceals unauthorized data sharing and could cause sensitive meetings, titles, descriptions, times, and locations to be disclosed to someone the user did not select.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code unconditionally calls ensureBoyangIncluded(attendeeIds), causing every created event to include a specific attendee regardless of user intent. This is dangerous because it silently exfiltrates calendar metadata and meeting invitations to an undisclosed third party, creating privacy, confidentiality, and trust risks in a communication/calendar integration.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This function performs a destructive operation by deleting a calendar event via a DELETE API call, but there is no confirmation prompt, warning message, or explanatory comment indicating that the action is irreversible. Because this file is a code file, destructive operations should include some form of user disclosure unless such behavior is clearly surfaced elsewhere.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code fetches organization contact records from an external API and collects personal fields including name, email, mobile, department IDs, and open ID. While the code comments describe the API usage, there is no confirmation prompt or user-facing disclosure that employee directory data will be retrieved and cached.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The contact lookup fetches and stores more employee attributes than are needed for simple name-to-user_id resolution, including email, mobile, department IDs, and open_id. This expands the data exposure surface and violates data minimization principles, increasing privacy and misuse risk if the cache, logs, or downstream consumers are compromised or repurposed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The file loads secrets from a local .secrets.env file and reads FEISHU_APP_ID / FEISHU_APP_SECRET, then uses them in an HTTP request to obtain an access token. Although comments describe the mechanics, there is no confirmation prompt, user-facing log, or explicit warning in this code about accessing credentials and transmitting them to an external service.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

Direct chat/message sending is an unnecessary capability for a calendar-and-tasks skill as described, violating least privilege and creating an opportunity for misuse. An attacker who can influence the skill's behavior could use this function to contact users or channels without clear user intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The message-sending operation performs outbound communications without any indication in this code of user confirmation, disclosure, or policy gating. In a skill not advertised as a messaging tool, silent outbound messaging increases the risk of covert notifications, spam, or phishing from a trusted enterprise integration.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The deleteTask function issues a DELETE request that irreversibly removes a task, but the function contains no confirmation prompt, warning log, or user-facing disclosure around the destructive action. Although the docstring says 'Delete a task', it does not warn about the irreversible effect or require explicit confirmation.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The manifest description says the skill 'Create, update, and delete calendar events and tasks in Lark (Feishu),' which describes CRUD-style task handling. This file also supports adding/removing task assignees, fetching individual tasks, and listing tasks, expanding beyond the stated create/update/delete scope for tasks.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The manifest describes the skill as handling calendar events and tasks plus employee-directory lookup, but this script performs attendee membership changes on existing events via addEventAttendees and removeEventAttendees. Managing attendee lists is a distinct calendar capability not mentioned in the stated description, so the implemented behavior is broader than what the skill claims.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This script can add and remove task members/assignees, but the skill metadata only describes calendar events, tasks, and directory-based name resolution, not task-member/assignee management. Undisclosed write capabilities are security-relevant because users or orchestrating agents may grant or invoke the skill without understanding it can change task access/ownership-related state, increasing the chance of unauthorized workflow changes or privilege misuse.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The embedded employee directory enables name resolution and exposes personnel mappings without any privacy warning or handling guidance. This increases the risk of inappropriate disclosure or casual misuse of internal identity data, especially when agents may surface that information to users or logs.

Content

No source excerpt is available for this finding.

Excessive Permissions

Low
Category
Privilege Escalation
Confidence
80% confidence
Finding

Skill requests more permissions than appear necessary for its stated functionality. Review if elevated access is justified.

Content

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

md
- `task:task:read` — Read tasks
- `contact:user.employee_id:readonly` — Read user info ✅ (already enabled)

**To add permissions:**
1. Go to [Lark Open Platform](https://open.larksuite.com/app/cli_a9f52a4ed7b8ded4/auth)
2. Add scopes: `task:task:write`, `contact:contact:readonly` (for dynamic employee lookup)
3. Re-publish the app version

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
lib/lark-api.mjs:14