Back to skill

Security audit

Open Notebook

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent local bridge client for Open Notebook, but its documented bridge grants broader search and write/delete authority than its per-agent controls appear to enforce.

Review before installing. This skill can add and irreversibly delete notebook data, and the included bridge sample appears to let restricted agent keys search across all upstream notebooks and mutate data even when readwrite is false. Use only with a trusted local bridge, avoid storing secrets or sensitive PII, restrict access to agents.json and audit logs, and fix the bridge authorization checks before relying on per-agent isolation.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:200
Finding

Cross-Notebook Search Bypasses the Per-Agent Notebook Allowlist

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 200-204
Vulnerability Type: Missing object-level authorization
Risk Level: High

Vulnerable Code

python
@app.post("/v1/search")
async def search(request: Request, agent=Depends(auth)):
    body = await request.json()
    audit.info(f"{agent['name']} POST /v1/search")
    async with httpx.AsyncClient(timeout=120) as c:
        r = await c.post(f"{UPSTREAM}/api/search", json=body, headers={"Authorization": f"Bearer {ON_PASSWORD}"})
    return Response(content=r.content, status_code=r.status_code)

Technical Analysis

The search endpoint authenticates the calling agent but does not invoke check_notebook, constrain the upstream query to the agent's allowed_notebooks, or filter the returned results.

Other bridge endpoints are intended to enforce a per-agent notebook allowlist. In contrast, this endpoint forwards an unrestricted search request using the bridge's upstream credential. The upstream service therefore executes the query with the bridge's full access rather than the caller's limited access.

The documentation explicitly describes search as cross-notebook. However, cross-notebook search across all upstream notebooks exceeds the least privilege promised by the per-agent allowlist. Authentication alone is insufficient because authorization must also be applied to every returned search result.

Attack Path

  1. An attacker obtains any valid per-agent API key, including a key restricted to one notebook.
  2. The attacker sends targeted queries to POST /v1/search.
  3. The bridge authenticates the key but forwards the search directly to the upstream API without notebook restrictions.
  4. The upstream service searches embeddings across notebooks available to the bridge.
  5. Matches from notebooks outside the attacker's allowlist are returned unchanged.

Impact Assessment

A restricted agent may retrieve indexed content, ex ...[truncated 296 chars]

Remediation
View remediation

Remediation Suggestions

  • Pass the authenticated agent's permitted notebook IDs to the upstream search API when supported.
  • Defensively filter every search result by its notebook ID before returning it.
  • Reject search requests when a result cannot be reliably associated with an authorized notebook.
  • If the upstream API cannot enforce notebook scoping, perform separate searches only against permitted notebooks or disable cross-notebook search for restricted agents.
  • Add authorization tests proving that a key restricted to notebook A cannot discover content from notebook B.
  • Treat wildcard access as an explicit privileged configuration rather than the default.

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:144
Finding

Read-Only Agent Configuration Is Not Enforced on Mutating Endpoints

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, mutating endpoint definitions at lines 144-198; agent permission example at lines 218-225
Vulnerability Type: Missing function-level authorization
Risk Level: High

Vulnerable Code

python
@app.post("/v1/notebooks")
async def create_notebook(request: Request, agent=Depends(auth)):
    body = await request.json()
    audit.info(f"{agent['name']} POST /v1/notebooks")
    async with httpx.AsyncClient(timeout=120) as c:
        r = await c.post(f"{UPSTREAM}/api/notebooks", json=body, headers={"Authorization": f"Bearer {ON_PASSWORD}"})
    return Response(content=r.content, status_code=r.status_code)

@app.delete("/v1/notebooks/{notebook_id}")
async def delete_notebook(notebook_id: str, agent=Depends(auth)):
    check_notebook(agent, notebook_id)
    audit.info(f"{agent['name']} DELETE /v1/notebooks/{notebook_id}")
    async with httpx.AsyncClient(timeout=120) as c:
        r = await c.delete(f"{UPSTREAM}/api/notebooks/{notebook_id}", headers={"Authorization": f"Bearer {ON_PASSWORD}"})
    return Response(content=r.content, status_code=r.status_code)

@app.post("/v1/notebooks/{notebook_id}/sources")
async def add_source(notebook_id: str, request: Request, agent=Depends(auth)):
    check_notebook(agent, notebook_id)
    body = await request.json()
    audit.info(f"{agent['name']} POST /v1/notebooks/{notebook_id}/sources")
    async with httpx.AsyncClient(timeout=120) as c:
        r = await c.post(f"{UPSTREAM}/api/sources/json", json={**body, "notebook_id": notebook_id}, headers={"Authorization": f"Bearer {ON_PASSWORD}"})
    return Response(content=r.content, status_code=r.status_code)

@app.delete("/v1/sources/{source_id}")
async def delete_source(source_id: str, agent=Depends(auth)):
    # Verify the source belongs to an allowed notebook before deleting it
    async with httpx.AsyncClient(timeout=120) as c:
        r = await c.get(f
...[truncated 2400 chars]
Remediation
View remediation

Remediation Suggestions

  • Implement a centralized write-authorization dependency, for example:

    python
    async def require_write(agent=Depends(auth)):
        if agent.get("readwrite") is not True:
            raise HTTPException(403, "write access required")
        return agent
    
  • Apply the write dependency to every POST, PUT, PATCH, and DELETE endpoint.

  • Preserve the notebook allowlist check in addition to the write check; neither control replaces the other.

  • Deny write access by default when readwrite is absent, malformed, or false.

  • Consider replacing the Boolean with explicit scopes such as notebooks:read, notebooks:create, sources:write, and notebooks:delete.

  • Add negative tests for every mutating route using a read-only key.

  • Require explicit confirmation at the client or service layer for irreversible deletion operations.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:115
Finding

Authentication Handler Logs API-Key Fragments and Fails on Missing Headers

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 115-118
Vulnerability Type: Sensitive credential logging and improper null handling
Risk Level: Medium

Vulnerable Code

python
async def auth(x_api_key: str | None = Header(None)):
    if not x_api_key or x_api_key not in AGENTS:
        audit.warning(f"REJECTED unknown api key ...{x_api_key[-8:]}")
        raise HTTPException(401, "invalid api key")
    return AGENTS[x_api_key]

Technical Analysis

For an invalid nonempty key, the handler writes the final eight characters of the supplied credential to the audit log. Authentication secrets should not be logged, even partially, because logs commonly have broader access, longer retention, and external aggregation.

When the header is absent, x_api_key is None. Although the condition is entered, the logging expression still evaluates x_api_key[-8:], causing a TypeError before the intended HTTPException(401) is raised. An unauthenticated request can therefore trigger an internal server error instead of a controlled authentication failure.

Attack Path

Missing-header failure:

  1. Send a request to an authenticated route without the X-API-Key header.
  2. The condition evaluates as true because x_api_key is None.
  3. The handler attempts to slice None.
  4. FastAPI returns an internal error and may record a traceback.

Credential-fragment exposure:

  1. Submit invalid or guessed API keys to authenticated routes.
  2. The bridge records the last eight characters of each supplied value.
  3. Anyone with access to the audit log or its aggregation system can inspect those fragments.
  4. If a legitimate key is mistakenly submitted in an invalid context or removed from AGENTS, part of that credential is retained in logs.

Impact Assessment

Unauthenticated users can repeatedly trigger server-side exceptions, causing noisy logs and limited availability degradati ...[truncated 272 chars]

Remediation
View remediation

Remediation Suggestions

  • Never log API keys or key fragments.

  • Separate missing-header and invalid-key handling without applying string operations to nullable values:

    python
    async def auth(x_api_key: str | None = Header(None)):
        if not x_api_key:
            audit.warning("REJECTED request with missing API key")
            raise HTTPException(401, "invalid api key")
        agent = AGENTS.get(x_api_key)
        if agent is None:
            audit.warning("REJECTED request with invalid API key")
            raise HTTPException(401, "invalid api key")
        return agent
    
  • Use a generated request or event identifier for log correlation instead of credential-derived values.

  • Restrict audit-log permissions and retention, and avoid forwarding authentication data to external log processors.

  • Add tests for missing, empty, malformed, revoked, and invalid API keys.

  • Apply rate limiting to repeated authentication failures.

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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
80% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

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

md
## Compatibility

- **open-notebook:** v1.9.0 or later
- **Bridge endpoint contract:** `DELETE /v1/sources/{id}`, `GET /v1/sources/{id}`, `GET /v1/notebooks/{id}`  -  all required
- **Tested embedding model:** `perplexity/pplx-embed-v1-4b` (OpenRouter)

## When to use

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
86% confidence
Finding

The bridge exposes notebook deletion to any agent that passes check_notebook, but the sample agent config includes a readwrite field that is never enforced. As a result, any agent with notebook visibility can perform irreversible deletion if prompted or manipulated, turning a read-capable integration into a destructive one.

Content

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

md
@app.delete("/v1/notebooks/{notebook_id}")
async def delete_notebook(notebook_id: str, agent=Depends(auth)):
    check_notebook(agent, notebook_id)
    audit.info(f"{agent['name']} DELETE /v1/notebooks/{notebook_id}")
    async with httpx.AsyncClient(timeout=120) as c:
        r = await c.delete(f"{UPSTREAM}/api/notebooks/{notebook_id}", headers={"Authorization": f"Bearer {ON_PASSWORD}"})
    return Response(content=r.content, status_code=r.status_code)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
88% confidence
Finding

Source deletion is allowed after only notebook membership is checked, with no enforcement of a separate write/delete privilege. Because source IDs can be obtained through normal use, an agent authorized to read a notebook can also irreversibly delete its contents, increasing the blast radius of prompt injection, user confusion, or agent misuse.

Content

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

md
return Response(content=r.content, status_code=404)
        else:
            r.raise_for_status()
        audit.info(f"{agent['name']} DELETE /v1/sources/{source_id}")
        r = await c.delete(f"{UPSTREAM}/api/sources/{source_id}", headers={"Authorization": f"Bearer {ON_PASSWORD}"})
    return Response(content=r.content, status_code=r.status_code)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/on.sh (reported line 7)May include surrounding context.

sh
set -euo pipefail

BRIDGE_URL="${OPEN_NOTEBOOK_BRIDGE_URL:-http://127.0.0.1:5077}"
API_KEY="${OPEN_NOTEBOOK_API_KEY:?OPEN_NOTEBOOK_API_KEY not set  -  add it to ~/.openclaw/.env and restart the gateway}"

call() {
  local method="$1" path="$2" body="${3:-}"

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · scripts/on.sh (reported line 94)May include surrounding context.

sh
delete-source)
    [[ $# -ge 2 ]] || { echo "usage: $0 delete-source ID" >&2; exit 1; }
    call DELETE "/v1/sources/$2"
    ;;

  delete-notebook)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · scripts/on.sh (reported line 99)May include surrounding context.

sh
delete-notebook)
    [[ $# -ge 2 ]] || { echo "usage: $0 delete-notebook ID" >&2; exit 1; }
    call DELETE "/v1/notebooks/$2"
    ;;

  *)

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

md
# open-notebook-skill

**Open Notebook skill for OpenClaw**  -  bridge client for [lfnovo/open-notebook](https://github.com/lfnovo/open-notebook). Create themed notebooks, add sources, cross-notebook search, and RAG-chat with your research notes.

## What this is

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

md
# Create agents.json  -  add your OpenClaw agent key
echo '{"<your-openclaw-agent-key>":{"name":"onyx","allowed_notebooks":"*","readwrite":true}}' > agents.json
chmod 600 agents.json

# Create bridge.env
echo 'OPEN_NOTEBOOK_PASSWORD=<your-open-notebook-password>' > bridge.env

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

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

echo 'AGENTS_FILE=/open-notebook/bridge/agents.json' >> bridge.env echo 'AUDIT_LOG=/open-notebook/bridge/audit.log' >> bridge.env echo 'BRIDGE_PORT=5077' >> bridge.env chmod 600 bridge.env

text

### 3. Install the bridge service

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

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

bash
systemctl --user daemon-reload
systemctl --user enable --now open-notebook-bridge

4. Get your agent key

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

md
---
name: open-notebook
description: Access and manage a self-hosted Open Notebook research system (NotebookLM alternative). Create notebooks, add sources (text/URL/file), cross-notebook search, and RAG-chat with your research notes. Use only when the user explicitly asks to save, search, or ask about specific content they have stored.
version: 1.3.2
homepage: "https://github.com/Crabsticksalad/open-notebook-skill"
permissions:

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The auth handler logs x_api_key[-8:] even when the header is absent, but x_api_key is declared as nullable and can be None. A request without X-API-Key can therefore trigger a TypeError before the intended 401 is raised, causing a server error/DoS condition and potentially bypassing expected audit behavior for rejected requests.

Content

No source excerpt is available for this finding.

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/on.sh (reported line 12)May include surrounding context.

sh
call() {
  local method="$1" path="$2" body="${3:-}"
  if [[ -n "$body" ]]; then
    curl -sf --max-time 120 -X "$method" \
      -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
      -d "$body" "${BRIDGE_URL}${path}"
  else