Back to skill

Security audit

Mac Multi User Agents

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly transparent about configuring macOS agent users, but its account-creation scripts handle passwords and generated admin commands unsafely enough to warrant review before use.

Review before installing or using. The skill is not hiding remote code or backdoors, but do not run its account-creation scripts on a real Mac until password handling is fixed, rendered commands are safely quoted or avoided, inputs are validated, and privileged execution requires explicit confirmation. Treat any dry-run or transcript output from the current version as potentially containing account credentials.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/create-user.sh:35
Finding

Account Password Exposed in Process Arguments and Dry-Run Output

Content
View full analysis

Vulnerability Details

File Location: scripts/create-user.sh, lines 35-60
Vulnerability Type: Credential exposure through command-line arguments and diagnostic output
Risk Level: High

Vulnerable Code

bash
PASSWORD="${!PASSWORD_ENV:-}"
if [[ -z "$PASSWORD" ]]; then
  echo "password env is empty: $PASSWORD_ENV" >&2
  exit 1
fi

if dscl . -read "/Users/$USER_NAME" >/dev/null 2>&1; then
  echo "user already exists: $USER_NAME" >&2
  exit 1
fi

create_cmd=(sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password "$PASSWORD")

if [[ "$DRY_RUN" == "yes" ]]; then
  printf '%q ' "${create_cmd[@]}"
  echo
  if [[ "$ADMIN" == "yes" ]]; then
    echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin"
  fi
  echo "sudo -u \"$USER_NAME\" mkdir -p \"/Users/$USER_NAME/.ssh\" \"/Users/$USER_NAME/.openclaw\""
  echo "sudo -u \"$USER_NAME\" chmod 700 \"/Users/$USER_NAME/.ssh\""
  echo "sudo -u \"$USER_NAME\" /bin/zsh -lc 'grep -q /opt/homebrew/bin ~/.zprofile 2>/dev/null || printf \"\\nexport PATH=/opt/homebrew/bin:/opt/homebrew/sbin:\\$PATH\\n\" >> ~/.zprofile'"
  exit 0
fi

"${create_cmd[@]}"

Technical Analysis

Although the password is initially supplied through an environment variable, the script copies it into the argument vector of sysadminctl using -password "$PASSWORD".

In normal execution, the plaintext password may consequently be exposed through local process inspection, process accounting, endpoint monitoring, command auditing, or diagnostic collection. The exact visibility of another process's arguments depends on macOS permissions and timing, but secrets should not be placed in command-line arguments.

The dry-run behavior creates a more direct disclosure: printf '%q ' "${create_cmd[@]}" prints every argument in create_cmd, including the plaintext account password. Dry-run output may be stored in terminal scrollback, C ...[truncated 1447 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not place passwords in command-line argument arrays.
  • Use a supported interactive or standard-input password mechanism, such as sysadminctl -password -, where practical.
  • Ensure the secret is supplied directly to the intended process without echoing it or embedding it in generated shell text.
  • In dry-run mode, print a redacted placeholder such as -password [REDACTED] rather than the actual value.
  • Avoid retaining the password in shell variables longer than necessary and unset it immediately after use.
  • Document that execution logs, tracing such as set -x, and terminal recording must not capture secrets.
  • Add an automated test asserting that dry-run output never contains the supplied password.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/render-user-create.sh:28
Finding

Shell Command Injection in Rendered Administrative Commands

Content
View full analysis

Vulnerability Details

File Location: scripts/render-user-create.sh, lines 28-43
Vulnerability Type: Shell command injection through unsafe command rendering
Risk Level: High

Vulnerable Code

bash
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

# Bootstrap per-user runtime
sudo -u "$USER_NAME" mkdir -p "/Users/$USER_NAME/.ssh" "/Users/$USER_NAME/.openclaw"
sudo -u "$USER_NAME" chmod 700 "/Users/$USER_NAME/.ssh"

# Ensure shared toolchain PATH
sudo -u "$USER_NAME" /bin/zsh -lc 'grep -q /opt/homebrew/bin ~/.zprofile 2>/dev/null || printf "\\nexport PATH=/opt/homebrew/bin:/opt/homebrew/sbin:\\$PATH\\n" >> ~/.zprofile'
EOF

Technical Analysis

The script renders shell commands by interpolating USER_NAME, FULL_NAME, and SHELL_PATH directly into a heredoc. Double quotation marks in the generated output do not make this safe because attacker-controlled values can themselves contain quotation marks, shell separators, command substitutions, redirections, or newlines.

The script only checks that the username and full name are nonempty. It does not enforce a valid macOS short-name syntax, restrict the shell to approved absolute paths, or shell-escape the rendered arguments.

The script's declared purpose is to produce repeatable commands for an administrator to execute. Consequently, crafted input can turn the generated command sequence into executable attacker-controlled shell syntax. Several rendered commands use sudo, making unsafe output particularly dangerous.

Attack Path

  1. An attacker supplies or persuades an operator to use a crafted value for --user, --full-name, or --shell.
  2. The value contains shell syntax that terminates the genera ...[truncated 1270 chars]
Remediation
View remediation

Remediation Suggestions

  • Strictly validate USER_NAME against the accepted macOS short-name format and reject separators, whitespace, path traversal, control characters, and shell metacharacters.
  • Restrict SHELL_PATH to an explicit allowlist of approved shells, such as entries from /etc/shells.
  • Treat FULL_NAME as untrusted input even though spaces are legitimate.
  • Render each argument with shell-safe escaping, for example by constructing command arrays and serializing every element with printf '%q'.
  • Prefer executing argument arrays directly rather than generating shell source.
  • If command rendering remains necessary, clearly label output as shell code and ensure every untrusted field is encoded by a single audited quoting function.
  • Add tests using embedded quotes, semicolons, command substitutions, newlines, redirections, and spaces to verify that generated output cannot alter command boundaries.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding

The skill claims to provide broader multi-user host configuration, SSH trust handling, Fast User Switching guidance, auditable rollback, and shared toolchain setup, but the described behavior only partially implements those promises. This is dangerous because operators may rely on the skill for security-sensitive setup they assume has been completed, leaving weak isolation, incomplete rollback, or missing access controls on a shared multi-user host.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill explicitly instructs running shell scripts that inspect the host, create macOS users, modify per-user state, and write receipts, but it declares no tool scope or permission boundaries. That mismatch is dangerous because an agent or operator may invoke file-write and shell-capable actions without an explicit least-privilege contract, increasing the chance of unintended host changes on an admin-level macOS system.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

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

md
- preparing a MacBook Pro to host 2-4 dedicated agent users
- estimating a sane number of agent users from the Mac's CPU and RAM
- keeping one shared Homebrew toolchain while isolating per-user OpenClaw state
- rendering or executing repeatable user-create commands instead of clicking around in System Settings
- checking whether a user has the required shell/tool paths
- documenting and auditing per-user rollout

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The default prompt uses a broad invocation phrase tied to high-impact actions like recommending agent-user counts, rendering or executing account creation, and verifying shared system layouts. Because the phrase is generic enough to overlap with normal administrative requests, it could cause the skill to activate unintentionally and steer the agent into performing privileged multi-user system changes on a macOS host.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script reads a password from an environment variable and then passes it to sysadminctl via the -password command-line argument. On multi-user systems, secrets in environment variables and process arguments can be exposed through shell history, process listings, logging, or CI/runtime telemetry, making credential disclosure plausible during account provisioning.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The script performs destructive system-level changes by creating a local user account and optionally granting administrator privileges via sudo, but it provides no confirmation prompt or explicit warning beyond the generic usage string. Because these are high-impact operations affecting system integrity, the lack of user disclosure meets the missing-warning criteria.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/create-user.sh (reported line 54)May include surrounding context.

sh
if [[ "$ADMIN" == "yes" ]]; then
    echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin"
  fi
  echo "sudo -u \"$USER_NAME\" mkdir -p \"/Users/$USER_NAME/.ssh\" \"/Users/$USER_NAME/.openclaw\""
  echo "sudo -u \"$USER_NAME\" chmod 700 \"/Users/$USER_NAME/.ssh\""
  echo "sudo -u \"$USER_NAME\" /bin/zsh -lc 'grep -q /opt/homebrew/bin ~/.zprofile 2>/dev/null || printf \"\\nexport PATH=/opt/homebrew/bin:/opt/homebrew/sbin:\\$PATH\\n\" >> ~/.zprofile'"
  exit 0

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/create-user.sh (reported line 66)May include surrounding context.

sh
sudo dseditgroup -o edit -a "$USER_NAME" -t user admin
fi

sudo -u "$USER_NAME" mkdir -p "/Users/$USER_NAME/.ssh" "/Users/$USER_NAME/.openclaw"
sudo -u "$USER_NAME" chmod 700 "/Users/$USER_NAME/.ssh"
sudo -u "$USER_NAME" /bin/zsh -lc 'grep -q /opt/homebrew/bin ~/.zprofile 2>/dev/null || printf "\nexport PATH=/opt/homebrew/bin:/opt/homebrew/sbin:\$PATH\n" >> ~/.zprofile'

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 46)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 52)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 54)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 55)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 56)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 63)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 66)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 67)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 68)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/render-user-create.sh (reported line 33)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/render-user-create.sh (reported line 36)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/render-user-create.sh (reported line 39)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/render-user-create.sh (reported line 40)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/render-user-create.sh (reported line 43)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/verify-user-shell.sh (reported line 53)May include surrounding context.

sh
cat <<EOF
# Create user
sudo sysadminctl -addUser "$USER_NAME" -fullName "$FULL_NAME" -shell "$SHELL_PATH" -password -

# Optional admin promotion
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/render-user-create.sh (reported line 39)May include surrounding context.

sh
$( [ "$ADMIN" = "yes" ] && echo "sudo dseditgroup -o edit -a \"$USER_NAME\" -t user admin" || echo "# no admin promotion requested" )

# Bootstrap per-user runtime
sudo -u "$USER_NAME" mkdir -p "/Users/$USER_NAME/.ssh" "/Users/$USER_NAME/.openclaw"
sudo -u "$USER_NAME" chmod 700 "/Users/$USER_NAME/.ssh"

# Ensure shared toolchain PATH

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/create-user.sh (reported line 55)May include surrounding context.

sh
# Bootstrap per-user runtime
sudo -u "$USER_NAME" mkdir -p "/Users/$USER_NAME/.ssh" "/Users/$USER_NAME/.openclaw"
sudo -u "$USER_NAME" chmod 700 "/Users/$USER_NAME/.ssh"

# Ensure shared toolchain PATH
sudo -u "$USER_NAME" /bin/zsh -lc 'grep -q /opt/homebrew/bin ~/.zprofile 2>/dev/null || printf "\\nexport PATH=/opt/homebrew/bin:/opt/homebrew/sbin:\\$PATH\\n" >> ~/.zprofile'

Static analysis

No suspicious patterns detected.