Back to skill

Security audit

honor

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches a Picqer dashboard purpose, but its Picqer tenant setting can redirect the API key to an unintended host, so it should be reviewed before installation.

Review before installing. Use only a tightly scoped Picqer API key, validate or hardcode the exact Picqer tenant hostname before use, and do not run the included cron.ts auto-refresh file unless you intentionally want recurring authenticated syncs. Pin dependencies and add a lockfile for a more controlled install.

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

T09 · Insecure Skill Coding Practices

Error
Location
tools/picqer-api.ts:5
Finding
Unvalidated Picqer Subdomain Can Redirect API Credentials to an Attacker-Controlled Host## Vulnerability Details **File Location**: `tools/picqer-api.ts`, lines 5–20 **Vulnerability Type**: Credential disclosure through unvalidated URL construction **Risk Level**: High ### Vulnerable Code ```ts const { subdomain, apiKey } = getPicqerConfig(); const url = new URL(`https://${subdomain}.picqer.com/api/v1${path}`); if (searchParams) { Object.entries(searchParams).forEach(([k, v]) => { if (v) url.searchParams.set(k, v); }); } const res = await fetch(url.toString(), { headers: { 'Authorization': `Basic ${Buffer.from(`${apiKey}:`).toString('base64')}`, 'User-Agent': 'FutureFulfillment-Dashboard (internal)' } }); if (!res.ok) { ``` The environment-controlled value originates in `env.ts`, lines 4–10: ```ts const subdomain = process.env.PICQER_SUBDOMAIN; const apiKey = process.env.PICQER_API_KEY; if (!subdomain || !apiKey) { throw new Error('Picqer API not configured. Set PICQER_SUBDOMAIN and PICQER_API_KEY in .env'); } return { subdomain, apiKey }; ``` ### Technical Analysis `PICQER_SUBDOMAIN` is interpolated directly into an absolute URL without validating that it is a single DNS label. URL delimiters such as `/` can therefore change which portion of the generated string is interpreted as the hostname. For example, setting the subdomain to `attacker.example/x` produces: ```text https://attacker.example/x.picqer.com/api/v1/picklists ``` The effective hostname is `attacker.example`, not a host beneath `picqer.com`. The application subsequently attaches the Picqer API key as a Basic Authorization header and sends the request to that effective hostname. Base64 encoding does not protect the credential; it is only an encoding of the API key followed by a colon. TLS protects the request in transit but intentionally delivers it to the attacker-controlled HTTPS endpoint selected by the manipulated URL. ### Attack Path 1. An attacker gains the ...[truncated 1616 chars]
Remediation
## Remediation Suggestions 1. Validate `PICQER_SUBDOMAIN` as exactly one DNS label before using it: ```ts const SUBDOMAIN_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i; if (!SUBDOMAIN_PATTERN.test(subdomain)) { throw new Error('Invalid Picqer subdomain'); } ``` 2. Construct and verify the expected hostname separately: ```ts const hostname = `${subdomain}.picqer.com`; const url = new URL(`/api/v1${path}`, `https://${hostname}`); if ( url.protocol !== 'https:' || url.hostname !== hostname || url.port !== '' ) { throw new Error('Invalid Picqer API URL'); } ``` 3. Prefer an allowlist containing the exact permitted Picqer tenant hostname when the deployment uses a single known tenant. 4. Validate `path` as an internal API path and reject absolute URLs, backslashes, control characters, and unexpected traversal sequences before URL construction. 5. Keep outbound network controls in place so the process can connect only to the approved Picqer hostname where practical. 6. If malicious configuration may already have been used, revoke and rotate `PICQER_API_KEY`, inspect outbound request logs for unexpected hosts, and review Picqer audit logs for unauthorized API activity.
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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (7)

Credential Access

High
Category
Privilege Escalation
Content
## Security

- API key only in local .env file
- No credentials in OpenClaw config
- Access via Tailscale only
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const apiKey = process.env.PICQER_API_KEY;
  
  if (!subdomain || !apiKey) {
    throw new Error('Picqer API not configured. Set PICQER_SUBDOMAIN and PICQER_API_KEY in .env');
  }
  
  return { subdomain, apiKey };
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The code reads an API key from configuration and sends authenticated HTTP requests to an external Picqer endpoint. Within this file there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring disclosing that user or system data may be transmitted to a third-party service.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"test": "node --experimental-specifier-resolution=node dist/test.js"
  },
  "dependencies": {
    "@openclaw/sdk": "^1.0.0",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
Confidence
92% confidence
Finding
The dependency uses a caret range, which permits automatic installation of newer minor and patch versions. This increases supply-chain risk because a compromised or breaking upstream release could be pulled in without explicit review, especially significant here because @openclaw/sdk appears central to the skill's runtime behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "@openclaw/sdk": "^1.0.0",
    "dotenv": "^16.3.1"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
Confidence
90% confidence
Finding
The dotenv dependency is specified with a caret range, allowing unreviewed minor/patch updates during installation. While dotenv is common and lower risk than a privileged SDK, this still creates a supply-chain exposure if an upstream release is malicious or vulnerable.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"dotenv": "^16.3.1"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "devDependencies": {
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

No suspicious patterns detected.