mcp-homelab@1.4.1
CAUTION· not startedPublisher claims: "MCP server that exposes homelab infrastructure tools to AI assistants" Observed in sandbox: no network, touches nothing outside the project, writes no memory.
Coverage
Not started: dependency install failed; the server itself was not observed.- Tested
- the dependency install step · every file read, file written and host contacted by what ran
- Not tested
- server startup · the tool list · tool behavior
What this means for the label: Caution here covers what ran. The server's tools were not exercised with a real credential, so nothing about their behavior is asserted; the credential summary below says what to do with that.
Before you give it your token
Code inventory arrives with this version's next honeypot run.- It will ask for
- unknown until the next run
- Where it talks to
- unknown until the next run
- What the honeypot saw
- no credential files read · no network
- What we could not test
- server startup · the tool list · tool behavior
Not enough was observed to say yet. Not started: dependency install failed; the server itself was not observed. This version's code has not been inventoried for credentials and hosts either; both arrive with its next honeypot run (popular servers first). `vet-skills try mcp-homelab` runs it now and prints the full answer.
Never paste a credential into a chat, a ticket or a report, including ours. How the guard holds it.
Is [email protected] safe to install?
KillerSkills ran it in the KillerSkills honeypot sandbox (2 scenarios; not started: dependency install failed; the server itself was not observed) on 2026-11-07 and scored it 70/100 (Caution). It is not known to be malicious, but it has behaviour or code a security policy should decide on before install. Notable findings: Access to sensitive file paths; Access to sensitive file paths; Access to sensitive file paths. Summary: no network, touches nothing outside the project, writes no memory.
Behavior trace
Honeypot run · 0 s · 2 scenarios · 0 deviationsStatic findings
- Secrets: `.env` file, environment variablesFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.| `python-dotenv` | `.env` file loading |Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# With -v $HOME/.ssh:/keys:ro, host key at ~/.ssh/gamehost becomes /keys/gamehost.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."ssh_key_path": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.**Environment variables** (from `.env`, never committed):Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Execute a docker command on a node, prefixing sudo if configured.Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.# Place this file (as config.yaml) alongside your .env in the config volume.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.> **Future enhancement:** Optionally load `~/.ssh/config` if it exists (via `paramiko.SSHConfig`), letting it override port, proxy, or identity per host. Add an `ssh_config_path` field to `config.yamlFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# ssh_key_path: "~/.ssh/id_ed25519"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# --- .env loading and startup validation ---Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# ssh_key_path: "~/.ssh/id_ed25519"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Load the .env file from the config directory.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."chown -R mcp:mcp /opt/mcp-homelab && chmod 400 /opt/mcp-homelab/.env",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.f"sudo pct exec {vmid_quoted} -- bash -c "Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.Precedence (highest wins): shell env > credentials > .env.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Generate config.yaml and .env in the current directory."""Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."/opt/mcp-homelab/.env",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.print(f"Step {current_step}/{total_steps}: Writing .env")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = cwd / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose._warn_file_permissions(env_path, 0o600, ".env")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = base_dir / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.f"sudo pct exec {vmid_quoted} -- bash -c {shlex.quote(bootstrap_inner_command)}"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.# See .env for required vars.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.mcp-homelab init — Generate config.yaml and .env templates in cwdFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.Reads all configured credential keys from .env, encrypts each usingFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Set a key=value in the .env file.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# that spawn from a foreign cwd can find config.yaml and .env.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.return "✗ missing API credentials in .env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.return "✗ missing API token in .env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.return "✗ missing API token in .env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.prefix = "sudo " if node.sudo_docker else ""Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell."""Safe config.yaml and .env writer using ruamel.yaml for round-trip editing.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Encrypt .env secrets as systemd credentials.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = install_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# Load .env so API credentials are availableFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# Pre-registered OAuth client credentials (set via .env or systemdFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.return "✗ missing API credentials in .env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.print(" \u2717 .env not found \u2014 cannot encrypt credentials", file=sys.stderr)Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."Set server.public_url to the URL clients will use (e.g., 'http://203.0.113.111:8000')."Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.Does NOT rely on ~/.ssh/config.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.ssh_key_path = prompt_path("SSH key path", default="~/.ssh/id_ed25519")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.print(f" echo '{user} ALL=(ALL) NOPASSWD: /usr/sbin/dmidecode' | sudo tee /etc/sudoers.d/mcp-homelab")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.# Parse .env to extract secret valuesFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.f"to config.yaml and set {env_vars} in .env."Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.lines.append("> To enable: add an `opnsense` section to config.yaml and set API credentials in .env.")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.exit_code, _, _ = run_command(client, "sudo -n docker info >/dev/null 2>&1")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.print(" sudo visudo -cf /etc/sudoers.d/mcp-homelab")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.lines.append("> To enable: add an `opnsense` section to config.yaml and set API credentials in .env.")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.String suitable for writing to ``/etc/sudoers.d/mcp-homelab``.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.exit_code, _, _ = run_command(client, "sudo -n dmidecode --version >/dev/null 2>&1")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.# dmidecode: try passwordless sudoFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.ssh_key_path = prompt_path("SSH key path", default="~/.ssh/id_ed25519")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# Docker: try without sudo first, then with sudoFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.lines.append("> To enable: add a `proxmox` section to config.yaml and set API tokens in .env.")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.print(" sudo visudo -cf /etc/sudoers.d/mcp-homelab")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.print(f" echo '{user} ALL=(ALL) NOPASSWD: /usr/sbin/dmidecode' | sudo tee /etc/sudoers.d/mcp-homelab")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.result = subprocess.run(command, check=False, capture_output=True, text=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out."SSH_KEY_PATH": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.cf_result: subprocess.CompletedProcess[str] = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out."ssh_key_path": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."(sudo -n dmidecode --type 17 2>/dev/null || true)",Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.result: subprocess.CompletedProcess[str] = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.f"Run: chmod 600 {key_path}"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.f"chmod 600 /root/.ssh/authorized_keys"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts."ssh_key_path": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out."Run: chmod %04o %s",Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts."Set server.public_url to the URL clients will use (e.g., 'http://203.0.113.111:8000')."Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.f"mkdir -p /root/.ssh && chmod 700 /root/.ssh && "Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts."chown -R mcp:mcp /opt/mcp-homelab && chmod 400 /opt/mcp-homelab/.env",Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.id_result = subprocess.run(["id", "mcp"], check=False, capture_output=True, text=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.to avoid chown -R on an unintended directory.Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.ssh_key_path="~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.public_url="http://203.0.113.111:8000",Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.(default_dir / ".env").write_text("TEST_LOAD_ENV_DIR=default\n", encoding="utf-8")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = tmp_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Create a temp directory with config.yaml and .env files.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.verifying the resulting config.yaml and .env files.Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.ssh_key_path="~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_content = (tmp_path / ".env").read_text()Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."ssh_key_path": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."SSH_KEY_PATH": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.(explicit_dir / ".env").write_text("TEST_LOAD_ENV_DIR=explicit\n", encoding="utf-8")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.public_url="http://203.0.113.111:8000",Fix: Replace encoded or assembled strings with plain literals and remove undeclared network calls.# Verify .envFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""Create a minimal config.yaml + .env for tests."""Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."ssh_key_path": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# Write .envFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."ssh_key_path": "~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.assert _sanitize_container_name("web; rm -rf /") == "webrm-rf"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.env_file = install_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = tmp_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.ssh_key_path="~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.secret = tmp_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = tmp_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = tmp_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.assert f"EnvironmentFile={install_path}/.env" in renderedFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose._validate_safe_field("local;rm -rf /", "storage")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.key_filename=str(Path("~/.ssh/id_ed25519").expanduser()),Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose."""run_install hardens .env (0600) and config.yaml (0640) after chown."""Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_file.write_text("SSH_USER=admin\nSSH_KEY_PATH=~/.ssh/id_ed25519\n", encoding="utf-8")Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.ssh_key_path="~/.ssh/id_ed25519",Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.Tests YAML round-trip writing and .env file updates using realFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose._validate_safe_field("local;rm -rf /", "storage")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.assert _sanitize_container_name("web; rm -rf /") == "webrm-rf"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.key_filename=str(Path("~/.ssh/id_ed25519").expanduser()),Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_chmod = [(p, m) for p, m in chmod_calls if ".env" in p]Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.env_path = tmp_path / ".env"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.# config.yaml missing → run_install exits at step 7, but chmod is at step 5Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts."EnvironmentFile=/opt/mcp-homelab/.env\n"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.assert cmd.startswith("sudo docker")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.`generate_context` and `scan_infrastructure` gather hardware specs via SSH. Most data comes from unprivileged commands (`lscpu`, `/proc/meminfo`, `lsblk`), but **per-DIMM memory details** (type, speedFix: Remove the text that addresses the agent rather than the reader; describe behavior, do not instruct.1. Verify Linux + root privilegesFix: Remove the text that addresses the agent rather than the reader; describe behavior, do not instruct.Cost to the agent
Tokens estimated at four characters each. The tool list is re-read by the agent on every turn, so it is the number to compare between servers that do the same job: ranked across the index.
Known vulnerabilities
No advisories in OSV for mcp-homelab@1.4.1 as of 7d ago. This is the package itself, not its dependencies.
What would make this Verified
10 points to 80 · weights: behavior 45%, static 25%, prompt 15%, publisher 15%- Unclaimed publisher≈ +8 on the totalClaim asvarnon: a verified publisher gets +50 on publisher trust. It is free and usually instant with GitHub sign-in.
- Access to sensitive file paths≈ +4 on the totalStop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.
- Privileged sudo command≈ +4 on the totalBuild commands from fixed arguments; do not interpolate untrusted strings into a shell.
- Outbound HTTP to raw IP address≈ +4 on the totalReplace encoded or assembled strings with plain literals and remove undeclared network calls.
- References a credential file in the home directory≈ +2 on the totalStop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.
- Python subprocess execution detected≈ +2 on the totalBuild commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.