Back to skill

Security audit

圖書館管家 Plus

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but its Notion credential handling and page update scope need user review before installation.

Install only if you are comfortable granting a Notion integration access to your library database. Use a dedicated Notion integration shared only with that database, avoid putting real tokens in cron messages or shell history, rotate any token already used that way, and be cautious with title-based updates until database-scoped matching is fixed.

Vulnerability Patterns
  • 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 DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:81
Finding

Notion integration token exposed through command-line arguments and persistent cron configuration

Content
View full analysis
--database-id 。若有逾期書籍,主動發送到期提醒。" ``` The scripts require or accept the token as a command-line argument: ```python parser.add_argument("--api-key", required=True, help="Notion API Key") parser.add_argument("--database-id", required=True, help="Notion Database ID") ``` ### Technical Analysis Passing a Notion integration token through `--api-key` places the secret in the process argument vector. Depending on the operating-system and monitoring configuration, process arguments may be visible through process inspection, audit logs, crash reports, shell history, job execution logs, or administrative dashboards. The documented cron configuration is more serious because it embeds the token in the persisted `--message` payload. The credential therefore survives the current process and may be repeatedly disclosed whenever the job definition or execution history is inspected. Although recurring overdue checks are part of the declared functionality, persisting the credential inside the job text is not necessary. The README mentions environment variables, but the implementation does not read `NOTION_KEY` or `NOTION_DATABASE_ID` from the environment. Consequently, the documented command examples still expand those values into process arguments. ### Attack Path 1. A user follows ...[truncated 1196 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/notion_client.py:72
Finding

Workspace-wide Notion search can cause unauthorized mutation of unrelated pages

Content
View full analysis
list: body = {"query": query, "page_size": 10} if filter_object: body["filter"] = {"property": "object", "value": "page"} result = api_post("/search", body) return result.get("results", []) ``` The update operation selects the first returned page without confirming its parent database: ```python def cmd_update(args): global NOTION_KEY, DATABASE_ID NOTION_KEY = args.api_key or NOTION_KEY DATABASE_ID = args.database_id or DATABASE_ID if not args.page_id and args.title: pages = search_pages(args.title) if pages: args.page_id = pages[0]["id"] else: print(f"找不到書籍:{args.title}", file=sys.stderr) sys.exit(1) properties = {} if args.author is not None: properties["作者"] = make_text(args.author) if args.isbn is not None: properties["ISBN"] = make_text(args.isbn) if args.category is not None: properties["分類"] = make_select(args.category) if args.category else {"select": None} if args.tags is not None: properties["標籤"] = make_multi_select(args.tags.split(",")) if args.tags else {"multi_select": []} if args.pages_read is not None: properties["閱讀頁數"] = make_number(int(args.pages_read)) if args.total_pages is not None: properties["總頁數"] = make_number(int(args.total_pages)) if args.location is not None: properties["所在位置"] = make_text(args.location) if ...[truncated 2308 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export_books.py:35
Finding

CSV export allows spreadsheet formula injection

Content
View full analysis
dict: props = page["properties"] return { "書名": props.get("名稱", {}).get("title", [{}])[0].get("plain_text", ""), "作者": props.get("作者", {}).get("rich_text", [{}])[0].get("plain_text", ""), "ISBN": props.get("ISBN", {}).get("rich_text", [{}])[0].get("plain_text", ""), "分類": props.get("分類", {}).get("select", {}).get("name", ""), "標籤": ",".join([t["name"] for t in props.get("標籤", {}).get("multi_select", [])]), "借出給": props.get("借出給", {}).get("rich_text", [{}])[0].get("plain_text", ""), "借出日期": props.get("借出日期", {}).get("date", {}).get("start", ""), "預定還日": props.get("預定還日", {}).get("date", {}).get("start", ""), "歸還日期": props.get("歸還日期", {}).get("date", {}).get("start", ""), "總頁數": props.get("總頁數", {}).get("number", 0) or 0, "所在位置": props.get("所在位置", {}).get("rich_text", [{}])[0].get("plain_text", ""), "語言": props.get("語言", {}).get("select", {}).get("name", ""), "出版年份": props.get("出版年份", {}).get("number", 0) or 0, } def export_csv(books: list, output_path: str): if not books: print("沒有書籍可匯出", file=sys.stderr) sys.exit(1) fieldnames = ["書名", "作者", "ISBN", "分類", "標籤", "借出給", "借出日期", "預定還日", "歸還日期", "總頁數", "所在位置", "語言", "出版年份"] with open(output_path, "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(books) ``` ### Technical Analysis The CSV module correctly quotes structural CSV characters, but it does not neutralize spreadsheet formulas. Text obtained from Notion is written directly into cells. Spreadsheet software may treat cells beginning with `=`, `+`, `-`, or `@` as formulas. A malicious or com ...[truncated 1457 chars]
Remediation
View remediation

T03 · Remote Payload Retrieval and Execution

Warning
Location
scripts/reading_stats_dashboard.py:288
Finding

Generated dashboard retrieves and executes remote JavaScript without integrity verification

Content
View full analysis
\n' '\n' '\n' '\n' '\n' '📚 閱讀統計報告 — ' + today.strftime(DATE_FMT) + '\n' '' + css + '\n' '\n' '\n' '\n' + body + '\n' '\n' ) ``` ### Technical Analysis The generated local report loads executable JavaScript from jsDelivr each time the report is opened. Although the package version is specified, the script has no Subresource Integrity hash and the report has no restrictive Content Security Policy. Consequently, browser execution depends on code returned by an external service after the Skill itself has been reviewed. A compromise of the package release, CDN account, CDN infrastructure, DNS or trust chain could change the effective browser payload. Loading the remote asset also makes an outbound request when the local report is viewed, disclosing metadata such as the viewer's IP address and request headers to the CDN. ### Attack Path 1. The user generates a reading dashboard. 2. The user opens the local HTML file in a browser. 3. The browser requests Chart.js from jsDelivr. 4. The external source returns modified JavaScript because of an upstream or delivery-chain compromise. 5. The browser executes the returned code in the dashboard's origin context. 6. The malicious code can manipulate the report, make network requests, and access data available to that browser context, subject to browser restrictions. ### Impact Assessment Potential impact includes: - Execution of attacker-controlled JavaScript whe ...[truncated 423 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/reading_stats_dashboard.py:270
Finding

User-controlled log path is inserted into generated HTML without escaping

Content
View full analysis
\n' '

📈 月度閱讀趨勢

\n' ' ' + trend_html + '\n' ' \n\n' '
\n' '
「一本書的意義,不在於你讀了多少頁,而在於你改變了多少。」
\n' '
— 致持續閱讀的你 📚
\n' '
\n\n' '
\n' ' 圖書館管家 Plus bookshelf-plus · 自動生成 · ' + today.strftime(DATE_FMT) + '
\n' + (' 數據來源:' + log_path if log_path else '') + '\n' '
\n' '\n' ) ``` The path originates from a command-line argument: ```python parser.add_argument("--log", default="~/.bookshelf-plus/reading_log.json") parser.add_argument("--output", "-o", default="~/Downloads/reading_dashboard.html") args = parser.parse_args() log_path = Path(args.log).expanduser() if log_path.exists(): stats = load_stats_from_log(str(log_path)) print("📊 讀取 " + str(len(stats.get("sessions", []))) + " 筆記錄中...") else: print("⚠️ reading_log.json 不存在,將生成空白模板報告") stats = { "total_pages_read": 0, "total_minutes": 0, "avg_pages_per_day": 0, "books_read": 0, "sessions": [], "streak": {"current": 0, "longest": 0}, "daily_reading": {}, "monthly_data": [], "category_distribution": {}, "top_books": [], } html = generate_html(stats, str(log_path)) ``` ### Technical Analysis The value supplied through `--log` is converted to a string and concatenated directly into the report's HTML footer. It is not passed through HTML escaping. On filesystems that permit markup characters in filenames, an attacker can create a valid JSON log whose filename contains HTML. If that path is provided to the dashboard generator, the crafted markup becomes part of the generated docu ...[truncated 1208 chars]
Remediation
View remediation
`, `&`, single quotes, and double quotes. 6. Review future dashboard fields such as titles and categories before enabling them, because `_build_rings()` and `_build_bars()` also concatenate strings without escaping. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:69
Finding

Third-party Python dependencies are installed without version or integrity pinning

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (52)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

This variant includes a concrete security-relevant mismatch: the skill appears to use or depend on the Notion API and broader external operations while declaring no permissions. That discrepancy can cause silent overreach of networked data access and modification capabilities, making consent and sandboxing less reliable.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The README advertises automatic Notion synchronization and daily overdue reminder automation, but it does not clearly warn that book metadata, borrower names, due dates, and reminder content may be transmitted to third-party services or sent automatically without a fresh user confirmation. In an assistant/agent setting, this can lead to unintended disclosure of personal data or unexpected outbound notifications because users may enable the skill without understanding its external data flows and scheduled behavior.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill advertises and orchestrates capabilities that require shell execution, network access, and file writes, but it does not declare any corresponding tool scope or permission boundaries. This creates a transparency and least-privilege problem: operators and users cannot accurately assess what the skill may do, and a runtime that grants broad defaults could allow unintended external calls or local file modification.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The description mentions Open Library, Google Books, and Notion integration but does not clearly warn that book metadata, ISBNs, or reading-related data may be transmitted to external services. Users may reasonably assume local-only processing and disclose personal reading habits or inventory details without informed consent.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The trigger keywords are very broad and include common terms related to reading, books, and progress, increasing the chance the skill activates in unrelated conversations. In a skill that may invoke network services, cron-related workflows, or data synchronization, accidental triggering can expose reading/library data or launch unintended actions.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This code file contains natural-language docstrings and CLI/help text entirely in Traditional Chinese, which can amount to a language-policy violation when no user opt-in or locale justification is provided. The stated policy requires flagging skills that force a specific language without offering a choice.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The argparse help strings and console output shown to users are all fixed to one language. Because the file does not offer a language selection mechanism or explain a justified locale limitation, this matches the policy’s language/locale violation criterion.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

Line L134 says that adding the --dry-run parameter will perform the real write, but the code at L117-L120 does the opposite: when --dry-run is present, it skips Notion writes entirely. This is an active documentation contradiction that can mislead operators about whether the script will modify remote data.

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/batch_import.py (reported line 35)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/check_overdue.py (reported line 30)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/export_books.py (reported line 27)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/lending.py (reported line 38)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/lending.py (reported line 53)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/lending.py (reported line 180)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/library_report.py (reported line 28)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

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/notion_client.py (reported line 28)May include surrounding context.

python
def fetch_all_books() -> list:
    url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
    body = json.dumps({"page_size": 100}).encode()
    req = urllib.request.Request(url, data=body, headers=notion_headers(), method="POST")
    with urllib.request.urlopen(req, timeout=15) as r:

Static analysis

No suspicious patterns detected.