Back to skill

Security audit

Personal Data Hub

Security checks for vulnerabilities and agentic risk

Overview

This skill has a coherent personal-data gateway purpose, but it automatically handles durable credentials and setup in ways users should review carefully before installing.

Review this before installing. It is meant to access personal data through PersonalDataHub, so some credential and network use is expected, but installation currently bootstraps services automatically, stores/reads a durable local API key, may log a full newly created key, and accepts hub URLs without clear trust boundaries. Prefer installing only in a controlled environment, with a trusted pinned PersonalDataHub CLI, validated localhost or HTTPS hub URL, and rotated keys if any logs may have captured credentials.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T09 · Insecure Skill Coding Practices

Error
Location
src/index.ts:108
Finding
Bearer API Key Is Written to Application Logs<![CDATA[ ## Vulnerability Details **File Location**: `src/index.ts:108-112` **Vulnerability Type**: Plaintext credential disclosure through logging **Risk Level**: High ### Vulnerable Code ```ts const keyResult = await createApiKey(hubUrl, 'OpenClaw Agent'); apiKey = keyResult.key; api.logger.info( `PersonalDataHub: Auto-created API key. Save this for your config: ${apiKey}`, ); ``` ### Technical Analysis During automatic setup, the plugin creates a bearer API key and interpolates the complete credential into an informational log message. Bearer credentials grant access based solely on possession, so they must not be exposed through logs. Application logs may be retained on disk, collected by centralized logging services, included in diagnostics, or made available to other operators and plugins. Marking the API key as sensitive in the plugin UI does not protect copies written to the logger. ### Attack Path 1. The plugin starts without a complete API key configuration. 2. It discovers or receives a reachable PersonalDataHub URL. 3. The plugin calls `/api/keys` and obtains a new bearer key. 4. The complete key is written to application logs. 5. An actor with access to local, diagnostic, or centralized logs extracts the key. 6. The actor submits authenticated requests to the configured PersonalDataHub using the exposed key. ### Impact Assessment An attacker who obtains the logged key can exercise all permissions assigned to that key. Depending on the PersonalDataHub policy, this can include retrieving authorized personal information and staging outbound actions. The exposure persists for as long as the log remains available or until the key is revoked. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Never log the plaintext API key. - Log only a non-sensitive key identifier or a short, non-reversible fingerprint. - Store newly generated credentials through the host platform's secret-management interface. - If file storage is required, use a dedicated file with owner-only permissions and atomic creation. - Add logger assertions to tests to ensure generated keys never appear in log arguments. - Revoke and replace any key that may already have been written to retained logs. - Apply redaction filters for fields and strings matching API-key formats as defense in depth. A safe replacement would be: ```ts const keyResult = await createApiKey(hubUrl, 'OpenClaw Agent'); apiKey = keyResult.key; api.logger.info( `PersonalDataHub: Auto-created API key with ID ${keyResult.id}`, ); ``` ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/hub-client.ts:48
Finding
Unvalidated Hub URL Can Receive Bearer Credentials and Sensitive Action Data<![CDATA[ ## Vulnerability Details **File Location**: `src/hub-client.ts:48-85` **Related Locations**: `src/index.ts:72-86`, `openclaw.plugin.json:9-18` **Vulnerability Type**: Untrusted destination and plaintext transmission of sensitive information **Risk Level**: High ### Vulnerable Code Configuration values are accepted without URL or origin validation: ```ts if (!config?.hubUrl || !config?.apiKey) { const envHubUrl = process.env.PDH_HUB_URL; const envApiKey = process.env.PDH_API_KEY; if (envHubUrl && envApiKey) { config = { hubUrl: envHubUrl, apiKey: envApiKey }; api.logger.info(`PersonalDataHub: Configured from environment variables (hub: ${envHubUrl})`); } } if (!config?.hubUrl || !config?.apiKey) { const creds = readCredentials(); if (creds) { config = { hubUrl: creds.hubUrl, apiKey: creds.apiKey }; api.logger.info(`PersonalDataHub: Configured from credentials file (hub: ${creds.hubUrl})`); } } ``` The resulting URL directly controls where authenticated requests are sent: ```ts constructor(config: HubClientConfig) { this.hubUrl = config.hubUrl.replace(/\/+$/, ''); this.apiKey = config.apiKey; } async pull(params: PullParams): Promise<PullResult> { const res = await fetch(`${this.hubUrl}/app/v1/pull`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, }, body: JSON.stringify(params), }); if (!res.ok) { const text = await res.text(); throw new HubApiError('pull', res.status, text); } return res.json() as Promise<PullResult>; } async propose(params: ProposeParams): Promise<ProposeResult> { const res = await fetch(`${this.hubUrl}/app/v1/propose`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, }, body: JSON.stringify(params), }); if (!res.ok) { const text = await res.text(); throw new HubApiError('propose', res ...[truncated 2058 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Parse `hubUrl` with the standard `URL` class before storing or using it. - Permit `https:` by default. - Permit `http:` only when the resolved destination is a verified loopback address. - Reject unsupported schemes, embedded usernames/passwords, fragments, malformed ports, and unexpected path prefixes. - Require explicit user confirmation or an allowlist for non-default origins. - Bind each credential to the expected hub origin and refuse to send it elsewhere. - Disable automatic redirects for authenticated requests or validate every redirect destination before following it. - Apply the same validation to plugin configuration, environment variables, and credential-file values. - Add tests covering hostile external URLs, non-HTTP schemes, embedded credentials, and redirects. Example validation: ```ts function validateHubUrl(value: string): URL { const url = new URL(value); const loopback = url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '::1'; if (url.protocol !== 'https:' && !(url.protocol === 'http:' && loopback)) { throw new Error('PersonalDataHub must use HTTPS unless it is on loopback'); } if (url.username || url.password || url.hash) { throw new Error('PersonalDataHub URL contains unsupported components'); } return url; } ``` ]]>

T08 · Insecure Dependencies

Error
Location
SKILL.md:9
Finding
Install Hook Executes Unpinned Registry Tool and Broad Parent-Workspace Scripts<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:9` **Vulnerability Type**: Unsafe dependency resolution and supply-chain code execution **Risk Level**: High ### Vulnerable Code ```yaml install: cd ../../ && pnpm install && pnpm build && npx pdh init "OpenClaw Agent" && npx pdh start ``` The project manifest does not declare `pdh` as a dependency: ```json { "devDependencies": { "typescript": "^5.8.3", "vitest": "^4.0.18", "@sinclair/typebox": "^0.34.33" } } ``` ### Technical Analysis The install hook changes directory two levels above the Skill before running `pnpm install` and `pnpm build`. This broadens installation from the audited Skill to an external parent workspace whose dependency graph and lifecycle scripts are not defined by this package. It then invokes the unversioned command `npx pdh`. Because `pdh` is not declared in this package, `npx` may resolve or download a package from the configured registry. The effective executable is therefore not pinned by this Skill's manifest or lockfile. Package installation and lifecycle scripts execute with the privileges of the user installing the Skill. The setup and start operations are related to the declared functionality, but unpinned remote package execution and parent-workspace installation are not the minimum privileges necessary to connect to an existing hub. ### Attack Path 1. A user installs the Skill, causing the metadata install hook to run. 2. `cd ../../` moves execution into a broader parent workspace. 3. `pnpm install` resolves dependencies and may execute lifecycle scripts from that workspace. 4. `pnpm build` runs the parent workspace's build logic. 5. `npx pdh` resolves an executable not pinned in this package. 6. If the registry package, registry configuration, parent workspace, or dependency chain is compromised or substituted, attacker-controlled code executes with the installing user's privileges. 7. That code can access files, environment variables, ...[truncated 481 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not leave the Skill directory during installation. - Declare the official PersonalDataHub CLI as an explicit dependency at an exact reviewed version. - Commit and enforce a lockfile with integrity metadata. - Invoke a verified local binary, such as `pnpm exec pdh` or `npx --no-install pdh`, instead of allowing an implicit registry download. - Separate hub installation from Skill installation where possible; require the owner to install and start the hub through a documented, explicit process. - Avoid running lifecycle scripts unless required and reviewed. - If installation must download artifacts, verify their publisher, cryptographic integrity, and expected package identity. - Ensure setup does not automatically start long-running background processes without clear user consent. - Review the parent workspace independently if parent-level installation remains necessary. A safer pattern would resemble: ```yaml install: pnpm install --frozen-lockfile && pnpm build ``` Hub initialization should then use a locally declared, pinned CLI through a separate user-confirmed setup step. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/setup.test.ts:133
Finding
Test Suite Reads the User's Real PersonalDataHub Credential File<![CDATA[ ## Vulnerability Details **File Location**: `src/setup.test.ts:133-137` **Vulnerability Type**: Unnecessary secret access and failure of test isolation **Risk Level**: Medium ### Vulnerable Code ```ts describe('readCredentials', () => { it('returns credentials from file or null without throwing', () => { // This test just verifies readCredentials doesn't throw const creds = readCredentials(); expect(creds === null || (typeof creds === 'object' && !!creds.hubUrl && !!creds.apiKey)).toBe(true); }); it('exports CREDENTIALS_PATH', () => { expect(CREDENTIALS_PATH).toContain('.pdh'); expect(CREDENTIALS_PATH).toContain('credentials.json'); }); }); ``` The tested implementation accesses the real home-directory path: ```ts export const CREDENTIALS_PATH = join(homedir(), '.pdh', 'credentials.json'); export function readCredentials(): Credentials | null { try { if (!existsSync(CREDENTIALS_PATH)) return null; const raw = readFileSync(CREDENTIALS_PATH, 'utf-8'); const parsed = JSON.parse(raw); if (parsed.hubUrl && parsed.apiKey) return parsed as Credentials; return null; } catch { return null; } } ``` ### Technical Analysis The test directly invokes `readCredentials()` without mocking the filesystem, overriding the home directory, or injecting a temporary credential path. On a developer or CI host with PersonalDataHub configured, the real bearer API key is read and parsed inside the test process. The assertion does not print the key, and network calls in the setup test are mocked. Therefore, the code does not by itself confirm network exfiltration. Nevertheless, reading a production credential is unnecessary for verifying parser behavior and exposes it to the test runtime, diagnostic tooling, instrumentation, and all dependencies loaded into that process. ### Attack Path 1. A developer or CI worker has a real `~/.pdh/credentials.json`. 2. The test suite runs under that user's home directory. 3. ...[truncated 591 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Never access production home-directory credentials from unit tests. - Refactor `readCredentials` to accept an optional path or inject a filesystem abstraction. - Create synthetic credentials in a temporary directory for the test. - Alternatively, mock `homedir`, `existsSync`, and `readFileSync`. - Ensure tests restore environment variables and filesystem mocks after each case. - Add separate cases for missing files, malformed JSON, missing fields, and valid synthetic credentials. - Run tests with an isolated temporary home directory in CI. Example design: ```ts export function readCredentials( path = CREDENTIALS_PATH, ): Credentials | null { try { if (!existsSync(path)) return null; const parsed = JSON.parse(readFileSync(path, 'utf-8')); if ( typeof parsed.hubUrl === 'string' && typeof parsed.apiKey === 'string' ) { return parsed; } return null; } catch { return null; } } ``` The test can then pass a temporary file containing a non-sensitive key such as `pk_test_only`. ]]>
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 (41)

Tp4

High
Category
MCP Tool Poisoning
Confidence
83% confidence
Finding
This skill instructs reading a local credential file and performing local service checks/startup, while presenting itself primarily as a filtered gateway for personal data. In a personal-data context, undisclosed credential access and provisioning behavior materially increase risk because the agent may obtain reusable secrets and operational control over the hub.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
This skill instructs reading a local credential file and performing local service checks/startup, while presenting itself primarily as a filtered gateway for personal data. In a personal-data context, undisclosed credential access and provisioning behavior materially increase risk because the agent may obtain reusable secrets and operational control over the hub.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
This skill instructs reading a local credential file and performing local service checks/startup, while presenting itself primarily as a filtered gateway for personal data. In a personal-data context, undisclosed credential access and provisioning behavior materially increase risk because the agent may obtain reusable secrets and operational control over the hub.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
This skill instructs reading a local credential file and performing local service checks/startup, while presenting itself primarily as a filtered gateway for personal data. In a personal-data context, undisclosed credential access and provisioning behavior materially increase risk because the agent may obtain reusable secrets and operational control over the hub.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This skill instructs reading a local credential file and performing local service checks/startup, while presenting itself primarily as a filtered gateway for personal data. In a personal-data context, undisclosed credential access and provisioning behavior materially increase risk because the agent may obtain reusable secrets and operational control over the hub.

Credential Access

High
Category
Privilege Escalation
Content
If the tools above are not available, you can call the PersonalDataHub API directly via HTTP.

**Credentials:** Read `~/.pdh/credentials.json` to get `hubUrl` and `apiKey`.

**Pull data:**
```bash
Confidence
96% confidence
Finding
Reading `~/.pdh/credentials.json` exposes a reusable API key and service location to the agent. In a personal-data skill, that credential can enable broad access to email, GitHub-connected data, or staged outbound actions, making compromise or misuse particularly damaging.

Credential Access

High
Category
Privilege Escalation
Content
If the server is not running, find and start it:
```bash
# Check where PersonalDataHub is installed
cat ~/.pdh/credentials.json   # look at hubDir
# Start the server
cd <hubDir> && node dist/index.js
```
Confidence
90% confidence
Finding
The troubleshooting flow instructs reading the credentials file to discover `hubDir`, exposing internal paths and potentially the same credential material during a support operation. This expands secret exposure into a path that users or agents may treat as low risk, increasing the chance of accidental leakage.

Credential Access

High
Category
Privilege Escalation
Content
The install hook bootstraps PersonalDataHub automatically:
1. Installs dependencies, builds the project, initializes the database
2. Creates an API key and saves it to `~/.pdh/credentials.json`
3. Starts the server in the background

After installation, open `http://localhost:3000` to connect Gmail/GitHub via OAuth.
Confidence
94% confidence
Finding
The install flow creates an API key and saves it to a predictable plaintext location in the user's home directory. Predictable, file-based secret storage raises the risk of unintended access by other tools, agents, or local malware, especially when the key grants access to sensitive personal-data workflows.

Credential Access

High
Category
Privilege Escalation
Content
After installation, open `http://localhost:3000` to connect Gmail/GitHub via OAuth.

Agents read credentials automatically from `~/.pdh/credentials.json` — no manual configuration needed.

## Query Syntax (Gmail)
Confidence
97% confidence
Finding
The statement that agents read credentials automatically from `~/.pdh/credentials.json` normalizes silent secret access by the skill. In context, this is more dangerous because the skill handles personal communications and outbound actions, so the credential can be used to access or stage highly sensitive operations without fresh user awareness.

Credential Access

High
Category
Privilege Escalation
Content
import { describe, it, expect, vi, afterEach } from 'vitest';

// Mock readCredentials so tests don't depend on real ~/.pdh/credentials.json
vi.mock('./setup.js', async (importOriginal) => {
  const actual = await importOriginal() as Record<string, unknown>;
  return {
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
import { describe, it, expect, vi, afterEach } from 'vitest';

// Mock readCredentials so tests don't depend on real ~/.pdh/credentials.json
vi.mock('./setup.js', async (importOriginal) => {
  const actual = await importOriginal() as Record<string, unknown>;
  return {
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
import { describe, it, expect, vi, afterEach } from 'vitest';

// Mock readCredentials so tests don't depend on real ~/.pdh/credentials.json
vi.mock('./setup.js', async (importOriginal) => {
  const actual = await importOriginal() as Record<string, unknown>;
  return {
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
}
    }

    // Step 3: Check credentials file (~/.pdh/credentials.json)
    if (!config?.hubUrl || !config?.apiKey) {
      const creds = readCredentials();
      if (creds) {
Confidence
90% confidence
Finding
The skill reads a local credentials file to obtain authentication material, which is a credential-access capability beyond simple data retrieval. In this skill's context, that is more dangerous because the plugin handles personal data and can silently bootstrap privileged access using local secrets the operator may not expect it to consume.

Missing User Warnings

High
Confidence
99% confidence
Finding
The code logs a newly created API key in plaintext, which can expose the credential to log files, consoles, telemetry pipelines, and other users on the host. Anyone who obtains those logs may be able to authenticate to the PersonalDataHub and access or manipulate personal data.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
The skill looks for persistent credentials in the user's home directory and is documented to participate in self-enrollment, neither of which is justified by the stated role of proposing actions through a preexisting gateway. In skill contexts handling personal data, automatic credential discovery is especially risky because it enables silent access to sensitive systems without explicit user approval at runtime.

Credential Access

High
Category
Privilege Escalation
Content
}

/** Path to the credentials file written by `npx pdh init`. */
export const CREDENTIALS_PATH = join(homedir(), '.pdh', 'credentials.json');

/**
 * Read credentials from ~/.pdh/credentials.json.
Confidence
92% confidence
Finding
This code targets a known credential file location in the user's home directory, enabling the skill to obtain an API key for the PersonalDataHub without separate authorization in this flow. Because the hub governs personal emails and issues, access to this secret can expose sensitive data and enable downstream actions under the user's authority.

Credential Access

High
Category
Privilege Escalation
Content
export const CREDENTIALS_PATH = join(homedir(), '.pdh', 'credentials.json');

/**
 * Read credentials from ~/.pdh/credentials.json.
 * Returns null if the file doesn't exist or is malformed.
 */
export function readCredentials(): Credentials | null {
Confidence
92% confidence
Finding
The `readCredentials` function operationalizes credential access by loading and parsing the persistent credentials file, making the secret directly consumable by the skill. In this context, that materially increases the skill's ability to access personal-data infrastructure outside of a tightly controlled, user-mediated flow.

Description-Behavior Mismatch

High
Confidence
96% confidence
Finding
The file implements autonomous discovery of a local PersonalDataHub and creates a fresh API key for the skill, which materially expands the skill's privileges beyond merely consuming gateway-filtered data. In the context of a skill whose stated purpose is data access through an existing access-control gateway, self-enrollment and credential minting are dangerous because they bypass expected provisioning controls and can silently grant the skill durable access.

Lp3

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding
The skill declares no explicit tool scope while its documented behavior includes network access and reading local files for credentials. That increases the chance an agent is granted broader capabilities than users expect, especially in a skill handling personal data and API keys.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
Using `npx pdh` without pinning a version allows execution of whatever package version resolves at install time. This creates a supply-chain risk where a malicious or compromised package release could execute arbitrary code during installation or startup.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The direct HTTP fallback transmits authorization credentials and personal-data requests, but the documentation provides no explicit privacy or transport-security guidance. In a skill centered on sensitive personal data, omitting such warnings can lead to insecure deployment, accidental logging, or use of non-TLS endpoints.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill explicitly instructs agents to read `~/.pdh/credentials.json` to obtain `hubUrl` and `apiKey` without a strong warning or consent gate. Access to local credential files gives the agent reusable secrets that could be abused for unauthorized data access or service actions beyond the immediate user request.

External Transmission

Medium
Category
Data Exfiltration
Content
**Pull data:**
```bash
curl -X POST <hubUrl>/app/v1/pull \
  -H "Authorization: Bearer <apiKey>" \
  -H "Content-Type: application/json" \
  -d '{"source": "gmail", "purpose": "reason for pulling data"}'
Confidence
74% confidence
Finding
The skill is designed to send data over HTTP to a hub service, so external transmission is expected, but it still represents a real security concern because personal data and bearer tokens are being sent across a network boundary. The context makes this behavior necessary, not harmless; its safety depends on strict transport security and endpoint trust.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.