Back to skill

Security audit

Council Brief

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real LLM council installer/query tool, but it runs mutable external code, copies credentials into plaintext files, kills unrelated local port users, and exposes a web UI on the LAN by default.

Review before installing. Only use this if you trust the external GitHub project at install time, are comfortable giving it your OpenRouter or OpenClaw gateway credential, and do not have important services on ports 8001, 5173, or 4173. Prefer installing in an isolated workspace, checking the cloned repo revision yourself, restricting .env permissions, and binding the UI to localhost only.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
Findings (5)

T03 · Remote Payload Retrieval and Execution

Error
Location
install.sh:78
Finding

Mutable Remote Code and Dependencies Are Downloaded and Executed

Content
View full analysis
&1 | tail -1 else info "Cloning llm-council to $INSTALL_DIR..." mkdir -p "$(dirname "$INSTALL_DIR")" git clone https://github.com/jeadland/llm-council.git "$INSTALL_DIR" fi cd "$INSTALL_DIR" # ── Backend: uv sync ────────────────────────────────────────────────────────── info "Running uv sync (Python backend)..." uv sync 2>&1 | tail -5 # ── Frontend: npm ci ────────────────────────────────────────────────────────── info "Running npm ci (frontend)..." cd frontend npm ci --silent cd .. ``` The retrieved backend is later executed: ```bash uv run python -m backend.main > "$BACKEND_LOG" 2>&1 & ``` ### Technical Analysis The installer clones or updates the current state of an external Git repository without pinning a reviewed commit, verifying a cryptographic checksum, or validating a signed release. Consequently, the effective code executed by the Skill can change after the Skill package itself has been audited. The installer also resolves Python and npm dependencies and may execute package installation hooks. Although `npm ci` uses a lockfile if one is present in the remote repository, that lockfile is itself mutable because the repository is not pinned. The reviewed Skill package does not contain the retrieved source or dependency manifests, so their runtime behavior cannot be established from this audit. This is not the pre-scan-reported `curl | bash` pattern: no such pipeline exists in `ask-council.sh`. Nevertheless, cloning mutable code and immediately installing and running it creates an equivalent remote code execution trust boundary. ### Attack Path 1. The upstr ...[truncated 1175 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
install.sh:46
Finding

API and Gateway Credentials Are Copied into a Plaintext Repository Environment File

Content
View full analysis
/dev/null && [[ -f "$OPENCLAW_CONFIG" ]]; then OPENCLAW_GATEWAY_TOKEN="$(jq -r '.gateway.auth.token // empty' "$OPENCLAW_CONFIG" 2>/dev/null || true)" ``` Plaintext persistence: ```bash info "Writing .env..." if [[ "$API_MODE" == "openclaw_gateway" ]]; then # Use local OpenClaw gateway — OpenAI-compatible endpoint, no external key needed cat > .env < .env <
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
install.sh:132
Finding

Installer Indiscriminately Terminates Processes Using Fixed Ports

Content
View full analysis
/dev/null || true)" if [[ -n "$STALE" ]]; then warn "Port ${port} in use by PID(s) $STALE — killing..." echo "$STALE" | xargs -r kill 2>/dev/null || true fi done # Wait for all ports to be freed sleep 2 for port in "${PORTS_TO_FREE[@]}"; do STALE="$(lsof -nP -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null || true)" if [[ -n "$STALE" ]]; then warn "Port ${port} still in use — force-killing PID(s) $STALE..." echo "$STALE" | xargs -r kill -9 2>/dev/null || true sleep 1 fi done ``` ### Technical Analysis The installer treats every listener on ports `8001`, `5173`, and `4173` as stale, without checking whether the process was created by this Skill. It does not verify the process owner, executable, command line, start time, working directory, or relationship to the stored PID file. The second pass escalates to `SIGKILL`, preventing affected processes from performing cleanup, flushing buffered data, or saving state. Ports `5173` and `4173` are commonly used by unrelated frontend development services, making accidental termination realistic. Installation does not require authority over unrelated processes. A least-privilege implementation should report the conflict, permit an alternate port, or terminate only a positively identified Skill-managed process. ### Attack Path 1. An unrelated same-user application listens on port `8001`, `5173`, or `4173`. 2. The user invokes `/council-brief install`. 3. `lsof` returns the unrelated application's PID. 4. The installer sends a normal termination signal without validating process identity. 5. If the process remains alive after the delay, the installer sends `SIGKILL`. 6. The u ...[truncated 788 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
stop.sh:15
Finding

Unvalidated PID File Can Terminate Unrelated Same-User Processes

Content
View full analysis
/dev/null; then kill "$pid" && echo " Killed PID $pid" || echo " Failed to kill PID $pid" else echo " PID $pid not running (already stopped)" fi done < "$PID_FILE" rm -f "$PID_FILE" ``` The installer similarly trusts existing PID-file entries: ```bash if [[ -f "$PID_FILE" ]]; then warn "Stopping existing services..." while IFS= read -r pid; do [[ -z "$pid" ]] && continue kill "$pid" 2>/dev/null && info " Killed PID $pid" || true done < "$PID_FILE" rm -f "$PID_FILE" sleep 1 fi ``` ### Technical Analysis The PID file is stored in the Skill directory and every nonempty line is passed to `kill` without validating that it is a decimal PID or that the corresponding process is an LLM Council service. Even if the PID file was originally legitimate, operating systems reuse process IDs. A service can exit unexpectedly, after which the same PID may be assigned to an unrelated process. The stop operation would then terminate the unrelated process. If another process or user can modify the Skill directory or PID file, it can insert the PID of a chosen same-user target. The code does not verify ownership, file permissions, symbolic links, process start time, executable path, or command line. ### Attack Path 1. A Skill-managed service exits and its PID is later reused by an unrelated process, or an attacker modifies the `pids` file to contain a target PID. 2. The user runs `/council-brief stop` or reruns installation. 3. The script confirms only that a process with the recorded PID exists. 4. It sends a termination signal without confirming process identity. 5. The unrelated same-user process is terminated. ### Impact Assessment An attacker a ...[truncated 392 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
install.sh:171
Finding

Frontend Is Exposed on All Network Interfaces by Default

Content
View full analysis
"$FRONTEND_LOG" 2>&1 & FRONTEND_PID=$! LOCAL_IP="$(hostname -I | awk '{print $1}')" ACCESS_URL="http://${LOCAL_IP}:${FRONTEND_PORT}" else FRONTEND_PORT=4173 info "Starting frontend (Vite preview on :${FRONTEND_PORT})..." npm run preview -- --host 0.0.0.0 --port "$FRONTEND_PORT" > "$FRONTEND_LOG" 2>&1 & FRONTEND_PID=$! LOCAL_IP="$(hostname -I | awk '{print $1}')" ACCESS_URL="http://${LOCAL_IP}:${FRONTEND_PORT}" fi ``` The query script directs users to the LAN-accessible interface: ```bash LOCAL_IP="$(hostname -I | awk '{print $1}')" if [[ -n "$SHORT_ID" ]]; then echo "Short ID: $SHORT_ID" echo "" echo "View full discussion: http://${LOCAL_IP}:5173/c/${SHORT_ID}" else echo "Conversation ID: $CONVO_ID" echo "" echo "View full discussion: http://${LOCAL_IP}:5173" fi ``` ### Technical Analysis Both Vite development and preview servers are started with `--host 0.0.0.0`, making the frontend reachable through every configured IPv4 interface rather than only through localhost. Local quick-query and browser functionality do not require this exposure. The retrieved frontend and backend implementations are not included in the audited project. Therefore, their authentication, authorization, CSRF protection, conversation access controls, and handling of short IDs cannot be confirmed. In the absence of verified access controls, exposing the interface to a shared LAN unnecessarily increases the attack surface. The Vite development server is intended primarily for development and should not be treated as a hardened production serv ...[truncated 1137 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (24)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
| `SKILL.md` | This documentation |

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · ask-council.sh (reported line 25)May include surrounding context.

sh
fi

# ── Check if backend is running ───────────────────────────────────────────────
if ! curl -s "$API_BASE/" > /dev/null 2>&1; then
  error "LLM Council backend not running. Start it first:\n  /council-brief install"
fi

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · ask-council.sh (reported line 38)May include surrounding context.

sh
fi

# ── Start council run ─────────────────────────────────────────────────────────
RUN_RESPONSE=$(curl -s -X POST "$API_BASE/api/conversations/$CONVO_ID/runs" \
  -H "Content-Type: application/json" \
  -d "{\"content\": $(echo "$QUESTION" | python3 -c 'import json, sys; print(json.dumps(sys.stdin.read().strip()))')}")
RUN_ID=$(echo "$RUN_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['run_id'])")

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

Scanner excerpt · ask-council.sh (reported line 58)May include surrounding context.

sh
error "Timed out after ${TIMEOUT_SEC}s. The council is still talking."
  fi

  STATUS=$(curl -s "$API_BASE/api/conversations/$CONVO_ID/runs/$RUN_ID")
  RUN_STATE=$(echo "$STATUS" | python3 -c "import sys, json; print(json.load(sys.stdin).get('status', 'unknown'))")
  
  case "$RUN_STATE" in

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 · install.sh (reported line 13)May include surrounding context.

sh
INSTALL_DIR="${HOME}/workspace/llm-council"
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PID_FILE="${SKILL_DIR}/pids"
WORKSPACE_ENV="${HOME}/.openclaw/workspace/.env"
OPENCLAW_CONFIG="${HOME}/.openclaw/openclaw.json"

# ── Arg parsing ───────────────────────────────────────────────────────────────

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 · install.sh (reported line 35)May include surrounding context.

sh
#
# Priority order:
#   1. Environment: OPENROUTER_API_KEY (already exported)
#   2. Workspace .env: ~/.openclaw/workspace/.env
#   3. OpenClaw local gateway: ~/.openclaw/openclaw.json → gateway.auth.token
#      Uses http://127.0.0.1:<port>/v1/chat/completions as drop-in OpenAI API
#

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 · install.sh (reported line 87)May include surrounding context.

sh
#
# Priority order:
#   1. Environment: OPENROUTER_API_KEY (already exported)
#   2. Workspace .env: ~/.openclaw/workspace/.env
#   3. OpenClaw local gateway: ~/.openclaw/openclaw.json → gateway.auth.token
#      Uses http://127.0.0.1:<port>/v1/chat/completions as drop-in OpenAI API
#

Credential Access

High
Category
Privilege Escalation
Confidence
91% confidence
Finding

At this point the script reads OPENROUTER_API_KEY from the user's OpenClaw workspace .env, accessing existing credentials without an explicit consent boundary. In the context of an installer, that is sensitive because it harvests secrets from outside the target project and reuses them automatically.

Content

Scanner excerpt · install.sh (reported line 44)May include surrounding context.

sh
OPENCLAW_GATEWAY_TOKEN=""
OPENCLAW_GATEWAY_PORT=""

# Try workspace .env
if [[ -z "$OPENROUTER_API_KEY" && -f "$WORKSPACE_ENV" ]]; then
  OPENROUTER_API_KEY="$(grep -E '^OPENROUTER_API_KEY=' "$WORKSPACE_ENV" \
    | cut -d= -f2- | tr -d '"' | tr -d "'" | head -1 || true)"

Credential Access

High
Category
Privilege Escalation
Confidence
97% confidence
Finding

This block writes the OpenClaw gateway token into a local .env file in plaintext, creating a durable copy of a sensitive authentication secret. Persisting a gateway token broadens exposure and may allow unauthorized use of the local gateway by anyone who can read the installation directory or any copied backups.

Content

Scanner excerpt · install.sh (reported line 91)May include surrounding context.

sh
info "Writing .env..."
if [[ "$API_MODE" == "openclaw_gateway" ]]; then
  # Use local OpenClaw gateway — OpenAI-compatible endpoint, no external key needed
  cat > .env <<EOF
# OpenClaw local gateway — no external API key required
OPENROUTER_API_KEY=${OPENCLAW_GATEWAY_TOKEN}
OPENROUTER_API_URL=http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/v1/chat/completions

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

This line writes the OpenRouter API key into a project .env file in plaintext, duplicating a sensitive credential into another location. That materially increases exposure risk through accidental disclosure, weak file permissions, backups, or source control mistakes.

Content

Scanner excerpt · install.sh (reported line 98)May include surrounding context.

sh
EOF
  info ".env: OpenClaw gateway mode (http://127.0.0.1:${OPENCLAW_GATEWAY_PORT}/v1/chat/completions)"
else
  cat > .env <<EOF
OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
EOF
  info ".env: OpenRouter direct mode"

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The script enumerates listeners on ports 8001, 5173, and 4173 and kills them, including forceful termination, without verifying they belong to this application. That can terminate unrelated local services, causing denial of service and potential data loss for other workloads on the machine.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The script kills processes on multiple ports and escalates to kill -9 without any confirmation prompt or ownership check. This is dangerous because it can abruptly terminate unrelated applications and bypass graceful shutdown, increasing the risk of corruption or lost work.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill documentation instructs the agent to execute shell scripts, but the manifest declares no tool scope or allowed-tools boundary. That weakens least-privilege controls and makes it harder for users or the platform to understand that invoking the skill can run code, install software, and manage services.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The shorthand form allows nearly any freeform text to activate the skill, creating an overly broad trigger surface. In practice this increases the chance of accidental invocation, unintended prompt transmission to external services, or unreviewed shell-driven actions associated with the skill.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill says it resolves credentials and sends questions to a backend, but it does not clearly disclose that prompts and API-backed requests may be transmitted to external gateways or model providers. This can expose sensitive user data or secrets entered into prompts, especially because the skill is positioned as a general-purpose shorthand for arbitrary questions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The install flow clones a repository, installs dependencies, and starts backend/frontend services, but the user-facing documentation does not present this as a clear warning about system modification and long-lived process creation. A user may invoke install expecting a harmless setup step and instead authorize code download, package execution, and network-exposed services.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The routing instructions send 'ask ' or any freeform text to query mode without clear boundaries, which expands the skill's operational scope beyond explicit commands. That makes accidental activation more likely and can result in user content being forwarded to backend/model infrastructure without deliberate consent.

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 · ask-council.sh (reported line 25)May include surrounding context.

sh
fi

# ── Check if backend is running ───────────────────────────────────────────────
if ! curl -s "$API_BASE/" > /dev/null 2>&1; then
  error "LLM Council backend not running. Start it first:\n  /council-brief install"
fi

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

This POST sends the full user question to the backend service, creating a real data egress path from the CLI to another process over HTTP. In the context of an LLM aggregation tool, prompts may contain secrets and may be forwarded onward, so silent transmission materially increases confidentiality risk.

Content

Scanner excerpt · ask-council.sh (reported line 38)May include surrounding context.

sh
fi

# ── Start council run ─────────────────────────────────────────────────────────
RUN_RESPONSE=$(curl -s -X POST "$API_BASE/api/conversations/$CONVO_ID/runs" \
  -H "Content-Type: application/json" \
  -d "{\"content\": $(echo "$QUESTION" | python3 -c 'import json, sys; print(json.dumps(sys.stdin.read().strip()))')}")
RUN_ID=$(echo "$RUN_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['run_id'])")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The user's prompt is transmitted to a backend service without an explicit warning or consent step, which can lead to accidental disclosure of sensitive data entered into the CLI. In a multi-model council context, the backend may further relay that content to additional models or services, increasing privacy and data handling risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The router exposes an install action that directly invokes install.sh with no confirmation, warning, or preview of side effects. In an agent-skill context, this is risky because a user may trigger installation expecting a harmless query tool, while the underlying script may modify the system, install dependencies, or launch background services without explicit consent.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

The installer reads secrets from ~/.openclaw/workspace/.env and ~/.openclaw/openclaw.json to auto-discover credentials, which expands its access beyond a minimal installer and silently consumes sensitive local data. While this may be intended for convenience, it creates unnecessary credential exposure risk and violates least-privilege expectations for an install script.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The installer copies credentials from existing user-controlled config into a new project .env file, creating an additional plaintext secret store without explicit warning or consent. This increases the chance of accidental disclosure through file permissions, backups, logs, or later commits of the installation directory.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script enumerates the host's local IP address and prints a network-accessible URL, exposing environment details that are not necessary for answering a CLI query. While low severity, this leaks local network information and may encourage users to open a service bound on the LAN rather than localhost only.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.