SkillTotal

Is safari-mcp safe?

No malicious indicators - review capabilities before installing
Notable — review in context (capabilities are not malware):
  • Node.js shell/command execution
  • npm install-time lifecycle hook
  • Dangerous MCP tool capability

safari-mcp is an AI npm_package analyzed by SkillTotal's deterministic static scanner. The scan found no malicious indicators, though 6 risky constructs are reported for review. It can: delegated authentication, install time execution, mcp tools detected, network egress and shell execution — capabilities are what the code can do, not a verdict on intent. Risk score 0/100 (low).

safari-mcp 2.22.4

npm_package · npm:safari-mcp
LOW
0
/ 100 risk score
Snapshot · scanned Sep 28, 2026 · safari-mcp@2.22.4 · engine 0.53.0 / ruleset 60

Automated static-analysis result. It can contain false positives and false negatives, and is not a claim about the intent of safari-mcp's authors. Report a false positive.

Capabilities — what this component can do (not a risk score):
delegated authenticationinstall time executionmcp tools detectednetwork egressshell execution

Behavioral traits

How this component maps to the CSA agentic threat model. Descriptive — it never affects the risk score.

Tool surface
Tool Usage
Execution authority
Tool Access Control / Direct Tool Access
Network egress
Interaction & Communication / Direct Communication
Delegated authentication
Tool Execution Context / User Delegated Credentials
Supply-chain provenance risk
General Protections / Supply Chain

Findings (6)

HIGHnpm install-time lifecycle hookST-INSTALL-NPM

package.json runs scripts automatically when the package is installed.

"postinstall": "node scripts/postinstall.cjs || true"

Why it matters: Install scripts are a favorite supply-chain foothold — they execute on every machine that installs the package.

Fix: Inspect the hook command. Install-time scripts are a common supply chain execution vector; ensure they do nothing beyond a documented build step.

HIGHDangerous MCP tool capabilityST-MCP-DANGEROUS-TOOL

An MCP tool exposes a powerful capability (files, shell, network, browser, or credentials).

server.tool(
  "safari_navigate",
server.tool(
  "safari_navigate_and_read",
server.tool(
  "safari_screenshot",
server.tool(
  "safari_screenshot_element",

Why it matters: Wired into an agent, these grant it real access to your machine — confirm each is required.

Fix: Confirm each powerful tool is required and constrained; broad MCP tools (shell/filesystem/network) grant an agent significant host access.

HIGHNode.js shell/command executionST-SHELL-NODE

The component can run operating-system commands or spawn processes.

import { execFile, execFileSync } from "node:child_process";
import { execFile, spawn, spawnSync } from "node:child_process";
_helperProc = spawn(helperPath, [], { stdio: ["pipe", "pipe", "ignore"] });
const proc = spawn("pbcopy", [], { stdio: ["pipe", "ignore", "ignore"] });
// window. Uses spawnSync (blocking) because we're on the exit path and can't await a Promise.
spawnSync("pbcopy", [], { input: content });
const { execFileSync, spawnSync } = require("child_process");
const result = spawnSync("codesign", ["-d", "--verbose=2", "--", helper], { encoding: "utf8" });

Why it matters: Powerful and often legitimate — confirm the commands aren't built from untrusted input.

Fix: Confirm the command and its arguments are fully controlled and not derived from untrusted input; prefer execFile with an argument array.

MEDIUMNode.js network egressST-NET-NODE

The component makes outbound network requests.

// Safari terminates idle service workers after ~30s, so we keep an active fetch() going
_bridgeAuthTokenPromise = fetch(browser.runtime.getURL("bridge-auth-token"), {
return fetch(url, { ...init, headers });
// The response is here, so a reconnect must no longer abort this fetch (that would
// 1. Active fetch() in pollForCommands() keeps the worker alive while connected
const response = await fetch(`${_mcpContentWakeBridgeUrl}/content-wakeup`, {
import { createServer } from "node:http";
const res = await fetch(`http://127.0.0.1:${HTTP_PORT}/proxy-check?profile=${profile}`, {
const res = await fetch(`http://127.0.0.1:${HTTP_PORT}/proxy-command`, {
// Async iff the *result* is a promise to wait on. `fetch(` alone is NOT async —
// Patch fetch (once)
import { createServer } from "node:http";

Why it matters: Usually legitimate, but confirm the destinations are expected and no sensitive data leaves.

Fix: Confirm the destination hosts are expected and that no sensitive data is sent off-host.

LOWDelegated authentication (OAuth 2.0 / OIDC)ST-AUTH-DELEGATED

An OAuth 2.0 / OpenID Connect delegated-authentication flow was detected (authorization-code / refresh-token / token-exchange grant, an OIDC authorize/discovery endpoint or id_token, or a delegation library). Tools authenticate with the end user's delegated, scoped credentials rather than a long-lived embedded service credential.

!/[?&#](code|token|access_token|id_token|state|session_state)=/i.test(payload.url || "");

Fix: Delegated auth is a lower-blast-radius execution context than an embedded static credential. Confirm the requested scopes are minimal and that tokens are never logged or forwarded off-host.

LOWMCP tool surface detectedST-MCP-DETECTED

An MCP tool surface (manifest or tool definitions) was found.

const server = new McpServer({
server.tool(
  "safari_navigate",
server.tool(
  "safari_go_back",
server.tool(
  "safari_go_forward",
server.tool(
  "safari_reload",
server.tool(
  "safari_read_page",
server.tool(
  "safari_get_source",
server.tool(
  "safari_snapshot",
server.tool(
  "safari_navigate_and_read",
server.tool(
  "safari_click",
server.tool(
  "safari_click_and_read",
server.tool(
  "safari_double_click",
server.tool(
  "safari_right_click",
server.tool(
  "safari_native_click",
server.tool(
  "safari_native_hover",
server.tool(
  "safari_native_keyboard",
server.tool(
  "safari_native_type",
server.tool(
  "safari_fill",
server.tool(
  "safari_clear_field",
server.tool(
  "safari_verify_state",
server.tool(
  "safari_select_option",
server.tool(
  "safari_react_select_set",
server.tool(
  "safari_react_select_list_options",
server.tool(
  "safari_fill_form",

Why it matters: Just context — review which tools it offers and their permissions.

Fix: Review the declared MCP tools and their permissions.

Check your own component

Run the same evidence-backed scan on any MCP server, agent skill, or package.

Scan your own component

How we determine this: deterministic static analysis (regex + AST), evidence-anchored, no code execution. Methodology →