Back to skill

Security audit

Local Ai Search

Security checks for vulnerabilities and agentic risk

Overview

This is a real local document-search skill, but its default service exposure, unrestricted upload endpoint, and scheduled-sync implementation create privacy and persistence risks that need review before installation.

Install only if you are comfortable with a Khoj service indexing selected local documents, storing sync state under ~/.khoj, and sending query content to configured cloud LLM providers. Before use, bind Khoj to 127.0.0.1, avoid anonymous mode on reachable networks, keep KHOJ_URL on a trusted loopback or authenticated HTTPS endpoint, review files before indexing or syncing, and avoid enabling scheduled sync until the cron construction is fixed.

Vulnerability Patterns
  • 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
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/schedule_sync.sh:36
Finding
Persistent Command Execution Through Unsafe Cron Entry Construction<![CDATA[ ## Vulnerability Details **File Location**: `scripts/schedule_sync.sh:36-60` and `scripts/schedule_sync.sh:112-120` **Vulnerability Type**: Cron command injection **Risk Level**: High ### Vulnerable Code ```bash setup_cron() { local directory="$1" local interval="$2" # Validate directory if [ ! -d "$directory" ]; then echo -e "${RED}Error: directory does not exist - $directory${NC}" exit 1 fi # Create log directory mkdir -p "$LOG_DIR" # Create cron task local cron_job="0 */$interval * * * $SYNC_SCRIPT \"$directory\" >> \"$LOG_FILE\" 2>&1" # Check whether a task already exists if crontab -l 2>/dev/null | grep -q "$SYNC_SCRIPT"; then echo -e "${YELLOW}Scheduled task already exists; updating...${NC}" crontab -l 2>/dev/null | grep -v "$SYNC_SCRIPT" | crontab - fi # Add the new task (crontab -l 2>/dev/null; echo "$cron_job") | crontab - } ``` The interval is accepted without validation: ```bash INTERVAL=1 while [[ $# -gt 0 ]]; do case $1 in --interval) INTERVAL="$2" shift 2 ;; ``` ### Technical Analysis The script interpolates the user-controlled `interval` and `directory` values directly into a crontab command. Cron executes task commands through a shell, so quoting the directory with double quotes does not prevent command substitution, variable expansion, or quote termination when the task later runs. The `interval` parameter can also contain spaces or newline characters because it is not restricted to an integer. Such input can alter the cron schedule or introduce an additional cron entry. The directory existence check does not eliminate the issue. Unix paths can contain spaces, quotes, dollar signs, parentheses, and other shell-significant characters. An attacker can create a directory whose literal name contains a command-substitution expression and then register that directory. Sched ...[truncated 1488 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `interval` as a bounded decimal integer before constructing the schedule: ```bash if ! [[ "$interval" =~ ^[0-9]+$ ]] || (( interval < 1 || interval > 24 )); then echo "Invalid interval" >&2 exit 1 fi ``` 2. Reject directory values containing newlines, carriage returns, NUL-equivalent input, or other characters that cannot be represented safely in a crontab entry. 3. Do not embed an arbitrary directory directly into a shell command. Store the canonical directory in a user-owned configuration file with mode `0600`, and install a fixed cron command that reads the configuration without evaluating it as shell syntax. 4. If direct insertion is unavoidable, use a rigorously tested shell-quoting routine rather than double quotes alone. 5. Resolve and validate the directory with `realpath`, and ensure the cron wrapper treats the resulting value strictly as data. 6. Add a unique marker to the managed cron entry and modify only that exact entry rather than removing every line that happens to contain the script path. 7. Display the complete proposed cron entry and require explicit confirmation before installation. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
config.yaml:50
Finding
Anonymous Khoj Service Configuration Can Expose Private Indexed Documents to the Network<![CDATA[ ## Vulnerability Details **File Location**: `config.yaml:50-53`, `scripts/start_server.sh:32-35`, and `SKILL.md:67,241` **Vulnerability Type**: Unauthenticated network service exposure **Risk Level**: High ### Vulnerable Code The supplied default configuration binds the service to every network interface: ```yaml # Service configuration server: host: 0.0.0.0 port: 42110 ``` The startup script explicitly enables anonymous mode: ```bash # Start service using embedded PostgreSQL export USE_EMBEDDED_DB="true" nohup khoj --anonymous-mode > /tmp/khoj.log 2>&1 & ``` The same anonymous startup mode is recommended in the Skill instructions: ```bash export USE_EMBEDDED_DB="true" khoj --anonymous-mode ``` ### Technical Analysis The declared functionality is local document search, for which listening only on the loopback interface is sufficient. Binding to `0.0.0.0` makes the service eligible to receive connections through every active interface, including local networks, VPN interfaces, container bridges, and potentially public interfaces. At the same time, the recommended startup path enables Khoj's anonymous mode. If Khoj loads the supplied host configuration or otherwise binds beyond loopback, the combination can expose content, search, chat, and indexing endpoints without user authentication. Even where the specific Khoj release defaults to loopback, the project configuration encourages an unsafe deployment state and does not explicitly force a loopback bind in either startup implementation. ### Attack Path 1. A user copies or activates the supplied configuration and starts Khoj using the documented anonymous-mode command. 2. The service listens on port `42110` on one or more non-loopback interfaces. 3. An attacker on a reachable network discovers the open port. 4. The attacker accesses unauthenticated Khoj endpoints. 5. Depending on the endpoint behavior and Khoj version, the attacker searches indexed content, retrieves metadata or document ...[truncated 642 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Change the default host to loopback: ```yaml server: host: 127.0.0.1 port: 42110 ``` 2. Pass an explicit loopback bind option in both `scripts/start_server.sh` and `khoj_cli.py` rather than relying on external configuration or dependency defaults. 3. Disable anonymous mode by default and require a strong API token. 4. Apply authentication consistently to health, statistics, search, chat, content-listing, and content-upload endpoints where supported. 5. If remote access is required, place Khoj behind a TLS-enabled authenticated reverse proxy and restrict source networks with firewall rules. 6. Print the actual listening interfaces after startup and warn prominently if the service is reachable beyond loopback. 7. Document anonymous mode as a local-development option only, not as the recommended production configuration. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
khoj_cli.py:21
Finding
Environment-Controlled Plaintext Endpoint Can Receive Documents, Queries, and Bearer Tokens<![CDATA[ ## Vulnerability Details **File Location**: `khoj_cli.py:21-32,78-91`, `scripts/sync.py:38-39,181-231`, and `scripts/query.py:19-33,49-57` **Vulnerability Type**: Sensitive-data transmission to an unrestricted endpoint **Risk Level**: High ### Vulnerable Code The primary CLI accepts an arbitrary service URL from the environment and attaches a bearer token: ```python KHOJ_URL = os.environ.get("KHOJ_URL", "http://localhost:42110") KHOJ_API_KEY = os.environ.get("KHOJ_API_KEY", "") class KhojClient: def __init__(self, base_url: str = KHOJ_URL, api_key: str = KHOJ_API_KEY): self.base_url = base_url.rstrip("/") self.api_key = api_key self.headers = {"Authorization": f"Bearer {api_key}"} if api_key else {} ``` It uploads complete files to that endpoint: ```python for i, file_path in enumerate(files): try: with open(file_path, "rb") as f: response = requests.patch( f"{self.base_url}/api/content", headers=self.headers, files={"file": (file_path.name, f)}, timeout=60 ) response.raise_for_status() success_count += 1 ``` The synchronization client has the same behavior: ```python KHOJ_URL = os.environ.get("KHOJ_URL", "http://localhost:42110") KHOJ_API_KEY = os.environ.get("KHOJ_API_KEY", "") class KhojSyncClient: def __init__(self, base_url: str = KHOJ_URL): self.base_url = base_url.rstrip('/') self.headers = {} if KHOJ_API_KEY: self.headers["Authorization"] = f"Bearer {KHOJ_API_KEY}" ``` ```python if converted_content: files = {'files': (file_path.name, converted_content, mime_type)} else: with open(file_path, 'rb') as f: files = {'files': (file_path.name, f.read(), mime_type)} response = requests.patch( f"{self.base_url}/api/content", headers=self.headers, files=files, timeout=API_TIMEOUT ) ``` The standalone query utility also se ...[truncated 2645 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Accept loopback destinations by default and reject non-loopback hosts unless the user explicitly enables remote operation. 2. Parse the URL using a standard URL parser and validate the scheme, hostname, port, username, and resolved IP address. 3. Require HTTPS for every non-loopback destination. 4. Display the exact destination and data classes to be uploaded, then require explicit confirmation before the first remote upload. 5. Maintain separate credentials for local and remote services and do not automatically forward a local bearer token after a host change. 6. Consider an allowlist of approved Khoj hosts or certificate fingerprints. 7. Prevent proxy-environment variables from unexpectedly routing sensitive localhost requests where appropriate. 8. Add a dry-run mode that lists files before transmission. 9. Clearly distinguish local indexing from cloud-assisted chat in the user interface and privacy documentation. 10. Provide exclusion rules for sensitive directories, hidden files, and document patterns before recursive synchronization. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:4
Finding
Open-Ended Dependency Constraints Permit Installation of Unreviewed Future Releases<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:4-14` **Vulnerability Type**: Unpinned executable third-party dependencies **Risk Level**: Medium ### Vulnerable Code ```text # Core khoj>=0.1.0 markitdown>=0.1.0 # Office document support openpyxl>=3.1.0 python-pptx>=0.6.21 python-docx>=1.1.0 # CLI click>=8.0.0 requests>=2.28.0 ``` The documentation also instructs users to install packages without reviewed versions: ```bash pip install khoj "markitdown[xlsx,pptx]" pip install khoj "markitdown[xlsx,pptx]" requests ``` ### Technical Analysis Every dependency has only a lower bound or no version constraint. Package resolution may therefore install any future release published under these package names. No lockfile or integrity hashes are supplied. These dependencies are security-sensitive: Khoj runs a network service, MarkItDown and the Office libraries parse complex user documents, and Requests transmits document contents and credentials. A compromised upstream release, malicious maintainer update, or unexpectedly incompatible version would execute within the user's environment with the same permissions as the Skill. This finding does not establish that any currently named package is malicious. It identifies a supply-chain control weakness that allows the installed code to change after the Skill itself has been reviewed. ### Attack Path 1. A dependency account, publishing workflow, or package release is compromised, or a future release introduces malicious behavior. 2. A user follows the supplied installation instructions or rebuilds the environment. 3. The package resolver selects the newest release satisfying the open-ended constraint. 4. Installation hooks, imported module code, parser code, or service code executes under the user's account. 5. The compromised dependency gains access to documents processed by the Skill, environment variables containing API keys, and network connectivity. ### Impact Assessment A malicious depe ...[truncated 374 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin each direct dependency to a reviewed exact version. 2. Generate a lockfile that includes transitive dependencies. 3. Require package hashes, for example through a hash-locked requirements file and `pip install --require-hashes`. 4. Install dependencies in an isolated virtual environment rather than a privileged or shared Python installation. 5. Use an automated dependency-update process that runs security scans and tests before accepting upgrades. 6. Record the trusted package index explicitly and disable unexpected additional indexes to reduce dependency-confusion risk. 7. Pin optional OCR and document-conversion dependencies in the same manner. 8. Update README and Skill installation commands to install from the reviewed lockfile rather than unconstrained package names. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (85)

Tainted flow: 'KHOJ_URL' from os.environ.get (line 21, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
# 获取统计信息
    try:
        response = requests.get(f"{KHOJ_URL}/api/content/stats", timeout=5)
        if response.status_code == 200:
            stats = response.json()
            click.echo(f"文档数: {stats.get('document_count', 'N/A')}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'KHOJ_URL' from os.environ.get (line 19, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
headers["Authorization"] = f"Bearer {KHOJ_API_KEY}"
    
    try:
        response = requests.get(
            f"{KHOJ_URL}/api/search",
            params={"q": query, "n": top_k},
            headers=headers,
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'KHOJ_URL' from os.environ.get (line 19, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
headers["Authorization"] = f"Bearer {KHOJ_API_KEY}"
    
    try:
        response = requests.post(
            f"{KHOJ_URL}/api/chat",
            json={"q": query},
            headers=headers,
Confidence
90% confidence
Finding
The chat path sends user prompts over HTTP to a configurable endpoint from KHOJ_URL, so an attacker who can influence that environment variable can redirect sensitive local-search queries to an unintended server. This is more concerning in the context of a skill advertised as local file search, because users may reasonably expect queries to stay local and may not realize the tool can transmit them to another host.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill’s real behavior includes indexing, synchronization, state persistence, API calls, OCR/conversion pipelines, and broader file-type handling than declared. This gap matters because users expecting a simple local search tool may unknowingly authorize mass ingestion, persistent storage, and broader document processing than intended.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill’s real behavior includes indexing, synchronization, state persistence, API calls, OCR/conversion pipelines, and broader file-type handling than declared. This gap matters because users expecting a simple local search tool may unknowingly authorize mass ingestion, persistent storage, and broader document processing than intended.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill’s real behavior includes indexing, synchronization, state persistence, API calls, OCR/conversion pipelines, and broader file-type handling than declared. This gap matters because users expecting a simple local search tool may unknowingly authorize mass ingestion, persistent storage, and broader document processing than intended.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill’s real behavior includes indexing, synchronization, state persistence, API calls, OCR/conversion pipelines, and broader file-type handling than declared. This gap matters because users expecting a simple local search tool may unknowingly authorize mass ingestion, persistent storage, and broader document processing than intended.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The skill’s real behavior includes indexing, synchronization, state persistence, API calls, OCR/conversion pipelines, and broader file-type handling than declared. This gap matters because users expecting a simple local search tool may unknowingly authorize mass ingestion, persistent storage, and broader document processing than intended.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill’s real behavior includes indexing, synchronization, state persistence, API calls, OCR/conversion pipelines, and broader file-type handling than declared. This gap matters because users expecting a simple local search tool may unknowingly authorize mass ingestion, persistent storage, and broader document processing than intended.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
click.echo("✓ Khoj 服务已在运行")
        return
    
    env = os.environ.copy()
    env["USE_EMBEDDED_DB"] = "true"
    
    cmd = ["khoj"]
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
Host process termination via `pkill` is not justified by a narrow local-search role and can impact unrelated processes. In the context of an agent skill, this kind of system-control behavior is especially risky because it violates least privilege and user expectations.

Context-Inappropriate Capability

High
Confidence
91% confidence
Finding
The skill executes external programs and shell scripts for document conversion, sync, and scheduling, which broadens privileges and behavior well beyond search. In agent contexts this is dangerous because it introduces supply-chain risk, persistence mechanisms, and host modification capabilities under a deceptively simple interface.

Description-Behavior Mismatch

High
Confidence
97% confidence
Finding
This script performs bulk document conversion and writes Markdown outputs to disk, which is materially different from the declared skill purpose of natural-language local file search. In an agent context, extra file-processing capabilities increase the attack surface and could enable unintended mass extraction or staging of local document contents for later indexing or exfiltration.

Context-Inappropriate Capability

High
Confidence
95% confidence
Finding
The code recursively traverses an input directory, converts supported documents, and writes extracted text to a parallel output tree. For a search skill, this is a high-risk overbroad capability because it can duplicate large volumes of sensitive local content into new Markdown files, expanding exposure and persistence without clear necessity.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The script modifies the user's crontab to establish recurring execution of sync.py, which is a persistence mechanism. Persistence is security-sensitive because it causes code to run automatically in the future and can be abused to repeatedly access local files or continue operating after the user forgets it was enabled.

YARA rule 'backdoor_persistence': Backdoor persistence with malicious payloads (shell commands, SSH key injection, hidden root users) [malware]

High
Category
YARA Match
Content
irectory="$1"
    local interval="$2"
    
    # 验证目录
    if [ ! -d "$directory" ]; then
        echo -e "${RED}错误: 目录不存在 - $directory${NC}"
        exit 1
    fi
    
    # 创建日志目录
    mkdir -p "$LOG_DIR"
    
    # 创建 cron 任务
    local cron_job="0 */$interval * * * $SYNC_SCRIPT \"$directory\" >> \"$LOG_FILE\" 2>&1"
    
    # 检查是否已存在
    if crontab -l 2>/dev/null | grep -q "$SYNC_SCRIPT"; then
        echo -e "${YELLOW}定时任务已存在,正在更新...${NC}"
        # 移除旧任务
        crontab -l 2>/dev/null | grep -v "$SYNC_SCRIPT" | crontab -
    fi
    
    # 添加新任务
    (crontab -l 2>/dev/null; echo "$cron_job") | crontab -
    
    echo -e "${GREEN}✓ 已设置定时同步${NC}"
    echo "  目录: $directory"
    echo "  间隔: 每 $interval 小时"
    echo "  日志: $LOG_FILE"
    echo ""
    echo "查看日志: tail -f $LOG_FILE"
}

disable_cron() {
    if crontab -l 2>/dev/null | grep -q "$SYNC_SC
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The script is presented as a local file search capability, but it reads local documents and uploads their contents to a Khoj HTTP endpoint for indexing. Even if the default target is localhost, the endpoint is configurable via environment variable, so sensitive local data can be sent to a different service without clear disclosure, making this a significant privacy and data-exposure issue in the skill context.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The natural-language instructions, triggers, and usage guidance are entirely in Chinese, and the trigger section assumes Chinese-language invocation phrases. There is no indication that this language constraint is optional, user-selected, or required for a region-specific purpose.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger conditions are very broad and are phrased as mandatory invocation rules for any request involving local/computer/folder search. In an agent setting, this can cause over-activation on common user requests and route sensitive local-file tasks to a skill that may index or query content through additional tooling, increasing the chance of unintended data exposure or unsafe execution paths.

External Transmission

Medium
Category
Data Exfiltration
Content
export OPENAI_API_KEY="your-api-key"
# 或 DeepSeek
export OPENAI_API_KEY="your-api-key"
export OPENAI_BASE_URL="https://api.deepseek.com/v1"
```

### 使用
Confidence
93% confidence
Finding
The documentation includes configuration for a third-party API endpoint, confirming that the skill can send data to an external service. In the context of a local document search skill, external transmission is more dangerous because users may assume their local files remain on-device; without strict disclosure and controls, sensitive document text and queries could be exposed to a remote provider.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README explicitly advertises cloud LLM API use for local document search but does not warn that document contents, extracted text, metadata, or user queries may be transmitted to external services. Because the skill is designed for large-scale local document indexing, this omission can mislead users into exposing sensitive local data under the assumption the workflow is fully local.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
brew install tesseract tesseract-lang

# Ubuntu 安装 Tesseract
sudo apt install tesseract-ocr tesseract-ocr-chi-sim

# .doc 文件支持(可选)
# macOS
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
# macOS
brew install libreoffice
# Ubuntu
sudo apt install libreoffice
```

## 文件结构
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill advertises substantial capabilities—shell execution, filesystem access, environment variable use, and network communication—but does not declare any tool scope or permissions boundary. That makes the agent’s effective authority opaque and increases the risk of unintended file access, service manipulation, or data exposure when the skill is invoked.

Natural-Language Policy Violations

Medium
Confidence
88% confidence
Finding
The trigger section defines activation phrases only in Chinese and presents them as the required invocation conditions. For a generally applicable local-search skill, this imposes a language-specific interaction model without user opt-in or a documented reason for the locale limitation.

Static analysis

No suspicious patterns detected.