Back to skill

Security audit

Digital Labour

Security checks for vulnerabilities and agentic risk

Overview

This is a disclosed command-line wrapper for a remote AI business automation API, with no local persistence or hidden execution, but users should treat submitted business data as leaving their machine.

Install only if you are comfortable sending prompts, documents, customer records, CRM-like data, transaction text, and generated pipeline outputs to the configured Digital Labour remote service and its listed LLM providers. Keep DIGITAL_LABOUR_API_URL pointed at the trusted HTTPS production endpoint unless you deliberately use another endpoint, and avoid setting DIGITAL_LABOUR_API_KEY in environments where other users or scripts can alter the API URL.

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

Warning
Location
scripts/dl-api.py:15
Finding
Unrestricted API Endpoint Configuration Can Expose Credentials and Sensitive Payloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dl-api.py:15-22, 32-35, 55-58`; `scripts/dl-pipeline.py:18-25, 32-35, 39-42` **Vulnerability Type**: Unvalidated remote endpoint and transport downgrade **Risk Level**: Medium ### Vulnerable Code #### `scripts/dl-api.py` ```python BASE_URL = os.environ.get( "DIGITAL_LABOUR_API_URL", "https://bitrage-labour-api-production.up.railway.app", ).rstrip("/") API_KEY = os.environ.get("DIGITAL_LABOUR_API_KEY", "") ``` ```python def _headers(): h = {"Content-Type": "application/json", "Accept": "application/json"} if API_KEY: h["X-Api-Key"] = API_KEY return h ``` ```python def _post(path, payload): """POST request with JSON body, returns parsed JSON.""" url = f"{BASE_URL}{path}" data = json.dumps(payload).encode("utf-8") req = urllib.request.Request(url, data=data, headers=_headers(), method="POST") ``` #### `scripts/dl-pipeline.py` ```python BASE_URL = os.environ.get( "DIGITAL_LABOUR_API_URL", "https://bitrage-labour-api-production.up.railway.app", ).rstrip("/") API_KEY = os.environ.get("DIGITAL_LABOUR_API_KEY", "") ``` ```python def _headers(): h = {"Content-Type": "application/json", "Accept": "application/json"} if API_KEY: h["X-Api-Key"] = API_KEY return h ``` ```python def _post(path, payload): url = f"{BASE_URL}{path}" data = json.dumps(payload).encode("utf-8") req = urllib.request.Request(url, data=data, headers=_headers(), method="POST") ``` ### Technical Analysis Both clients accept `DIGITAL_LABOUR_API_URL` without validating its URL scheme, hostname, port, or destination. The same request-building logic automatically attaches the value of `DIGITAL_LABOUR_API_KEY` as an `X-Api-Key` header. Consequently, setting the base URL to an attacker-controlled endpoint causes the client to transmit the API key and complete JSON request body to that endpoint. A URL using plain HTTP also permits network-positioned ...[truncated 2617 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Require encrypted transport** - Parse the configured endpoint with `urllib.parse.urlsplit`. - Reject every scheme other than `https`. - Reject URLs containing embedded credentials, fragments, or unexpected ports. 2. **Restrict trusted destinations** - Maintain an explicit allowlist of approved API hostnames. - Compare normalized hostnames exactly rather than using suffix or substring checks. - If custom endpoints are a required feature, require an explicit opt-in flag and display a warning before transmitting credentials or sensitive data. 3. **Bind credentials to trusted hosts** - Add `X-Api-Key` only when the normalized destination hostname is approved. - Fail closed instead of silently sending authenticated requests to arbitrary hosts. 4. **Centralize secure endpoint validation** - Implement a shared URL-validation function and use it in both Python clients. - Validate the endpoint before processing agent inputs or reading sensitive batch data. 5. **Reduce exposure** - Avoid submitting unnecessary personal, financial, or confidential data. - Redact sensitive fields before constructing requests. - Use narrowly scoped, revocable API keys and rotate any key suspected of exposure. A suitable validation pattern should enforce all relevant properties before creating a request: ```python from urllib.parse import urlsplit TRUSTED_API_HOSTS = { "bitrage-labour-api-production.up.railway.app", } def validate_base_url(value): parsed = urlsplit(value) if parsed.scheme != "https": raise ValueError("The API URL must use HTTPS") if parsed.username or parsed.password: raise ValueError("Embedded URL credentials are not permitted") if parsed.fragment: raise ValueError("URL fragments are not permitted") if parsed.hostname not in TRUSTED_API_HOSTS: raise ValueError("Untrusted API hostname") if parsed.port not in (None, 443): ra ...[truncated 189 chars]
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (8)

Tainted flow: 'req' from os.environ.get (line 65, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
url = f"{BASE_URL}{path}"
    req = urllib.request.Request(url, headers=_headers(), method="GET")
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")
Confidence
95% confidence
Finding
The request destination is derived from the DIGITAL_LABOUR_API_URL environment variable and then used directly in urllib.request.urlopen. If an attacker can influence the runtime environment, they can redirect this client to an arbitrary host and cause API requests, including the X-Api-Key header, to be sent to an attacker-controlled endpoint, creating SSRF/exfiltration risk.

Tainted flow: 'req' from os.environ.get (line 65, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers=_headers(), method="POST")
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")
Confidence
95% confidence
Finding
The POST target is built from an environment-controlled base URL and invoked with urllib.request.urlopen, while authentication headers are added automatically. An attacker who can set DIGITAL_LABOUR_API_URL can redirect agent runs and batch payloads to an arbitrary server, potentially leaking the API key and sensitive business inputs submitted to the tool.

Tainted flow: 'req' from os.environ.get (line 40, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers=_headers(), method="POST")
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            return json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")
Confidence
96% confidence
Finding
The script builds its destination URL from the DIGITAL_LABOUR_API_URL environment variable and then sends requests, including the X-Api-Key header and user workflow data, to that endpoint. Because the endpoint is fully overrideable by environment without validation or allowlisting, a local attacker, poisoned runtime environment, or malicious wrapper script could redirect sensitive data and credentials to an attacker-controlled server.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises and depends on capabilities that read environment variables, access files, and make network requests, but it does not declare an explicit tool scope such as permissions or allowed-tools. That omission weakens user understanding and platform enforcement, making it easier for a skill handling business data to perform broader actions than a user may reasonably expect.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill routes user prompts and business data to a remote production API, and the API in turn uses multiple third-party LLM providers, but the documentation does not prominently warn users about that external data transfer. This creates a real privacy and compliance risk because users may submit proprietary, customer, financial, or personal data under the assumption that processing is local or limited to one provider.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The examples explicitly encourage submission of sensitive categories such as support tickets, contact information, transaction data, invoices, resumes, CRM records, and business plans, yet they provide no privacy, consent, or data-minimization warning. In this context, the omission is more dangerous because the skill is marketed for back-office automation, where users are likely to paste real customer, employee, and financial data into a multi-provider remote service.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The pipeline collects user-provided inputs and transmits them to a remote third-party API, but the CLI does not clearly warn users that their data will leave the local machine. In a business automation skill handling client docs, lead data, proposals, or market research, this creates a real privacy and compliance risk if operators assume processing is local.

Vague Triggers

Medium
Confidence
88% confidence
Finding
This JSON manifest contains high-level descriptions such as "Full pipeline" and shorthand sequences like "Research → blog → repurpose → social → ads" that describe broad business tasks without specifying narrow activation conditions or exclusions. In a manifest file, this can create unclear trigger scope and increase the chance of unintended invocation for common requests related to sales, content, onboarding, or launches.

Static analysis

No suspicious patterns detected.