Back to skill

Security audit

Mufi Calendar

Security checks for vulnerabilities and agentic risk

Overview

This calendar skill is purpose-related, but it needs Review because its reminder script can execute unsafe shell commands and it handles powerful calendar tokens with weak safeguards.

Install only after the reminder command is changed to avoid shell execution, token files are protected with restrictive permissions, deletion requires explicit confirmation, Discord reminders clearly disclose what calendar details are sent, cron setup has an easy disable path, and the Naver support claims are corrected or implemented.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/remind.js:66
Finding
Shell Command Injection Through Reminder Arguments and Calendar Event Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/remind.js:66-71` **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```js // Discord 전송 if (argv.channel) { try { execSync(`openclaw message send --target "${argv.channel}" --message "${message.replace(/"/g, '\\"')}"`, { stdio: 'inherit', }); console.log('\n✅ Discord 전송 완료'); ``` ### Technical Analysis The Skill constructs a shell command by interpolating both `argv.channel` and `message` into a string passed to `execSync()`. Because `execSync()` executes string commands through a shell, shell metacharacters in either value may be interpreted as commands. Escaping only double quotation marks is insufficient. Shell substitutions such as `$(command)` and backtick substitutions remain active inside double-quoted shell arguments. The generated `message` includes Google Calendar event summaries and locations, which are externally sourced and may be controlled through shared calendars, invitations, or compromised calendar accounts. Consequently, calendar data is incorrectly treated as trusted shell input. The documented cron execution makes this especially dangerous because a malicious event could trigger command execution automatically at the scheduled reminder time. ### Attack Path 1. The attacker obtains the ability to create or modify an event visible on the calendar processed by the Skill, such as through a shared calendar or calendar invitation. 2. The attacker places shell substitution syntax in the event summary or location. 3. The scheduled cron job invokes `scripts/remind.js` with a Discord channel. 4. The Skill retrieves the malicious event and appends its summary or location to `message`. 5. The interpolated message is passed to `execSync()` as part of a shell command. 6. The local shell evaluates the injected substitution and executes the attacker-selected command. A local caller can also exploit the same flaw by supplying shell ...[truncated 717 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Avoid invoking a shell. Replace `execSync()` with `execFileSync()` or `spawnSync()` and pass every argument separately: ```js const { execFileSync } = require('child_process'); execFileSync('openclaw', [ 'message', 'send', '--target', argv.channel, '--message', message, ], { stdio: 'inherit', }); ``` Additional hardening should include: 1. Validate Discord channel identifiers against the exact expected format, such as `/^\d+$/`. 2. Treat all calendar fields as untrusted input. 3. Do not attempt to solve shell injection through manual escaping; eliminate shell interpretation instead. 4. Run scheduled reminders under a dedicated, minimally privileged account where practical. 5. Add regression tests using event titles and locations containing quotes, semicolons, backticks, dollar signs, newlines, and command substitutions. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.js:62
Finding
OAuth Refresh Tokens Stored Without Explicit Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Locations**: `scripts/auth.js:62-63`, `scripts/lib/gcal.js:36-40` **Vulnerability Type**: Insecure sensitive credential storage **Risk Level**: Medium ### Vulnerable Code Authentication initially writes the token without an explicit file mode: ```js // 토큰 저장 await fs.mkdir(path.dirname(TOKEN_PATH), { recursive: true }); await fs.writeFile(TOKEN_PATH, JSON.stringify(tokens, null, 2)); console.log(`✅ 토큰 저장 완료: ${TOKEN_PATH}`); ``` Token refresh handling also rewrites the token without enforcing restrictive permissions: ```js // 토큰 갱신 콜백 oauth2Client.on('tokens', async (tokens) => { if (tokens.refresh_token) { const currentTokens = JSON.parse(await fs.readFile(TOKEN_PATH, 'utf8')); currentTokens.refresh_token = tokens.refresh_token; await fs.writeFile(TOKEN_PATH, JSON.stringify(currentTokens, null, 2)); } }); ``` ### Technical Analysis The stored JSON may contain a long-lived OAuth refresh token granting access under the full Google Calendar scope. Neither the secrets directory nor the token file is assigned an explicit restrictive mode. The resulting permissions depend on the process umask and any pre-existing file permissions. In an environment with a permissive umask, other local users may be able to read the token. Rewriting an existing file also does not repair an already unsafe mode. The risk is amplified by the requested scope: ```js const SCOPES = ['https://www.googleapis.com/auth/calendar']; ``` That scope permits read and write operations, consistent with the Skill's declared functionality but sensitive if the token is disclosed. ### Attack Path 1. The user authenticates while the process has a permissive umask, or the token file already has overly broad permissions. 2. The Skill writes `~/.secrets/google-calendar-token.json` without enforcing mode `0600`. 3. Another local account or process reads the token file. 4. The attacker extracts the access or refresh token. 5. The attacker a ...[truncated 714 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Create the secrets directory and token file with explicit restrictive permissions: ```js await fs.mkdir(path.dirname(TOKEN_PATH), { recursive: true, mode: 0o700, }); await fs.writeFile( TOKEN_PATH, JSON.stringify(tokens, null, 2), { mode: 0o600 } ); await fs.chmod(TOKEN_PATH, 0o600); ``` Apply the same controls during token refresh. Prefer atomic replacement: 1. Write the updated token to a temporary file in the same protected directory with mode `0600`. 2. Flush and close the file. 3. Atomically rename it over the token file. 4. Reapply mode `0600` to correct unsafe permissions on pre-existing files. 5. Verify the secrets directory is owned by the expected user and has mode `0700`. 6. Document token revocation procedures for suspected exposure. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/auth.js:31
Finding
OAuth Callback Lacks State Validation and Explicit Loopback Binding<![CDATA[ ## Vulnerability Details **File Location**: `scripts/auth.js:31-53` **Vulnerability Type**: OAuth request-correlation weakness **Risk Level**: Medium ### Vulnerable Code ```js const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, }); console.log('브라우저에서 인증을 진행합니다...'); console.log(authUrl); opener(authUrl); const code = await new Promise((resolve, reject) => { const server = http.createServer(async (req, res) => { try { if (req.url.indexOf('/oauth2callback') > -1) { const qs = parse(req.url, true).query; res.end('인증 완료! 이 창을 닫고 터미널로 돌아가세요.'); server.close(); resolve(qs.code); } } catch (e) { reject(e); } }); server.listen(3000, () => { console.log('로컬 서버 시작: http://localhost:3000'); }); }); ``` ### Technical Analysis The authorization URL is generated without a cryptographically random `state` parameter. The callback accepts the first request whose URL contains `/oauth2callback`, extracts its `code`, closes the server, and proceeds without correlating the response to the authorization request. OAuth `state` protects authorization flows from callback injection, login CSRF, and response confusion. Without it, a party able to influence or race the callback may supply an authorization response that was not initiated by the current process. The callback server also uses `server.listen(3000)` without explicitly binding to `127.0.0.1` or `::1`. Although the redirect URI uses `localhost`, explicit loopback binding is safer and prevents accidental exposure on configurations where the default listening address accepts non-loopback connections. The implementation additionally lacks explicit handling for missing codes, OAuth error responses, unexpected paths, and authentication timeout. ### Attack Path 1. The user launches `scripts/auth.js`, which opens a Google authorization URL without a request-specific state value. 2. Before the legitimate ...[truncated 1122 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Generate a high-entropy state value and validate it before accepting the callback: ```js const crypto = require('crypto'); const state = crypto.randomBytes(32).toString('hex'); const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, state, }); ``` On callback: 1. Parse the URL using the WHATWG `URL` API. 2. Require the pathname to exactly equal `/oauth2callback`. 3. Reject OAuth error responses. 4. Require both `code` and `state`. 5. Compare the returned state against the expected state using a timing-safe comparison. 6. Bind explicitly to loopback: ```js server.listen(3000, '127.0.0.1'); ``` 7. Add a short timeout that closes the server if authentication is not completed. 8. Return appropriate HTTP error status codes for malformed or mismatched callbacks. 9. Use PKCE in addition to state where supported by the OAuth client and provider. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (43)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code chunk only creates events in Google Calendar using either explicit arguments or a Korean natural-language parser. This does align with part of the description about Korean natural-language scheduling and Google Calendar interaction. However, the declared description materially overstates the functionality: there is no evidence of Naver Calendar support, no reminder configuration, and no broader unified management behavior in this chunk. Therefore the description does not accurately represent what this code actually does.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared description promises an integrated calendar-management skill for both Google Calendar and Naver Calendar, including Korean natural-language parsing and reminders. The supplied code does not implement those features. Instead, it only performs Google Calendar OAuth setup: reading client credentials, launching a browser, listening for an OAuth callback, exchanging the code for tokens, and storing them on disk. While authentication can be a supporting detail for a calendar skill, this chunk's actual behavior is narrowly focused on Google OAuth and omits the key declared capabilities, especially Naver support, scheduling/parsing, and reminders. Therefore the description does not accurately represent this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
The declared description presents a broader calendar integration skill covering both Google Calendar and Naver Calendar, Korean natural-language schedule parsing, and reminders. The supplied code chunk instead implements a specific command-line utility for deleting a Google Calendar event by ID. This is a materially different concrete behavior from the declared description: it performs destructive deletion, does not show any Naver Calendar integration, and contains no natural-language parsing or reminder handling. Because event deletion is a significant capability not explicitly represented in the description, this should be flagged as a mismatch.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code’s behavior is limited to Google Calendar authentication/setup: it reads credential and token files from the local filesystem, creates an OAuth2 client, updates refresh tokens, and returns a Google Calendar API client. This is only a supporting piece for Google Calendar access. The declared description claims integrated Google + Naver calendar management, Korean natural-language parsing, and reminder support, none of which are evidenced in the provided code. Therefore the description overstates and materially differs from the actual functionality shown here.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The supplied code chunk is limited to parsing Korean date/time phrases and durations from text and returning a structured event object. This aligns with the '한국어 자연어 일정 파싱' portion of the description, but it does not implement any Google Calendar or Naver Calendar management/integration, nor any reminder creation/support logic. Because the declared purpose emphasizes integrated calendar management and reminder support, while the actual code only performs parsing, the description materially overstates the implemented capabilities in this chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
설명은 Google Calendar와 네이버 캘린더를 함께 관리하고, 한국어 자연어 일정 파싱 및 리마인더까지 제공하는 종합 일정 관리 기능을 주장합니다. 그러나 실제 코드 조각은 Google Calendar API의 events.list를 호출해 일정만 조회하는 list.js 스크립트입니다. 지원하는 입력도 today/tomorrow/YYYY-MM-DD 형태의 date 옵션과 days 범위 조회 정도이며, 네이버 캘린더 접근이나 동기화/통합 관리 로직은 전혀 없습니다. 또한 리마인더 생성·조회·알림 관련 동작도 없고, 일정 생성/수정 같은 관리 기능도 나타나지 않습니다. 따라서 선언된 설명이 실제 코드의 기능 범위를 상당히 과장하거나 다르게 대표하고 있어 불일치로 판단됩니다.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
설명은 Google Calendar와 네이버 캘린더의 통합 관리, 한국어 자연어 일정 파싱, 리마인더 지원을 강조한다. 그러나 제공된 코드 조각은 Google Calendar API로 오늘 일정만 조회하고 이를 포맷팅해 출력하며, 옵션으로 Discord 채널에 메시지를 보내는 리마인더 기능만 수행한다. 리마인더 자체는 설명과 일부 일치하지만, 네이버 캘린더 관련 처리와 자연어 파싱은 전혀 나타나지 않는다. 또한 Discord로 외부 메시지를 전송하는 능력은 설명이나 권한 선언에 없다. 따라서 설명이 코드의 실제 동작을 정확히 대표한다고 보기 어렵다.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
설명은 Google Calendar와 네이버 캘린더를 함께 관리하는 보다 넓은 캘린더 통합 기능과 한국어 자연어 파싱, 리마인더 지원을 약속합니다. 그러나 실제 코드는 단일 Node.js CLI로 Google Calendar API를 사용해 기존 이벤트를 조회하고 일부 필드만 수정하는 기능만 제공합니다. 접근 자원도 Google Calendar에만 국한되며, 네이버 캘린더 연동 로직이나 자연어 해석, 리마인더 처리 코드는 전혀 없습니다. 따라서 선언된 설명이 실제 동작을 과장하거나 잘못 대표하고 있어 불일치로 판단됩니다.

Credential Access

High
Category
Privilege Escalation
Content
Google Cloud Console에서 OAuth 2.0 클라이언트 ID 생성:
1. https://console.cloud.google.com/apis/credentials
2. "OAuth 2.0 클라이언트 ID" 생성 → "데스크톱 앱"
3. JSON 다운로드 → `~/.secrets/google-calendar-credentials.json` 저장

### 2. 초기 인증
Confidence
93% confidence
Finding
The skill instructs users to download OAuth client credentials and store them in a local secrets path, then create persistent token files for Google Calendar access. This is legitimate for the feature, but it still introduces high-value credential material that, if mishandled, can grant calendar read/write access and expose sensitive schedule data.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
| 문제 | 해결 |
|------|------|
| 401 Unauthorized | `node scripts/auth.js` 재인증 |
| 토큰 만료 | `rm ~/.secrets/google-calendar-token.json` → 재인증 |
| 한국어 파싱 실패 | `--start`, `--end` 명시적 지정 |
| 네이버 로그인 필요 | OpenClaw 브라우저에서 naver.com 로그인 후 재시도 |
Confidence
85% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
const { google } = require('googleapis');
const os = require('os');

const CREDENTIALS_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-credentials.json');
const TOKEN_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-token.json');

async function getCalendar() {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const { google } = require('googleapis');
const os = require('os');

const CREDENTIALS_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-credentials.json');
const TOKEN_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-token.json');

async function getCalendar() {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const { google } = require('googleapis');
const os = require('os');

const CREDENTIALS_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-credentials.json');
const TOKEN_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-token.json');

async function getCalendar() {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const { google } = require('googleapis');
const os = require('os');

const CREDENTIALS_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-credentials.json');
const TOKEN_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-token.json');

async function getCalendar() {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const { google } = require('googleapis');
const os = require('os');

const CREDENTIALS_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-credentials.json');
const TOKEN_PATH = path.join(os.homedir(), '.secrets', 'google-calendar-token.json');

async function getCalendar() {
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The README documents commands that can add, update, and delete calendar events without clearly warning users that these operations are state-changing and potentially destructive. In an agent/skill context, this increases the risk of accidental execution or user surprise, especially if a downstream system invokes commands from documentation-derived usage patterns.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The reminder feature explicitly supports sending calendar event data to a Discord channel, but the documentation does not warn that event titles, times, and other potentially sensitive scheduling information may be disclosed to third parties. In a business-calendar skill, this can cause unintended privacy leakage if reminders are posted to the wrong channel or to a broadly accessible workspace.

Lp3

Medium
Category
MCP Least Privilege
Confidence
84% confidence
Finding
The skill declares no explicit tool scope or permission boundaries even though it clearly depends on environment access, local secret files, OAuth tokens, cron, and external service interaction. In an agent setting, missing scope declarations can cause the runtime or user to underestimate what the skill can access, increasing the chance of over-broad execution and secret exposure.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill documents direct deletion of calendar events with no warning, confirmation, backup, or recovery guidance. In an agent-assisted workflow, destructive commands can be triggered too easily, leading to irreversible loss of calendar data through mistakes, prompt confusion, or unsafe automation.

Session Persistence

Medium
Category
Rogue Agent
Content
```bash
# cron 등록 (매일 9시 오늘 일정 알림)
crontab -e
# 추가: 0 9 * * * node /Users/mupeng/.openclaw/workspace/skills/mufi-calendar/scripts/remind.js
```
Confidence
85% confidence
Finding
The cron-based reminder setup establishes persistent scheduled execution using previously stored authentication material. Persistence itself is not malicious here, but unattended recurring execution increases the blast radius of misconfiguration, stale permissions, and unintended data disclosure through repeated reminders or message delivery.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The Naver-to-Google sync instructions encourage copying calendar contents across services without warning that events may contain sensitive personal, business, location, or meeting data. Cross-service replication can unintentionally broaden access, violate data handling expectations, or expose private schedules if the destination calendar has different sharing settings.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
Natural-language strings and parsing are explicitly Korean-focused, and event times are hard-coded to Asia/Seoul. This creates a language/locale restriction that is not presented as optional or region-specific, which can violate language/locale policy when no user choice is offered.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
This code performs a network API call that sends event details such as title, time, location, and description to an external Google Calendar service. Although the file header shows usage examples, there is no explicit disclosure in code comments, prompts, or warnings that user-provided data will be transmitted to Google.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This script performs a destructive calendar deletion immediately after printing the target event, with no interactive confirmation, dry-run mode, or safety flag. In a CLI tool that operates on live calendar data, a mistyped event ID, wrong calendar selection, or automation misuse can cause irreversible loss of scheduling information.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The code explicitly formats date/time using the 'ko-KR' locale in a general-purpose utility function. Under the policy, forcing a specific language or locale without user opt-in or a clearly documented region-specific justification is a natural-language policy violation.