Back to skill

Security audit

openlist

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent OpenList automation skill, but it needs Review because authenticated file/task changes can bypass or weaken the documented confirmation model.

Install only if you understand the Chinese documentation, use a least-privileged OpenList token, keep plan files in a private directory, prefer HTTPS with verified certificates, and treat task cancellation as an immediate state-changing action rather than a previewed operation.

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

Error
Location
scripts/openlist.py:470
Finding
Operation plans are unsigned, mutable, replayable, and do not enforce expiration<![CDATA[ ## Vulnerability Details **File Location**: `scripts/openlist.py`, lines 470-524 and 551-580 **Vulnerability Type**: Unauthenticated operation plan / replay weakness **Risk Level**: High ### Vulnerable Code ```python def validate_plan_schema(plan: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, Any]: if not isinstance(plan, dict): raise UserFacingError("Plan file must contain a JSON object.") required = ["plan_id", "request_id", "created_at", "type", "api", "prechecks", "conflicts", "risk", "resolved"] missing = [field for field in required if field not in plan] if missing: raise UserFacingError("Plan file is missing required fields: %s." % ", ".join(missing)) plan_type = plan.get("type") if plan_type not in ALLOWED_PLAN_TYPES: raise UserFacingError("Unsupported plan type: %s." % plan_type) api = plan.get("api") or {} if not isinstance(api, dict) or api.get("base_url") != config.get("base_url"): raise UserFacingError( "Plan base_url does not match the current OPENLIST_BASE_URL.", hints=["Re-run the preview command against the same OpenList instance and use the new plan file."], ) resolved = plan.get("resolved") if not isinstance(resolved, dict): raise UserFacingError("Plan resolved section must be an object.") endpoint = resolved.get("endpoint") if endpoint not in ALLOWED_ENDPOINTS: raise UserFacingError("Plan endpoint is not allowed: %s." % endpoint) if endpoint != PLAN_ENDPOINTS.get(plan_type): raise UserFacingError("Plan endpoint does not match the plan type: %s." % endpoint) findings = scan_for_dangerous_signals(plan) if findings: raise UserFacingError( "Plan validation failed because it contains unsafe fields.", data={"findings": findings}, hints=["Generate a fresh preview plan instead of editing the plan file by hand."], ) prechecks = pla ...[truncated 4769 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Sign every generated plan using an HMAC or asymmetric signature covering all security-relevant fields, including: - `plan_id`, `request_id`, `created_at`, and `expires_at`. - Operation type and base URL. - The complete request and resolved body. - Endpoint, prechecks, conflicts, risk data, and resolved extras. 2. Verify the signature using constant-time comparison before processing any plan field during `apply`. 3. Parse and enforce `expires_at`; reject missing, malformed, or expired plans. 4. Store consumed plan IDs in a protected state file or database and reject replay attempts. 5. Validate each operation body's exact schema rather than only scanning for dangerous key names. 6. Cross-check all resolved fields against the original request fields for move, rename, and offline-download operations, as is partially done for deletion. 7. Re-run online preconditions immediately before every mutation, including source existence, target state, and conflict detection. 8. Create plan files atomically with restrictive permissions and document that they must not be stored in shared or world-writable directories. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/openlist.py:1320
Finding
Task cancellation bypasses the documented preview and confirmation workflow<![CDATA[ ## Vulnerability Details **File Location**: `scripts/openlist.py`, lines 1233-1235 and 1320-1330; `SKILL.md`, lines 42-49 **Vulnerability Type**: Missing confirmation for a state-changing operation **Risk Level**: Medium ### Vulnerable Code ```python task_cancel = subparsers.add_parser("task-cancel", help="Cancel a task") task_cancel.add_argument("--task-type", required=True, choices=sorted(TASK_TYPES)) task_cancel.add_argument("--tid", required=True) ``` ```python if args.command == "task-cancel": endpoint = TASK_TYPES[args.task_type] + "/cancel" response = client.request("POST", endpoint, params={"tid": args.tid}) result = handle_read_only( config, "task_cancel", response, inputs={"task_type": args.task_type, "tid": args.tid}, task_type=args.task_type, ) if not result["ok"] and not result.get("hints"): result["hints"] = ["If cancellation is not supported on this instance, keep monitoring the task with task-info."] return result ``` The documentation states that every state-changing operation must use a preview followed by an apply step, while also exposing direct task cancellation: ```text - task-cancel --task-type offline_download --tid <tid> All state-changing operations must use two steps: 1. First execute preview-* to generate an OperationPlan. 2. After user confirmation, execute apply against that same plan. ``` ### Technical Analysis Task cancellation changes remote server state but is implemented as an immediate authenticated POST request. It does not generate an operation plan, invoke plan validation, or require an explicit confirmation through `apply`. The implementation passes the cancellation response through `handle_read_only`, even though cancellation is not read-only. This makes its control flow and audit phase inconsistent with its actual effect. The parser permits every type in `TASK_TYPES`, which includes both `move` and `offline_download`. The docume ...[truncated 1132 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace direct cancellation with a `preview-task-cancel` command that retrieves and displays: - Exact task ID. - Task type. - Current task state. - Relevant source, destination, or download information. - The consequences of cancellation. 2. Generate a signed, expiring operation plan and require `apply` after explicit user confirmation. 3. Re-fetch the task immediately before cancellation and reject the operation if its identity or state changed after preview. 4. Restrict cancellation to `offline_download` if that is the intended documented boundary. Otherwise, explicitly document move-task cancellation and its risks. 5. Audit cancellation using a state-changing phase such as `preview`, `apply`, or `deny`, rather than routing it through `handle_read_only`. 6. Apply least privilege to the OpenList token so it can cancel only the task categories required by the workflow. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/openlist.py:125
Finding
Authentication tokens can be transmitted over plaintext HTTP or without verified TLS<![CDATA[ ## Vulnerability Details **File Location**: `scripts/openlist.py`, lines 125-141 and 588-617 **Vulnerability Type**: Insecure transport of authentication credentials **Risk Level**: High ### Vulnerable Code ```python def sanitize_base_url(base_url: Optional[str]) -> str: if not base_url: raise UserFacingError( "Missing OPENLIST_BASE_URL.", hints=[ "Set OPENLIST_BASE_URL to your OpenList root URL, for example http://localhost:5244.", "You can also place the value in the repository .env or skills/openlist/.env file.", ], ) parsed = urllib_parse.urlsplit(base_url.strip()) if parsed.scheme not in {"http", "https"} or not parsed.netloc: raise UserFacingError( "OPENLIST_BASE_URL must be a valid http or https URL.", hints=["Example: OPENLIST_BASE_URL=http://localhost:5244"], ) path = parsed.path.rstrip("/") return urllib_parse.urlunsplit((parsed.scheme, parsed.netloc, path, "", "")) ``` ```python class OpenListClient(object): def __init__(self, config: Dict[str, Any]) -> None: self.config = config def _ssl_context(self) -> Optional[ssl.SSLContext]: if self.config.get("verify_tls", True): return None return ssl._create_unverified_context() # type: ignore[attr-defined] def request( self, method: str, endpoint: str, *, body: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: url = join_base_url(self.config["base_url"], endpoint, params=params) data = None headers = {"Accept": "application/json"} token = self.config.get("token") if token: headers["Authorization"] = token if body is not None: data = json.dumps(body).encode("utf-8") headers["Content-Type"] = "application/json" request ...[truncated 2394 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require HTTPS for all non-loopback and non-local development addresses. 2. If HTTP support is necessary for local development, restrict it to validated loopback hosts such as `127.0.0.1`, `::1`, and `localhost`. 3. Keep certificate verification mandatory in normal operation. 4. For private certificate authorities, support a configurable CA bundle instead of disabling verification. 5. If an insecure override must remain, require an explicit high-risk command-line flag and emit a prominent warning before sending credentials. 6. Refuse to send the `Authorization` header when transport security requirements are not satisfied. 7. Use a least-privileged, short-lived OpenList token and establish a token-rotation procedure for suspected exposure. 8. Update documentation to discourage plaintext HTTP and to recommend installing the correct CA certificate rather than setting `OPENLIST_VERIFY_TLS=false`. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (12)

Credential Access

High
Category
Privilege Escalation
Content
def load_dotenv_values() -> Dict[str, str]:
    env_values = {}
    for env_file in (repo_root() / ".env", skill_root() / ".env"):
        if env_file.exists():
            env_values.update(parse_env_text(env_file.read_text(encoding="utf-8")))
    return env_values
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
return False
    raise UserFacingError(
        "OPENLIST_VERIFY_TLS must be one of true/false/1/0.",
        hints=["Update OPENLIST_VERIFY_TLS in your environment or .env file."],
    )
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
return False
    raise UserFacingError(
        "OPENLIST_VERIFY_TLS must be one of true/false/1/0.",
        hints=["Update OPENLIST_VERIFY_TLS in your environment or .env file."],
    )
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def build_effective_env() -> Dict[str, str]:
    effective = load_dotenv_values()
    for key, value in os.environ.items():
        effective[key] = value
    return effective
Confidence
70% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Natural-Language Policy Violations

High
Confidence
97% confidence
Finding
The delete preview includes critical warnings about irreversibility and the need for explicit confirmation, but these safety messages are only in Chinese. Forcing a single language for destructive-operation warnings can prevent users from understanding the risk, making this a stronger policy violation.

Natural-Language Policy Violations

Medium
Confidence
98% confidence
Finding
The entire skill documentation is written in Chinese and does not indicate that users may choose another language or locale. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation unless the locale constraint is explicitly justified, which is not present here.

Session Persistence

Medium
Category
Rogue Agent
Content
- `preview-move --src-path <path> --dst-dir <dir> [--conflict-policy fail|auto_rename|skip]`
- `preview-rename --path <path> --new-name <name> [--conflict-policy fail|auto_rename]`
- `preview-delete --path <path>`
- `preview-offline-create --url <url> [--url <url> ...] --dst-dir <dir> [--tool <tool>] [--delete-policy <policy>]`
- `apply --plan-file <file>`
- `task-cancel --task-type offline_download --tid <tid>`
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.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This Python file includes natural-language guidance that is shown to users during preview and apply flows, but the text is fixed to Chinese. That creates a locale-policy issue because the skill does not provide any user opt-in, fallback, or language selection for these critical messages.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
The rename preview path emits notes in Chinese that describe overwrite and conflict behavior. Because these are user-visible instructions affecting file operations, forcing a specific language without opt-in violates the language/locale policy.

Natural-Language Policy Violations

Medium
Confidence
93% confidence
Finding
These notes communicate filtering, delete policy defaults, and task follow-up steps, but they are only presented in Chinese. The file does not offer an explicit locale choice or document a justified region-specific limitation.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
def command_result(args: argparse.Namespace) -> Dict[str, Any]:
    if args.command == "audit-show":
        config = load_config(require_auth=False)
        records = filter_audit_records(
            load_audit_records(config),
            event_id=args.event_id,
Confidence
75% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
def command_result(args: argparse.Namespace) -> Dict[str, Any]:
    if args.command == "audit-show":
        config = load_config(require_auth=False)
        records = filter_audit_records(
            load_audit_records(config),
            event_id=args.event_id,
Confidence
80% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Static analysis

No suspicious patterns detected.