Back to skill

Security audit

Personal Client Management System & Finance System

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its stated purpose, but it handles a powerful account API key in ways that could expose it if misconfigured or run in a shared environment.

Install only if you trust the OurProject service and understand the API key's scope. Use the default HTTPS API URL, avoid custom or HTTP endpoints, keep the skill directory out of shared repos and backups, restrict .config.json permissions, and rotate the API key if it may have appeared in logs or been stored insecurely.

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/setup.js:18
Finding
Bearer API Credentials Can Be Transmitted over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.js:18-32`, `scripts/setup.js:75-98`, `scripts/api.js:23-44` **Vulnerability Type**: Cleartext transmission of bearer credentials **Risk Level**: High ### Vulnerable Code ```js function testApiKey(apiUrl, apiKey) { return new Promise((resolve, reject) => { const url = new URL(apiUrl + '/integrations/me'); const isHttps = url.protocol === 'https:'; const lib = isHttps ? https : http; const options = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 80), path: url.pathname, method: 'GET', headers: { 'Authorization': `Bearer ${apiKey}`, 'User-Agent': 'OurProject-OpenClaw-Skill/1.0' }, }; ``` ```js // API URL const apiUrl = await ask(`API URL [${DEFAULT_API_URL}]: `); const finalApiUrl = apiUrl.trim() || (existingConfig?.apiBaseUrl || DEFAULT_API_URL); // API Key const apiKey = await ask('API Key (starts with op_): '); if (!apiKey.trim()) { console.error('❌ API key is required. Get one from Integrations → API Keys.'); rl.close(); process.exit(1); } if (!apiKey.trim().startsWith('op_')) { console.error('❌ Invalid API key format. Must start with "op_"'); rl.close(); process.exit(1); } // Test connection console.log('\n🔍 Testing connection...'); try { const result = await testApiKey(finalApiUrl, apiKey.trim()); ``` ```js function makeRequest(method, endpoint, body = null) { const config = loadConfig(); return new Promise((resolve, reject) => { const baseUrl = config.apiBaseUrl || 'https://api.ourproject.app/api'; // Normalize: if endpoint already has /api/ prefix, strip it since baseUrl already includes /api const normalizedEndpoint = endpoint.startsWith('/api/') ? endpoint.slice(4) : endpoint; const fullEndpoint = normalizedEndpoint.startsWith('/') ? normalizedEndpoi ...[truncated 2716 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject all API URLs whose protocol is not exactly `https:` before any request is made. 2. Prefer a fixed, trusted production origin such as `https://api.ourproject.app`. 3. If custom hosts are a legitimate requirement, require explicit confirmation and maintain an allowlist of approved HTTPS origins. 4. Normalize and validate the URL with the `URL` API before combining it with endpoint paths. 5. Ensure authorization headers are never forwarded across cross-origin redirects. 6. Apply normal TLS certificate and hostname verification without disabling Node.js certificate checks. 7. Document that development HTTP endpoints must never be used with production credentials. 8. Revoke and rotate any API key that may previously have been transmitted over HTTP. Example validation: ```js function validateApiUrl(value) { const url = new URL(value); if (url.protocol !== 'https:') { throw new Error('The API URL must use HTTPS.'); } return url.toString().replace(/\/+$/, ''); } ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup.js:97
Finding
API Key and Personal Information Are Stored without Explicit Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.js:97-116` **Vulnerability Type**: Insecure local storage of sensitive credentials **Risk Level**: Medium ### Vulnerable Code ```js try { const result = await testApiKey(finalApiUrl, apiKey.trim()); const user = result.user; console.log(`\n✅ Connection successful!`); console.log(` User: ${user.name}`); console.log(` Email: ${user.email}`); console.log(` Role: ${user.role}`); // Save config const config = { apiBaseUrl: finalApiUrl, apiKey: apiKey.trim(), userName: user.name, userEmail: user.email, configuredAt: new Date().toISOString() }; fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); console.log(`\n💾 Config saved to .config.json`); ``` ### Technical Analysis The setup script stores the complete bearer API key, user name, and email address in the project-root `.config.json` file. The call to `fs.writeFileSync()` does not specify a restrictive mode. For a newly created file, effective permissions depend on the operating system and process umask. Under a permissive configuration, other local users or processes may be able to read the file. If the file already exists with overly broad permissions, rewriting it does not automatically correct those permissions. Storing the file in the project directory also increases the chance of accidental inclusion in backups, archives, support bundles, container images, or source-control commits. The reviewed project does not include evidence of a `.gitignore` rule protecting `.config.json`. ### Attack Path 1. The user runs `node scripts/setup.js` in an environment with permissive default file permissions, or `.config.json` already exists with broad permissions. 2. The script writes the complete API key and personal data to `.config.json`. 3. Another local account, compromised process, shared build worker, backup collector, or artifact-packaging ...[truncated 1017 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an operating-system credential manager, secret service, or dedicated secret provider instead of a plaintext project file. 2. If file storage is unavoidable, create the file with owner-only permissions: ```js fs.writeFileSync( CONFIG_FILE, JSON.stringify(config, null, 2), { encoding: 'utf8', mode: 0o600 } ); fs.chmodSync(CONFIG_FILE, 0o600); ``` 3. Verify and repair permissions when an existing configuration file is loaded or replaced. 4. Use an atomic write procedure: create a temporary file with mode `0o600`, write and synchronize it, and then rename it into place. 5. Avoid storing `userName` and `userEmail` unless they are required for functionality. 6. Add `.config.json` to `.gitignore` and equivalent package, backup, and artifact exclusion rules. 7. Display a warning if the project directory is shared or if restrictive permissions cannot be applied. 8. Provide a credential revocation and rotation procedure for users who accidentally expose the file. 9. Consider supporting an environment variable or secret-manager reference while warning users not to expose credentials through command-line arguments. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/test.js:11
Finding
Connection Test Exposes a Substantial API-Key Prefix in Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/test.js:11-15` **Vulnerability Type**: Partial secret disclosure through terminal output **Risk Level**: Low ### Vulnerable Code ```js // Check config exists const config = loadConfig(); console.log(` URL: ${config.apiBaseUrl}`); console.log(` Key: ${config.apiKey.slice(0, 11)}...`); console.log(''); ``` ### Technical Analysis The connection test prints the first 11 characters of the API key. Although this is not the complete secret, it exposes more key material than is necessary to confirm that a credential is configured. Terminal output may be retained by CI systems, Agent execution logs, shell-session recordings, support transcripts, monitoring products, or screenshots. Prefix disclosure reduces the unknown search space and provides an identifier that can be correlated with other leaks. Because the setup documentation states that keys start with `op_`, part of the displayed value is predictable, but the output still reveals additional secret characters. ### Attack Path 1. A user, Agent, CI job, or support operator runs `node scripts/test.js`. 2. The script prints the first 11 characters of the configured API key. 3. The output is retained in a build log, transcript, screenshot, telemetry record, or shared support message. 4. An unauthorized party obtains the logged prefix. 5. The party correlates it with another partial disclosure, a weak key-generation issue, or leaked metadata to identify or reconstruct the credential more efficiently. 6. If the complete key is recovered through combined information, it can be reused within its assigned API scopes. ### Impact Assessment This issue does not independently reveal the complete token under normal conditions. Its primary impact is unnecessary loss of credential confidentiality and reduced key entropy in environments where logs are accessible to a broader audience than the secret itself. If combined with another disclosure or weak token ...[truncated 214 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not print any portion of the API key by default. 2. Replace the output with a non-sensitive status message: ```js console.log(` URL: ${config.apiBaseUrl}`); console.log(' Key: configured'); ``` 3. If credential identification is operationally necessary, show no more than the final four characters rather than a prefix. 4. Ensure CI and Agent logs redact values matching the API-key format. 5. Avoid including credential fragments in errors, debug messages, telemetry, or support diagnostics. 6. Rotate the key if its prefix has been published alongside other potentially identifying secret material. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (17)

Ae1

High
Category
analysis-evasion
Content
| `node scripts/projects.js` | List all projects |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/projects.js` | List all projects |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/deadlines.js [days]` | Tasks with deadlines (default: 3 days) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/deadlines.js [days]` | Tasks with deadlines (default: 3 days) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/bills.js [days]` | Upcoming bills (default: 7 days) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/bills.js [days]` | Upcoming bills (default: 7 days) |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/finance.js` | Finance overview |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
| `node scripts/finance.js` | Finance overview |
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Transmission

Medium
Category
Data Exfiltration
Content
const https = require('https');

const CONFIG_FILE = path.join(__dirname, '..', '.config.json');
const DEFAULT_API_URL = 'https://api.ourproject.app/api';

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
const https = require('https');

const CONFIG_FILE = path.join(__dirname, '..', '.config.json');
const DEFAULT_API_URL = 'https://api.ourproject.app/api';

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The script prints a portion of the API key to console output (`config.apiKey.slice(0, 11)`), which leaks sensitive credential material into terminal history, CI logs, screen recordings, or shared debugging output. Even partial secret disclosure is unsafe because it aids secret identification, correlation across systems, and may expose enough of the token format or prefix to assist further compromise.

Vague Triggers

Low
Confidence
89% confidence
Finding
The markdown says users can 'ask your OpenClaw agent things like' followed by broad natural-language examples such as 'Give me a daily summary' and 'Any unread notifications?'. It does not define clear trigger boundaries, exclusions, or negative examples, so these phrases could overlap with ordinary conversation and lead to unintended invocation.

Vague Triggers

Low
Confidence
91% confidence
Finding
The section maps generic user utterances like 'Any notifications?' and 'Give me today's summary' to skill actions, but does not specify that these only apply in the context of ourproject.app data. Because the trigger wording is not domain-specific and lacks disambiguation, it risks accidental activation during unrelated conversations.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
The function formats currency using `toLocaleString('id-ID')`, which forces a specific locale in user-facing output. The file does not offer any user opt-in or explain that this skill is intentionally region-specific, so it appears to violate the language/locale policy for natural-language behavior.

Missing User Warnings

Low
Confidence
84% confidence
Finding
This code makes HTTP requests to notification endpoints, which necessarily transmit authentication context and retrieve user-associated data. Although the file has a brief header comment and prints results, it does not disclose that it will contact a remote service or access notification data, which is the kind of behavior SQP-2 asks to warn about for code files.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The code formats dates with toLocaleString('en-US'), which hard-codes a specific language/locale choice. Under SQP-3, forcing a locale without user choice or documented justification is a natural-language policy concern.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The summary date is always formatted with the 'en-US' locale via toLocaleDateString, which forces English-language output regardless of user preference. This is a natural-language policy concern because the file does not offer a locale choice or explain why English is required.