Back to skill

Security audit

Bilibili Notion Pipeline Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its stated Bilibili-to-Notion purpose, but it has broad upload and deletion capabilities that need user review before installation.

Review the upload destination, require HTTPS, and use a narrowly scoped upload token before running this skill. Avoid --replace-children unless you are certain the Notion page can have all existing top-level blocks archived. Only run cleanup against metadata produced by this pipeline and consider using cleanup-mode none until paths have been inspected.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/pipeline.py:443
Finding

Arbitrary File Deletion Through Untrusted Metadata Paths

Content
View full analysis
Dict[str, Any]: deleted: List[str] = [] kept: List[str] = [] for key in ("wav_path", "transcript_path"): path_value = meta.get(key) if not path_value: continue path = Path(path_value) if mode in {"temp", "all"} and path.exists(): path.unlink() deleted.append(str(path)) elif path.exists(): kept.append(str(path)) video_value = meta.get("local_file") if video_value: video_path = Path(video_value) if mode == "all" and video_path.exists(): video_path.unlink() deleted.append(str(video_path)) elif video_path.exists(): kept.append(str(video_path)) ``` The affected cleanup command loads the paths directly from a user-selected metadata file: ```python def cmd_cleanup(args: argparse.Namespace) -> None: meta = load_metadata(Path(args.metadata)) mode = "all" if args.delete_video else args.mode result = cleanup_from_meta(meta, mode) meta["cleanup"] = result meta["state"] = "cleaned" if mode != "none" else meta.get("state", "prepared") save_metadata(meta, meta.get("bvid", "metadata"), Path(meta["metadata_path"])) print(json.dumps(result, ensure_ascii=False, indent=2)) ``` ### Technical Analysis The cleanup implementation treats the `wav_path`, `transcript_path`, and `local_file` properties in the metadata JSON as trusted filesystem paths. It does not canonicalize these paths or verify that they are located beneath the intended `TEMP_DIR` and `DOWNLOAD_DIR` directories. Because the `--metadata` option accepts an arbitrary JSON fil ...[truncated 1831 chars]
Remediation
View remediation
Path: resolved = candidate.resolve(strict=True) trusted_root = root.resolve(strict=True) if not resolved.is_relative_to(trusted_root): raise RuntimeError(f"Refusing to delete out-of-scope path: {resolved}") if not resolved.is_file(): raise RuntimeError(f"Refusing to delete non-file target: {resolved}") return resolved ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/pipeline.py:225
Finding

Upload Token and Video Can Be Transmitted Over Plaintext HTTP

Content
View full analysis
Optional[str]: if not UPLOAD_URL or not UPLOAD_TOKEN: return None with video_path.open("rb") as fh: resp = requests.post( UPLOAD_URL, headers={"Authorization": f"Bearer {UPLOAD_TOKEN}"}, files={"file": (video_path.name, fh, "video/mp4")}, timeout=120, ) resp.raise_for_status() payload = resp.json() if isinstance(payload, list) and payload: item = payload[0] src = item.get("src") or item.get("url") if src: if src.startswith("http"): return src base = re.match(r"^(https?://[^/]+)", UPLOAD_URL) if base: return base.group(1) + src if isinstance(payload, dict): for key in ("url", "src", "download_url"): if payload.get(key): return payload[key] raise RuntimeError(f"Upload succeeded but no public URL found: {payload}") ``` ### Technical Analysis The upload operation is part of the Skill's declared functionality, and no covert or unrelated exfiltration destination was identified. However, `UPLOAD_URL` is taken from the environment and used without scheme validation. The implementation therefore permits an `http://` endpoint. When plaintext HTTP is used, the request exposes both: - The bearer credential in the `Authorization` header - The complete MP4 file in the multipart request body The `requests` library verifies TLS certificates for HTTPS destinations by default, but that protection is irrelevant when the configured URL uses HTTP. The code also accepts returned URLs beginning with either HTTP or HTTPS, which can propagate an insecure public download URL into Notion. ### Atta ...[truncated 1501 chars]
Remediation
View remediation
str: parsed = urlparse(value) if parsed.scheme.lower() != "https": raise RuntimeError("UPLOAD_URL must use HTTPS") if not parsed.hostname: raise RuntimeError("UPLOAD_URL must contain a valid hostname") if parsed.username or parsed.password: raise RuntimeError("Embedded URL credentials are not permitted") return value ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Tainted flow: 'UPLOAD_URL' from os.getenv (line 30, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The skill uploads a local video file and bearer token to a fully environment-configurable UPLOAD_URL with no allowlist, origin validation, or user confirmation. If that endpoint is misconfigured or malicious, the pipeline can exfiltrate downloaded media and credentials to an attacker-controlled service, which is especially risky because upload is automatic during normal execution.

Content

Scanner excerpt · scripts/pipeline.py (reported line 241)May include surrounding context.

python
if not UPLOAD_URL or not UPLOAD_TOKEN:
        return None
    with video_path.open("rb") as fh:
        resp = requests.post(
            UPLOAD_URL,
            headers={"Authorization": f"Bearer {UPLOAD_TOKEN}"},
            files={"file": (video_path.name, fh, "video/mp4")},

Tainted flow: 'NOTION_DATABASE_ID' from os.getenv (line 29, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/pipeline.py (reported line 307)May include surrounding context.

python
if not NOTION_DATABASE_ID:
        raise RuntimeError("NOTION_DATABASE_ID is required when creating a new page")

    resp = requests.post(
        "https://api.notion.com/v1/pages",
        headers=headers,
        json={"parent": {"database_id": NOTION_DATABASE_ID}, "properties": props},

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description presents an end-to-end ingestion pipeline for Bilibili content into Notion, involving external resource access and multiple workflow steps. The supplied code does not perform any of those actions. It is a local text-processing utility that parses Markdown lines and emits Notion-compatible block dictionaries. While this could be a supporting helper for the described system, the code chunk itself does not implement the declared primary purpose and instead has a much narrower, materially different function.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

Archiving existing Notion page children is a destructive remote action performed without an interactive confirmation or a strong safety check. In practice, a mistaken page_id or accidental use of --replace-children can wipe visible page content and create significant integrity and availability issues for user data.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill describes operations that inherently require shell, filesystem, environment access, and network connectivity, but it does not declare any explicit tool scope or permission boundaries. This creates an over-privilege and reviewability problem: a host agent may grant broader capabilities than necessary, making misuse, secret exposure, or unintended destructive actions harder to constrain.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The description text specifies the skill's use cases entirely in Chinese phrasing, which indicates a language-specific interaction expectation. There is no accompanying note that the skill supports multiple languages or that Chinese is optional, so this creates a natural-language locale constraint without user opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Most operational instructions, triggers, and workflow guidance are presented only in Chinese, which effectively imposes a language requirement on users and operators. The file does not explain that this locale restriction is intentional for a region-specific compliance or product need, nor does it offer an alternative language path.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The workflow explicitly includes uploading an mp4 and obtaining a public URL, but the notes provide no warning, consent gate, or privacy controls around making user media publicly accessible. In this skill context, videos may contain copyrighted, private, or sensitive content, so automatically exposing them via a public link creates a real confidentiality and compliance risk.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/pipeline.py (reported line 61)May include surrounding context.

python
def run(cmd: List[str]) -> None:
    subprocess.run(cmd, check=True)


def extract_bvid(value: str) -> str:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/pipeline.py (reported line 131)May include surrounding context.

python
def audio_duration_seconds(path: Path) -> float:
    result = subprocess.run(
        [
            "ffprobe", "-v", "error",
            "-show_entries", "format=duration",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The code transmits a local MP4 to a remote endpoint without an explicit warning or consent step at the point of upload. In this skill context, that is dangerous because users may expect local processing plus Notion sync, but not secondary transfer of the full video to an arbitrary configured service.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The pipeline sends transcript content, video metadata, and page updates to Notion, an external service, without an explicit warning at the transmission point. This aligns with the advertised purpose, so it is not covert exfiltration, but it is still a real privacy and data-handling concern because potentially sensitive transcript text is uploaded automatically.

Content

No source excerpt is available for this finding.

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 · scripts/pipeline.py (reported line 294)May include surrounding context.

python
if page_id:
        resp = requests.patch(
            f"https://api.notion.com/v1/pages/{page_id}",
            headers=headers,
            json={"properties": props},
            timeout=60,

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 · scripts/pipeline.py (reported line 308)May include surrounding context.

python
if page_id:
        resp = requests.patch(
            f"https://api.notion.com/v1/pages/{page_id}",
            headers=headers,
            json={"properties": props},
            timeout=60,

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 · scripts/pipeline.py (reported line 327)May include surrounding context.

python
if page_id:
        resp = requests.patch(
            f"https://api.notion.com/v1/pages/{page_id}",
            headers=headers,
            json={"properties": props},
            timeout=60,

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 · scripts/pipeline.py (reported line 345)May include surrounding context.

python
if page_id:
        resp = requests.patch(
            f"https://api.notion.com/v1/pages/{page_id}",
            headers=headers,
            json={"properties": props},
            timeout=60,

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 · scripts/pipeline.py (reported line 357)May include surrounding context.

python
if page_id:
        resp = requests.patch(
            f"https://api.notion.com/v1/pages/{page_id}",
            headers=headers,
            json={"properties": props},
            timeout=60,

External Transmission

Medium
Category
Data Exfiltration
Confidence
70% 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 · scripts/pipeline.py (reported line 307)May include surrounding context.

python
if not NOTION_DATABASE_ID:
        raise RuntimeError("NOTION_DATABASE_ID is required when creating a new page")

    resp = requests.post(
        "https://api.notion.com/v1/pages",
        headers=headers,
        json={"parent": {"database_id": NOTION_DATABASE_ID}, "properties": props},

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · scripts/pipeline.py (reported line 307)May include surrounding context.

python
if not NOTION_DATABASE_ID:
        raise RuntimeError("NOTION_DATABASE_ID is required when creating a new page")

    resp = requests.post(
        "https://api.notion.com/v1/pages",
        headers=headers,
        json={"parent": {"database_id": NOTION_DATABASE_ID}, "properties": props},

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill supports archiving all existing top-level children on a Notion page when --replace-children is used, which is a destructive content-modification capability beyond simple append behavior. In a pipeline meant to organize content, this can silently remove prior page material and cause data loss if the page ID is wrong or the operator misunderstands the flag.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The code invokes external binaries such as ffmpeg, ffprobe, and potentially the whisper CLI via subprocess to process media and transcription. While media processing is related to the pipeline's purpose, spawning host binaries is a stronger capability than the manifest text communicates and may be contextually surprising for a skill framed mainly as a Bilibili-to-Notion content pipeline.

Content

No source excerpt is available for this finding.