Back to skill

Security audit

Opys Calendar Skill

Security checks for vulnerabilities and agentic risk

Overview

This calendar skill matches its stated purpose, but it needs review because it exposes and mutates calendar data through weakly scoped local APIs, stores Google OAuth tokens in plaintext, and ships real-looking personal calendar metadata.

Review before installing. Run it only on a trusted local machine, do not expose port 8787 to a network, and do not enable Google sync unless you are comfortable granting broad Google Calendar access and addressing plaintext token storage. Remove the packaged personal calendar, sync state, snapshots, and settings from any reusable copy, add those runtime files to ignore/exclusion rules, and update flagged dependencies.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
server/index.ts:837
Finding

Unauthenticated Calendar Disclosure and Modification APIs

Content
View full analysis
{ try { const raw = await fsp.readFile(settingsPath, "utf8"); res.json(JSON.parse(raw)); } catch { res.json({}); } }); app.post("/api/settings", async (req, res) => { try { let current = {}; try { const raw = await fsp.readFile(settingsPath, "utf8"); current = JSON.parse(raw); } catch { // ignore } const next = { ...current, ...req.body }; await fsp.writeFile(settingsPath, JSON.stringify(next, null, 2), "utf8"); res.json(next); } catch (error) { const message = error instanceof Error ? error.message : String(error); res.status(500).json({ error: message }); } }); app.get("/api/calendar", async (_req, res) => { const document = await loadCalendarFromFile(calendarPath); res.json(document); }); app.get("/api/calendar/export", async (_req, res) => { const document = await loadCalendarFromFile(calendarPath); res.type("text/markdown").send(serializeCalendarMarkdown(document)); }); app.post("/api/calendar/import", async (req, res) => { const markdown = req.body?.markdown; if (typeof markdown !== "string" || markdown.trim().length === 0) { res.status(400).json({ error: "Body must include a markdown string" }); return; } try { const parsed = parseCalendarMarkdown(markdown); await saveCalendarToFile(calendarPath, parsed); res.json(parsed); } catch (error) { const message = error instanceof Error ? error.message : "Unable to import markdown"; res.status(400).json({ error: message }); } }); app.post("/api/events", async (req, res) => { // Creates or repla ...[truncated 2472 chars]
Remediation
View remediation
{ await loadCalendarFromFile(calendarPath); }); ``` 2. Require authentication and explicit authorization on every read and mutation route. 3. Do not treat automatically created anonymous sessions as authenticated identities. 4. Restrict CORS to an exact allowlist derived from a validated frontend origin: ```ts cors({ origin: ["http://localhost:5173"], credentials: true }); ``` 5. Add CSRF protection or strict Origin validation to all state-changing routes. 6. Disable or separately protect bulk import and export endpoints. 7. Apply rate limits and request-size limits appropriate to each route. 8. Document that exposing port 8787 through a reverse proxy or container requires TLS and access control. 9. Add integration tests verifying that anonymous and cross-origin callers receive `401` or `403`. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
server/index.ts:102
Finding

Google OAuth Access and Refresh Tokens Stored in Plaintext

Content
View full analysis
(); const sessionStorePath = path.join(root, ".calendar-sessions.json"); const authTokens = new Map(); const AUTH_TOKEN_TTL = 60_000; function loadSessionsFromDisk() { if (!fs.existsSync(sessionStorePath)) return; try { const raw = fs.readFileSync(sessionStorePath, "utf-8"); const parsed = JSON.parse(raw) as Record; Object.entries(parsed || {}).forEach(([sid, data]) => { sessions.set(sid, data || {}); }); } catch { // ignore corrupt session store } } function persistSessionsToDisk() { try { const data: Record = {}; sessions.forEach((value, key) => { data[key] = value; }); fs.writeFileSync(sessionStorePath, JSON.stringify(data, null, 2)); } catch { // ignore write errors } } ``` ```ts req.sessionData.tokens = { accessToken, refreshToken, expiryAt: Date.now() + expiresIn * 1000, scope: typeof tokenResult.scope === "string" ? tokenResult.scope : undefined, tokenType: typeof tokenResult.token_type === "string" ? tokenResult.token_type : undefined }; req.sessionData.user = await fetchGoogleUser(req.sessionData); req.sessionData.selectedCalendarId = undefined; persistSessionsToDisk(); ``` ### Technical Analysis OAuth access and refresh tokens are stored in each `SessionData` object. `persistSessionsToDisk()` serializes every session, including these bearer credentials, into `.calendar-sessions.json`. The file is neither encrypted nor created with an explicit restrictive mode. Its effective permissions therefore depend on the process umask and surrounding filesystem configuration. The implementation also silently ignores write err ...[truncated 1428 chars]
Remediation
View remediation

other

Warning
Location
.calendar-google-sync-state.json:3
Finding

Distribution Artifact Contains Real Personal Calendar and Google Account Metadata

Content
View full analysis
2026-02-16 16:00 | **Purdue's for Me - College of Engineering** (`life`) - [ ] `evt_E-VeMWOL` | 2026-02-22 01:00 -> 2026-02-22 07:15 | **Sleep** (`life`) - [ ] `evt_3ZuWYLoF` | 2026-02-22 20:00 -> 2026-02-22 20:45 | **Workout** (`life`) - [ ] `evt_6HvgkiVO` | 2026-02-23 19:00 -> 2026-02-23 20:30 | **🎸 Guitar Concert** (`life`) ``` ### Technical Analysis The packaged synchronization state identifies a real Google account through an email address and stable Google subject identifier. It also includes numerous Google event identifiers and local-to-remote mappings. The included `calendar.md` appears to contain a detailed personal schedule rather than minimal synthetic sample data. The events disclose educational activities, sleep routines, health or hygiene routines, appointments, and availability patterns. These records are not necessary for distributing or operating reusable calendar Skill code. Their inclusion creates a privacy disclosure independent of whether the application is executed. ### Attack Path 1. An attacker downloads, clones, receives, or indexes the project artifact. 2. The attacker opens `.calendar-google-sync-state.json`. 3. The attacker associates the exposed email address and stable account identifier with the calendar data. 4. The attacker reads `calendar.md` and correlates timestamps, routines, educa ...[truncated 661 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
server/index.ts:1175
Finding

One-Time Session Adoption Token Exposed in OAuth Redirect URL

Content
View full analysis
Remediation
View remediation
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (57)

Known Vulnerable Dependency: shell-quote==1.8.3 — 2 advisory(ies): CVE-2026-13311 (shell-quote: Quadratic-complexity Denial of Service in `parse()` (CWE-407)); CVE-2026-9277 (shell-quote quote() does not escape newlines in object .op values)

Critical
Category
Supply Chain
Confidence
90% confidence
Finding

shell-quote 1.8.3 has advisories including DoS and insufficient newline escaping in quoted operator values. In this lockfile it is only a transitive dev dependency of concurrently, so it is not directly exposed at runtime; however, if build or script orchestration incorporates untrusted values into shell command construction, it could lead to command-line injection or script misexecution.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
84% confidence
Finding

The description omits behavior that writes a rolling agent snapshot file, including a user-configurable path via CALENDAR_AGENT_SNAPSHOT. Undisclosed file-writing behavior is security-relevant because it can create unexpected data exposure, overwrite files, or persist sensitive calendar content outside the stated source-of-truth file.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The description omits behavior that writes a rolling agent snapshot file, including a user-configurable path via CALENDAR_AGENT_SNAPSHOT. Undisclosed file-writing behavior is security-relevant because it can create unexpected data exposure, overwrite files, or persist sensitive calendar content outside the stated source-of-truth file.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: browserslist==4.28.1 — 2 advisory(ies): CVE-2026-73088 (Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.); CVE-2026-73089 (Browserslist: Unbounded memory growth (no cache eviction) via distinct query res)

High
Category
Supply Chain
Confidence
87% confidence
Finding

browserslist 4.28.1 is reported with crash/prototype-write and unbounded memory growth issues. It is a real supply-chain weakness, but in this project it is only used in the frontend build pipeline, so the main risk is to developer machines or CI when processing attacker-controlled stats or queries, not the deployed app directly.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: js-yaml==3.14.2 — 4 advisory(ies): CVE-2026-84375 (js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources); CVE-2026-59869 (js-yaml: YAML merge-key chains can force quadratic CPU consumption); GHSA-5p4m-2wfm-xmqj (JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026) +1 more

High
Category
Supply Chain
Confidence
91% confidence
Finding

js-yaml 3.14.2 has multiple CPU exhaustion issues and is used transitively by gray-matter for parsing markdown front matter. Since this calendar skill is explicitly markdown-backed, untrusted or synced markdown content could trigger excessive parsing cost, making this more relevant than a generic dev-only finding.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: nanoid==5.1.6 — 2 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
80% confidence
Finding

nanoid 5.1.6 is flagged for generator edge-case issues such as looping on negative size or integer wraparound. This is a real dependency weakness, but exploitation generally depends on unsafe API usage with attacker-controlled size values; a lockfile alone does not show such usage, so practical risk is limited.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: path-to-regexp==8.3.0 — 2 advisory(ies): CVE-2026-4923 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple w); CVE-2026-4926 (path-to-regexp vulnerable to Denial of Service via sequential optional groups)

High
Category
Supply Chain
Confidence
84% confidence
Finding

path-to-regexp 8.3.0 is reported vulnerable to ReDoS/DoS patterns. Because it is used by Express router infrastructure, malformed paths can potentially impact request handling performance if the app defines affected route patterns, making this more operationally relevant than a build-only issue.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: picomatch==4.0.3 — 2 advisory(ies): CVE-2026-33672 (Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Mat); CVE-2026-33671 (Picomatch has a ReDoS vulnerability via extglob quantifiers)

High
Category
Supply Chain
Confidence
83% confidence
Finding

picomatch 4.0.3 has reported glob-matching issues including ReDoS and incorrect matching behavior. In this project it appears in dev tooling such as Vite/tinyglobby, so the likely impact is CI or local build disruption when attacker-controlled glob patterns are introduced, not direct runtime compromise.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: postcss==8.5.6 — 4 advisory(ies): CVE-2026-45623 (PostCSS: Arbitrary file read and information disclosure via attacker-controlled ); CVE-2026-69153 (PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappi); CVE-2026-41305 (PostCSS has XSS via Unescaped </style> in its CSS Stringify Output) +1 more

High
Category
Supply Chain
Confidence
88% confidence
Finding

postcss 8.5.6 is flagged for arbitrary file read and XSS-related issues in processing attacker-controlled CSS/source map content. Here it is part of the frontend toolchain, so the danger is primarily to developers/CI during builds or previews rather than end users of the calendar app, but it is still a genuine dependency risk.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: nanoid==3.3.11 — 3 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2026-73086 (nanoid: Integer Overflow or Wraparound)

High
Category
Supply Chain
Confidence
80% confidence
Finding

nanoid 3.3.11, pulled transitively by PostCSS, carries similar loop and integer handling issues. Because this copy is dev-only and typically used internally by tooling, the exploitability is low unless untrusted size parameters can reach it through custom tool usage.

Content

No source excerpt is available for this finding.

Possible Typosquatting: 'preact' resembles popular package 'react'

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.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
93% confidence
Finding

The code stores OAuth access and refresh tokens inside an in-memory session object that is later persisted wholesale to .calendar-sessions.json on disk. If local files are exposed, backed up insecurely, or read by another local user/process, an attacker can reuse the refresh token to obtain ongoing access to the user's Google Calendar account.

Content

Scanner excerpt · server/index.ts (reported line 426)May include surrounding context.

ts
const accessToken = typeof tokenResult.access_token === "string" ? tokenResult.access_token : "";
  if (!accessToken) {
    throw new ApiError(502, "Google did not return an access token");
  }

  const expiresIn = typeof tokenResult.expires_in === "number" ? tokenResult.expires_in : 3600;

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

During OAuth callback, the application saves newly issued access and refresh tokens into session state and then persists that session state to disk, creating a plaintext credential cache. In a local markdown-backed skill context, this is especially risky because the app appears designed for local use, where developers may assume the environment is trusted and overlook file exposure, malware, shared accounts, or accidental sync of dotfiles to cloud storage.

Content

Scanner excerpt · server/index.ts (reported line 1159)May include surrounding context.

ts
const accessToken = typeof tokenResult.access_token === "string" ? tokenResult.access_token : "";
    if (!accessToken) {
      throw new ApiError(502, "Google did not return an access token");
    }

    const refreshToken = typeof tokenResult.refresh_token === "string" ? tokenResult.refresh_token : undefined;

Missing User Warnings

High
Category
Not specified by scanner
Confidence
89% confidence
Finding

Triggering sync can create, modify, and delete events both locally and in Google Calendar via network calls, including deletion of remote events based on mapping state. Although syncing is the endpoint's purpose, this file lacks any inline user disclosure that the action is bidirectional and potentially destructive.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The README explicitly promotes that AI agents can read and modify the user's calendar files directly, but it does not warn that autonomous or semi-autonomous agents could alter scheduling data, overwrite entries, or introduce fraudulent events. In an agent-integrated context, this omission is security-relevant because users may grant broad filesystem access without understanding the integrity risks to sensitive personal scheduling data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README advertises two-way Google Calendar sync as a feature but does not warn that enabling synchronization shares local scheduling data with a third-party service and may propagate unintended or malicious changes in both directions. In this skill's context, bi-directional sync increases the blast radius of agent mistakes or abuse from a local markdown file to an external calendar ecosystem.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The setup instructions tell users to place Google OAuth credentials in a .env file but provide no warning that these are sensitive secrets that must not be committed, exposed to agents, or logged. In an agent-focused project, this is more dangerous because local agents may read workspace files by design, increasing the likelihood of credential disclosure or misuse.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill declares access to sensitive environment variables and describes Google sync/networked behavior, but it does not declare an explicit tool scope such as permissions or allowed-tools. That weakens least-privilege boundaries and makes it harder for a host agent to constrain what the skill may access, especially where OAuth secrets and network operations are involved.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The file is described as a local markdown calendar, but it embeds live Google Calendar linkage data including external IDs, account identifiers, locations, and sync metadata. This creates an unnecessary privacy and data-exposure risk because any consumer of the supposedly local file can extract remote calendar mappings and sensitive personal context, and a sync-capable tool could use those identifiers to modify or correlate remote events.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The file claims the Event Records section is authoritative, but the checklist is also being used as active event data and already diverges from records, including category mismatches and duplicate/ignore semantics. This ambiguity can be exploited by tooling or agents that read the wrong section, leading to unauthorized edits, stale overwrites, accidental sync corruption, or leakage of incorrect event state to connected services.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · server/index.ts (reported line 390)May include surrounding context.

ts
}

async function requestGoogleToken(form: URLSearchParams): Promise<Record<string, unknown>> {
  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
86% confidence
Finding

The import endpoint parses supplied markdown and immediately saves it to the main calendar file, replacing existing local calendar content. In this file there is no confirmation prompt, user-facing log/print, or inline comment/docstring warning that the operation can overwrite user data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The delete endpoint removes events from the persisted calendar file and returns success, but this file provides no confirmation prompt, warning, or user-facing disclosure around the destructive action. Because this is an irreversible data-modifying operation, the lack of any visible warning meets the missing-user-warning criterion for code files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The code deletes the selected event via a DELETE request and also binds that action directly to Backspace/Delete key presses. There is no confirmation prompt, warning dialog, or inline disclosure before this destructive action occurs, which increases the risk of accidental data loss.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The document declares lang="en", which sets a specific language/locale preference in the skill's natural-language surface. Under the policy, forcing a specific language without offering user choice or documenting a justified locale constraint can be a violation.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal, suspicious.potential_exfiltration

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
server/index.ts:18

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
server/index.ts:418

Sensitive-looking file read is paired with a network send.

Warn
Code
suspicious.potential_exfiltration
Location
server/index.ts:110