Back to skill

Security audit

kannaka-node

Security checks for vulnerabilities and agentic risk

Overview

The skill is openly designed to provision a long-running server node, but its installer and service setup contain unsafe remote execution and injection risks that need review before use.

Install only after reviewing or fixing the provisioning script. Use a dedicated, non-sensitive VPS and service user, require explicit approval before running the remote installer or sudo service step, do not allow untrusted values for INSTALL_URL, BRAIN_EMAIL, NATS_USER, NATS_PASSWORD, display name, or config fields, and verify the exact installer, binary digest, systemd units, and timer before enabling persistence.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • 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
scripts/provision.sh:121
Finding
Mutable Remote Installer Is Executed Directly Through a Shell Pipeline<![CDATA[ ## Vulnerability Details **File Location**: `scripts/provision.sh`, lines 21-22 and 121 **Vulnerability Type**: Remote payload retrieval and execution **Risk Level**: Critical ### Vulnerable Code ```bash INSTALL_URL="${INSTALL_URL:-https://install.ninja-portal.com/kannaka}" NATS_DEFAULT="nats://swarm.ninja-portal.com:4222" ``` ```bash as_user "curl -fsSL '$INSTALL_URL' | sh -s -- $flags" || { echo "installer failed" >&2; return 1; } ``` ### Technical Analysis The provisioning script downloads content from a mutable external URL and immediately passes it to `sh`. The fetched installer is not saved, version-pinned, signature-verified, or digest-verified before execution. The documentation claims that the remote installer reads a signed manifest and downloads SHA-256-checked releases. Those downstream checks do not authenticate the bootstrap installer itself. If the installer endpoint, delivery infrastructure, DNS/TLS trust chain, or server account is compromised, the returned shell program can perform arbitrary operations as the target login user before any release verification occurs. `INSTALL_URL` is also environment-overridable. Because it is interpolated into a command string consumed by `bash -c`, an attacker who can control the provisioning environment may redirect execution to an arbitrary source. A value containing a single quote could additionally escape the intended shell quoting. ### Attack Path 1. The attacker compromises the configured installer endpoint or gains control over `INSTALL_URL`. 2. The attacker serves a malicious shell script instead of the expected installer. 3. An operator runs `bash provision.sh install` or `bash provision.sh all`. 4. `curl` downloads the attacker-controlled response. 5. The response is passed directly to `sh` and executes as the configured Kannaka user. 6. The payload can steal user-readable credentials and node data or replace the Kannaka binary. 7. A later service step can copy the malicious binary ...[truncated 459 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `curl | sh` execution pattern. 2. Vendor a reviewed installer in the Skill or download it to a temporary file before execution. 3. Pin the installer to an immutable version and expected cryptographic digest. 4. Verify a detached digital signature using a public key distributed with the audited Skill. 5. Abort before execution if signature, digest, ownership, or permissions are unexpected. 6. Remove the unrestricted `INSTALL_URL` override or restrict it to an explicit HTTPS hostname allowlist. 7. Avoid constructing commands for `bash -c`; pass URLs and installer arguments through positional parameters or arrays. 8. Independently verify downloaded binaries again before copying them into privileged system locations. ]]>

T06 · System Persistence

Error
Location
scripts/provision.sh:273
Finding
Unverified Downloaded Binary Is Installed as Persistent System Services and a Scheduled Timer<![CDATA[ ## Vulnerability Details **File Location**: `scripts/provision.sh`, lines 273-298 **Vulnerability Type**: System persistence **Risk Level**: High ### Vulnerable Code ```bash if ! cmp -s "$LOCAL_BIN" "$SYS_BIN" 2>/dev/null; then [ -f "$SYS_BIN" ] && $SUDO mv "$SYS_BIN" "$SYS_BIN.previous" $SUDO install -m755 "$LOCAL_BIN" "$SYS_BIN" have restorecon && $SUDO restorecon "$SYS_BIN" 2>/dev/null || true ok "binary copied to $SYS_BIN"; sys_bin_changed=1 else ok "$SYS_BIN is current"; sys_bin_changed=0; fi printf '%s\n' "$runner_body" | $SUDO tee "$RUNNER" >/dev/null && $SUDO chmod 755 "$RUNNER" printf '%s\n' "$unit_node" | $SUDO tee "$UNIT_NODE" >/dev/null if [ "$ROLE" = serve ]; then printf '%s\n' "$unit_serve" | $SUDO tee "$UNIT_SERVE" >/dev/null; fi if [ "$DREAM" = true ]; then printf '%s\n' "$unit_dream" | $SUDO tee "$UNIT_DREAM" >/dev/null; printf '%s\n' "$timer_dream" | $SUDO tee "$TIMER_DREAM" >/dev/null; fi $SUDO systemctl daemon-reload $SUDO systemctl enable --now kannaka-node.service >/dev/null 2>&1 || $SUDO systemctl restart kannaka-node.service [ "$sys_bin_changed" = 1 ] && $SUDO systemctl restart kannaka-node.service [ "$ROLE" = serve ] && $SUDO systemctl enable --now kannaka-serve.service >/dev/null 2>&1 [ "$DREAM" = true ] && $SUDO systemctl enable --now kannaka-dream.timer >/dev/null 2>&1 ``` ### Technical Analysis The Skill uses root privileges to copy the downloaded binary into `/usr/local/bin`, writes units under `/etc/systemd/system`, and enables them across reboots. The primary service uses `Restart=always`, while the dream timer executes the binary on a recurring schedule. Persistent services are relevant to the declared purpose of an always-running swarm node. However, the implementation does not independently verify the provenance or integrity of `$LOCAL_BIN` before privileged installation. Its trust is inherited from the mutable remote installer described in the preceding finding. The services run as the selected login us ...[truncated 1293 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Verify the binary using an independently pinned signature and digest immediately before privileged installation. 2. Display the exact version, digest, signer identity, unit files, and enabled timers before requesting approval. 3. Separate installation from service enablement and require explicit confirmation for each persistent unit. 4. Add systemd hardening directives such as: - `NoNewPrivileges=true` - `PrivateTmp=true` - `ProtectSystem=strict` - `ProtectHome=read-only` - `ProtectKernelTunables=true` - `ProtectControlGroups=true` - `RestrictSUIDSGID=true` - `RestrictAddressFamilies=` with only required families - `ReadWritePaths=` limited to required Kannaka data paths 5. Use a dedicated low-privilege service account instead of the general login account where feasible. 6. Ensure role changes remove and disable obsolete units rather than leaving previously installed services behind. 7. Extend uninstallation to remove installed binaries when explicitly requested and report all residual files. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/provision.sh:127
Finding
Unvalidated Display Name Enables Persistent Shell Command Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/provision.sh`, lines 127-160 and 211-221 **Vulnerability Type**: Shell command injection in generated service runner **Risk Level**: High ### Vulnerable Code ```bash while [ $# -gt 0 ]; do case "$1" in --agent-id) AGENT_ID="$2"; shift;; --display-name) DISPLAY="$2"; shift;; --nats-url) NATS_URL="$2"; shift;; --no-swarm) SWARM=false;; *) echo "configure: unknown flag $1" >&2; return 1;; esac; shift; done ``` ```bash case "$AGENT_ID" in *[!A-Za-z0-9._-]*) echo "agent id must be [A-Za-z0-9._-]" >&2; return 1;; esac [ -n "$DISPLAY" ] || DISPLAY="$AGENT_ID" ``` ```bash runner_body="$(cat <<EOF #!/bin/bash # kannaka-node-run — written by kannaka-node/provision.sh; safe to edit. export KANNAKA_DATA_DIR="$DATA" [ -f "$NATS_ENV" ] && { set -a; . "$NATS_ENV"; set +a; } BIN="$SYS_BIN" "\$BIN" swarm join --agent-id "$AGENT_ID" --display-name "$DISPLAY" exec "\$BIN" swarm listen --auto-sync --agent-id "$AGENT_ID" EOF )" ``` The generated runner is subsequently installed and executed: ```bash printf '%s\n' "$runner_body" | $SUDO tee "$RUNNER" >/dev/null && $SUDO chmod 755 "$RUNNER" $SUDO systemctl enable --now kannaka-node.service >/dev/null 2>&1 || $SUDO systemctl restart kannaka-node.service ``` ### Technical Analysis `AGENT_ID` is restricted to a safe character set, but `DISPLAY` has no corresponding validation. The display name is inserted directly into the source code of a generated Bash script between double quotes. Double quotes do not suppress command substitution. A display name containing `$(command)` or backticks is therefore evaluated when the generated runner executes. Embedded double quotes or newlines can also terminate the intended argument and insert additional shell statements. Because the generated runner is installed in `/usr/local/bin` and launched by an enabled systemd service, the injected command becomes persistent and runs whenever the node service starts or restarts. ### ...[truncated 1029 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not generate executable shell source containing interpolated configuration values. 2. Replace the runner with a fixed, audited executable that reads structured configuration without shell evaluation. 3. If a shell wrapper is unavoidable, encode every generated value with `printf '%q'` and test the resulting script with adversarial input. 4. Apply a strict display-name allowlist and maximum length. 5. Reject quotes, backticks, dollar signs, backslashes, shell metacharacters, control characters, carriage returns, and newlines. 6. Validate values again during the privileged service step rather than trusting an existing configuration file. 7. Prefer direct systemd arguments or a fixed wrapper that passes values as array elements. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/provision.sh:103
Finding
BRAIN_EMAIL Environment Value Enables Shell Command Injection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/provision.sh`, lines 103-121 **Vulnerability Type**: Shell command injection through unsafe command construction **Risk Level**: High ### Vulnerable Code ```bash install_bins() { say "== install" flags="--skip-statusline" case "${BRAIN:-none}" in hosted) [ -n "${BRAIN_EMAIL:-}" ] || { echo "BRAIN=hosted needs BRAIN_EMAIL" >&2; return 1; }; flags="$flags --brain hosted --email $BRAIN_EMAIL";; local) flags="$flags --brain local";; esac ``` ```bash as_user "curl -fsSL '$INSTALL_URL' | sh -s -- $flags" || { echo "installer failed" >&2; return 1; } ``` The command is evaluated by: ```bash as_user() { if [ "$(id -un)" = "$U" ]; then bash -c "$*"; else sudo -u "$U" -H bash -c "$*"; fi; } ``` ### Technical Analysis `BRAIN_EMAIL` is concatenated into the `flags` string without validation or shell escaping. The complete string is then passed to `as_user`, which evaluates it through `bash -c`. Consequently, the email is not treated strictly as one installer argument. Shell separators, command substitutions, redirections, or newlines in the environment value are interpreted as shell syntax by the local Bash process. Even an otherwise trustworthy remote installer does not mitigate this issue because injection occurs in the local command shell used to launch the installer. ### Attack Path 1. An attacker controls or influences the environment used for provisioning. 2. The attacker sets `BRAIN=hosted`. 3. The attacker assigns shell syntax to `BRAIN_EMAIL`. 4. The operator runs `bash provision.sh install` or `bash provision.sh all`. 5. The value is concatenated into `flags`. 6. `as_user` passes the complete command string to `bash -c`. 7. Bash interprets the injected syntax and executes the attacker's command as the configured Kannaka user. ### Impact Assessment Successful exploitation grants arbitrary command execution as the target user during installation. The attacker can access the ...[truncated 191 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace string-based command construction with Bash arrays. 2. Pass `BRAIN_EMAIL` as a single positional argument without re-evaluating it through `bash -c`. 3. Redesign `as_user` to accept a command and argument array rather than a preformatted shell program. 4. Validate the email against a conservative expected format and maximum length. 5. Reject control characters and newline characters even after format validation. 6. If privilege switching is required, use `sudo -u "$U" -H -- command "${args[@]}"`. 7. Add automated tests using values containing spaces, quotes, substitutions, semicolons, redirections, and newlines. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/provision.sh:169
Finding
Unvalidated NATS Username Enables Command Injection During Credential Storage<![CDATA[ ## Vulnerability Details **File Location**: `scripts/provision.sh`, lines 169-181 **Vulnerability Type**: Shell command injection in credential handling **Risk Level**: High ### Vulnerable Code ```bash credentials() { say "== credentials" if [ -z "${NATS_USER:-}" ] || [ -z "${NATS_PASSWORD:-}" ]; then if [ -f "$NATS_ENV" ]; then ok "credentials file already present (kept)"; return 0; fi warn "NATS_USER/NATS_PASSWORD not in the environment; the node will join anonymously (it still joins, publishes phase and syncs; it cannot create the presence stream or serve recall)"; return 0 fi case "$NATS_PASSWORD" in *"'"*) echo "a password containing a single quote cannot be stored safely by this script" >&2; return 1;; esac # Single-quoted on purpose: the file is sourced by a shell, and an unquoted # value containing $( ) would execute. as_user "umask 077 && printf \"NATS_USER='%s'\nNATS_PASSWORD='%s'\n\" '$NATS_USER' '$NATS_PASSWORD' > '$NATS_ENV' && chmod 600 '$NATS_ENV'" ok "wrote $NATS_ENV (0600, single-quoted; value not shown)" } ``` ### Technical Analysis The script recognizes that the credential file is executable shell input and rejects single quotes in `NATS_PASSWORD`. It does not apply the same restriction to `NATS_USER`. `NATS_USER` is embedded inside a single-quoted segment of a command string passed to `bash -c`. A username containing a single quote can terminate the quote and inject arbitrary shell syntax. The injected syntax executes immediately while the credentials file is being written. The generated credential file is later sourced by the service runner: ```bash [ -f "$NATS_ENV" ] && { set -a; . "$NATS_ENV"; set +a; } ``` This design unnecessarily treats credentials as executable shell code and increases the consequences of any serialization defect or later file modification. ### Attack Path 1. An attacker supplies or influences swarm credentials. 2. The attacker places a quote followed by shell syntax in `NATS ...[truncated 802 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Stop storing credentials in a file that is sourced as shell code. 2. Use a format that does not permit evaluation, or use systemd credentials with restrictive permissions. 3. Write credential values without `bash -c`, using a fixed helper or safely passed environment variables. 4. Validate both `NATS_USER` and `NATS_PASSWORD`; do not protect only the password field. 5. Reject control characters and newlines, and enforce documented length limits. 6. If an environment file must be retained, implement a correct serializer and parser rather than using the shell `.` command. 7. Ensure the credentials file remains mode `0600`, is owned by the intended service account, and is not writable by other users. 8. Add adversarial tests covering quotes, substitutions, backticks, newlines, backslashes, and shell separators in both credential fields. ]]>
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 Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (44)

Ae1

High
Category
analysis-evasion
Content
**1. Connect and preflight.** Copy `scripts/provision.sh` to the host (or pipe it) and run
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
`references/traps.md` is the list of things that have actually gone wrong on real
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Script Fetching

High
Category
Supply Chain
Content
warn "in use:$busy (under $(dirname "$LOCAL_BIN")). The installer would write onto a running binary and delete it on failure. Close the kannaka-tui / chat that holds it, then re-run install."
    return 1
  fi
  as_user "curl -fsSL '$INSTALL_URL' | sh -s -- $flags" || { echo "installer failed" >&2; return 1; }
  [ -x "$LOCAL_BIN" ] || { echo "installer finished but $LOCAL_BIN is missing" >&2; return 1; }
  ok "$("$LOCAL_BIN" --version 2>/dev/null | head -1) at $LOCAL_BIN"
}
Confidence
99% confidence
Finding
The script downloads remote content and pipes it directly into sh, allowing arbitrary code from the remote endpoint or any compromise in the delivery chain to execute immediately. This is especially dangerous because the skill provisions long-lived services on fresh servers, so a malicious installer can implant persistence, steal credentials, or backdoor the host before any verification occurs.

Chaining Abuse

High
Category
Tool Misuse
Content
check       systemctl status kannaka-node; journalctl -u kannaka-node -f
status      KANNAKA_READONLY=1 kannaka status
peers       kannaka swarm peers
update      bash provision.sh install && sudo bash provision.sh service   (manifest-pinned; swaps $SYS_BIN with a move-aside and restarts)
uninstall   bash provision.sh uninstall   (keeps $DATA)
EOF
}
Confidence
75% confidence
Finding
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill explicitly directs an agent to use shell and SSH to copy and execute provisioning scripts on a remote host, but it declares no tool/permission scope. That increases the chance an agent will run high-impact commands without an explicit sandbox or approval boundary, especially because the workflow includes network access, file writes, package installation, and service management on third-party infrastructure.

Session Persistence

Medium
Category
Rogue Agent
Content
which reads the constellation's signed manifest and downloads a pinned, sha256-checked
release for this architecture into `~/.local/bin`. It also installs the dashboard and the
KannakaHDL binary. With `BRAIN=hosted BRAIN_EMAIL=…` or `BRAIN=local` in the environment
it sets up the model as well. It is idempotent; if one of the three binaries is in use (a `kannaka-tui`, a chat) it stops and names it rather than let the installer write over a busy file.

**3. Configure.** `bash provision.sh configure --agent-id NAME [--display-name "Name"]`.
Writes `~/.kannaka/config.toml` with the identity and the swarm bus, mode 0600. If a
Confidence
60% 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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
`bash provision.sh credentials`: it writes `~/.kannaka-nats.env`, single-quoted, 0600,
and never prints the value.

**4. Service.** `sudo bash provision.sh service [--role serve] [--no-dream]`. This is the
root step; say so to the human before you run it. It copies the binary to
`/usr/local/bin` (a binary under a home directory runs confined on SELinux hosts and
cannot read the node's own files), writes a small runner script, installs
Confidence
93% confidence
Finding
This step instructs the agent to run a root-level provisioning action via sudo that installs binaries into /usr/local/bin, writes runner scripts, and enables systemd services. Even if operationally legitimate, giving a skill broad sudo-guided behavior is dangerous because any mistake, prompt injection, or compromised script content can lead to full host compromise or persistence.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
## After hand-off

The node keeps itself in sync and dreams nightly. Updating is `bash provision.sh install`
(manifest-pinned, sha256-verified) followed by `sudo bash provision.sh service`, which swaps
the copy in `/usr/local/bin` with a move-aside and restarts the unit. Do not use
`kannaka update` on a node: it follows the first `kannaka` on PATH, pulls the latest release
rather than the manifest pin, and cannot write `/usr/local/bin` as the login user. The human can watch it with
Confidence
91% confidence
Finding
The update workflow again instructs the agent to perform privileged replacement of binaries in /usr/local/bin and restart services. Repeated root-capable maintenance paths increase attack surface because an agent may fetch or deploy updated artifacts over time, and any compromise of the install path, manifest trust chain, or operator decision-making can yield persistent code execution as root-managed system software.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
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
**Packages.** `dnf`. `curl`, `tar`, `awk`, `sha256sum` are present on the base image.
Nothing else is required for a member node.

**Updates.** `sudo dnf -y upgrade --refresh` replaces `sshd` mid-transaction; your
session may drop with a `kex_exchange_identification` error. That is not a failure; wait
and reconnect. Kernel updates need a reboot; `needs-restarting -r` tells you.
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
`ExecStart` there.

**`%h` in a system unit is root's home.** `%h`, `%u` and friends resolve before `User=`
applies. Write absolute paths in units installed under `/etc/systemd/system`.

**One writer.** The store has a single-writer lock. The listening node writes; a
concurrent `kannaka dream`, `remember` or `triage` from a shell contends with it. Read
Confidence
60% 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.

Session Persistence

Medium
Category
Rogue Agent
Content
## Re-running the installer while kannaka is running

Seen 2026-09-11 on a user's Ubuntu box. The user had `kannaka-tui` open; `install` re-ran the
installer, which downloads straight onto `~/.local/bin/kannaka`. Linux refuses to write an
executing file (`Text file busy`), the installer treated that as a failed download and removed
the destination, and the user's binary was gone until the installer ran again with nothing
holding the path, and the next run did the same to `kannaka-tui`. `provision.sh install`
Confidence
60% 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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
return 1
}

need_root() { if [ "$(id -u)" -ne 0 ]; then if sudo -n true 2>/dev/null; then SUDO="sudo"; else echo "this step needs root (passwordless sudo, or run as root)" >&2; exit 2; fi; else SUDO=""; fi; }

# A value for a TOML key in a table, if the file has it. Crude on purpose: the
# config is small and flat, and this keeps the script dependency-free.
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
if [ "$mem_mb" -ge 900 ]; then ok "memory ${mem_mb} MB"; else fail "memory ${mem_mb} MB (< 1 GB)"; fi
  disk_gb=$(df -BG --output=avail "$H" | tail -1 | tr -dc '0-9')
  if [ "$disk_gb" -ge 5 ]; then ok "disk ${disk_gb} GB free under $H"; else fail "disk ${disk_gb} GB free (< 5 GB; the store grows and a full disk strands saves)"; fi
  if have systemctl && [ -d /run/systemd/system ]; then ok "systemd present"; else warn "no systemd: the service step will print units instead of installing them"; fi
  if [ "$(id -u)" -eq 0 ]; then ok "running as root"; elif sudo -n true 2>/dev/null; then ok "passwordless sudo"; else warn "no passwordless sudo: install/configure work; service needs root"; fi
  if have getenforce; then se="$(getenforce 2>/dev/null)"; ok "SELinux $se (the binary is placed in /usr/local/bin for the unit; see references/traps.md)"; else ok "SELinux not present"; fi
  for tool in curl awk tar sha256sum; do have "$tool" && ok "$tool" || fail "$tool missing"; done
Confidence
80% 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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
--nats-url) NATS_URL="$2"; shift;; --no-swarm) SWARM=false;;
    *) echo "configure: unknown flag $1" >&2; return 1;; esac; shift; done
  say "== configure"
  as_user "mkdir -p '$DATA' && chmod 700 '$DATA'"
  if [ -f "$CFG" ]; then
    have_id="$(toml_get "$CFG" agent id)"
    if [ -n "$have_id" ]; then ok "config has agent id '$have_id' (kept; pass a different --agent-id only by editing the file)"; AGENT_ID="$have_id"; fi
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
grep -q '^\[swarm\]' "$CFG" || as_user "printf '\n[swarm]\nenabled = %s\nnats_url = \"%s\"\nrole = \"worker\"\n' '$SWARM' '$NATS_URL' >> '$CFG'"
    ok "config kept; missing tables added if any"
  fi
  as_user "chmod 600 '$CFG'"
  echo "$AGENT_ID" > /tmp/.kannaka-node-agent-id.$$ 2>/dev/null || true
}
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
grep -q '^\[swarm\]' "$CFG" || as_user "printf '\n[swarm]\nenabled = %s\nnats_url = \"%s\"\nrole = \"worker\"\n' '$SWARM' '$NATS_URL' >> '$CFG'"
    ok "config kept; missing tables added if any"
  fi
  as_user "chmod 600 '$CFG'"
  echo "$AGENT_ID" > /tmp/.kannaka-node-agent-id.$$ 2>/dev/null || true
}
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
[Service]
Type=oneshot
ExecStartPre=/bin/systemctl stop kannaka-node.service
ExecStart=/usr/bin/sudo -u $U -H env KANNAKA_DATA_DIR=$DATA $SYS_BIN dream --mode deep
ExecStopPost=/bin/systemctl start kannaka-node.service
TimeoutStartSec=2h
EOF
Confidence
87% confidence
Finding
The generated systemd unit embeds unquoted user-controlled values into an ExecStart command run via /usr/bin/sudo. If KANNAKA_USER or the derived home path contains shell metacharacters or whitespace, the unit command line can be altered, potentially resulting in command injection or execution failure under root-managed service control.

Session Persistence

Medium
Category
Rogue Agent
Content
EOF
)"
  if ! have systemctl || [ ! -d /run/systemd/system ]; then
    warn "no systemd here; these are the files to install on a systemd host:"
    printf '\n--- %s\n%s\n--- %s\n%s\n' "$RUNNER" "$runner_body" "$UNIT_NODE" "$unit_node"
    [ "$ROLE" = serve ] && printf -- '--- %s\n%s\n' "$UNIT_SERVE" "$unit_serve"
    return 0
Confidence
80% 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.

Static analysis

No suspicious patterns detected.