Back to skill

Security audit

Neckr0ik Api Wrapper

Security checks for vulnerabilities and agentic risk

Overview

The skill has a legitimate API-wrapper purpose, but its generator handles untrusted API specs with too little containment, creating risks around file writes, generated code, prompt injection, network access, and credentials.

Use this only with trusted OpenAPI specs, preferably local files or known public HTTPS hosts. Always provide a safe --name and controlled --output directory, review generated SKILL.md and scripts/api.py before installing or running them, and use sandbox or read-only API credentials rather than production tokens.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T03 · Remote Payload Retrieval and Execution

Error
Location
scripts/generator.py:259
Finding
Remote OpenAPI Fields Can Inject Executable Python into Generated Clients<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generator.py:70-100, 259-318` **Vulnerability Type**: Untrusted remote input embedded into executable source code **Risk Level**: Critical ### Vulnerable Code ```python title=info.get('title', 'API').replace(' ', '-').lower(), ``` ```python endpoint = APIEndpoint( operation_id=operation.get( 'operationId', f"{method}_{path}" ).replace('/', '_').replace('{', '').replace('}', ''), method=method.upper(), path=path, summary=operation.get('summary', ''), description=operation.get('description', ''), parameters=operation.get('parameters', []), request_body=operation.get('requestBody'), responses=operation.get('responses', {}), auth_required=bool(spec_data.get('security', [])), tags=operation.get('tags', []), ) ``` ```python method_code = f''' def {endpoint.operation_id}(self, {param_str}): """ {endpoint.summary or endpoint.method + ' ' + endpoint.path} Method: {endpoint.method} Path: {endpoint.path} {chr(10).join(param_docs) if param_docs else ' '} Returns: dict: API response """ path = "{endpoint.path}" {self._generate_path_params_code(endpoint)} {self._generate_query_params_code(endpoint)} return self._request("{endpoint.method}", path){self._generate_body_code(endpoint)} ''' ``` ```python return f'''#!/usr/bin/env python3 """ {name} API Client Auto-generated from OpenAPI specification. """ ... class {name.replace('-', '').replace('_', '').title()}Client: ``` ### Technical Analysis The generator accepts OpenAPI documents from remote URLs and copies specification-controlled values into a Python source-code template. Values such as the API title, operation ID, endpoint path, summary, and parameter names are not validated as Python identifiers or safely serialized as Python string literals. Basic replacements of spaces, slashes, and braces do not preve ...[truncated 1786 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate every generated Python identifier against a strict rule such as: ```python ^[A-Za-z_][A-Za-z0-9_]*$ ``` Reject invalid values or convert them through a deterministic safe-identifier function. 2. Reject Python keywords using `keyword.iskeyword()`. 3. Never interpolate untrusted values directly into Python string literals or docstrings. Serialize literal values with `repr()` or generate an abstract syntax tree and emit code through a trusted code-generation library. 4. Keep human-readable OpenAPI text in non-executable data files rather than generated Python source where possible. 5. Parse the completed output with `ast.parse()` before writing or publishing it. This detects malformed output but must supplement, not replace, contextual escaping and identifier validation. 6. Treat remote specifications as untrusted content. Require explicit confirmation before generating executable artifacts and clearly identify their source. 7. Add adversarial tests for quotes, triple quotes, newlines, parentheses, colons, Unicode control characters, Python keywords, and exceptionally long field values. ]]>

T01 · Skill Instruction Hijacking

Error
Location
scripts/generator.py:196
Finding
Remote OpenAPI Description Can Inject Instructions into Generated SKILL.md<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generator.py:196-240` **Vulnerability Type**: Generated Skill instruction injection **Risk Level**: High ### Vulnerable Code ```python return f"""--- name: {name} version: 1.0.0 description: {spec.description[:200] if spec.description else f"OpenClaw skill for {spec.title} API"}. Auto-generated from OpenAPI spec. --- # {spec.title} API {spec.description or f"Interact with {spec.title} API from OpenClaw."} **Base URL:** `{spec.base_url}` **Version:** {spec.version} ``` The OpenAPI description is populated from untrusted specification data: ```python return APISpec( title=info.get('title', 'API').replace(' ', '-').lower(), version=info.get('version', '1.0.0'), base_url=base_url, description=info.get('description', ''), endpoints=endpoints, auth_type=auth_type, auth_config=auth_config, ) ``` ### Technical Analysis The OpenAPI `info.description` and title are copied directly into `SKILL.md`. These values can contain arbitrary Markdown and natural-language instructions. No trust boundary, sanitization, or separation exists between remote API documentation and instructions that an AI agent may interpret when loading the generated Skill. The description is also inserted into YAML front matter without YAML-safe serialization. Newlines or YAML syntax can alter metadata structure. The body receives the complete description without a length restriction. An attacker can therefore cause a generated Skill to contain instructions that attempt to override its declared purpose, request sensitive data, direct the agent to attacker-controlled resources, or alter tool-use behavior. ### Attack Path 1. An attacker publishes an OpenAPI document with adversarial instructions in `info.description` or related descriptive fields. 2. A user generates a Skill from the attacker-controlled URL. 3. The generator writes the attacker-controlled text into the generated `SKILL.md`. 4. The gener ...[truncated 723 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not insert remote descriptions into the instructional portion of `SKILL.md` by default. 2. Store untrusted API documentation in a clearly delimited reference file and label it as untrusted data, not executable instructions. 3. YAML-serialize front matter with a trusted YAML emitter rather than string interpolation. 4. Escape or remove control characters, front-matter delimiters, HTML comments, and other structural Markdown tokens. 5. Apply strict size limits to all descriptive fields. 6. Require human review before installing or loading a Skill generated from a remote specification. 7. Consider an allowlisted template that only generates fixed instructions and structured endpoint metadata. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/generator.py:164
Finding
Specification-Controlled Skill Name Permits Output Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generator.py:164-183` **Vulnerability Type**: Path traversal and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code ```python def generate_skill(self, spec: APISpec, name: Optional[str] = None) -> Path: """Generate OpenClaw skill from API spec.""" skill_name = name or spec.title skill_dir = self.output_dir / skill_name skill_dir.mkdir(parents=True, exist_ok=True) # Generate SKILL.md skill_md = self._generate_skill_md(spec, skill_name) (skill_dir / "SKILL.md").write_text(skill_md) # Generate claw.json claw_json = self._generate_claw_json(spec, skill_name) (skill_dir / "claw.json").write_text(claw_json) # Generate API client api_script = self._generate_api_client(spec, skill_name) scripts_dir = skill_dir / "scripts" scripts_dir.mkdir(exist_ok=True) (scripts_dir / "api.py").write_text(api_script) ``` The default name originates from the OpenAPI title: ```python title=info.get('title', 'API').replace(' ', '-').lower(), ``` ### Technical Analysis When `--name` is omitted, `skill_name` is controlled by `info.title` in the OpenAPI document. Only spaces are replaced during title processing. Directory separators, absolute paths, and `..` traversal components remain valid. `self.output_dir / skill_name` is used without canonicalization or a containment check. The generator then creates directories and writes predictable files with `write_text()`, which overwrites existing files. Although the filenames are fixed, an attacker can control their parent directory and cause `SKILL.md`, `claw.json`, and `scripts/api.py` to be written outside the selected output directory. ### Attack Path 1. An attacker creates an OpenAPI document whose title contains path traversal components or an absolute path. 2. A victim invokes `generate` without explicitly supplying a safe `--name`. 3. The title becomes `skill_name`. 4. The destination path ...[truncated 605 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict Skill names to a safe slug, for example: ```python ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$ ``` 2. Reject names containing path separators, `.` or `..` components, absolute paths, drive prefixes, null characters, and control characters. 3. Resolve the output root and destination and enforce containment: ```python output_root = self.output_dir.resolve() destination = (output_root / safe_name).resolve() if destination.parent != output_root: raise ValueError("Destination escapes output directory") ``` 4. Refuse to overwrite an existing destination unless the user supplies an explicit overwrite option. 5. Consider writing to a newly created temporary directory and atomically renaming it after generation succeeds. 6. Apply the same validation to user-provided `--name`, not only specification-derived names. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/generator.py:112
Finding
Unrestricted OpenAPI URL Fetching Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generator.py:45-55, 112-118` **Vulnerability Type**: Unrestricted outbound request and internal-network access **Risk Level**: High ### Vulnerable Code ```python def parse_openapi(self, spec_url: str) -> APISpec: """Parse OpenAPI specification from URL or file.""" # Load spec if spec_url.startswith(('http://', 'https://')): content = self._fetch_url(spec_url) else: content = Path(spec_url).read_text() ``` ```python def _fetch_url(self, url: str) -> str: """Fetch content from URL.""" try: req = urllib.request.Request( url, headers={'Accept': 'application/json, application/yaml'} ) response = urllib.request.urlopen(req, timeout=30) return response.read().decode('utf-8') except urllib.error.HTTPError as e: raise Exception(f"Failed to fetch {url}: {e.code}") ``` ### Technical Analysis The generator accepts arbitrary HTTP and HTTPS URLs and performs requests from the local execution environment. It does not restrict hosts, resolve and reject private addresses, prevent access to loopback or link-local networks, or validate redirect destinations. Consequently, a caller can make the generator contact services that are not reachable from the caller's own network position, including local development services, internal administrative endpoints, and cloud instance metadata services. The returned content must parse as JSON or YAML and resemble an OpenAPI document for generation to continue, but the outbound request occurs before that validation. Redirects can also move an initially public URL to a prohibited internal address. ### Attack Path 1. An attacker supplies a URL targeting a loopback, private, link-local, or cloud metadata address, or supplies a public URL that redirects to one. 2. The victim or agent runs the `generate` or `validate` command with that URL. 3. `urllib.request.urlopen()` p ...[truncated 874 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit HTTPS only unless the user explicitly enables insecure HTTP. 2. Resolve the destination host and reject loopback, private, link-local, multicast, reserved, unspecified, and cloud metadata addresses. 3. Repeat DNS and address validation for every redirect destination. 4. Disable automatic redirects or implement a redirect handler that enforces the same policy at every hop. 5. Consider an allowlist of trusted OpenAPI hosts for automated or agent-driven use. 6. Set a strict maximum response size and stop reading after that limit. 7. Apply connection and total-operation timeouts. 8. Log the final resolved destination and request confirmation before contacting non-allowlisted origins. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generator.py:318
Finding
Generated Clients Can Send Environment Credentials to Arbitrary or Redirected Origins<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generator.py:318-350, 441-452` **Vulnerability Type**: Credential disclosure through destination override and redirects **Risk Level**: Medium ### Vulnerable Code ```python def __init__(self, base_url: str = "{spec.base_url}", api_key: Optional[str] = None): self.base_url = base_url.rstrip('/') self.api_key = api_key or os.environ.get( '{name.replace('-', '_').upper()}_API_KEY' ) self.timeout = 30 def _request(self, method: str, path: str, params: Optional[Dict] = None, data: Optional[Dict] = None) -> Dict: """Make HTTP request to API.""" url = self.base_url + path # Add query parameters if params: query = '&'.join( f'{k}={v}' for k, v in params.items() if v is not None ) if query: url += '?' + query headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', } # Add authentication if self.api_key: {self._generate_auth_code(spec)} req_data = json.dumps(data).encode() if data else None req = urllib.request.Request( url, data=req_data, headers=headers, method=method ) try: response = urllib.request.urlopen(req, timeout=self.timeout) ``` Authentication is inserted into a header or query parameter: ```python if location == 'header': return f" headers['{name}'] = self.api_key" else: # query return " params['api_key'] = self.api_key" ``` ```python elif spec.auth_type == "bearer": return " headers['Authorization'] = f'Bearer {self.api_key}'" elif spec.auth_type == "basic": return " headers['Authorization'] = f'Basic {self.api_key}'" ``` The destination can be overridden: ```python parser.add_argument( '--base-url', default="{spec.base_url}", help='API base URL' ) parser.add_argument('--api-key', help='API key') args, ...[truncated 2091 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not automatically attach an environment-derived credential when `base_url` differs from the origin declared in the OpenAPI specification. 2. Require explicit confirmation or an explicit credential argument for custom destinations. 3. Require HTTPS for authenticated requests. 4. Disable redirects for authenticated requests or validate each redirect and remove authentication on every cross-origin redirect. 5. Bind credentials to an allowlisted scheme, host, and port. 6. Never put API keys into query strings unless the API strictly requires it, because URLs may be exposed through logs, proxies, and history. 7. Warn users before transmitting credentials and display the normalized destination origin without displaying the secret. 8. Encourage generated Skills to use narrowly scoped API credentials. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill advertises behaviors that imply network access, reading local files, writing generated files, and likely reading environment-based credentials, but it does not declare any tool scope or permission boundaries. This is dangerous because users and hosting platforms cannot clearly see or constrain what the skill may access, increasing the risk of over-privileged execution and unintended data exposure.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The skill encourages generating files and testing against live APIs, but it does not warn users that these actions can write to disk, send requests to external systems, consume API quotas, or modify real remote data. In context, this wrapper is specifically designed to interact with arbitrary third-party APIs, which makes the omission more dangerous because generated clients and test operations may perform state-changing actions unexpectedly.

External Transmission

Medium
Category
Data Exfiltration
Content
API Wrapper Generator - Generate OpenClaw skills from REST APIs

Usage:
    python generator.py generate --spec https://api.example.com/openapi.json --name my-api
    python generator.py validate --spec ./openapi.yaml
    python generator.py test --skill ./my-api --endpoint <operationId>
"""
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
API Wrapper Generator - Generate OpenClaw skills from REST APIs

Usage:
    python generator.py generate --spec https://api.example.com/openapi.json --name my-api
    python generator.py validate --spec ./openapi.yaml
    python generator.py test --skill ./my-api --endpoint <operationId>
"""
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
84% confidence
Finding
When --spec is an HTTP(S) URL, the script automatically performs a network request to retrieve the specification. The docstring shows URL usage, but it does not warn that execution will contact external servers and transmit request metadata, so the network behavior lacks clear disclosure.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The generator creates directories and writes SKILL.md, claw.json, scripts/api.py, and references/endpoints.md, but provides no prior disclosure that running generation will modify the filesystem. Although the script prints output paths afterward, there is no confirmation prompt or pre-action warning in the code comments/docstrings for this safety-relevant behavior.

Static analysis

No suspicious patterns detected.