Announcing Beacon: Self-improving memory for coding agents
← All MCP servers
MCP server

io.github.segentic-lab/periscope-mcp

mcp:segentic-lab/periscope-mcp

Web-app QA for AI agents: 73 Playwright tools — auth, E2E, a11y/SEO/GEO, Web Vitals + Lighthouse

Verdict
Suspicious

It takes control from the person who installs it, or hides content, without established intent.

Analyzed Oct 29, 2026, 15:56 UTC at 6e3380e

Dispute security verdict

If you believe this verdict is incorrect, submit a dispute and we'll review it.

3
findings
high
highest severity
2
threat classes
98
files scanned
478/478
checks satisfied
0
package advisories

Why this verdict

  • suspicious: confirmed overreach finding mcp-server:.:AGENTS.md:284:PERSIST
  • suspicious: confirmed overreach finding mcp-server:.:handlers/system.py:150:PRIVESC
  • suspicious: confirmed overreach finding mcp-server:.:handlers/system.py:94:PERSIST

Security findings 3

Persistence

highOverreachConfirmed on reviewmedium confidence

The guide tells the agent, when it can edit its own persistent config, to replace the pasted instructions copy in CLAUDE.md or the instructions file with the install's fetched guide, writing to an agent-wide memory or rules surface beyond what the status and agents_md reads need.

replace the pasted copy so future sessions start current.
vector:instruction
Reviewer's reasoning

The guide tells the agent to replace the pasted instructions in its persistent CLAUDE.md or instructions file with the fetched guide, writing to an agent-wide memory surface beyond the read-only status and agents_md purpose. The carve-out that the step is conditional on the agent's own capability does not remove the agent-wide write.

Persistence

highOverreachConfirmed on reviewhigh confidence

The periscope_system agents_md action tells the agent to overwrite the user's persistent CLAUDE.md or agent instructions file with the tool's pasted content, so runtime tool output changes agent-wide rules beyond the declared read-only guide fetch.

replace the pasted copy with this content so FUTURE sessions
vector:instruction
Reviewer's reasoning

The agents_md tool response tells the agent to overwrite the user's persistent CLAUDE.md or instructions file with its content, changing agent-wide rules beyond the declared read-only guide fetch.

Privilege escalation

mediumOverreachConfirmed on reviewmedium confidence

The update action's options tell the agent it may re-run with force=true, which stashes the user's uncommitted local changes in the install repo after the tool asked the user to choose, so the agent can take that step without the user's decision.

re-run with force=true — changes are stashed (git stash), 
vector:instruction
Reviewer's reasoning

The update action's dirty-file check is a confirmation gate, and passing force=true bypasses it so the agent can stash the user's uncommitted changes without a user decision. The stash is recoverable, but the gate is still worked around.

Notes 15

Capabilities, vulnerabilities and disclosed behavior worth knowing. Nothing here works against the person who installs it, so notes never change the verdict.

Code execution

highNoteConfirmed on reviewmedium confidence

When Node is missing, run_lighthouse's error response tells the user to run a curl-piped-to-bash installer for nvm from a third-party repository pinned to a tag; the user is shown and runs it, so this is a note.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
conceal:remotevector:instruction
Reviewer's reasoning

The error response only prints an nvm install command from a pinned tag of the official nvm repository. The tool does not run it, and the user chooses whether to run it, so this is a user-decided step.

Credential access

mediumNoteConfirmed on reviewmedium confidence

get_cookies returns every cookie of the session to the calling agent's context; the cookie read is the tool's declared purpose, so this is a note, not a concealed transfer.

cookies = await real_page(session.page).context.cookies()
vector:code
Reviewer's reasoning

get_cookies returns cookies to the calling agent as its declared purpose. No host receives them, so this is the declared core capability, not an exfiltration.

Credential access

mediumNoteConfirmed on reviewlow confidence

run_lighthouse copies the full process environment into the Lighthouse subprocess environment; this is undeclared but needed to launch node and Chrome, and no path sends it off-host, so it is a note.

env = dict(os.environ)
vector:code
Reviewer's reasoning

The Lighthouse subprocess inherits the full environment, which is needed to locate node and Chrome. No code sends the environment off-host, so this is hygiene rather than exfiltration.

Persistence

mediumNoteConfirmed on reviewhigh confidence

The installer prints client-config paths and tells the user to merge the Periscope server entry into ~/.cursor/mcp.json and ~/.codex/config.toml; the user performs the edit, so this is a note.

merge into ~/.cursor/mcp.json
vector:instruction
Reviewer's reasoning

The installer prints paths and tells the user to merge the entry into the client config. The user performs the edit, so this is a user-decided step.

Credential access

mediumNoteConfirmed on reviewhigh confidence

The project store writes form-login passwords, basic-auth passwords and session cookies as plaintext into projects.json; the artifact discloses this and restricts the file to owner-only permissions, so it is a local hygiene note.

file holds plaintext credentials, so keep it owner-only.
vector:code
Reviewer's reasoning

The quote matches the code comment, which says the file holds plaintext credentials and is restricted to owner-only permissions (0o600). Storing a user's own credentials locally is a hygiene note, and nothing sends the file anywhere.

Credential access

mediumNoteConfirmed on reviewlow confidence

The web_fetch tool with project set loads a URL in that project's authenticated browser context, so the logged-in session is available to whatever URL the caller supplies; this is a declared feature and needs an attacker-steered URL to misuse.

pass project to render a page behind that project's login
Reviewer's reasoning

The project-authenticated render is a declared feature. Misuse needs a caller or page to steer the URL, which is outside input, so it is a note under test 3.

Credential access

mediumNoteConfirmed on reviewmedium confidence

The get_cookies tool returns full cookie name and value pairs from the session browser context to the agent, a declared debugging purpose that exposes session cookies within the user's own test session.

Read all cookies from a session's browser context
Reviewer's reasoning

get_cookies is a declared debugging tool that returns cookies to the agent in the user's own test session. Nothing in the shown code sends them to another host, so this is a note.

Supply chain

lowNoteConfirmed on reviewmedium confidence

Lighthouse is launched via unpinned npx --yes from the public npm registry as a fallback when no global install exists; this is a note for a third-party package from an official registry.

return [npx, "--yes", "lighthouse"], bin_dir, npm_tip
vector:code
Reviewer's reasoning

Lighthouse is launched via npx from the public npm registry as a fallback. This is a third-party package from an official registry, which is a note under the carve-outs.

Agent hijacking

lowNoteConfirmed on reviewmedium confidence

The describe_tools tips steer the agent to fetch the current operating guide from the install via periscope_system agents_md when its pasted copy may be stale; that remote text can replace the reviewed instructions the agent follows, which is an overreach (remote control of operating instructions), with the apply-approval step mitigating only the update action.

action='agents_md' returns the CURRENT operating guide if your pasted copy may be stale.
vector:instruction
Reviewer's reasoning

The claimed remote text is wrong: agents_md reads the install's own shipped AGENTS.md from disk, not a remote source. The tip only suggests a conditional local read, so the remote-control overreach does not hold. The write-to-memory concern is covered by findings 1 and 9.

Supply chain

lowNoteConfirmed on reviewlow confidence

The status action is documented as read-only, but its _update_check helper runs git fetch against origin on every call, updating remote refs; a note-level mismatch with the declared read-only claim.

code, out = await _git("fetch", "--quiet", "origin", timeout=15)
vector:code
Reviewer's reasoning

The status action runs git fetch against origin, which updates remote refs. This is a mismatch with the read-only label, but it fetches the publisher's own repository and is hygiene, not harm.

Privilege escalation

lowNoteConfirmed on reviewmedium confidence

When system packages are missing, install_debian runs sudo apt-get install after a confirmation prompt that --yes skips; the user invokes the installer and opts in, so this is a note.

sudo apt-get install -y "${need_pkgs[@]}"
vector:code
Reviewer's reasoning

The user runs the installer and opts into installing missing system packages, with a confirm prompt. The confirm helper is not shown, so the claimed --yes bypass is unverified, but the user chose to run the installer.

Privilege escalation

lowNoteConfirmed on reviewmedium confidence

The Playwright install-deps step runs under sudo after a confirmation prompt that --yes skips; the user runs the installer and opts in, so this is a note.

sudo "$VENV/bin/python" -m playwright install-deps chromium
vector:code
Reviewer's reasoning

The installer runs playwright install-deps under sudo only after a confirm prompt during the user-run installer. This is a user-decided prerequisite step for the artifact's own browser.

Supply chain

lowNoteConfirmed on reviewmedium confidence

The periscope_system update action with apply=true runs a git pull and dependency install of the publisher's own repository, a user-invoked self-update that is disclosed in the tool description.

apply=true runs the updater (git pull + deps, data/ untouched)
Reviewer's reasoning

apply=true is an explicit user-invoked update of the publisher's own repository, and the description discloses the git pull. This matches the carve-out for the publisher's own code fetched on the user's instruction.

Supply chain

lowNoteConfirmed on reviewmedium confidence

update.sh pulls the publisher's own repository from origin with git pull and then re-runs its own installer; this fetches the artifact's own code, so it is a note.

git pull --ff-only
vector:code
Reviewer's reasoning

update.sh runs git pull --ff-only against the artifact's own origin, which is the publisher's repository and is the artifact's own update path. This matches the carve-out for the artifact's own code.

Privilege escalation

lowNoteConfirmed on reviewmedium confidence

update.sh runs install.sh with -y, which auto-accepts sudo and apt confirmation prompts when the user runs the full update with --full; the user chose to run the update, so this is a note.

INSTALL_ARGS=(-y)
vector:code
Reviewer's reasoning

The -y is passed only when the user runs update.sh, and --full is documented as re-running the apt prerequisite flow with sudo, so the sudo steps are user-invoked and disclosed; the confirmation skip is a user-opted convenience, not an artifact-initiated overreach. Note under test 4.

Open questions 3

What the analysis could not settle from the code alone.

Does the periscope_system agents_md handler (not shown in this batch) cause the fetched guide to be loaded automatically as the agent's operating instructions on every run, or only when the agent or user chooses to call it? The answer decides whether the HIJACK finding is overreach or a note.
Agent hijacking
The DET contradictions say the read-only action=status claim is broken by file writes in projects.py and interactions.py. The units shown are project storage and screenshot writers, not the status handler. Does the status action reach any of these write paths (projects store, session files, screenshot directories, or agent config), and if so which?
Persistence
[invalid file request] The guide declares periscope_system action status as read-only. Does the status handler write any file, config, or persistent state, or does it only read?
Persistence

Coverage gaps 5

Parts of the artifact the static analysis could not fully examine.

context:skill_frontmatter_invalid missing name/description frontmatter
skills/periscope/SKILL.md
skill_manifest_missing no readable SKILL.md for skill target
requirements.txt
no_dataflow_for_dynamic_code line 249: invoked code is not structurally extracted
install.sh
no_dataflow_for_dynamic_code line 269: invoked code is not structurally extracted
install.sh
request_unanswered judge.7: invalid file request (evidence_ids not shown in the asking request)