Back to skill

Security audit

Wakapi Sync

Security checks for vulnerabilities and agentic risk

Overview

This skill coherently fetches Wakapi coding stats and writes local CSV summaries, with disclosed credential and output requirements, but users should configure it carefully.

Install only if you are comfortable giving the skill a Wakapi API key and writing CSV files to the configured output directory. Set WAKAPI_URL to a trusted HTTPS Wakapi server, prefer a read-only or least-privileged API key if available, and be cautious opening generated CSVs in spreadsheet apps that evaluate formulas.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wakapi-daily-summary.mjs:43
Finding

Wakapi API Key Can Be Sent to an Untrusted or Unencrypted Endpoint

Content
View full analysis

Vulnerability Details

File Location: scripts/wakapi-daily-summary.mjs, lines 19–31, 43–52, and 187–205
Vulnerability Type: Unrestricted credential transmission
Risk Level: Medium

The Skill accepts an arbitrary WAKAPI_URL and sends the Wakapi API key to that destination in an HTTP Basic authorization header. The URL is not validated to ensure that it uses HTTPS or identifies a trusted Wakapi host.

js
function getConfig() {
  const WAKAPI_URL = process.env.WAKAPI_URL;
  const WAKAPI_API_KEY = process.env.WAKAPI_API_KEY;
  const OUT_DIR = process.env.WAKAPI_OUT_DIR;
  const TOP_N_PROJECTS = Number(process.env.WAKAPI_TOP_N_PROJECTS || 10);
  const TOP_N_LANGUAGES = Number(process.env.WAKAPI_TOP_N_LANGUAGES || 10);

  if (!WAKAPI_URL || !WAKAPI_API_KEY || !OUT_DIR) {
    console.error('Missing required env vars: WAKAPI_URL, WAKAPI_API_KEY, WAKAPI_OUT_DIR');
    process.exit(2);
  }

  return { WAKAPI_URL, WAKAPI_API_KEY, OUT_DIR, TOP_N_PROJECTS, TOP_N_LANGUAGES };
}
js
async function httpJson(url, apiKey) {
  const headers = {
    'Accept': 'application/json',
    'Authorization': `Basic ${b64(apiKey)}`,
  };

  const res = await fetch(url, { headers });
  if (!res.ok) {
    const text = await res.text().catch(() => '');
    throw new Error(`HTTP ${res.status} ${res.statusText} for ${url}\n${text.slice(0, 500)}`);
  }
  return res.json();
}
js
const base = WAKAPI_URL.replace(/\/$/, '');
const statusbarUrl = `${base}/api/v1/users/current/statusbar/today`;
const summariesUrl = `${base}/api/v1/users/current/summaries?range=today`;

let statusbar;
try {
  statusbar = await httpJson(statusbarUrl, WAKAPI_API_KEY);
} catch (e) {
  console.error(`[wakapi-sync] Failed statusbar/today: ${e.message}`);
  throw e;
}

let { totalSeconds, projects, languages } = extractTopFromStatusbarToday(statusbar);

// If statusbar doesn't con
...[truncated 2252 chars]
Remediation
View remediation

Remediation Suggestions

  1. Parse WAKAPI_URL with new URL() before constructing any request.
  2. Require the https: scheme by default and reject unsupported schemes, embedded user information, fragments, and malformed URLs.
  3. If plaintext HTTP is needed for a loopback-only deployment, require a clearly named explicit opt-in and restrict it to verified loopback addresses.
  4. Provide an optional hostname allowlist or configured trusted-origin check for managed deployments.
  5. Build endpoints with the URL API rather than string concatenation.
  6. Disable automatic redirects or validate every redirect target against the original trusted origin before following it.
  7. Use a minimally privileged, read-only Wakapi API key where the server supports scoped credentials.
  8. Add tests confirming rejection of http://, non-HTTP schemes, embedded credentials, untrusted hosts, and cross-origin redirects.

Example hardening:

js
function validateWakapiBase(rawUrl) {
  const url = new URL(rawUrl);

  if (url.protocol !== 'https:') {
    throw new Error('WAKAPI_URL must use HTTPS');
  }
  if (url.username || url.password) {
    throw new Error('WAKAPI_URL must not contain embedded credentials');
  }
  if (url.search || url.hash) {
    throw new Error('WAKAPI_URL must not contain a query string or fragment');
  }

  return url;
}

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wakapi-daily-summary.mjs:61
Finding

Remote Project and Language Names Are Vulnerable to CSV Formula Injection

Content
View full analysis

Vulnerability Details

File Location: scripts/wakapi-daily-summary.mjs, lines 61–65, 242–263, and 269–271
Vulnerability Type: CSV formula injection
Risk Level: Medium

Project and language names obtained from the Wakapi API are written into CSV output. The escaping routine protects CSV structure but does not prevent spreadsheet applications from interpreting attacker-controlled cell values as formulas.

js
function csvEscape(value) {
  if (value === null || value === undefined) return '';
  const s = String(value);
  if (/[",\n\r]/.test(s)) return `"${s.replaceAll('"', '""')}"`;
  return s;
}
js
const projectRows = projectsTop.map((p, i) => ({
  date,
  rank: String(i + 1),
  project: p.name ?? '',
  seconds: String(p.seconds ?? 0),
  hours: String(toHours(p.seconds ?? 0)),
  percent: p.percent != null ? String(p.percent) : '',
}));

const languagesHeader = ['date','rank','language','seconds','hours','percent'];
const languageRows = languagesTop.map((l, i) => ({
  date,
  rank: String(i + 1),
  language: l.name ?? '',
  seconds: String(l.seconds ?? 0),
  hours: String(toHours(l.seconds ?? 0)),
  percent: l.percent != null ? String(l.percent) : '',
}));
js
await upsertCsvByKeys(totalFile, totalHeader, ['date'], totalRows);
await upsertCsvByKeys(projectsFile, projectsHeader, ['date','rank'], projectRows);
await upsertCsvByKeys(languagesFile, languagesHeader, ['date','rank'], languageRows);

Technical Analysis

RFC-style CSV quoting and spreadsheet formula neutralization are separate security controls. Quoting a field containing commas or quotation marks does not stop spreadsheet software from evaluating a value beginning with formula-significant characters such as =, +, -, or @. Tabs and carriage returns can also be relevant because some spreadsheet import paths ignore leading control characters before identifying a formul ...[truncated 1506 chars]

Remediation
View remediation

Remediation Suggestions

  1. Distinguish trusted numeric fields from untrusted textual fields.
  2. Before ordinary CSV escaping, neutralize untrusted text whose first significant character is =, +, -, or @.
  3. Account for leading tabs, carriage returns, newlines, and other whitespace that spreadsheet importers may ignore.
  4. Prefix dangerous textual values with an apostrophe or use another mitigation documented for all supported spreadsheet applications.
  5. Apply the protection to both newly fetched values and existing rows before rewriting CSV files.
  6. Add tests for formula-leading values, quoted values, commas, quotation marks, tabs, carriage returns, and newline edge cases.
  7. Document that the CSV files contain externally sourced labels and should not be opened with formula execution enabled unless neutralization is guaranteed.

Example hardening:

js
function spreadsheetSafeText(value) {
  if (value === null || value === undefined) return '';
  const s = String(value);
  return /^[\t\r\n ]*[=+\-@]/.test(s) ? `'${s}` : s;
}

const projectRows = projectsTop.map((p, i) => ({
  date,
  rank: String(i + 1),
  project: spreadsheetSafeText(p.name ?? ''),
  seconds: String(p.seconds ?? 0),
  hours: String(toHours(p.seconds ?? 0)),
  percent: p.percent != null ? String(p.percent) : '',
}));
Vulnerability Patterns
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (1)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill requires environment variables and makes outbound network requests, but it does not declare an explicit tool scope such as permissions or allowed-tools. That creates an authorization gap where the runtime or reviewer cannot clearly constrain or audit what external access the skill expects, increasing the risk of unintended secret exposure or unauthorized network use.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/wakapi-daily-summary.mjs:21