Back to skill

Security audit

Observability Lgtm

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent local observability setup, but it exposes no-login monitoring services more broadly than its local-only description suggests.

Install only on a trusted machine and network. Before running it, bind exposed Docker ports to 127.0.0.1, disable anonymous Grafana Admin access or add authentication, validate service names passed to helper scripts, and consider using pinned dependencies or a reviewed lockfile.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/docker-compose.yml:26
Finding
Unauthenticated observability services are exposed on all host interfaces<![CDATA[ ## Vulnerability Details **File Location**: `assets/docker-compose.yml:26-61, 68-107`; `assets/config/loki/loki.yml:1-5`; `assets/config/tempo/tempo.yml:5-12` **Vulnerability Type**: Missing authentication and excessive network exposure **Risk Level**: High ### Vulnerable Code ```yaml # assets/docker-compose.yml grafana: image: grafana/grafana-oss:11.4.0 container_name: obs-grafana restart: unless-stopped networks: [obs] ports: - "3000:3000" environment: # No-auth for local dev GF_AUTH_ANONYMOUS_ENABLED: "true" GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin" GF_AUTH_DISABLE_LOGIN_FORM: "true" prometheus: image: prom/prometheus:v3.1.0 container_name: obs-prometheus restart: unless-stopped networks: [obs] ports: - "9091:9090" command: - --config.file=/etc/prometheus/prometheus.yml - --storage.tsdb.path=/prometheus - --storage.tsdb.retention.time=30d - --web.enable-lifecycle - --web.enable-remote-write-receiver loki: image: grafana/loki:3.3.2 container_name: obs-loki restart: unless-stopped networks: [obs] ports: - "3300:3100" tempo: image: grafana/tempo:2.7.1 container_name: obs-tempo restart: unless-stopped networks: [obs] ports: - "4317:4317" - "4318:4318" - "3400:3200" alloy: image: grafana/alloy:v1.5.1 container_name: obs-alloy restart: unless-stopped networks: [obs] ports: - "12345:12345" command: - run - /etc/alloy/config.alloy - --server.http.listen-addr=0.0.0.0:12345 ``` ```yaml # assets/config/loki/loki.yml auth_enabled: false server: http_listen_port: 3100 grpc_listen_port: 9096 ``` ```yaml # assets/config/tempo/tempo.yml distributor: receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 ``` ### Technical Analysis Docker Compose port declarations such as `"3000:3000"` bind to all host interfaces by default, not exclusively to loopbac ...[truncated 1856 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind all services intended for local use explicitly to loopback: ```yaml ports: - "127.0.0.1:3000:3000" ``` Apply the equivalent binding to ports 3300, 9091, 3400, 4317, 4318, and 12345. 2. Disable anonymous Grafana administration. Require authentication and assign the least-privileged role: ```yaml GF_AUTH_ANONYMOUS_ENABLED: "false" GF_AUTH_DISABLE_LOGIN_FORM: "false" ``` 3. If anonymous access is essential for disposable development environments, use a viewer role rather than Admin and document that the stack must not run on an untrusted network. 4. Add authentication or an authenticated reverse proxy in front of Loki, Tempo, Prometheus, and Alloy when access outside loopback is required. 5. Remove `--web.enable-lifecycle` and `--web.enable-remote-write-receiver` unless hot reload and remote ingestion are explicitly required. 6. Apply host firewall rules and Docker network segmentation as defense in depth. 7. Add resource limits, ingestion limits, and storage quotas to reduce denial-of-service impact. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
assets/scripts/register_app.sh:13
Finding
Target registration permits path traversal and JSON injection<![CDATA[ ## Vulnerability Details **File Location**: `assets/scripts/register_app.sh:13-25` **Vulnerability Type**: Path traversal and configuration injection **Risk Level**: Medium ### Vulnerable Code ```bash SERVICE="${1:?Usage: $0 <service_name> <port>}" PORT="${2:?Usage: $0 <service_name> <port>}" TARGETS_DIR="$(cd "$(dirname "$0")/../config/prometheus/targets" && pwd)" TARGET_FILE="${TARGETS_DIR}/${SERVICE}.json" cat > "${TARGET_FILE}" <<JSON [ { "targets": ["host.docker.internal:${PORT}"], "labels": { "job": "${SERVICE}", "service": "${SERVICE}", "env": "dev" } } ] JSON ``` ### Technical Analysis The script uses `SERVICE` directly as part of a filesystem path. Shell quoting prevents shell word splitting and command substitution in the supplied value, but it does not prevent filesystem traversal. A value containing `../` can cause `TARGET_FILE` to resolve outside the intended Prometheus target directory. The script also inserts `SERVICE` and `PORT` directly into a JSON here-document without JSON encoding. Quotes, backslashes, or newline characters can terminate the intended JSON strings and inject additional fields or documents. The expected `config/prometheus/targets` directory is absent from the reviewed package. Consequently, the script may fail at the `cd` operation unless the directory is created separately. ### Attack Path 1. An automation workflow or Agent invokes `register_app.sh` with an untrusted or insufficiently reviewed service name. 2. The attacker supplies a value such as `../other/location` to influence the destination path. 3. The script resolves the resulting filename outside `config/prometheus/targets` and writes a `.json` file with the privileges of the invoking user. 4. Alternatively, the attacker embeds quotes and newlines in `SERVICE` or `PORT`. 5. The generated discovery document becomes malformed or contains attacker-selected JSON fields. 6. If the resulting file is consumed by Prometheus ...[truncated 710 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict service names to a safe identifier syntax: ```bash if [[ ! "$SERVICE" =~ ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$ ]]; then echo "Invalid service name" >&2 exit 1 fi ``` 2. Validate that the port is numeric and within the valid TCP range: ```bash if [[ ! "$PORT" =~ ^[0-9]+$ ]] || (( PORT < 1 || PORT > 65535 )); then echo "Invalid port" >&2 exit 1 fi ``` 3. Create the expected target directory before resolving it: ```bash TARGETS_DIR="$(dirname "$0")/../config/prometheus/targets" mkdir -p "$TARGETS_DIR" TARGETS_DIR="$(cd "$TARGETS_DIR" && pwd -P)" ``` 4. Resolve the destination and verify that it remains under `TARGETS_DIR`. 5. Generate JSON with a real JSON serializer rather than string interpolation. For example, use `jq -n --arg` or a short Python program using `json.dump`. 6. Write to a temporary file in the same directory and atomically rename it after successful validation to prevent partial configuration files. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
assets/lib/observability.py:56
Finding
Unvalidated service name permits log-path traversal<![CDATA[ ## Vulnerability Details **File Location**: `assets/lib/observability.py:56-58, 61-73` **Vulnerability Type**: Filesystem path traversal **Risk Level**: Medium ### Vulnerable Code ```python _LOG_BASE = Path(os.environ.get( "OPENCLAW_LOG_DIR", Path(__file__).parent.parent / "logs", )) def _setup_logging(service_name: str, log_level: int = logging.INFO) -> logging.Logger: """ Configure root logger with: - JSON formatter → file (Alloy scrapes this → Loki) - Plain formatter → stderr (local dev readability) Log file: <OPENCLAW_LOG_DIR>/<service_name>/app.log """ log_dir = _LOG_BASE / service_name log_dir.mkdir(parents=True, exist_ok=True) log_file = log_dir / "app.log" ``` ### Technical Analysis `service_name` is treated as a trusted directory name without validation. Python's `pathlib` allows `..` components, and joining an absolute path as the right operand can replace the intended base path entirely. For example, a traversal-bearing service name can resolve outside the configured log root. The function then creates parent directories and opens `app.log` through `logging.FileHandler`, using the application process's permissions. Although typical documented usage supplies a hardcoded service identifier, the public API does not enforce that assumption. Applications commonly source service names from environment variables or deployment configuration, which may make the value attacker-influenced. ### Attack Path 1. An attacker gains control over, or influences, the value passed as `service_name`. 2. The attacker supplies an absolute path or a value containing parent-directory components. 3. `_LOG_BASE / service_name` resolves outside the intended observability log directory. 4. `mkdir(parents=True)` creates reachable directories and `FileHandler` creates or appends to `app.log`. 5. Subsequent application logs are written to the attacker-selected location. ### Impact Assessment An attacker can cause ...[truncated 502 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate `service_name` against a restrictive identifier pattern, such as letters, digits, underscores, and hyphens. 2. Explicitly reject absolute paths, path separators, `.` components, and `..` components. 3. Resolve both the base and candidate paths and verify containment: ```python base = _LOG_BASE.resolve() candidate = (base / service_name).resolve() if candidate.parent != base: raise ValueError("Invalid service name") ``` Adjust the containment check if nested service directories are intentionally supported. 4. Create log files with restrictive permissions and ensure the configured log root is not writable by unrelated users. 5. Document that `service_name` must be a logical identifier rather than a filesystem path, while still enforcing that requirement in code. ]]>

T08 · Insecure Dependencies

Note
Location
assets/requirements.txt:4
Finding
Dependencies are unbounded and are not integrity-pinned<![CDATA[ ## Vulnerability Details **File Location**: `assets/requirements.txt:4-13`; `SKILL.md:102-108` **Vulnerability Type**: Non-reproducible dependency resolution and missing integrity verification **Risk Level**: Low ### Vulnerable Code ```text # Metrics prometheus-fastapi-instrumentator>=7.0.0 # Traces (OpenTelemetry) opentelemetry-sdk>=1.25.0 opentelemetry-exporter-otlp-proto-grpc>=1.25.0 opentelemetry-instrumentation-fastapi>=0.46b0 # Structured JSON logging (Loki-compatible) python-json-logger>=2.0.7 ``` The installation instructions repeat the same lower-bound-only constraints: ```bash pip install \ "prometheus-fastapi-instrumentator>=7.0.0" \ "opentelemetry-sdk>=1.25.0" \ "opentelemetry-exporter-otlp-proto-grpc>=1.25.0" \ "opentelemetry-instrumentation-fastapi>=0.46b0" \ "python-json-logger>=2.0.7" ``` ### Technical Analysis Every Python dependency uses a minimum version with no upper bound or exact lock. Installation therefore selects whatever compatible release is available from the package index at installation time. No hashes are supplied to authenticate the downloaded artifacts. The reviewed package names appear consistent with the intended observability libraries; no direct evidence of typosquatting or a currently malicious package was found. The weakness is that future builds are non-reproducible and automatically trust future upstream artifacts. The Compose images are version-tagged, which is preferable to `latest`, but they are not pinned by immutable digest. ### Attack Path 1. A dependency publisher account, package-index distribution channel, or container registry is compromised, or an unsafe future release is published. 2. A user follows the Skill instructions at a later time. 3. The resolver accepts the newer package because it satisfies the lower-bound constraint, or the registry serves changed content under an image tag. 4. Installation hooks, imported Python modules, or container entrypoints execute the changed c ...[truncated 684 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate a reviewed lock file containing exact transitive versions. 2. Use hash verification, for example a requirements file generated for installation with: ```bash pip install --require-hashes -r requirements.lock ``` 3. Keep broad compatibility constraints in a separate input file, but install deployments from the reviewed lock file. 4. Pin container images by immutable digest in addition to the human-readable version tag: ```yaml image: grafana/grafana-oss:11.4.0@sha256:<reviewed-digest> ``` 5. Automate vulnerability scanning and dependency-update review rather than silently accepting all future versions. 6. Rebuild and retest lock files on a controlled schedule so security updates can be adopted without sacrificing reproducibility. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill includes shell commands, file copying, chmod, Docker orchestration, pip installs, and local HTTP requests, but it does not declare an explicit tool scope such as permissions or allowed-tools. This creates a governance gap: an agent may execute impactful local actions without a machine-readable restriction boundary, increasing the chance of unintended command execution or over-broad tool use.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The helper automatically enables OTLP trace export and FastAPI request instrumentation by default, which can transmit request metadata to a network endpoint without any explicit consent or warning at the call site. In an observability utility, this is likely intended for convenience rather than abuse, but it still creates a privacy and data-governance risk because developers may unknowingly emit telemetry containing sensitive route, header, or application context data.

External Script Fetching

Low
Category
Supply Chain
Content
Wait ~15 seconds for all services to start, then verify:

```bash
curl -s -o /dev/null -w "Grafana: %{http_code}\n"    http://localhost:3000/api/health
curl -s -o /dev/null -w "Prometheus: %{http_code}\n" http://localhost:9091/-/healthy
curl -s -o /dev/null -w "Loki: %{http_code}\n"       http://localhost:3300/ready
curl -s -o /dev/null -w "Tempo: %{http_code}\n"      http://localhost:4318/ready
Confidence
15% confidence
Finding
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Unpinned Dependencies

Low
Category
Supply Chain
Content
# Install: pip install -r requirements.txt

# Metrics
prometheus-fastapi-instrumentator>=7.0.0

# Traces (OpenTelemetry)
opentelemetry-sdk>=1.25.0
Confidence
96% confidence
Finding
The dependency is specified with a lower-bound only (>=7.0.0), which permits installation of any newer release, including unreviewed major versions with breaking changes or newly introduced malicious/supply-chain issues. In a reusable skill, this increases build non-determinism and can cause consumers to pull different packages over time than the author tested.

Unpinned Dependencies

Low
Category
Supply Chain
Content
prometheus-fastapi-instrumentator>=7.0.0

# Traces (OpenTelemetry)
opentelemetry-sdk>=1.25.0
opentelemetry-exporter-otlp-proto-grpc>=1.25.0
opentelemetry-instrumentation-fastapi>=0.46b0
Confidence
97% confidence
Finding
Using opentelemetry-sdk>=1.25.0 allows future versions to be installed automatically, making deployments non-reproducible and exposing users to potential supply-chain risk if a later release is compromised or introduces insecure defaults. Observability libraries are often deeply integrated into request handling, so unexpected version drift can affect broad application behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
# Traces (OpenTelemetry)
opentelemetry-sdk>=1.25.0
opentelemetry-exporter-otlp-proto-grpc>=1.25.0
opentelemetry-instrumentation-fastapi>=0.46b0

# Structured JSON logging (Loki-compatible)
Confidence
97% confidence
Finding
The exporter dependency is unpinned and may resolve to any later version, creating non-deterministic builds and increasing the chance of pulling a vulnerable or malicious upstream release. Because exporters handle telemetry egress, version changes can affect data transmission, reliability, and security-sensitive configuration behavior.

Unpinned Dependencies

Low
Category
Supply Chain
Content
opentelemetry-instrumentation-fastapi>=0.46b0

# Structured JSON logging (Loki-compatible)
python-json-logger>=2.0.7
Confidence
95% confidence
Finding
python-json-logger>=2.0.7 allows installation of arbitrary later versions, which weakens reproducibility and increases software supply-chain exposure. Logging libraries can influence how application data is serialized and emitted, so unexpected changes may create operational or security issues if a future release is problematic.

Static analysis

No suspicious patterns detected.