Back to skill

Security audit

Notion Database Automation

Security checks for vulnerabilities and agentic risk

Overview

This Notion automation skill mostly does what it claims, but it handles and mutates workspace data with weak safety boundaries around exports, destructive actions, and token handling.

Review carefully before installing. Use a least-privilege Notion integration shared only with the needed databases, prefer OpenClaw secrets or environment injection over --api-token or hardcoded tokens, avoid exporting sensitive databases to shared paths, treat exported CSVs as potentially active spreadsheet content, and require explicit review before any bulk update or archive/delete workflow.

Vulnerability Patterns
  • 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
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/batch_create.py:153
Finding
Notion API Token Can Be Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/batch_create.py`, lines 153-171 **Vulnerability Type**: Sensitive credential exposure through process arguments and shell history **Risk Level**: Medium ### Complete Code Snippet ```python def main(): parser = argparse.ArgumentParser(description='Batch create Notion database entries from CSV.') parser.add_argument('--api-token', help='Notion API token') parser.add_argument('--database-id', required=True, help='Notion database ID') parser.add_argument('--csv', required=True, help='CSV file to import') parser.add_argument('--title-column', required=True, help='Column name for page title') parser.add_argument('--title-property', default='Name', help='Property name for title in Notion') # Schema mapping should be in a separate JSON file parser.add_argument('--schema', required=True, help='JSON file with CSV column -> Notion property mapping') args = parser.parse_args() with open(args.schema, 'r') as f: schema = json.load(f) api_token = args.api_token or os.environ.get('NOTION_API_TOKEN') if not api_token: raise ValueError("API token required via --api-token or NOTION_API_TOKEN environment variable") ``` ### Technical Analysis The CLI accepts a Notion bearer token through `--api-token`. Command-line arguments can be exposed through: - Process inspection facilities such as `ps`, `/proc/<pid>/cmdline`, or system monitoring software. - Shell history files when a user enters the token directly in a command. - CI/CD job logs, command tracing, terminal recordings, and process audit logs. - Diagnostic output collected by orchestration or endpoint-management systems. The token is subsequently placed in the `Authorization` header by `NotionDBClient`. Sending it to the fixed HTTPS endpoint `https://api.notion.com/v1` is necessary for the declared functionality; the security issue is the optional command-line credential transpo ...[truncated 1328 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `--api-token` argument from both `scripts/batch_create.py` and `scripts/export_csv.py`. 2. Prefer a dedicated secret manager or the documented OpenClaw secret facility. 3. If environment variables remain supported, document that they should be injected by the runtime rather than placed inline in a shell command. 4. Optionally support interactive entry through `getpass.getpass()` for local use. 5. Never print, serialize, or include the token in exception messages. 6. Configure the Notion integration with access only to the specific databases required by the workflow. 7. Rotate any token that has previously been passed through command-line arguments or exposed in CI logs. 8. Add credential-redaction rules to CI/CD and process-monitoring systems. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/export_csv.py:93
Finding
Exported Notion Values Are Vulnerable to CSV Formula Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/export_csv.py`, lines 93-99 **Vulnerability Type**: CSV formula injection **Risk Level**: Medium ### Complete Code Snippet ```python with open(output_file, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow(prop_names) for entry in entries: row_values = [get_property_value(entry, prop) for prop in prop_names] writer.writerow(row_values) ``` The exported values are returned without spreadsheet-specific neutralization. For example: ```python elif prop_type == 'rich_text': if not prop['rich_text']: return "" return " ".join([t['plain_text'] for t in prop['rich_text']]) ``` ### Technical Analysis `csv.writer` correctly performs CSV quoting, but CSV quoting does not prevent spreadsheet formula interpretation. Values beginning with formula indicators such as `=`, `+`, `-`, or `@` may be treated as formulas when the exported file is opened in spreadsheet software. Notion properties can contain attacker-controlled content. The exporter writes titles, rich text, URLs, email addresses, phone numbers, and select names directly to CSV. No check is made for dangerous leading characters, including cases where whitespace, tabs, or control characters precede the formula marker. Depending on the spreadsheet client and its security configuration, a malicious formula may: - Trigger outbound network requests and disclose selected cell contents. - Present deceptive links or content to the user. - Manipulate displayed spreadsheet results. - Invoke legacy external-data or command mechanisms in clients where those features remain enabled. This issue does not arise from the necessary Notion network traffic. It arises when externally controlled Notion content crosses into a formula-capable CSV consumer without neutralization. ### Attack Path 1. An attacker obtains permission to create or edit an entry in the Notion database, or supplies ...[truncated 993 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Neutralize every exported text value that begins with a spreadsheet formula marker. 2. Check after stripping or accounting for leading spaces, tabs, carriage returns, and other control characters. 3. Prefix dangerous values with an apostrophe or use a documented neutralization method appropriate for the supported spreadsheet clients. 4. Apply sanitization to property names as well as row values. 5. Preserve an explicit raw-export mode only if users are clearly warned that the output must not be opened in formula-capable software. 6. Consider exporting JSON for untrusted content because JSON consumers do not normally evaluate values as spreadsheet formulas. 7. Add tests covering values beginning with `=`, `+`, `-`, `@`, tab, carriage return, and combinations of whitespace with formula markers. 8. Document that CSV files containing untrusted Notion content should be treated as potentially active content. Example hardening logic: ```python def neutralize_csv_formula(value: str) -> str: if not isinstance(value, str): return value candidate = value.lstrip(" \t\r\n") if candidate.startswith(("=", "+", "-", "@")): return "'" + value return value ``` Apply the function to both headers and data: ```python writer.writerow([neutralize_csv_formula(name) for name in prop_names]) for entry in entries: row_values = [ neutralize_csv_formula(get_property_value(entry, prop)) for prop in prop_names ] writer.writerow(row_values) ``` ]]>

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding
Dependency Is Not Pinned or Integrity-Verified<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt`, line 1 **Vulnerability Type**: Unbounded third-party dependency and non-reproducible installation **Risk Level**: Low ### Complete Code Snippet ```text requests>=2.28.0 ``` ### Technical Analysis The dependency declaration permits any current or future version of `requests` greater than or equal to 2.28.0. It also provides no package hashes or lock file. Consequently, separate installations can resolve to different package versions even when the project itself has not changed. A future compromised, malicious, or incompatible release accepted by this range would be installed automatically. Resolution also depends on the configured Python package index, so a compromised mirror or index configuration could affect the selected artifact. No evidence was found that `requests` itself is malicious. This is a supply-chain hardening and reproducibility weakness rather than evidence of an intentionally malicious dependency. ### Attack Path 1. A user or automated build installs the project dependencies at a later date. 2. The package resolver selects the newest release satisfying `requests>=2.28.0`. 3. A compromised release or artifact from the configured package source is downloaded because no exact version or expected hash is enforced. 4. Installation or subsequent import executes attacker-controlled package code. 5. That code runs with the privileges of the user, CI worker, container, or deployment account performing the operation. ### Impact Assessment If dependency distribution were compromised, code could execute in the installation or runtime environment with the privileges of the invoking process. Because this Skill handles a Notion API token and database content, malicious dependency code could potentially read the token, inspect imported or exported records, alter network requests, and access other files or credentials available to the process. Exploitation requires compromise or s ...[truncated 105 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `requests` to a reviewed exact version instead of using an open-ended lower bound. 2. Generate a lock file containing transitive dependencies. 3. Use hash-verified installation, such as a requirements file generated with hashes and installed with `pip --require-hashes`. 4. Depend only on trusted HTTPS package indexes and disable unexpected extra indexes. 5. Add automated dependency vulnerability and update scanning. 6. Review and deliberately update the lock file on a controlled schedule rather than accepting new releases automatically. 7. Build dependencies in an isolated, minimally privileged environment without production secrets. A hardened generated requirements entry should pin the version and include hashes, conceptually: ```text requests==<reviewed-version> \ --hash=sha256:<verified-distribution-hash> ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (19)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code clearly supports one subset of the declared description: batch creation of Notion database entries from CSV/JSON. However, it does not implement data filtering, content generation, export functionality, or synchronization with other tools. The primary behavior is narrower than the declared purpose. There is no evidence of unrelated or dangerous undeclared capabilities beyond Notion page creation, but the description materially overstates what this code chunk does.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill advertises capabilities that imply environment access, local file reads/writes, and network communication, but it does not declare any explicit tool scope or permission boundaries. This is dangerous because an agent or operator cannot easily verify the minimum required privileges, increasing the risk of overbroad execution, unintended data access, or misuse of credentials and exported data.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill promotes exporting entire Notion databases and syncing data with other tools without any privacy or data-handling warning. This is dangerous because database contents may include sensitive internal notes, customer records, or PII, and exporting or syncing broad datasets increases the chance of unintended disclosure or insecure downstream storage.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly supports bulk archive or delete operations but does not warn users about irreversible or large-scale data loss. In the context of Notion database automation, batch actions can affect many records quickly, so missing safety guidance materially raises the risk of accidental destruction of business data.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The authentication guidance explicitly says tokens can be passed directly in code, but it does not warn that hardcoding credentials can expose secrets through source control, logs, screenshots, or shared snippets. Because this skill uses a Notion API token with access to shared databases, leaked credentials could allow unauthorized reading, modification, export, or deletion of workspace data.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This code sends row data to the Notion API via create_page, which transmits user-provided CSV contents to an external service. While it prints after each page is created, there is no prior user disclosure in the script comments/docstrings or an explicit warning at the operation point that local file contents will be uploaded.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The create_from_json function transmits properties derived from the JSON file to Notion using create_page. Although the file docstring describes creating entries, it does not explicitly warn that the contents of the local JSON file will be sent to a remote service.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This script exports all queried database rows and properties directly to a local CSV file without any warning, confirmation, or minimization controls. In the context of a Notion database automation skill, that behavior is functional but can still cause unintended disclosure of sensitive business or personal data if users export a full database to an insecure path or shared system.

External Transmission

Medium
Category
Data Exfiltration
Content
if not self.api_token:
            raise ValueError("API token required. Set NOTION_API_TOKEN or pass it explicitly.")
        
        self.base_url = "https://api.notion.com/v1"
        self.headers = {
            "Authorization": f"Bearer {self.api_token}",
            "Notion-Version": "2022-06-28",
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
if start_cursor:
                payload["start_cursor"] = start_cursor
            
            response = requests.post(
                f"{self.base_url}/databases/{database_id}/query",
                headers=self.headers,
                json=payload
Confidence
80% 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
86% confidence
Finding
This client sends database queries and page content to the Notion API through multiple HTTP requests, but there is no visible print/log statement or warning in the code that user or system data is being transmitted externally. The same pattern appears across create, update, archive, schema, and content retrieval methods, so users may not be informed that their data is being sent to a third-party service.

External Transmission

Medium
Category
Data Exfiltration
Content
if children:
            payload["children"] = children
        
        response = requests.post(
            f"{self.base_url}/pages",
            headers=self.headers,
            json=payload
Confidence
80% 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
88% confidence
Finding
The archive_page method performs a destructive state change by archiving or unarchiving a Notion page, but the code provides no confirmation prompt, logging, or explicit warning beyond a brief docstring. For code files, destructive or irreversible operations should have some form of user disclosure unless clearly surfaced elsewhere.

Unpinned Dependencies

Low
Category
Supply Chain
Content
requests>=2.28.0
Confidence
94% confidence
Finding
The dependency is specified as `requests>=2.28.0`, which allows any future version to be installed and makes builds non-reproducible. This can unintentionally pull in vulnerable or breaking releases over time, increasing supply-chain risk even though the file itself does not force a known-bad version.

Unverifiable Dependency: requests has 16 known advisory(ies) (CVE-2014-1830 (Exposure of Sensitive Information to an Unauthorized Actor in Requests); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
89% confidence
Finding
Because `requests` is not pinned, it is impossible to verify from this manifest whether the installed version is affected by any of the listed advisories. In a skill that may sync data with external tools and services, an unverified HTTP client dependency increases the chance of credential leakage, TLS/verification issues, or other known library flaws entering the deployment unnoticed.

Missing User Warnings

Low
Confidence
79% confidence
Finding
This line reads an API token from an environment variable, which is a sensitive credential access pattern covered by the warning requirement for code files. The code raises an error if the token is absent, but it does not clearly disclose or document that the script will read credentials from the environment.

Context-Inappropriate Capability

Low
Confidence
82% confidence
Finding
The manifest describes automating Notion database operations such as creation, filtering, generation, and export. Reading credentials from the host environment is not part of that stated functional purpose; it is an additional capability to access local runtime secrets. While common operationally, it is still outside the declared user-facing scope.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The script accesses the sensitive NOTION_API_TOKEN credential from the environment, but there is no accompanying comment, docstring, or warning describing this credential dependency or advising safe handling. For code files, access to sensitive environment variables should have some visible disclosure unless already documented elsewhere.

Missing User Warnings

Low
Confidence
81% confidence
Finding
The initializer reads NOTION_API_TOKEN from the environment, which is a credential access pattern covered by the missing-warning rule for code files. Although the docstring documents the source, it does not provide a user-facing warning or disclosure about handling a sensitive credential.

Static analysis

No suspicious patterns detected.