Back to skill

Security audit

Knods

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for connecting Knods to OpenClaw, but it installs a persistent local bridge that forwards remote chat content into a local agent and exposes gateway tokens in logs.

Install only if you are comfortable running a persistent user service that connects Knods to a local OpenClaw agent. Use a dedicated least-privileged agent profile, keep only Knods-specific secrets in a separate locked-down env file, avoid embedding tokens in logged URLs, require HTTPS/trusted Knods hosts, and be prepared to stop/disable the systemd service and rotate gateway tokens if logs may have exposed them.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T06 · System Persistence

Error
Location
scripts/install_local.sh:21
Finding

Persistent Automatically Restarting User Service

Content
View full analysis

Vulnerability Details

File Location: scripts/install_local.sh:21-39
Vulnerability Type: Persistent systemd user service
Risk Level: High

Vulnerable Code

bash
cat >"$SERVICE_PATH" <<EOF
[Unit]
Description=Knods Iris Bridge (OpenClaw)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=$HOME
EnvironmentFile=$ENV_FILE
Environment=PYTHONUNBUFFERED=1
Environment=PATH=$HOME/.npm-global/bin:$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=OPENCLAW_BIN=$OPENCLAW_BIN_DETECTED
ExecStart=/usr/bin/python3 $BRIDGE_DST
Restart=always
RestartSec=2

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable --now "$SERVICE_NAME"

Technical Analysis

The installer creates a systemd user unit, enables it for future sessions, starts it immediately, and configures it to restart indefinitely. This persistence is disclosed and supports the polling bridge's intended operation, but it causes code to continue executing across sessions without requiring another explicit user action.

The persistent process runs with the user's privileges, imports an environment file, invokes the local OpenClaw agent, and maintains continuous network access. No systemd sandboxing directives are applied to restrict filesystem, process, device, or privilege access.

Attack Path

  1. The user runs scripts/install_local.sh.
  2. The script copies the bridge into ~/.openclaw/scripts.
  3. A systemd user service is created and enabled.
  4. The bridge starts immediately and starts again in later user sessions.
  5. If the bridge, its configuration, the gateway, or the invoked agent is compromised, the service provides a durable execution and network channel.
  6. Restart=always causes the process to return after crashes or termination attempts unless the service is explicitly disabled.

Impact Assessment

Th ...[truncated 357 chars]

Remediation
View remediation

Remediation Suggestions

  • Separate file installation from service activation.
  • Do not execute systemctl --user enable --now automatically. Require an explicit, documented opt-in command.
  • Provide an uninstall operation that stops, disables, and removes the unit and copied bridge.
  • Ask for confirmation before enabling cross-session execution.
  • Add systemd hardening controls where compatible, including:
    • NoNewPrivileges=true
    • PrivateTmp=true
    • ProtectSystem=strict
    • ProtectHome=read-only or a narrowly scoped allowlist
    • RestrictSUIDSGID=true
    • LockPersonality=true
    • RestrictAddressFamilies=AF_INET AF_INET6
  • Use bounded restart behavior and systemd start-rate limits rather than unrestricted automatic restarts.
  • Document the service's execution scope, environment access, network access, and removal procedure prominently before installation.

T01 · Skill Instruction Hijacking

Error
Location
scripts/knods_iris_bridge.py:118
Finding

Untrusted Gateway Content Is Forwarded to a Locally Privileged Agent

Content
View full analysis

Vulnerability Details

File Location: scripts/knods_iris_bridge.py:118-157; related instruction at SKILL.md:35-40
Vulnerability Type: Remote prompt injection and instruction-boundary failure
Risk Level: High

Vulnerable Code

python
def format_history(history: List[Dict], max_items: int = 16) -> str:
    if not history:
        return "(none)"
    lines: List[str] = []
    for item in history[-max_items:]:
        role = str(item.get("role") or "unknown").strip().lower()
        content = str(item.get("content") or "").strip()
        if not content:
            continue
        if role not in {"user", "assistant", "system"}:
            role = "user"
        lines.append(f"{role}: {content}")
    return "\n".join(lines) if lines else "(none)"


def build_knods_prompt(message: str, history: List[Dict]) -> str:
    return (
        "Channel context:\n"
        "- You are replying to a user in Knods Iris chat via the Knods polling gateway.\n"
        "- This is not OpenClaw TUI/webchat. Do not mention TUI/webchat unless the user asks.\n"
        "- If relevant, include valid [KNODS_ACTION]{...}[/KNODS_ACTION] blocks.\n\n"
        "Conversation history from Knods:\n"
        f"{format_history(history)}\n\n"
        "Current user message:\n"
        f"{message}"
    )
python
agent_input = build_knods_prompt(message, history)
try:
    reply = run_openclaw_agent(openclaw_bin, agent_id, agent_input, timeout_sec)
    if not reply.strip():
        print(f"empty_reply_retry={message_id}")
        reply = run_openclaw_agent(openclaw_bin, agent_id, message, timeout_sec)

The Skill instructions further state:

markdown
On first turn in a conversation, expect prepended context in `message`
describing node types and action rules. **Always prefer the node catalog
from this context over the defaults below.**

Technical Analysis

Gateway-prov ...[truncated 2087 chars]

Remediation
View remediation

Remediation Suggestions

  • Run gateway messages through a dedicated, least-privileged agent profile.
  • Disable shell, unrestricted filesystem, secret access, persistent memory, and unrelated network tools for that profile.
  • Reject gateway history entries with the system role; only permit strictly validated user and assistant roles.
  • Represent remote messages as explicitly delimited untrusted data rather than authoritative instructions.
  • Add a fixed local policy stating that gateway content cannot modify safety rules, tool permissions, or trust boundaries.
  • Validate remotely supplied node catalogs against a local allowlist instead of automatically preferring arbitrary remote definitions.
  • Enforce JSON schemas, string type checks, maximum message sizes, history limits by bytes, and allowed action structures.
  • Authenticate and authorize message producers at the gateway, not merely the bridge connection.
  • Require confirmation for operations that access local data, invoke commands, spend credits, or mutate resources.
  • Avoid retrying with the raw message alone, because doing so removes even the limited channel framing supplied by build_knods_prompt.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/knods_iris_bridge.py:195
Finding

Gateway Credentials Are Written Unredacted to Service Logs

Content
View full analysis

Vulnerability Details

File Location: scripts/knods_iris_bridge.py:31-53,195-196
Vulnerability Type: Sensitive credential exposure through logging
Risk Level: High

Vulnerable Code

python
def add_token(url: str, token: str) -> str:
    parsed = parse.urlparse(url)
    qs = parse.parse_qs(parsed.query, keep_blank_values=True)
    if "token" not in qs:
        qs["token"] = [token]
    query = parse.urlencode(qs, doseq=True)
    return parse.urlunparse(parsed._replace(query=query))


def resolve_urls(base_url: str, token: Optional[str]) -> Tuple[str, str]:
    parsed = parse.urlparse(base_url)
    path = parsed.path.rstrip("/")
    qs = parse.parse_qs(parsed.query, keep_blank_values=True)
    has_query_token = "token" in qs and bool(qs["token"] and qs["token"][0])

    if path.endswith("/updates"):
        updates = base_url
        respond = parse.urlunparse(parsed._replace(path=path[:-8] + "/respond"))
    else:
        updates = base_url.rstrip("/") + "/updates"
        respond = base_url.rstrip("/") + "/respond"

    if has_query_token:
        return updates, respond
    if not token:
        raise ValueError("Missing gateway token: provide KNODS_GATEWAY_TOKEN or include token in KNODS_BASE_URL.")

    return add_token(updates, token), add_token(respond, token)
python
updates_url, respond_url = resolve_urls(base_url, token)
print(f"updates_url={updates_url}")
print(f"respond_url={respond_url}")

Technical Analysis

The gateway protocol embeds its authentication token in the token query parameter. resolve_urls returns complete credential-bearing URLs, after which main prints both URLs without redaction.

When installed through the included installer, standard output is captured by the systemd journal. The token can therefore remain in persistent logs even after configuration changes. Exceptions generated by URL operations may also expose comp ...[truncated 974 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove startup logging of complete update and response URLs.
  • Implement a URL-redaction helper that replaces the value of token with [REDACTED].
  • Apply redaction to normal logs, debugging output, and exception messages.
  • Log only the scheme, hostname, sanitized path, and whether authentication is configured.
  • Prefer an Authorization header instead of a query parameter if the gateway protocol can be changed.
  • Configure conservative journal retention and access controls.
  • Regenerate any gateway token that has already been logged by the affected implementation.
  • Add automated tests asserting that gw_ tokens and query credentials never appear in emitted logs.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/install_local.sh:27
Finding

Shared OpenClaw Environment and Unrelated Secrets Are Inherited by the Bridge and Agent

Content
View full analysis

Vulnerability Details

File Location: scripts/install_local.sh:27; related subprocess execution at scripts/knods_iris_bridge.py:95-105
Vulnerability Type: Excessive secret and environment exposure
Risk Level: Medium

Vulnerable Code

ini
[Service]
Type=simple
WorkingDirectory=$HOME
EnvironmentFile=$ENV_FILE
Environment=PYTHONUNBUFFERED=1
Environment=PATH=$HOME/.npm-global/bin:$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=OPENCLAW_BIN=$OPENCLAW_BIN_DETECTED
ExecStart=/usr/bin/python3 $BRIDGE_DST
Restart=always

The invoked agent inherits the bridge process environment:

python
def run_openclaw_agent(openclaw_bin: str, agent_id: str, message: str, timeout_sec: int) -> str:
    cmd = [
        openclaw_bin,
        "agent",
        "--agent",
        agent_id,
        "--message",
        message,
        "--json",
        "--timeout",
        str(timeout_sec),
    ]
    out = subprocess.check_output(cmd, text=True)
    data = json.loads(out)
    return extract_text_from_result(data)

The bridge's environment loader also imports every key from the selected file:

python
def load_env_file(path: Path) -> None:
    if not path.exists():
        return
    for raw in path.read_text(encoding="utf-8").splitlines():
        line = raw.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, value = line.split("=", 1)
        key = key.strip()
        value = value.strip().strip('"').strip("'")
        if key and key not in os.environ:
            os.environ[key] = value

Technical Analysis

The service imports the general ~/.openclaw/.env file rather than a dedicated Knods configuration. Such a shared file may contain unrelated model-provider credentials, service tokens, or other sensitive values. The Python environment loader similarly imports all entries without a ...[truncated 1227 chars]

Remediation
View remediation

Remediation Suggestions

  • Create a dedicated environment file for this service, such as ~/.config/knods-bridge/env.
  • Set its permissions to 0600 and ensure its parent directory is not writable by other users.
  • Store only the variables required by the selected mode.
  • Replace unrestricted environment loading with an allowlist containing:
    • KNODS_BASE_URL
    • KNODS_GATEWAY_TOKEN
    • OPENCLAW_AGENT_ID
    • OPENCLAW_BIN
  • Launch the OpenClaw subprocess with an explicit minimal env mapping rather than inheriting os.environ.
  • Do not expose KNODS_API_KEY to the polling bridge unless it is genuinely required.
  • Keep unrelated provider credentials outside the bridge's service environment.
  • Document the environment inheritance boundary and advise users to rotate credentials if they were exposed to an untrusted agent profile.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/knods_headless.py:35
Finding

Bearer Credentials Can Be Sent to Arbitrary or Plaintext Endpoints

Content
View full analysis

Vulnerability Details

File Location: scripts/knods_headless.py:35-48,129-137; related gateway handling at scripts/knods_iris_bridge.py:31-53
Vulnerability Type: Missing transport and destination validation for credential-bearing requests
Risk Level: Medium

Vulnerable Code

python
def resolve_api_base(cli_value: str | None) -> str:
    if cli_value:
        return cli_value.rstrip("/")
    env_value = os.environ.get("KNODS_API_BASE_URL", "").strip()
    if env_value:
        return env_value.rstrip("/")
    derived = derive_api_base_from_gateway(os.environ.get("KNODS_BASE_URL", "").strip())
    if derived:
        return derived.rstrip("/")
    raise SystemExit("missing KNODS_API_BASE_URL")


def resolve_api_key(cli_value: str | None) -> str:
    if cli_value:
        return cli_value.strip()
    env_value = os.environ.get("KNODS_API_KEY", "").strip()
    if env_value:
        return env_value
    raise SystemExit("missing KNODS_API_KEY")
python
session = requests.Session()
session.headers.update(
    {
        "Authorization": f"Bearer {api_key}",
        "Accept": "application/json",
        "User-Agent": "openclaw-knods-headless/1.0",
    }
)

The gateway bridge similarly appends its token to any supplied base URL:

python
def add_token(url: str, token: str) -> str:
    parsed = parse.urlparse(url)
    qs = parse.parse_qs(parsed.query, keep_blank_values=True)
    if "token" not in qs:
        qs["token"] = [token]
    query = parse.urlencode(qs, doseq=True)
    return parse.urlunparse(parsed._replace(query=query))

Technical Analysis

The headless client accepts an arbitrary --base-url or environment-provided URL and attaches the Knods API key as a bearer token. It does not require HTTPS, reject embedded URL credentials, or constrain the destination to an expected Knods host.

The polling bridge has the same destinatio ...[truncated 1435 chars]

Remediation
View remediation

Remediation Suggestions

  • Parse and validate all configured URLs before attaching credentials.
  • Require the https scheme by default.
  • Reject URLs containing username or password components.
  • Reject missing hostnames, malformed ports, fragments, and unexpected schemes.
  • Optionally maintain an administrator-configurable allowlist of trusted Knods hostnames.
  • Permit HTTP only through an explicit development flag restricted to loopback addresses.
  • Display the sanitized destination and require confirmation when an interactive CLI key is sent to a new host.
  • Avoid following cross-origin redirects with authorization headers; explicitly validate redirect destinations.
  • Keep TLS verification enabled and provide no insecure certificate-bypass option.
  • Use narrowly scoped, revocable API keys and rotate any key sent to an untrusted endpoint.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The documented purpose claims workflow-building and headless flow-management capabilities, but the analysis indicates the actual behavior is primarily a bridge between Knods messages and an OpenClaw agent subprocess. That mismatch is dangerous because users may authorize the skill under false assumptions while it performs a different, less transparent control-plane function with message forwarding and subprocess orchestration.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

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

md
- On `failed`, surface `error.message` and `error.nodeId` if present
- On timeout, optionally cancel the run

## Output Rules

- Return normal assistant text; do not wrap the full reply in a custom envelope.
- Include `[KNODS_ACTION]...[/KNODS_ACTION]` inline only when a canvas mutation is intended.

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

sh
BRIDGE_SRC="$SKILL_DIR/scripts/knods_iris_bridge.py"
BRIDGE_DST="$OPENCLAW_HOME/scripts/knods_iris_bridge.py"
ENV_FILE="$OPENCLAW_HOME/.env"
SERVICE_NAME="knods-iris-bridge.service"
SERVICE_PATH="$SYSTEMD_USER_DIR/$SERVICE_NAME"

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/knods_headless.py (reported line 90)May include surrounding context.

python
BRIDGE_SRC="$SKILL_DIR/scripts/knods_iris_bridge.py"
BRIDGE_DST="$OPENCLAW_HOME/scripts/knods_iris_bridge.py"
ENV_FILE="$OPENCLAW_HOME/.env"
SERVICE_NAME="knods-iris-bridge.service"
SERVICE_PATH="$SYSTEMD_USER_DIR/$SERVICE_NAME"

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/knods_iris_bridge.py (reported line 187)May include surrounding context.

python
BRIDGE_SRC="$SKILL_DIR/scripts/knods_iris_bridge.py"
BRIDGE_DST="$OPENCLAW_HOME/scripts/knods_iris_bridge.py"
ENV_FILE="$OPENCLAW_HOME/.env"
SERVICE_NAME="knods-iris-bridge.service"
SERVICE_PATH="$SYSTEMD_USER_DIR/$SERVICE_NAME"

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill declares operational requirements that imply environment access, file reads, network communication, and shell execution, but it does not declare any explicit tool scope or permissions boundary. This makes the effective trust surface larger than advertised and increases the chance that a user or platform will invoke the skill with broader capabilities than intended.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The installer instructions direct the user to copy files into persistent locations and enable a user-level systemd service, but they do not prominently warn that this creates a long-running background process. Persistent execution changes the host's security posture and can continue processing messages or consuming credentials after the initial install.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

Enabling the service with systemd creates session persistence, allowing the bridge to restart and continue running beyond the user's immediate action. Persistence is security-relevant because it can maintain network connectivity, process future messages, and keep access to configured credentials without repeated user approval.

Content

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

md
Then runs:

- `systemctl --user daemon-reload`
- `systemctl --user enable --now knods-iris-bridge.service`

### Environment Variables

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill instructs users to place API keys and gateway tokens into an environment file without any guidance on secret storage, file permissions, rotation, or exposure risk. In a skill that operates a persistent bridge and performs network calls, weak credential handling increases the chance of token leakage or misuse.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The protocol explicitly places the gateway token in the URL query string and transmits full conversation content via polling endpoints, but the documentation does not warn about transport security, log exposure, or token handling risks. Query-string secrets are commonly captured in logs, proxies, browser history, and monitoring systems, so implementers may unintentionally expose credentials and user data if TLS, redaction, and retention controls are not enforced.

Content

No source excerpt is available for this finding.

Behavior Manipulation

Medium
Category
Prompt Injection
Confidence
78% confidence
Finding

The instruction to always prefer first-message context over the default catalog creates a trust-on-input boundary where untrusted remote content can override local assumptions and behavior. Because the prepended context includes action rules and catalog data that drive flow construction, a malicious or compromised upstream sender could manipulate the agent into using attacker-chosen node types or unsafe workflow logic.

Content

Scanner excerpt · references/protocol.md (reported line 38)May include surrounding context.

md
- Empty `messages` array means no work.
- Poll every 1-2 seconds.
- The first message in a conversation includes prepended context with the full node catalog and action rules. **Always prefer this context over any default catalog.**

### 2) Send streamed response

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The script installs and enables a persistent user-level systemd service that will automatically restart and run on login, creating durable execution. In a skill context, persistence materially increases risk because any later compromise, malicious bridge update, or leaked environment token can be continuously leveraged without further user interaction.

Content

Scanner excerpt · scripts/install_local.sh (reported line 44)May include surrounding context.

sh
EOF

systemctl --user daemon-reload
systemctl --user enable --now "$SERVICE_NAME"

echo "Installed bridge script: $BRIDGE_DST"
echo "Installed service unit:  $SERVICE_PATH"

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/knods_iris_bridge.py (reported line 109)May include surrounding context.

python
"--timeout",
        str(timeout_sec),
    ]
    out = subprocess.check_output(cmd, text=True)
    data = json.loads(out)
    return extract_text_from_result(data)

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The manifest says the skill can perform direct flow discovery/execution tasks such as listing flows, reading schemas, starting runs, polling status, cancelling runs, and retrieving outputs programmatically. This file only implements a polling bridge that fetches chat-style updates, invokes an OpenClaw agent subprocess, and posts reply chunks back to a gateway; it contains no code for Knods headless flow discovery or execution APIs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script prints updates_url and respond_url after resolve_urls may append the gateway token as a query parameter. This can leak credentials into terminal scrollback, logs, CI output, or process monitors, enabling unauthorized access to the Knods polling gateway if those logs are exposed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

This markdown file documents starting runs, cancelling running jobs, and installing a user service, all of which can affect remote processing state or the user's local environment. The description presents these actions as simple commands but does not include any warning or disclosure about system changes, service installation, or the effect of cancelling active jobs.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

The stated purpose is building/modifying Knods workflows and handling Knods polling or flow API tasks. This file additionally reads arbitrary key/value pairs from a local .env file and mutates the process environment, a capability not mentioned in the manifest and not inherent to Knods workflow operations themselves.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.