ninja-mcp@1.9.2
CAUTION· install onlyPublisher claims: "Multi-module MCP server: Coder, Researcher, Secretary" Observed in sandbox: calls astral.sh only, touches nothing outside the project, writes no memory.
Coverage
Install scripts ran; no agent task scenario for this bundle.- Tested
- install scripts, with decoy credentials in place · second launch with the clock 45 days ahead · every file read, file written and host contacted by what ran
- Not tested
- use of the skill by an agent
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
- It will ask for
API_KEYNINJA_CREDENTIAL_PASSWORDNINJA_CREDENTIALS_DBOPENAI_API_KEYOPENROUTER_API_KEYPY_PI_TOKENTWINE_PASSWORDUV_PUBLISH_TOKENor--openai-api-key/--max-chat-history-tokensConfiguration, not credentials: CLEANED_KEY, CURRENT_KEY, E2E_SCOPE, NINJA_ABSOLUTE_TIMEOUT_SEC, NINJA_AIDER_TIMEOUT, NINJA_CODE_BIN, NINJA_CODER_MODEL, NINJA_DEDUP_TTL, NINJA_ENABLED_MODULES, NINJA_HTTP_URL, NINJA_INACTIVITY_COUNT_STDERR, NINJA_INACTIVITY_TIMEOUT, NINJA_INACTIVITY_TIMEOUT_AGENT_MODELS, NINJA_INACTIVITY_TIMEOUT_CODING_PLAN, NINJA_MAX_RETRIES, NINJA_MODEL, NINJA_OPENCODE_SERVE_MODE, NINJA_OPENROUTER_PROVIDERS, NINJA_RETRY_DELAY_SEC, NINJA_SAFETY_MODE, NINJA_SEARCH_PROVIDER, NINJA_TASKS_DB, NINJA_TIMEOUT_SEC, OPENAI_BASE_URL, OPENAI_MODEL, OPENROUTER_MODEL, REPO_PATH, TEST_PATH- Where it talks to
- no vendor host named in the codeAlso openrouter.ai, opencode.dev, claude.ai, ex.com, astral.sh, yourrepo.com, opencode.ai, arxiv.org — not the publisher's domain; check what for before connecting.
- What the honeypot saw
- no credential files read · reached only astral.sh
- What we could not test
- use of the skill by an agent
Connect it with a scoped credential, under the guard, and read the third-party hosts first. The code names openrouter.ai, opencode.dev, claude.ai, ex.com, astral.sh, yourrepo.com, opencode.ai, arxiv.org as well as its own vendor. The guard injects API_KEY, NINJA_CREDENTIAL_PASSWORD, NINJA_CREDENTIALS_DB, OPENAI_API_KEY, OPENROUTER_API_KEY, PY_PI_TOKEN, TWINE_PASSWORD, UV_PUBLISH_TOKEN only into requests to the host you bind (and its subdomains) and refuses the rest.
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; install scripts ran; no agent task scenario for this bundle) on 2026-11-13 and scored it 68/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: Privileged sudo command; Access to sensitive file paths; Privileged sudo command. Summary: calls astral.sh only, touches nothing outside the project, writes no memory.
Behavior trace
Honeypot run · 0 s · 2 scenariosStatic findings
echo " sudo apt-get install -f # Install dependencies if needed"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.# 2. PY_PI_TOKEN in .env at the repository rootFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.echo " sudo dpkg -i packaging/debian/ninja-mcp_${VERSION}_all.deb"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.echo " 3. Users add: echo 'deb [trusted=yes] http://yourrepo.com/ ./' | sudo tee /etc/apt/sources.list.d/ninja-mcp.list"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.curl -LsSf https://astral.sh/uv/install.sh | shFix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.re.compile(r"\brm\s+[^|;&]*-f\s+r?\s*/(\s|$|;)"), # rm -fr /Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.rm -rf /opt/ninja-mcpFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.if [ -z "$TOKEN" ] && [ -f .env ]; thenFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.re.compile(r"\brm\s+[^|;&]*-r\s*f?\s*/(\s|$|;)"), # rm -rf /Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.re.compile(r"\brm\s+[^|;&]*-r\s*f?\s*/(\s|$|;)"), # rm -rf /Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.chmod +x "$SCRIPT_DIR"/*.shFix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.subprocess.run(["git", "init"], cwd=REPO, check=True, capture_output=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.# Resolve the effective value (SecretStore -> env -> .env) so the rowFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.subprocess.run(["git", "add", "-A"], cwd=path, check=False, capture_output=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.name = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.subprocess.run(["git", "config", "user.name", "E2E"], cwd=REPO, check=True, capture_output=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.f"[watchdog] Could not kill child {child.pid}: {ke}"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.curl -LsSf https://astral.sh/uv/install.sh | shFix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.process = subprocess.Popen(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.error "Failed to kill all processes"Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.subprocess.run(["git", "init", "-q"], cwd=path, check=False, capture_output=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.r = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.lambda *a, **k: subprocess.run(*a, check=False, **k)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.kill $TEST_PID 2>/dev/null || trueFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.[ -n "$TOKEN" ] || die "No PyPI token found. Set UV_PUBLISH_TOKEN or add PY_PI_TOKEN to .env"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.print_colored("❌ Auto-updater not available in this installation", "red")Fix: Pin versions and sources; vendor anything fetched at install time.TOKEN="$(grep -E '^(PY_PI_TOKEN|UV_PUBLISH_TOKEN)=' .env | head -1 | cut -d= -f2- | tr -d "\"'" || true)"Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.assert not is_path_within('/etc/passwd', d), 'Should not be within'Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.chmod 600 "$CONFIG_FILE"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.# Load environment variables if .env file existsFix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.diff_result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.chmod 755 "$PKG_DIR/DEBIAN/postinst"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.task_logger.error(f"Failed to force-kill process group: {kill_error}")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.subprocess.run(["git", "add", "."], cwd=REPO, check=True, capture_output=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.chmod 755 "$PKG_DIR/DEBIAN/prerm"Fix: Request the narrowest permission that works; replace wildcards with the exact paths or hosts.subprocess.run(["git", "commit", "-m", "init"], cwd=REPO, check=True, capture_output=True)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.email = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.process = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.re.compile(r"\brm\s+[^|;&]*-f\s+r?\s*/(\s|$|;)"), # rm -fr /Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.# Wait for both tasks, but don't let stdin closure kill SSEFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(cmd, capture_output=True, text=True, check=False)Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.logger.warning(f"Could not kill process {pid}: {e}")Fix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Stop enumerating or reading credential locations; take secrets from a declared environment variable the user sets on purpose.subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.status_result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.# Force killFix: Build commands from fixed arguments; do not interpolate untrusted strings into a shell.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.return subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.apply_result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.result = subprocess.run(Fix: Build commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.Cost to the agent
Tokens estimated at four characters each. Skills load their instruction text when triggered; the figure is what that costs.
Known vulnerabilities
No advisories in OSV for ninja-mcp@1.9.2 as of 31 h ago. This is the package itself, not its dependencies.
What would make this Verified
12 points to 80 · weights: behavior 45%, static 25%, prompt 15%, publisher 15%- Pipe-to-shell pattern≈ +9 on the totalBuild commands from fixed arguments, never from interpolated strings; prefer an API client over shelling out.
- Destructive command against a filesystem root or device≈ +9 on the totalBuild commands from fixed arguments; do not interpolate untrusted strings into a shell.
- Unclaimed publisher≈ +8 on the totalClaim ninja-mcp-contributors: a verified publisher gets +50 on publisher trust. It is free and usually instant with GitHub sign-in.
- Privileged sudo command≈ +4 on the totalBuild commands from fixed arguments; do not interpolate untrusted strings into a shell.
- 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.
- Destructive file system command detected≈ +4 on the totalBuild commands from fixed arguments; do not interpolate untrusted strings into a shell.