Back to skill

Security audit

Autonomous Procurement Agent

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its procurement purpose, but it combines autonomous purchasing decisions with weak license/auth controls and a network-facing webhook that need careful review before installation.

Install only in a controlled environment. Do not expose the webhook or /license endpoint publicly, do not set PROCU_ALLOWED_TIER outside local testing, and disable or wrap auto-approval behind your own verified requester, vendor-risk, spend-limit, and human-approval controls.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
auth-middleware.js:74
Finding
Spoofable User Identity and Fail-Open License Authorization<![CDATA[ ## Vulnerability Details **File Location**: `auth-middleware.js:74-89`, `auth-middleware.js:175-205`, and `auth-middleware.js:224-233` **Vulnerability Type**: Authentication bypass and fail-open authorization **Risk Level**: High ### Vulnerable Code ```javascript async function getUserLicense(email) { if (!email) return { tier: 'FREE', features: {} }; // 1. Cache hit const cached = getCachedLicense(email); if (cached) return cached; // 2. Webhook lookup try { const license = await httpGet(`${PROCU_WEBHOOK_URL}/license?email=${encodeURIComponent(email)}`); setCachedLicense(email, license); return license; } catch (err) { // 3. Webhook unreachable → dev/fallback mode using env var console.warn(`[Auth] Webhook unreachable (${err.message}) — using PROCU_ALLOWED_TIER=${PROCU_ALLOWED_TIER}`); return { tier: PROCU_ALLOWED_TIER, features: getFeaturesForTier(PROCU_ALLOWED_TIER) }; } } ``` ```javascript function authorizeSync(email, feature) { if (!email) { const err = new Error(`Error: This feature requires an Enterprise License. [${feature}]`); err.code = 'LICENSE_DENIED'; err.requiredFeature = feature; err.userTier = 'FREE'; throw err; } const cached = getCachedLicense(email); if (cached) { const allowed = cached.features && cached.features[feature]; if (!allowed) { const err = new Error(`Error: This feature requires an Enterprise License. [${feature}]`); err.code = 'LICENSE_DENIED'; err.requiredFeature = feature; err.userTier = cached.tier || 'FREE'; throw err; } return cached; } // Cache miss in sync context — use env fallback (do NOT block) // Caller should use authorize() for production async contexts const tier = PROCU_ALLOWED_TIER; const features = getFeaturesForTier(tier); const allowed = features[feature]; if (!allowed) { const err = new Error(`Error: This feature requires an Enterprise License. [${feature}]`); ...[truncated 2970 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace email-based identity assertions with signed, expiring authentication tokens issued by a trusted identity provider. 2. Validate token signatures, issuer, audience, expiry, and subject before performing a license lookup. 3. Derive the license email or customer ID from verified token claims rather than query parameters. 4. Remove support for base64-encoded emails as bearer credentials. 5. Fail closed when the license service is unavailable in production. 6. Remove `PROCU_ALLOWED_TIER` from production authorization paths. If a development bypass is required: - Require an explicit development mode. - Refuse to start if the bypass is enabled in production. - Bind development services to localhost. - Emit a prominent startup warning. 7. Avoid `authorizeSync()` for security-sensitive operations. Use the asynchronous verifier and require a successful authoritative lookup or a cryptographically protected cache entry. 8. Add tests covering forged emails, malformed bearer values, license-service outages, cache misses, and privileged fallback configurations. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
webhook-handler.js:272
Finding
Unauthenticated License Enumeration Endpoint<![CDATA[ ## Vulnerability Details **File Location**: `webhook-handler.js:272-281` **Vulnerability Type**: Unauthenticated sensitive information disclosure **Risk Level**: Medium ### Vulnerable Code ```javascript // Internal license query endpoint if (req.method === 'GET' && url.pathname === '/license') { const email = url.searchParams.get('email'); if (!email) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'email required' })); return; } const db = readDb(); const lic = db[email]; if (!lic) { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'License not found' })); return; } // Never echo back raw email in logs res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ tier: lic.tier, features: lic.features, status: lic.status })); return; } ``` ### Technical Analysis The `/license` route is described as internal but performs no caller authentication, service-token validation, or source-network restriction. The server is started with `server.listen(PORT)` and no hostname, which normally exposes it on all available interfaces. The route also provides an account-enumeration oracle: - Existing email: HTTP 200 with tier, feature, and subscription-status data. - Unknown email: HTTP 404 with `License not found`. Consequently, any party able to reach the port can test email addresses and retrieve subscription metadata for matching accounts. Sanitizing logs does not protect data returned directly in the HTTP response. ### Attack Path 1. The webhook service is exposed directly, through container port publishing, or through a reverse proxy. 2. An attacker obtains a list of candidate email addresses. 3. The attacker sends requests such as: `GET /license?email=candidate@example.com` 4. The attacker distinguishes registered customers from non-customers using the 200 and 404 responses. 5. For registered customers, the attacker reco ...[truncated 678 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind the webhook server to `127.0.0.1` by default if license lookup is intended only for local middleware: ```javascript server.listen(PORT, '127.0.0.1', callback); ``` 2. Prefer separating the public webhook receiver and internal license API onto different listeners. 3. Require authenticated service-to-service requests using one of: - Mutual TLS. - A signed internal JWT. - A dedicated high-entropy service token sent in a header. 4. Do not expose `/license` through a public reverse proxy. 5. Return a uniform response for existing and unknown accounts where practical, or expose only a boolean authorization decision for a verified subject. 6. Add rate limiting and monitoring for repeated license lookups. 7. Normalize and validate identifiers before lookup, and avoid placing sensitive identifiers in URL query strings because URLs may be retained by intermediary logs. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
webhook-handler.js:217
Finding
Unbounded Webhook Request Buffering Enables Memory Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `webhook-handler.js:217-222` and `webhook-handler.js:243-245` **Vulnerability Type**: Unauthenticated denial of service through unbounded request buffering **Risk Level**: Medium ### Vulnerable Code ```javascript function parseBody(req) { return new Promise((resolve, reject) => { const chunks = []; req.on('data', c => chunks.push(c)); req.on('end', () => resolve(Buffer.concat(chunks))); req.on('error', reject); }); } ``` ```javascript if (req.method === 'POST' && url.pathname === '/webhook/lemon-squeezy') { const rawBody = await parseBody(req); const sig = req.headers['x-signature'] || ''; if (!verifySignature(rawBody, sig)) { ``` ### Technical Analysis The public webhook endpoint stores every incoming request chunk and concatenates the complete body before validating the HMAC signature. No maximum body size, streamed byte counter, request timeout, or early `Content-Length` rejection is implemented. Because signature verification occurs only after the body is fully buffered, an attacker does not need a valid Lemon Squeezy signature to consume memory. Multiple large or slowly transmitted requests can retain substantial buffers and open connections in the Node.js process. The wildcard CORS response is not the primary cause of this server-side denial-of-service condition; direct HTTP clients can exploit the endpoint regardless of browser policy. ### Attack Path 1. An attacker connects to the publicly reachable webhook endpoint. 2. The attacker sends a POST request to `/webhook/lemon-squeezy` with a very large body and either an invalid or absent `X-Signature`. 3. `parseBody()` appends every chunk to the in-memory `chunks` array. 4. The server does not reject the request until transmission ends and the full body is concatenated. 5. The attacker repeats the request concurrently or keeps multiple uploads slow and active. 6. Process memory and connection capacity are exhausted ...[truncated 494 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Enforce a strict maximum webhook body size appropriate for Lemon Squeezy events. 2. Reject requests whose declared `Content-Length` exceeds the limit before reading the body. 3. Track received bytes while streaming and destroy the request immediately when the limit is exceeded: ```javascript function parseBody(req, maxBytes = 1024 * 1024) { return new Promise((resolve, reject) => { const chunks = []; let total = 0; req.on('data', chunk => { total += chunk.length; if (total > maxBytes) { const error = new Error('Request body too large'); error.code = 'BODY_TOO_LARGE'; req.destroy(error); reject(error); return; } chunks.push(chunk); }); req.on('end', () => resolve(Buffer.concat(chunks, total))); req.on('error', reject); }); } ``` 4. Return HTTP 413 for oversized requests. 5. Configure server-level `requestTimeout`, `headersTimeout`, and connection limits. 6. Apply reverse-proxy request-size limits and per-source rate limiting. 7. Monitor rejected oversized requests and abnormal connection concurrency. 8. Preserve the exact accepted raw bytes for HMAC verification; do not parse or transform the body before validating the signature. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (36)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
## 数据删除

```bash
rm -rf ~/.procurement-data/
```

---
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
## 数据删除

```bash
rm -rf ~/.procurement-data/
```

---
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This finding indicates the advertised core capability may not exist while undeclared webhook/license-management behavior does. A deceptive or materially inaccurate description prevents informed review and can mask entirely different code paths, which is especially risky in procurement and finance contexts where trust, approval flow, and data handling are sensitive.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
This finding indicates the advertised core capability may not exist while undeclared webhook/license-management behavior does. A deceptive or materially inaccurate description prevents informed review and can mask entirely different code paths, which is especially risky in procurement and finance contexts where trust, approval flow, and data handling are sensitive.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This file presents core policy content in Chinese, including data collection, fraud detection, and compliance sections, while not indicating that users can choose another language. Under the language/locale policy rule, forcing a specific language without opt-in is a natural-language policy violation unless the locale restriction is clearly documented and justified.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill requests or relies on environment-variable access and runtime capabilities but does not declare an explicit tool scope such as permissions or allowed-tools. That creates hidden capability expansion: an agent or reviewer may believe the skill is documentation-only procurement logic while it actually depends on sensitive runtime state like API keys and webhook secrets.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"variant_detected": null,         // "V4" | "V8" | "V10" | null
  "anomaly_flags":   [],
  "fraud_flags":     [],
  "recommendation":  "AUTO_APPROVED", // "AUTO_APPROVED" | "REVIEW" | "REJECT"
  "safety_freeze":   false,
  "llm_error":       null
}
Confidence
86% confidence
Finding
The documented output includes autonomous recommendations such as `AUTO_APPROVED`, `REVIEW`, and `REJECT`, indicating automated decision-making in a procurement/finance workflow. In this context, unsupervised approval logic can cause financial loss, blocked operations, or policy violations if parsing, baselines, or fraud heuristics are wrong or manipulated.

Session Persistence

Medium
Category
Rogue Agent
Content
For Claude Code, Codex, Copilot, or other agents:

```bash
mkdir -p ~/.openclaw/skills/autonomous-procurement-agent
git clone https://github.com/arya-openclaw/autonomous-procurement-agent.git \
  ~/.openclaw/skills/autonomous-procurement-agent
cd ~/.openclaw/skills/autonomous-procurement-agent
Confidence
60% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The documentation simultaneously claims there is no production bypass for webhook signature validation while also documenting `PROCU_ALLOWED_TIER` as a bypass override. Contradictory guidance around signature enforcement is dangerous because deployers may accidentally leave a bypass enabled, allowing forged webhook events or unauthorized feature activation.

Vague Triggers

Medium
Confidence
91% confidence
Finding
Broad trigger keywords like generic procurement and approval terms can cause unintended activation during ordinary business discussions. In this skill's context, accidental activation is more dangerous because the skill is described as making blocking/review decisions, handling quote content, and invoking optional external processing, so false activation can affect sensitive finance workflows or leak data to fallback systems.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The auth flow transmits the user's email in an HTTP GET request to `${PROCU_WEBHOOK_URL}/license?email=<email>` for license verification. This network transmission is described in code comments for developers, but there is no user-facing warning or explicit disclosure that user identity data is sent to a webhook service.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The middleware treats a Bearer header as a base64-encoded email string rather than a cryptographically verified authentication token. Any client can forge `Authorization: Bearer <base64(victim@example.com)>` and be authorized based on that user's license, enabling impersonation and bypass of feature gating.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The documentation implies standard Bearer-token authentication, but the implementation only decodes base64 and performs no verification. This mismatch is dangerous because integrators may assume the route is protected when it is actually vulnerable to trivial spoofing, increasing the likelihood of insecure deployment.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"slug": "autonomous-procurement-agent",
  "tagline": "Zero-leak quote parsing with dual-engine AI. F1/F2/F3 fraud detection built in.",
  "shortDescription": "Enterprise procurement automation with hybrid dual-engine parsing (regex + GPT-4o), built-in F1/F2/F3 fraud detection, auto-escalation approval flows, Safety-Freeze circuit breakers, and Lemon Squeezy MoR payment.",
  "description": "The procurement agent that actually understands messy supplier quotes. Engine 1 (regex pipeline, ms response) handles structured JSON, HTML tables, and CSV. Engine 2 (GPT-4o LLM fallback) handles plain-text emails, OCR scans, and qty\u00d7price-on-same-line ambiguity.\n\n**Every result carries risk metadata:**\n- F1: unit_price \u00d7 quantity \u2260 line_total \u2192 calculation error detected, auto-block\n- F2: current price > historical avg \u00d7 1.20 \u2192 price spike flagged\n- F3: same vendor, same total, within 7 days \u2192 duplicate warning\n\n**Approval flow with Safety-Freeze:**\n- Under $10,000 limit: auto-approved instantly\n- Over limit: emails primary approver \u2192 5s timeout \u2192 escalates to backup\n- Both unreachable: Safety-Freeze + emergency alert + order locked\n\n**Lemon Squeezy MoR:** Global VAT/Sales Tax handled by LS. Payout via Payoneer/Wise/WorldFirst \u2014 no Stripe, no PayPal disputes.\n\n**Handles formats others can't:**\n- Email body: \"Servo motor SME-200: 8 units \u00d7 $2,800 = $22,400\" (qty and price on same line)\n- SAP-exported HTML with merged cells\n- Chinese RMB quotes auto-converted to USD\n- Multi-currency: CNY, EUR, GBP, JPY, AUD, CAD \u2192 normalized USD",
  "icon": "https://raw.githubusercontent.com/openclaw/clawhub/main/assets/procurement-icon.png",
  "iconBackground": "#E17055",
  "screenshots": [],
Confidence
89% confidence
Finding
The skill explicitly advertises autonomous approval behavior: purchases under $10,000 are 'auto-approved instantly,' and higher-value requests enter an automated escalation path. In a finance/procurement setting, allowing an AI-driven workflow to authorize transactions without an explicit human checkpoint can enable fraudulent, erroneous, or policy-noncompliant purchases if parsing, thresholds, vendor data, or upstream inputs are manipulated.

Vague Triggers

Medium
Confidence
92% confidence
Finding
The listing markets the skill as 'zero-leak' while also advertising a GPT-4o fallback for messy quotes, but it does not disclose what procurement data is transmitted to the external model, under what trigger conditions, or whether sensitive fields are redacted first. In a procurement/finance context, quote contents can include vendor identities, pricing, banking, and contract details, so undisclosed LLM egress creates a meaningful confidentiality and compliance risk.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
"F3 duplicate detection: same vendor + same total within 7 days",
    "Every result: confidence_score, risk_score, anomaly_flags, risk_alerts",
    "Currency normalizer: CNY/RMB/EUR/GBP/JPY/AUD/CAD \u2192 USD",
    "Approval flow: auto-approve under limit \u2192 email primary \u2192 escalate backup \u2192 Safety-Freeze",
    "Safety-Freeze: both approvers unreachable \u2192 emergency alert + order locked",
    "Lemon Squeezy MoR: global VAT handled, payouts via Payoneer/Wise/WorldFirst",
    "Zero hardcoded keys: all secrets from process.env"
Confidence
88% confidence
Finding
The features list reiterates an auto-approve workflow under a defined limit, confirming that autonomous financial decision-making is a core capability rather than incidental wording. Because the skill handles supplier quotes, fraud signals, and approval routing, a bad parse or adversarially crafted quote could directly influence whether money is committed or procurement controls are bypassed.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger list contains broad business phrases such as 'procurement', 'supplier quote', and 'vendor management' that are likely to match routine enterprise conversations. In a skill that can parse quotes, perform fraud/risk actions, and participate in approval/payment-related workflows, over-broad activation increases the chance of unintended invocation on sensitive financial data or accidental execution of procurement logic without clear user intent.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Procurement quotes can contain sensitive commercial pricing, vendor identities, and contact data. This code sends quote content to OpenAI whenever the regex pipeline fails or confidence is low, with no user-facing consent, policy gate, or tenant-level control beyond environment configuration; the regex masking is partial and can miss sensitive fields.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The code invokes callLLMStructuredExtract from parseQuote, but that path calls callLLMSync, which always throws. In deployments with OPENAI_API_KEY set, malformed or low-confidence inputs consistently force the fallback path into failure, creating a reliable denial-of-service condition for messy documents and silently degrading fraud-review behavior.

Description-Behavior Mismatch

Medium
Confidence
87% confidence
Finding
The manifest emphasizes parsing, reconciliation, fraud detection F1/F2/F3, and approval thresholds, but the code adds general quote ranking via vendor/delivery scoring and duplicate detection workflows. Duplicate detection may align with the F3 mention, but vendor recommendation scoring is a broader sourcing/procurement optimization capability not described in the stated purpose.

Description-Behavior Mismatch

Medium
Confidence
93% confidence
Finding
The manifest describes quote parsing, invoice reconciliation, fraud detection, and approval escalation, but this file also includes Lemon Squeezy subscription tier mapping and webhook event handling. Billing/subscription management is a separate product capability and not an obvious implementation detail of procurement analysis itself.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
// Safety-Freeze: circuit breaker open
    if (isApprovalCircuitOpen()) {
      result.status = 'SAFETY_FREEZE';
      result.action  = 'CIRCUIT_BREAKER_OPEN — no approvals sent, system in safety freeze';
      await sendEmergencyAlert('Safety Freeze: Approval circuit breaker is OPEN for order ' + orderId);
      return result;
    }
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
return result;
    }

    // Under limit: auto-approve
    if (orderAmount <= approvalLimit) {
      result.status       = 'AUTO_APPROVED';
      result.finalApprover = 'SYSTEM_AUTO';
Confidence
93% confidence
Finding
The approval executor automatically approves orders below a configurable threshold with no human review, secondary verification, vendor risk check, or anti-abuse guard. In a procurement context, this can be exploited through invoice splitting, manipulated totals, or low-value fraudulent orders that bypass oversight and directly trigger financial commitments.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
// Under limit: auto-approve
    if (orderAmount <= approvalLimit) {
      result.status       = 'AUTO_APPROVED';
      result.finalApprover = 'SYSTEM_AUTO';
      result.action        = 'Auto-approved: $' + orderAmount + ' <= $' + approvalLimit + ' limit';
      return result;
Confidence
93% confidence
Finding
The AUTO_APPROVED status reflects a real autonomous procurement decision, not just a label. Because the skill is positioned for enterprise finance and fraud detection, this behavior is more dangerous: users may rely on the system to block risky submissions, yet sub-threshold transactions can pass automatically despite parsing errors, fraud indicators, or manipulated order structuring.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
if (orderAmount <= approvalLimit) {
      result.status       = 'AUTO_APPROVED';
      result.finalApprover = 'SYSTEM_AUTO';
      result.action        = 'Auto-approved: $' + orderAmount + ' <= $' + approvalLimit + ' limit';
      return result;
    }
Confidence
92% confidence
Finding
The explicit action string shows the decision criterion is only orderAmount <= approvalLimit, which is too simplistic for autonomous spending authority. In this skill context, that is materially risky because the same system claims fraud detection and approval enforcement, so operators may overtrust automated approvals that can be gamed or applied to bad data.

Static analysis

No suspicious patterns detected.