Back to skill

Security audit

CyberLens Security Scanner

Security checks for vulnerabilities and agentic risk

Overview

CyberLens appears to be a legitimate security scanner, but it needs Review because some scan and account-connection paths are under-contained for untrusted targets.

Review before installing. Use it only in an environment where active outbound scans are acceptable, avoid non-loopback HTTP callback URLs, prefer CYBERLENS_API_KEY or tightly protected config storage for credentials, and scan untrusted direct skill packages in a sandbox or resource-limited environment until archive limits and redirect controls are added.

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

Warning
Location
src/auth.py:146
Finding

Plaintext Remote Authentication Callback Exposes Account Exchange Credentials

Content
View full analysis

Vulnerability Details

File Location: src/auth.py:146-161
Related Documentation: README.md:232-238
Vulnerability Type: Plaintext transmission of authentication material
Risk Level: Medium

Vulnerable Code

python
parsed = urlparse(configured_callback)
if parsed.scheme not in {"http", "https"} or not parsed.hostname:
    raise ValueError(
        "CYBERLENS_CONNECT_CALLBACK_URL must be a valid http:// or https:// URL."
    )

callback_path = parsed.path or "/callback"
callback_url = parsed._replace(path=callback_path).geturl()

bind_port_value = os.environ.get("CYBERLENS_CONNECT_BIND_PORT", "").strip()
if bind_port_value:
    bind_port = int(bind_port_value)
elif parsed.port is not None:
    bind_port = parsed.port
else:
    raise ValueError(
        "Configured callback URLs must include a port or set "
        "CYBERLENS_CONNECT_BIND_PORT explicitly."
    )

bind_host = os.environ.get("CYBERLENS_CONNECT_BIND_HOST", "").strip()
if not bind_host:
    bind_host = "127.0.0.1" if _is_loopback_host(parsed.hostname) else "0.0.0.0"

The documented remote-installation workflow explicitly permits this configuration:

bash
# Direct LAN or VPN callback
export CYBERLENS_CONNECT_CALLBACK_URL="http://10.0.0.5:54321/callback"

Technical Analysis

The authentication callback transports the OAuth-like state, one-time connection code, and exchange URL to the callback server. The implementation accepts both HTTP and HTTPS callback URLs regardless of whether the callback hostname is loopback or remote.

HTTP is appropriate for a callback restricted to the local loopback interface because traffic does not normally leave the host. For a non-loopback callback, however, authentication parameters cross a LAN, VPN, proxy path, or other network without transport encryption.

The random state value protects against unsolicited callback injection, but it does not protect against an attacker who can observe the legitimate plaintext callback ...[truncated 1245 chars]

Remediation
View remediation

Remediation Suggestions

  1. Permit http:// callbacks only when the callback hostname is a loopback address such as localhost, 127.0.0.1, or ::1.
  2. Require https:// for every non-loopback callback URL.
  3. Reject configurations where a remote callback uses HTTP, even if the user explicitly supplies a bind address.
  4. Update the remote-installation documentation to use an HTTPS reverse proxy, authenticated tunnel, or equivalent encrypted channel.
  5. Consider binding externally reachable callback listeners only after an explicit secure configuration check.
  6. Preserve the existing random state validation and one-time exchange-code semantics as defense-in-depth.

T09 · Insecure Skill Coding Practices

Warning
Location
src/scanner.py:102
Finding

Unvalidated Redirects Allow External Scan Targets to Reach Internal Network Services

Content
View full analysis

Vulnerability Details

File Location: src/scanner.py:102-107, src/scanner.py:313-330
Vulnerability Type: Server-Side Request Forgery through redirects
Risk Level: Medium

Vulnerable Code

python
self._client = httpx.AsyncClient(
    timeout=httpx.Timeout(self.timeout),
    follow_redirects=True,
    max_redirects=self.max_redirects,
    headers={"User-Agent": self.user_agent},
)

The client then requests the user-selected URL and analyzes the final redirected response:

python
# Make request
response = await self._client.get(url)
headers = dict(response.headers)

# Collect all findings
all_findings: List[Finding] = []

# HTTPS check
all_findings.extend(self._check_https(str(response.url), headers))

# Header checks
all_findings.extend(self._check_headers(headers))

# Information disclosure
all_findings.extend(self._check_information_disclosure(headers))

# Page content analysis
all_findings.extend(await self._analyze_page(str(response.url), response))

Technical Analysis

Selecting an initial URL is part of the scanner’s intended behavior and does not by itself constitute SSRF. The trust boundary is crossed when an independently controlled external website chooses a redirect destination that the user did not select.

The HTTP client automatically follows up to the configured redirect limit. The implementation does not validate redirect destinations or resolved IP addresses. Consequently, an Internet-hosted scan target can redirect the scanner to:

  • Loopback services;
  • RFC1918 private-network addresses;
  • Link-local addresses;
  • Cloud instance metadata endpoints;
  • Other services reachable only from the scanner host.

The resulting response is parsed for headers, forms, technologies, and security findings. Although the implementation does not return the complete response body, these derived results, errors, and timing can expose limited information about otherwise inaccessible services.

Attack Path

  1. An attacker pu ...[truncated 1160 chars]
Remediation
View remediation

Remediation Suggestions

  1. Disable automatic redirect handling and process redirects explicitly.
  2. Validate the destination of every redirect, not only the initial URL.
  3. Resolve each hostname and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges unless the user explicitly authorized an internal scan.
  4. Repeat address validation for every redirect hop and immediately before connecting.
  5. Protect against DNS rebinding by ensuring the validated address is the address used for the connection.
  6. Reject redirects that change to unsupported schemes or include credentials in the authority component.
  7. Return a clear validation error when a public target attempts to redirect into a protected network range.

T09 · Insecure Skill Coding Practices

Warning
Location
src/skill_scanner.py:322
Finding

Unbounded Skill Archive Download and Extraction Enables Resource Exhaustion

Content
View full analysis

Vulnerability Details

File Location: src/skill_scanner.py:322-341
Vulnerability Type: Uncontrolled resource consumption during archive processing
Risk Level: Medium

Vulnerable Code

python
# Download
async with httpx.AsyncClient(timeout=timeout) as client:
    response = await client.get(download_url)
    response.raise_for_status()
    zip_path.write_bytes(response.content)

# Extract
extract_dir.mkdir()
with zipfile.ZipFile(zip_path) as zf:
    zf.extractall(extract_dir)

# Read all files
files: Dict[str, str] = {}
for file_path in extract_dir.rglob("*"):
    if file_path.is_file():
        try:
            relative = str(file_path.relative_to(extract_dir))
            files[relative] = file_path.read_text(encoding="utf-8", errors="ignore")
        except Exception:
            pass

Technical Analysis

Skill packages are untrusted by design because this feature scans packages before the user decides whether to install them. The package publisher therefore controls the downloaded response, archive metadata, compressed contents, member count, and extracted file sizes.

The implementation has no limit on:

  • Downloaded response size;
  • Number of archive members;
  • Per-member uncompressed size;
  • Aggregate uncompressed size;
  • Compression ratio;
  • Number or size of files loaded into memory.

The entire HTTP response is first buffered in response.content. The archive is then extracted in full, and every extracted file is subsequently read into the in-memory files dictionary. A crafted archive can therefore consume memory during both download and analysis while also consuming disk space during extraction.

A network timeout does not constrain response size, extraction cost, or decompression expansion.

Attack Path

  1. A malicious publisher supplies a direct skill download URL or publishes a package reachable through the supported marketplace workflow.
  2. The package is constructed as an oversized archive, a high-compression- ...[truncated 1043 chars]
Remediation
View remediation

Remediation Suggestions

  1. Stream downloads to disk instead of buffering response.content.
  2. Enforce a strict maximum download size using both Content-Length when available and a running byte counter while streaming.
  3. Inspect archive metadata before extraction and reject packages exceeding:
    • A maximum member count;
    • A maximum per-file uncompressed size;
    • A maximum aggregate uncompressed size;
    • A safe compression-ratio threshold.
  4. Extract members individually while maintaining an aggregate extracted-byte counter.
  5. Stop extraction immediately when any configured resource limit is exceeded.
  6. Analyze eligible files incrementally rather than retaining every file’s complete contents in a dictionary.
  7. Apply per-file text-analysis limits and skip oversized or unsupported files with an explicit finding.
  8. Run package analysis in a resource-constrained worker or sandbox with disk, memory, CPU, and execution-time limits.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (49)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill's stated purpose understates the existence of an authentication flow with callback configuration and remote key exchange. That matters because callback listeners and token exchange logic are security-sensitive components; hiding them behind a generic scanning description reduces informed consent and may mask additional exposure such as misconfigured callback endpoints or credential interception risks.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The skill's stated purpose understates the existence of an authentication flow with callback configuration and remote key exchange. That matters because callback listeners and token exchange logic are security-sensitive components; hiding them behind a generic scanning description reduces informed consent and may mask additional exposure such as misconfigured callback endpoints or credential interception risks.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The skill's stated purpose understates the existence of an authentication flow with callback configuration and remote key exchange. That matters because callback listeners and token exchange logic are security-sensitive components; hiding them behind a generic scanning description reduces informed consent and may mask additional exposure such as misconfigured callback endpoints or credential interception risks.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The skill's stated purpose understates the existence of an authentication flow with callback configuration and remote key exchange. That matters because callback listeners and token exchange logic are security-sensitive components; hiding them behind a generic scanning description reduces informed consent and may mask additional exposure such as misconfigured callback endpoints or credential interception risks.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill's stated purpose understates the existence of an authentication flow with callback configuration and remote key exchange. That matters because callback listeners and token exchange logic are security-sensitive components; hiding them behind a generic scanning description reduces informed consent and may mask additional exposure such as misconfigured callback endpoints or credential interception risks.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill's stated purpose understates the existence of an authentication flow with callback configuration and remote key exchange. That matters because callback listeners and token exchange logic are security-sensitive components; hiding them behind a generic scanning description reduces informed consent and may mask additional exposure such as misconfigured callback endpoints or credential interception risks.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The README explicitly says Claw Hub skill packages are downloaded, extracted, and analyzed locally, but it does not warn that these packages are untrusted content and may contain malicious files, oversized archives, or path traversal payloads if extraction is not carefully constrained. For a tool that encourages scanning third-party skills before trust decisions, omission of that warning can normalize risky handling of attacker-controlled archives and increase the chance of unsafe implementation or misuse.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The README says 'Just tell your agent what to scan' and includes examples like 'Check this skill before I install it' and 'Give me a quick security score', which are relatively broad natural-language triggers. Because the skill also 'auto-detects the target type,' the activation scope is not tightly bounded and the documentation does not provide negative examples or exclusion conditions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The account connection flow documents that an API key is stored locally in a predictable path, but it does not tell users to protect that credential as a secret or restrict filesystem permissions. If the host is shared, compromised, or backed up insecurely, the stored key could be stolen and used to access the user's CyberLens account or consume scan quota.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
81% confidence
Finding

This describes persistent local storage of an account key after an authentication flow, which creates a session persistence surface on disk. Persistent tokens are common and not inherently malicious, but storing them in a regular YAML file means compromise of the local user account, weak file permissions, or accidental disclosure through backups could allow reuse of that credential.

Content

Scanner excerpt · README.md (reported line 220)May include surrounding context.

md
The first time you want the full cloud scan, tell your agent to "connect my CyberLens account" (or it will prompt you for cloud-only features). This runs the `connect_account` tool, which:

1. Opens your browser to [cyberlensai.com/connect](https://cyberlensai.com/connect)
2. You sign in or create a free account
3. A local callback server receives a short-lived connect code
4. The skill exchanges that code for your account key over HTTPS
5. The key is stored at `~/.openclaw/skills/cyberlens/config.yaml`

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
85% confidence
Finding

The skill advertises and documents capabilities that involve environment access, reading and writing local files, making network requests, and invoking Python, but it does not declare any explicit tool scope or permissions boundary. That increases risk because users and the platform cannot easily constrain what the skill may access, and the skill also handles API keys and downloaded content, making the missing scope more consequential.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

The skill persists an API key in a local config file under the user's home directory. Persisting credentials is common, but it becomes a security issue if file permissions, encryption, rotation, and revocation are not carefully handled, because any local compromise or overbroad file access could expose the account key and enable unauthorized scans or account abuse.

Content

Scanner excerpt · SKILL.md (reported line 21)May include surrounding context.

md
## First-Time Setup

Website quick scans and Claw Hub skill package scans work without an account. Connect a CyberLens account when the user wants the full cloud website scan or any repository scan. Run the `connect_account` tool. This opens a browser to cyberlensai.com where they sign in or create an account. A short-lived connect code is delivered through the callback, the skill exchanges that code for the real account key over HTTPS on official CyberLens hosts, and the key is stored at `~/.openclaw/skills/cyberlens/config.yaml`.

Browser authentication uses `https://cyberlensai.com/connect`. The hosted scan API runs at `https://api.cyberlensai.com/functions/v1/public-api-scan`. If the user needs to override the scan API endpoint explicitly, set `CYBERLENS_API_BASE_URL`.

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

The skill transmits data to external CyberLens services for authentication and cloud scanning, and it also allows overriding the API base URL through an environment variable. External transmission is expected for a cloud scanner, but it is still security-relevant because scanned targets, findings, tokens, or metadata may leave the local environment, and endpoint override increases SSRF/phishing/exfiltration risk if misused.

Content

Scanner excerpt · SKILL.md (reported line 23)May include surrounding context.

md
Website quick scans and Claw Hub skill package scans work without an account. Connect a CyberLens account when the user wants the full cloud website scan or any repository scan. Run the `connect_account` tool. This opens a browser to cyberlensai.com where they sign in or create an account. A short-lived connect code is delivered through the callback, the skill exchanges that code for the real account key over HTTPS on official CyberLens hosts, and the key is stored at `~/.openclaw/skills/cyberlens/config.yaml`.

Browser authentication uses `https://cyberlensai.com/connect`. The hosted scan API runs at `https://api.cyberlensai.com/functions/v1/public-api-scan`. If the user needs to override the scan API endpoint explicitly, set `CYBERLENS_API_BASE_URL`.

If the user doesn't have a CyberLens account, direct them to https://cyberlensai.com to sign up. Free tier includes 5 scans/month (3 website + 2 repository). Repository scanning requires a connected account. If the user exhausts quota, the skill opens the pricing page and returns an upgrade URL in the tool result.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · proposed readme.md (reported line 88)May include surrounding context.

md
import httpx


DEFAULT_API_BASE = "https://api.cyberlensai.com/functions/v1/public-api-scan"


class CyberLensQuotaExceededError(RuntimeError):

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · skill.yaml (reported line 32)May include surrounding context.

yaml
import httpx


DEFAULT_API_BASE = "https://api.cyberlensai.com/functions/v1/public-api-scan"


class CyberLensQuotaExceededError(RuntimeError):

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · src/api_client.py (reported line 11)May include surrounding context.

python
import httpx


DEFAULT_API_BASE = "https://api.cyberlensai.com/functions/v1/public-api-scan"


class CyberLensQuotaExceededError(RuntimeError):

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_auth.py (reported line 105)May include surrounding context.

python
import httpx


DEFAULT_API_BASE = "https://api.cyberlensai.com/functions/v1/public-api-scan"


class CyberLensQuotaExceededError(RuntimeError):

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_auth.py (reported line 109)May include surrounding context.

python
import httpx


DEFAULT_API_BASE = "https://api.cyberlensai.com/functions/v1/public-api-scan"


class CyberLensQuotaExceededError(RuntimeError):

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · tests/test_auth.py (reported line 128)May include surrounding context.

python
import httpx


DEFAULT_API_BASE = "https://api.cyberlensai.com/functions/v1/public-api-scan"


class CyberLensQuotaExceededError(RuntimeError):

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code issues an outbound HTTP request to a user-supplied URL, which transmits network metadata such as the User-Agent and initiates contact with an external system. In this file there is no confirmation prompt, user-facing disclosure, or warning comment/docstring near the operation explaining that the scan will actively contact the target URL.

Content

No source excerpt is available for this finding.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill grants unrestricted tool access without appropriate constraints. An agent with unfettered tool access can perform arbitrary actions including file modification, network requests, and code execution.

Content

Scanner excerpt · src/skill_scanner.py (reported line 92)May include surrounding context.

python
"pattern": re.compile(r"import\s+pickle|from\s+pickle\s+import|pickle\.loads?\("),
        "severity": "medium",
        "title": "Pickle deserialisation",
        "description": "Pickle can execute arbitrary code during deserialisation.",
        "recommendation": "Use JSON or another safe serialisation format instead of pickle.",
    },
    {

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill exposes functionality to generate and write PDF reports to the local filesystem, which exceeds the stated scanning-only description. Hidden or undocumented file-write capabilities matter in agent skills because they create persistence and data-handling side effects users may not expect, especially if scan results can contain sensitive targets or findings.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Opening a browser upgrade page automatically as a side effect of a scan failure/quota event is an unexpected external action. In an agent context, this can be abused for unwanted navigation, phishing exposure if the URL source is compromised, or disruptive UX that breaks the principle of explicit user consent for outbound actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

Automatically launching a browser without explicit warning or consent at the point of operation is a real safety and security issue for an agent skill. Even if intended for upgrades, this creates non-transparent side effects and increases risk if the upgrade URL is ever tampered with or if scans are triggered in automated environments.

Content

No source excerpt is available for this finding.

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · tests/test_scanner.py (reported line 350)May include surrounding context.

python
error=httpx.TimeoutException("Request timed out"),
        )

        async with SecurityScanner(timeout=0.1) as scanner:
            result = await scanner.scan("https://example.com/slow")
            
            assert result.error is not None

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
src/skill_scanner.py:30