T09 · Insecure Skill Coding Practices
- Location
scripts/get_password_from_user.py:553- Finding
LAN Secret Intake Uses an Unverifiable Self-Signed TLS Certificate
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a coherent password-helper skill, but its LAN credential intake and TOTP enrollment handling create real secret-exposure risks users should review before installing.
Install only if you are comfortable with a local password broker handling real credentials. Prefer localhost mode, avoid the LAN phone flow on untrusted networks, treat the QR code and otpauth URL as secrets, and review every command that will run with injected credentials. Be aware that this stores account secrets in pass and stores the broker's TOTP seed locally under ~/.passwordstore-broker.
scripts/get_password_from_user.py:553LAN Secret Intake Uses an Unverifiable Self-Signed TLS Certificate
scripts/get_password_from_user.py:481Unbounded Request Body and Threaded Handling Allow LAN Denial of Service
The skill claims secrets are collected through one-time HTTPS forms and used via an environment-injection wrapper so raw values do not enter chat context or logs, but the documented interface also includes direct secret retrieval via scripts/vault.sh get and the analysis indicates the promised collection/execution mechanisms may be absent. This is dangerous because it creates a false sense of safety: an agent may rely on protections that do not actually exist, causing direct secret disclosure to stdout, chat, or logs during normal operation.
The skill claims secrets are collected through one-time HTTPS forms and used via an environment-injection wrapper so raw values do not enter chat context or logs, but the documented interface also includes direct secret retrieval via scripts/vault.sh get and the analysis indicates the promised collection/execution mechanisms may be absent. This is dangerous because it creates a false sense of safety: an agent may rely on protections that do not actually exist, causing direct secret disclosure to stdout, chat, or logs during normal operation.
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
scripts/vault.sh put test/secret <<< "ok" scripts/vault.sh exists test/secret scripts/run_with_secret.sh --secret test/secret --env TEST_SECRET -- sh -c 'test "$TEST_SECRET" = "ok"' scripts/vault.sh rm test/secret
The payload printed to stdout always includes the full otpauth URL, which embeds the raw TOTP shared secret, and error paths also print it to stderr. In a skill explicitly intended to keep raw secrets out of chat context and logs, emitting the enrollment secret directly defeats that guarantee and can allow anyone with log or transcript access to clone the second factor.
The skill declares operational steps that invoke local scripts, read and write files under the user's home directory, use networked HTTPS intake flows, and execute shell commands, yet it does not define any explicit tool scope or allowed-tools boundaries. That omission increases the chance an agent will run powerful filesystem, shell, and network actions without least-privilege controls or clear user-visible constraints.
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
## Non-Negotiable Guardrails
- Never ask users to paste raw secrets into chat.
- Never echo secret values back to user.
- Never store secrets in repo files, commit messages, issue comments, or transcripts.
- Never expose intake over public interfaces or tunnels.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
sudo apt-get update
sudo apt-get install -y pass gnupg2 openssl python3 qrencode
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
sudo apt-get update
sudo apt-get install -y pass gnupg2 openssl python3 qrencode
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
sudo apt-get update
sudo apt-get install -y pass gnupg2 openssl python3 qrencode
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
sudo apt-get update
sudo apt-get install -y pass gnupg2 openssl python3 qrencode
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.
gpg --full-generate-key
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
raise RuntimeError("Missing required command 'ip' for Linux LAN detection")
try:
route_proc = subprocess.run(
[ip_cmd, "-j", "route", "get", "1.1.1.1"],
check=True,
stdout=subprocess.PIPE,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
raise RuntimeError("Linux LAN detection failed: route output missing interface name")
try:
addr_proc = subprocess.run(
[ip_cmd, "-j", "-f", "inet", "addr", "show", "dev", iface_name],
check=True,
stdout=subprocess.PIPE,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
raise RuntimeError("Missing required command 'route' for macOS LAN detection")
try:
route_proc = subprocess.run(
[route_cmd, "-n", "get", "default"],
check=True,
stdout=subprocess.PIPE,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
raise RuntimeError("macOS LAN detection failed: default route missing interface name")
try:
proc = subprocess.run(
["ifconfig", iface_name],
check=True,
stdout=subprocess.PIPE,
This code accepts a remotely submitted secret over the HTTPS form and forwards it directly to an external script, vault.sh, using a secret name supplied from the command line. While the subprocess call itself is not shell-injectable, the security boundary is weak because untrusted input is handed to another executable with no local validation of the secret identifier or confinement of what vault.sh may write, so a malformed or unexpected secret name could become dangerous depending on vault.sh's implementation. In this skill context, which is explicitly designed to handle secrets, this trust boundary matters more because it can lead to secret misrouting, overwrite of unintended entries, or downstream injection in the storage helper.
return
try:
subprocess.run(
[self.state.vault_script, "put", self.state.secret_name],
input=secret_value.encode("utf-8"),
check=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
except ValueError:
san = f"DNS:{hostname},DNS:localhost,IP:127.0.0.1"
subprocess.run(
[
openssl,
"req",
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
if sys.platform == "darwin":
return "brew install qrencode"
if shutil.which("apt-get"):
return "sudo apt-get install -y qrencode"
if shutil.which("dnf"):
return "sudo dnf install -y qrencode"
if shutil.which("pacman"):
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
if sys.platform == "darwin":
return "brew install qrencode"
if shutil.which("apt-get"):
return "sudo apt-get install -y qrencode"
if shutil.which("dnf"):
return "sudo dnf install -y qrencode"
if shutil.which("pacman"):
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
if sys.platform == "darwin":
return "brew install qrencode"
if shutil.which("apt-get"):
return "sudo apt-get install -y qrencode"
if shutil.which("dnf"):
return "sudo dnf install -y qrencode"
if shutil.which("pacman"):
This script introduces a new secret-management path for TOTP enrollment that is outside the skill's stated design of handling secrets via one-time HTTPS forms and pass-backed storage. In a secret-handling skill, undocumented alternate secret flows increase the attack surface, bypass expected controls, and can lead operators to assume protections exist where they do not.
The script stores the TOTP shared secret directly in ~/.passwordstore-broker/totp.secret instead of using the manifest-described pass storage path. Even with chmod 0600, this creates an unmanaged plaintext secret at rest that may be copied by backups, endpoint tooling, or local compromise, undermining the broker's stated safe-secret-handling model.
When qrencode is missing, the script prints a manual enrollment URL containing the full TOTP secret to stderr without warning. stderr is frequently captured by terminals, CI systems, wrappers, or agent logs, so this can leak the shared secret and permit unauthorized enrollment of a duplicate authenticator.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return 2
try:
subprocess.run(
[qrencode, "-o", str(qr_png_path), "-t", "PNG", otpauth_url],
check=True,
stdout=subprocess.DEVNULL,
On QR generation failure, the script again prints the full otpauth enrollment URL, exposing the TOTP shared secret during an error path where users are likely to paste logs for troubleshooting. Error-path secret disclosure is especially risky because it propagates secrets into support channels and automation logs outside the intended secret boundary.