Back to skill

Security audit

appointment-scheduler

Security checks for vulnerabilities and agentic risk

Overview

This appointment scheduler is mostly purpose-aligned, but it handles customer data and Google Calendar access with overbroad permissions, weak local data protections, and insufficient user controls.

Install only after reviewing the data-handling model. Use a dedicated calendar, restrict token and data-file permissions, avoid syncing phone numbers or notes unless customers have consented, validate date inputs before use, and treat cron-based reminders or sync as an explicit opt-in because they keep running after setup.

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
scripts/book.js:18
Finding
Path Traversal Through Unvalidated Date Arguments<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/book.js:18-18, 54-54, 115-115` - `scripts/block-time.js:18-18, 33-33` - `scripts/check-schedule.js:13-24` - `scripts/waitlist.js:30-41, 67-73` **Vulnerability Type**: Path traversal leading to unintended JSON file access or modification **Risk Level**: High ### Vulnerable Code From `scripts/book.js`: ```js const date = getArg('--date'); ``` ```js const dailyFile = path.join(DATA_DIR, `${date}.json`); let bookings = []; if (fs.existsSync(dailyFile)) { bookings = JSON.parse(fs.readFileSync(dailyFile, 'utf8')); } ``` ```js const eventFile = path.join(EVENT_DIR, `appointment-${date}.json`); ``` From `scripts/block-time.js`: ```js const date = getArg('--date'); ``` ```js const filePath = path.join(DATA_DIR, `${date}.json`); let bookings = []; if (fs.existsSync(filePath)) { bookings = JSON.parse(fs.readFileSync(filePath, 'utf8')); } ``` From `scripts/check-schedule.js`: ```js const args = process.argv.slice(2); const dateArg = args.includes('--date') ? args[args.indexOf('--date') + 1] : null; const weekMode = args.includes('--week'); const DATA_DIR = path.join(process.env.HOME, '.openclaw', 'workspace', 'data', 'appointments', 'bookings'); function formatDate(date) { return date.toISOString().split('T')[0]; } function getBookingsForDate(dateStr) { const filePath = path.join(DATA_DIR, `${dateStr}.json`); if (!fs.existsSync(filePath)) { return []; } return JSON.parse(fs.readFileSync(filePath, 'utf8')); } ``` From `scripts/waitlist.js`: ```js const date = getArg('--date'); const time = getArg('--time'); const customer = getArg('--customer'); const phone = getArg('--phone'); const email = getArg('--email'); if (!date || !time || !customer) { console.error('Usage: node waitlist.js add --date YYYY-MM-DD --time HH:MM --customer "name" --phone "phone"'); process.exit(1); } const filePath = path.join(WAITLIST_DIR, `${date}.json`); ``` ```js const date = getArg('--date'); ...[truncated 2496 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce the documented date syntax before any filesystem operation: ```js function validateDate(value) { if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) { throw new Error('Date must use YYYY-MM-DD format'); } const parsed = new Date(`${value}T00:00:00Z`); if ( Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value ) { throw new Error('Invalid calendar date'); } return value; } ``` 2. Resolve and constrain every generated path: ```js function safeJsonPath(baseDirectory, date) { const validatedDate = validateDate(date); const base = path.resolve(baseDirectory); const target = path.resolve(base, `${validatedDate}.json`); if (!target.startsWith(`${base}${path.sep}`)) { throw new Error('Path escapes the permitted data directory'); } return target; } ``` 3. Apply the validation consistently in `book.js`, `block-time.js`, `check-schedule.js`, and `waitlist.js`. 4. Validate dates again when loading persisted booking objects before using `booking.date` to form event or waitlist paths. 5. Add negative tests covering `../`, absolute paths, encoded separators, invalid dates, and values containing path separators. 6. Run the Skill under a dedicated account with write access limited to its configuration and appointment-data directories. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/sync-google-calendar.js:51
Finding
Overprivileged Google Calendar Authorization and Unnecessary PII Synchronization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/sync-google-calendar.js:51-53, 88-112` **Vulnerability Type**: Excessive OAuth privileges and external disclosure of customer data **Risk Level**: Medium ### Vulnerable Code ```js const authUrl = oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: ['https://www.googleapis.com/auth/calendar'] }); ``` ```js const event = { summary: `${booking.service} - ${booking.customer.name}`, description: `고객: ${booking.customer.name}\n전화: ${booking.customer.phone || 'N/A'}\n메모: ${booking.notes || 'N/A'}`, start: { dateTime: startDateTime, timeZone: 'Asia/Seoul' }, end: { dateTime: endTime.toISOString(), timeZone: 'Asia/Seoul' }, reminders: { useDefault: false, overrides: [ { method: 'popup', minutes: 120 } ] } }; try { const response = await calendar.events.insert({ calendarId: calendarId, resource: event }); ``` ### Technical Analysis The integration only creates appointment events, but it requests the broad Google Calendar OAuth scope: ```text https://www.googleapis.com/auth/calendar ``` This scope provides substantially more authority than event insertion alone and can allow reading, modifying, sharing, and deleting calendar resources available to the authorized account. The script also transfers customer names, telephone numbers, and arbitrary free-form booking notes to Google Calendar. Phone numbers and notes are not required to create a functional appointment event or perform basic calendar synchronization. Free-form notes may contain additional sensitive information, particularly when the Skill is used by clinics. The use of offline access also means the stored refresh token can provide continuing access after the initial authorization. This behavior supports scheduled synchronization, but it increases the consequences of token disclosure and makes strict scope minimization important. ### Attack Path 1. The user follows ...[truncated 1364 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the full Calendar scope with the narrowest scope compatible with the required event-creation workflow. 2. Consider using a dedicated calendar created specifically for appointments rather than the account’s primary calendar. 3. Do not synchronize customer phone numbers or arbitrary notes by default. Use a minimal event payload such as: ```js const event = { summary: booking.service, description: `Booking ID: ${booking.id}`, start: { dateTime: startDateTime, timeZone: 'Asia/Seoul' }, end: { dateTime: endDateTime, timeZone: 'Asia/Seoul' } }; ``` 4. Make customer-name, phone-number, and note synchronization separate opt-in configuration options with clear privacy warnings. 5. Filter notes against an explicit schema rather than uploading unrestricted free-form text. 6. Protect credential and token directories with mode `0700` and token files with mode `0600`. 7. Document token revocation, calendar-data deletion, and consent requirements. 8. Clearly disclose what fields leave the local device before authorization and synchronization occur. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/book.js:79
Finding
Plaintext Customer Records Created Without Explicitly Restrictive Permissions or Retention Controls<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/book.js:79-105` - `scripts/waitlist.js:47-61` - `scripts/mark-noshow.js:65-78, 88-107` - `scripts/send-reminders.js:118-132` - `scripts/init-config.js:57-62` **Vulnerability Type**: Insecure storage of personal information **Risk Level**: Medium ### Vulnerable Code From `scripts/book.js`: ```js const booking = { id: crypto.randomBytes(6).toString('hex'), date, time, duration, service, customer: { name: customer, phone: phone || null, email: email || null }, notes, status: 'confirmed', created_at: new Date().toISOString(), reminded: { day_before: false, hour_before: false } }; bookings.push(booking); bookings.sort((a, b) => a.time.localeCompare(b.time)); // Save fs.writeFileSync(dailyFile, JSON.stringify(bookings, null, 2)); ``` From `scripts/waitlist.js`: ```js const entry = { id: crypto.randomBytes(6).toString('hex'), date, time, customer: { name: customer, phone: phone || null, email: email || null }, added_at: new Date().toISOString(), notified: false }; waitlist.push(entry); fs.writeFileSync(filePath, JSON.stringify(waitlist, null, 2)); ``` From `scripts/mark-noshow.js`: ```js history.push({ booking_id: booking.id, customer_name: booking.customer.name, customer_phone: booking.customer.phone, customer_email: booking.customer.email, date: booking.date, time: booking.time, service: booking.service, marked_at: booking.noshow_marked_at }); fs.writeFileSync(HISTORY_FILE, JSON.stringify(history, null, 2)); ``` ```js flagged[key] = { customer_name: booking.customer.name, phone: booking.customer.phone, email: booking.customer.email, noshow_count: customerNoshows.length, flagged_at: new Date().toISOString(), require_deposit: config.noshow_policy?.require_deposit_when_flagged || false }; fs.writeFileSync(FLAGGED_FILE, JSON.stringify(flagged, null, 2)); ``` From `scripts/send-reminders.js`: ```j ...[truncated 2730 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create private data directories explicitly: ```js fs.mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 }); ``` 2. Create or replace sensitive files with mode `0600`: ```js fs.writeFileSync(filePath, payload, { encoding: 'utf8', mode: 0o600 }); ``` 3. Verify and repair permissions on existing directories and files during initialization. 4. Use atomic writes through a private temporary file followed by `renameSync()` to avoid partial or corrupted records. 5. Minimize duplicated personal information: - Avoid retaining contacts in reminder logs. - Reference a booking ID instead of copying phone or email fields. - Store only the identifiers required for no-show enforcement. 6. Add configurable retention periods for completed bookings, reminder logs, waitlists, and no-show history. 7. Provide deletion and customer-data export workflows. 8. Consider encrypting sensitive fields at rest when the threat model includes other local users, shared backups, or broad workspace integrations. 9. Document the data collected, its purpose, storage location, retention period, and access requirements. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
Findings (36)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
This mismatch becomes security-relevant because the skill claims broad appointment automation while also referencing access to OAuth credentials, tokens, and local data without declaring permissions. Users may consent based on the stated purpose without realizing the actual behavior includes sensitive credential and file access, creating a transparency and over-privilege risk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding
This mismatch becomes security-relevant because the skill claims broad appointment automation while also referencing access to OAuth credentials, tokens, and local data without declaring permissions. Users may consent based on the stated purpose without realizing the actual behavior includes sensitive credential and file access, creating a transparency and over-privilege risk.

Credential Access

High
Category
Privilege Escalation
Content
### Google Calendar

1. Enable Google Calendar API
2. Download OAuth credentials → save as `~/.secrets/google-calendar-credentials.json`
3. First run will prompt browser auth
4. Refresh token saved to `~/.secrets/google-calendar-token.json`
Confidence
93% confidence
Finding
The skill explicitly references storage and use of Google Calendar OAuth credential and token files in the user's home directory. In this context, those secrets enable ongoing access to external calendar data and operations, so insufficient protection or unexpected access to them can lead to unauthorized reading, modification, or persistence in the connected account.

Ae1

High
Category
analysis-evasion
Content
| 예약 파싱 실패 | Check `parse-booking.js` output, add custom patterns |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Possible Typosquatting: 'gaxios' resembles popular package 'axios'

High
Category
Supply Chain
Confidence
70% confidence
Finding
Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.

Credential Access

High
Category
Privilege Escalation
Content
* 
 * Setup:
 * 1. Enable Google Calendar API in Google Cloud Console
 * 2. Download OAuth credentials → save as ~/.secrets/google-calendar-credentials.json
 * 3. First run will open browser for auth
 * 4. Refresh token saved to ~/.secrets/google-calendar-token.json
 */
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
* 
 * Setup:
 * 1. Enable Google Calendar API in Google Cloud Console
 * 2. Download OAuth credentials → save as ~/.secrets/google-calendar-credentials.json
 * 3. First run will open browser for auth
 * 4. Refresh token saved to ~/.secrets/google-calendar-token.json
 */
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
* 
 * Setup:
 * 1. Enable Google Calendar API in Google Cloud Console
 * 2. Download OAuth credentials → save as ~/.secrets/google-calendar-credentials.json
 * 3. First run will open browser for auth
 * 4. Refresh token saved to ~/.secrets/google-calendar-token.json
 */
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
* 
 * Setup:
 * 1. Enable Google Calendar API in Google Cloud Console
 * 2. Download OAuth credentials → save as ~/.secrets/google-calendar-credentials.json
 * 3. First run will open browser for auth
 * 4. Refresh token saved to ~/.secrets/google-calendar-token.json
 */
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Session Persistence

Medium
Category
Rogue Agent
Content
### Cron Setup (자동화)

```bash
# crontab -e 실행 후 추가:

# 매일 오전 9시: 하루 전 리마인더
0 9 * * * cd /Users/mupeng/.openclaw/workspace/skills/appointment-scheduler/scripts && node send-reminders.js --type day-before
Confidence
85% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README explicitly describes automated reminder and waitlist notifications and Google Calendar sync for bookings containing customer names and phone numbers, but it provides no privacy notice, consent requirement, data-minimization guidance, or warning that personal data will be transmitted to external systems. In an appointment-booking skill handling customer PII, this omission increases the risk of unauthorized disclosure or non-compliant data handling through messaging and calendar integrations.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill describes use of environment-dependent capabilities, local files, OAuth secrets, cron, and external calendar/message integrations, but it declares no explicit tool scope or permissions. That omission weakens reviewability and least-privilege controls, making it easier for a host agent to grant broader access than users expect.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill processes customer names, phone numbers, booking details, reminders, and no-show history, all of which are personal data, yet it provides no privacy notice, consent flow, retention policy, or handling guidance. In an appointment-management context, this increases the risk of unlawful collection, oversharing, and insecure storage of customer information.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
Much of the operational content, examples, service names, reminder text, and troubleshooting material are written in Korean, which can impose a language constraint on users. The file does not explicitly offer alternative language support or state that the skill is intentionally Korean-only for a defined regional context.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The file instructs users to store OAuth credentials and refresh tokens locally and to sync appointment data to third-party calendars, but it gives no warning about the sensitivity of those secrets or the exposure of customer data to external services. Compromise of these files could allow persistent calendar access and disclosure or manipulation of appointment records.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The test results explicitly demonstrate storage of personally identifiable information such as customer names, phone numbers, booking history, and no-show status in local workspace files, but do not mention retention, access controls, or user-facing disclosure. In an appointment-management context, this creates a real privacy risk because operators may persist sensitive customer data without understanding where it is stored or how long it remains available.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The file states Google Calendar sync is a feature and recommends documenting OAuth setup, but it does not warn that enabling sync may transmit appointment details and customer metadata to an external third-party service. In this skill, appointment data likely includes sensitive scheduling and contact information, so omission of this disclosure can lead to unintended external sharing.

Session Persistence

Medium
Category
Rogue Agent
Content
- **Next step**: Document OAuth setup process for users

3. **Reminder Timing**: Requires cron setup or manual testing with mock dates
   - **Next step**: Create test mode for reminders

## Recommendations
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script directly prints personally identifiable and potentially sensitive appointment data, including customer names, phone numbers, and free-form notes, to stdout. In the context of salon/clinic/studio scheduling, this increases the risk of shoulder-surfing, terminal log exposure, shell history capture in surrounding tooling, and accidental disclosure of medical or personal details contained in notes.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The default configuration embeds Korean business and service names such as "MUFI 포토부스", "포토촬영", and other Korean-only labels. This imposes a specific language/locale in the generated config without user opt-in or any indication that the skill is region-specific, which matches the language/locale policy violation criteria.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This script makes immediate, persistent changes to booking status, no-show history, and customer flagging records with no confirmation prompt, dry-run mode, undo capability, or secondary validation of operator intent. In an appointment-management context, a mistaken or automated invocation can wrongly label customers as no-shows and trigger downstream penalties such as deposit requirements, creating operational and reputational harm.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The usage example is exclusively in Korean and the extraction logic is explicitly tied to Korean keywords and Korean-name patterns, which makes the skill effectively operate only for Korean-language booking requests. There is no user opt-in, language choice, or documented justification that this is a Korea-specific or Korean-only skill, so this is a natural-language locale policy concern.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The message templates use Korean text directly and the code does not offer any language selection, fallback, or indication that the skill is intentionally limited to Korean-speaking users. This can violate language/locale policy when users or recipients may expect communication in their preferred language.

Session Persistence

Medium
Category
Rogue Agent
Content
console.error('\nSetup guide:');
  console.error('1. Go to https://console.cloud.google.com/');
  console.error('2. Enable Google Calendar API');
  console.error('3. Create OAuth 2.0 credentials');
  console.error('4. Download and save to ~/.secrets/google-calendar-credentials.json');
  process.exit(1);
}
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script automatically uploads customer PII, including names and phone numbers, into Google Calendar event summaries and descriptions without any explicit consent gate, minimization, or warning at sync time. In an appointment-scheduling context this is sensitive business and customer data, and placing it into a third-party calendar can expose it to broader account access, sharing settings, mobile notifications, and retention outside the local booking system.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/cancel-booking.js:58