Back to skill

Security audit

Langfuse Backup

Security checks for vulnerabilities and agentic risk

Overview

The skill is for local Langfuse backups and restores, but its destructive restore workflow uses weakly validated inputs and mutable Docker images, so it should be reviewed before installation.

Install only if you are comfortable reviewing and hardening the scripts first. Pin or pre-vet the alpine image, restrict backup directory permissions, validate backup dates and database names, require and verify manifests or hashes, and test restores on disposable data before using it on a real Langfuse deployment.

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

T08 · Insecure Dependencies

Warning
Location
scripts/backup_langfuse.sh:58
Finding
Unpinned Docker Image May Retrieve and Execute Unreviewed Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backup_langfuse.sh:58-61`; `scripts/restore_langfuse.sh:125-128`; related network declaration at `SKILL.md:8-9` **Vulnerability Type**: Unpinned third-party container image **Risk Level**: Medium ### Vulnerable Code ```bash # scripts/backup_langfuse.sh:58-61 docker run --rm \ -v "$MINIO_VOL":/data:ro \ -v "$BACKUP_DIR":/backup \ alpine tar czf /backup/minio-data.tar.gz -C /data . 2>/dev/null ``` ```bash # scripts/restore_langfuse.sh:125-128 docker run --rm \ -v "$MINIO_VOL":/data \ -v "$BACKUP_DIR":/backup:ro \ alpine sh -c "rm -rf /data/* && tar xzf /backup/minio-data.tar.gz -C /data" ``` ```yaml # SKILL.md:8-9 network: outbound: false reason: "Backs up local Docker volumes only. No data is sent to remote servers." ``` ### Technical Analysis Both scripts execute the mutable `alpine` image without specifying an immutable digest. If the image is not present locally, Docker can retrieve it from the configured registry. Consequently, the code that runs is not fully fixed by the reviewed project and may change when the registry tag is updated or if the registry configuration is compromised. This behavior also conflicts with the metadata assertion that outbound network access is not required. During backup, the container receives read access to the MinIO volume and write access to the backup directory. During restoration, it receives write access to the MinIO volume and read access to the selected backup directory. ### Attack Path 1. An attacker compromises or controls the Docker registry, registry mirror, DNS path, or local Docker image associated with the mutable `alpine` tag. 2. The expected image is absent locally, or the local mutable tag is replaced. 3. An operator executes the backup or restore script. 4. Docker retrieves or starts the attacker-controlled image. 5. The image reads sensitive MinIO content, modifies the backup, or corrupts the writable MinIO volum ...[truncated 478 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin the image to a reviewed immutable digest, for example `alpine@sha256:<verified-digest>`. - Preload and verify the image before executing either script. - Use `docker run --pull=never` so the operation fails rather than silently retrieving an image. - Document any required image retrieval accurately in `SKILL.md`. - Consider replacing the container dependency with trusted host utilities when practical. - Retain read-only mounts wherever possible and limit writable mounts to the narrowest required directory. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/restore_langfuse.sh:99
Finding
SQL Injection Through Unvalidated Database Name<![CDATA[ ## Vulnerability Details **File Location**: `scripts/restore_langfuse.sh:99-103` **Vulnerability Type**: SQL injection through environment-controlled identifier **Risk Level**: High ### Vulnerable Code ```bash # Drop and recreate the database docker exec "$DB_CONTAINER" psql -U "$DB_USER" postgres \ -c "DROP DATABASE IF EXISTS $DB_NAME;" 2>/dev/null || true docker exec "$DB_CONTAINER" psql -U "$DB_USER" postgres \ -c "CREATE DATABASE $DB_NAME;" 2>/dev/null || true ``` ### Technical Analysis `DB_NAME` is populated from the `LANGFUSE_DB_NAME` environment variable and interpolated directly into SQL statements. Shell quoting protects the shell command structure but does not quote or validate the value as a PostgreSQL identifier. A value containing SQL delimiters can terminate the intended statement and append additional SQL. The injected statements execute through `psql` using `DB_USER` against the `postgres` database. The two commands also suppress errors and continue because of `|| true`, potentially concealing failed or partially malicious operations before restoration proceeds. ### Attack Path 1. An attacker gains influence over the restore process environment, a service definition, a scheduled execution configuration, or an operator-provided shell configuration. 2. The attacker assigns a crafted value to `LANGFUSE_DB_NAME`, such as a value containing a semicolon followed by an additional SQL statement. 3. The operator starts the restore script and confirms the destructive restoration. 4. The script inserts the value into the `DROP DATABASE` and `CREATE DATABASE` command strings. 5. PostgreSQL parses and executes any appended statements with the privileges of `LANGFUSE_DB_USER`. ### Impact Assessment The attacker can execute SQL with the database privileges assigned to the configured PostgreSQL user. Depending on that role's permissions, impact may include dropping or modifying other databases, deleting or altering application data, ch ...[truncated 215 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Reject database names that do not match a strict identifier policy, such as `^[A-Za-z_][A-Za-z0-9_]*$`. - Quote identifiers through PostgreSQL-aware functionality rather than shell interpolation. For example, use PostgreSQL `format('%I', value)` with controlled `\gexec` processing. - Avoid accepting security-sensitive object names from an uncontrolled environment where a fixed database name is sufficient. - Remove blanket `|| true` handling from destructive database operations. - Verify that the drop and create operations succeeded before piping the backup into `psql`. - Run the restoration through a least-privileged PostgreSQL role with only the permissions required for the designated database. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/restore_langfuse.sh:31
Finding
Untrusted Backup Selection and Restore Without Integrity Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/restore_langfuse.sh:31-55`, `scripts/restore_langfuse.sh:106-108`, and `scripts/restore_langfuse.sh:125-128` **Vulnerability Type**: Path traversal and unauthenticated destructive restore input **Risk Level**: High ### Vulnerable Code ```bash # Resolve backup date DATE_ARG="${1:-latest}" if [ "$DATE_ARG" = "latest" ]; then BACKUP_DATE=$(ls -1 "$BACKUP_BASE" | grep -E '^20[0-9]{2}-[0-9]{2}-[0-9]{2}$' | sort | tail -1) if [ -z "$BACKUP_DATE" ]; then err "No backups found in $BACKUP_BASE" fi log "Resolved 'latest' → $BACKUP_DATE" else BACKUP_DATE="$DATE_ARG" fi BACKUP_DIR="$BACKUP_BASE/$BACKUP_DATE" [ -d "$BACKUP_DIR" ] || err "Backup directory not found: $BACKUP_DIR" log "=== Langfuse restore: $BACKUP_DATE ===" # ── Validate manifest ───────────────────────────────────────────────────────── if [ -f "$BACKUP_DIR/manifest.json" ]; then log "Manifest:" cat "$BACKUP_DIR/manifest.json" echo "" else log "⚠️ No manifest.json found — proceeding anyway" fi # ── Validate backup files exist ─────────────────────────────────────────────── PG_FILE="$BACKUP_DIR/postgres-langfuse.sql.gz" MINIO_FILE="$BACKUP_DIR/minio-data.tar.gz" [ -f "$PG_FILE" ] && log "✅ Found: postgres-langfuse.sql.gz" || log "⚠️ Missing: postgres-langfuse.sql.gz" [ -f "$MINIO_FILE" ] && log "✅ Found: minio-data.tar.gz" || log "⚠️ Missing: minio-data.tar.gz" ``` ```bash # Restore zcat "$PG_FILE" | docker exec -i "$DB_CONTAINER" \ psql -U "$DB_USER" "$DB_NAME" -q ``` ```bash docker run --rm \ -v "$MINIO_VOL":/data \ -v "$BACKUP_DIR":/backup:ro \ alpine sh -c "rm -rf /data/* && tar xzf /backup/minio-data.tar.gz -C /data" ``` ### Technical Analysis For arguments other than `latest`, the script accepts the input without enforcing the documented `YYYY-MM-DD` format. Values containing `..` or path separators can cause `BACKUP_DIR` to resolve outside `BACKUP_BASE`. The manifest i ...[truncated 1777 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Permit only `latest` or a strict date matching `^20[0-9]{2}-[0-9]{2}-[0-9]{2}$`. - Canonicalize both `BACKUP_BASE` and `BACKUP_DIR`, then reject selections that are not descendants of the canonical backup root. - Reject symlinks and require the selected backup directory and files to have trusted ownership and restrictive permissions. - Require a manifest instead of proceeding when it is absent. - Record cryptographic hashes for every backup artifact and verify them before any destructive action. - Authenticate the manifest with a signature or MAC whose verification key is stored separately from the backup. - Validate gzip streams and enumerate archive members before deleting existing data. - Reject absolute archive paths, `..` components, links, device nodes, and other unsafe entries. - Restore first into temporary database and volume targets, validate the result, and switch to them only after successful verification. - Create a pre-restore snapshot or backup so failed and malicious restorations can be rolled back. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/backup_langfuse.sh:29
Finding
Sensitive Backup Files and Predictable Temporary Log Use Inherited Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/backup_langfuse.sh:29-35` and `scripts/backup_langfuse.sh:43-46` **Vulnerability Type**: Insecure file permissions and unsafe predictable temporary file **Risk Level**: Medium ### Vulnerable Code ```bash RETENTION_DAYS="${LANGFUSE_RETENTION_DAYS:-14}" LOG="/tmp/langfuse-backup.log" log() { echo "$(date '+%H:%M:%S') $*" | tee -a "$LOG"; } log "=== Langfuse backup: $DATE ===" mkdir -p "$BACKUP_DIR" ``` ```bash docker exec "$DB_CONTAINER" \ pg_dump -U "$DB_USER" "$DB_NAME" 2>/dev/null \ | gzip > "$BACKUP_DIR/postgres-langfuse.sql.gz" SIZE=$(du -sh "$BACKUP_DIR/postgres-langfuse.sql.gz" | cut -f1) log "✅ Postgres: $SIZE" ``` ### Technical Analysis The script does not set a restrictive `umask` or explicitly assign secure modes to backup directories and files. As a result, the permissions of database dumps, MinIO archives, and manifests depend on the invoking process's environment. With a permissive umask, other local users may be able to read sensitive Langfuse traces, scores, evaluations, or uploaded blobs. The log uses a fixed path in the shared `/tmp` directory and appends through `tee`. The script does not securely create the file or check whether it is a symbolic link. A local attacker may pre-create the path or point it to another file. The practical write target remains constrained by the invoking user's filesystem permissions, but elevated executions increase the impact. ### Attack Path 1. The backup runs under an account with a permissive umask, causing generated backup artifacts to be readable by other local users; or a local attacker pre-creates `/tmp/langfuse-backup.log` as a file or symbolic link. 2. The operator executes the backup script. 3. Sensitive backup artifacts are created with inherited permissions, allowing unauthorized local reads. 4. In the log-path scenario, `tee -a` follows the attacker-controlled path and appends log content to a target writable by the backu ...[truncated 468 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Set `umask 077` near the beginning of both scripts. - Create `BACKUP_BASE` and each dated backup directory with mode `0700`. - Ensure database dumps, archives, and manifests are created with mode `0600`. - Store logs in a private application state directory rather than a globally shared temporary directory. - If temporary storage is necessary, securely create the file with `mktemp`, verify ownership, and reject symbolic links. - Avoid running the scripts as root unless Docker access and filesystem ownership explicitly require it. - Verify backup directory ownership before writing or pruning content. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The code clearly aligns with part of the description: it performs backups for a self-hosted Langfuse deployment, specifically Postgres and MinIO, and it includes 14-day retention pruning by default. However, the declared purpose claims broader functionality than the code chunk actually provides. The chunk contains only a backup script; there is no restore logic or validation workflow. It also states optional ClickHouse and Redis coverage, but this script does not back up either service—it only comments that they are optional/transient. This is a description-to-behavior mismatch due to missing declared capabilities in the provided code chunk, though there is no evidence of unrelated or malicious behavior.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The description overstates the supplied code chunk's scope. The code clearly implements Langfuse restore operations only, specifically for Postgres and MinIO. It includes validation and destructive restore behavior, but there is no backup logic, no cron-oriented retention pruning, and no optional clickhouse or redis support in this chunk. Additionally, Postgres handling is database-level SQL restore rather than direct Docker volume restore. While some parts align with the declared purpose (self-hosted Langfuse restore, validation, Postgres/MinIO resources), the overall declared description does not accurately represent what this specific code chunk actually does.

Session Persistence

Medium
Category
Rogue Agent
Content
## Cron setup (macOS LaunchAgent)

```xml
<!-- ~/Library/LaunchAgents/com.yourname.langfuse-backup.plist -->
<key>StartCalendarInterval</key>
<dict>
  <key>Hour</key><integer>2</integer>
Confidence
75% 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.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding
The script runs an unpinned container image with `docker run --rm alpine ...`, relying on whatever `alpine:latest` resolves to locally or from a registry. If the image is replaced, poisoned in a registry, or changes unexpectedly over time, the backup process will execute untrusted code with mounted access to the MinIO volume contents and the host backup directory, which is especially sensitive in a backup/restore skill.

Rp1

Medium
Category
MCP Rug Pull
Confidence
95% confidence
Finding
The script runs the unpinned image `alpine` for a privileged restore step that mounts the MinIO data volume and executes destructive commands inside it. Because no tag or digest is specified, behavior depends on whatever image is current at execution time, which creates supply-chain risk and can lead to unexpected or malicious image changes affecting a highly sensitive restore operation.

Tool Parameter Abuse

Low
Category
Tool Misuse
Content
if [ -n "$MINIO_VOL" ]; then
        # Clear and restore
        docker run --rm \
            -v "$MINIO_VOL":/data \
            -v "$BACKUP_DIR":/backup:ro \
            alpine sh -c "rm -rf /data/* && tar xzf /backup/minio-data.tar.gz -C /data"
Confidence
15% 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).

Tool Parameter Abuse

Low
Category
Tool Misuse
Content
docker run --rm \
            -v "$MINIO_VOL":/data \
            -v "$BACKUP_DIR":/backup:ro \
            alpine sh -c "rm -rf /data/* && tar xzf /backup/minio-data.tar.gz -C /data"
        log "✅ MinIO restored from volume $MINIO_VOL"
    else
        log "⚠️  Could not find MinIO volume — skipping"
Confidence
15% 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).

Static analysis

No suspicious patterns detected.