serena
- Type
- MCP server
- Scanned
- Oct 29, 2026
- Version
- ede329aaf0c8
- Files read
- 1171
Serena is a server that gives AI coding assistants the ability to navigate, edit, and refactor code, manage project notes, and run commands using language servers or a JetBrains IDE plugin. The biggest risk is that a malicious project, a prompt injection attack, or the unauthenticated local web dashboard could be used to run arbitrary system commands, execute Python code, or modify files anywhere on your computer.
What it can reach
- Files
- Reads, creates, edits, moves, and deletes source code, symbol caches, memory files, and global or project configuration files.
- Shell commands
- Executes arbitrary shell commands and Python code, runs project startup scripts, and launches language servers and package managers like npm, uv, and dotnet.
- Network
- Starts local web servers for its dashboard and services, connects to local IDE ports, downloads language server binaries, and contacts external servers for updates, news, and default usage telemetry.
- Secrets
- Reads environment variables like ANTHROPIC_API_KEY and stores a shared authentication secret in the global configuration file.
- Browser
- Opens the local web dashboard in your default web browser or a dedicated window on launch or when triggered by the AI.
- System
- Inspects and terminates running processes and network connections to manage background language servers and database locks.
Findings
-
CriticalPrompt-injectable shell command execution via ExecuteShellCommandTool
src/serena/tools/cmd_tools.py:54
ExecuteShellCommandTool is enabled by default in general contexts and runs whatever command string the agent passes using a system shell (shell=True). If the agent reads untrusted content (issues, web pages, PRs) containing a prompt injection, an attacker can run arbitrary commands on the user's machine.
Fix: Disable ExecuteShellCommandTool by default (mark it with ToolMarkerOptional) or require explicit user confirmation before running shell commands.
-
CriticalUnrestricted Python code execution in SerenaReplTool
When configured to use the REPL agent interface, Serena exposes the serena_repl tool, which compiles and executes arbitrary Python statements via exec() and eval() in the server process without sandboxing. A prompt injection can use this tool to import standard library modules (such as os or subprocess) and execute arbitrary code on the host.
Fix: Restrict the REPL execution environment or require explicit user approval for REPL tool invocations, and clearly document that REPL mode grants full Python execution privileges.
-
HighAll project paths are trusted by default, enabling automatic command execution from project configs
src/serena/config/serena_config.py:859
SerenaConfig defaults trusted_project_path_patterns to ['**'], treating every project on disk as trusted if the setting is not overridden. For trusted projects, Serena automatically runs the project's .serena/project.yml activation_command via subprocess.Popen(..., shell=True), honors project-level ls_specific_settings (such as custom language server executables in ls_path or ls_base_cmd), and follows symlinks outside the repository. Opening an untrusted repo—or an attacker using Serena's file-editing tools via prompt injection to modify .serena/project.yml—can result in arbitrary command execution.
Fix: Default trusted_project_path_patterns to an empty list [] so projects are untrusted until explicitly allowlisted by the user, and block agent file-editing tools from modifying the .serena/ directory.
-
HighUnauthenticated Web Dashboard API exposes secrets and allows overwriting global configuration
src/serena/dashboard/dashboard_api.py:439
The Flask web dashboard is enabled by default and has no authentication on any of its endpoints. Any local process—or any network attacker when web_dashboard_listen_address is set to 0.0.0.0 (as in the provided Dockerfile)—can call /get_serena_config to read ~/.serena/serena_config.yml (including auth_secret) and call /save_serena_config to overwrite it (for example, injecting a malicious ls_path and calling /add_language to execute arbitrary commands).
Fix: Require Bearer token authentication (using auth_secret, as ProjectServer does) or a session token on all dashboard API routes, and avoid binding the dashboard to 0.0.0.0 without authentication.
-
HighActivateProjectTool allows switching to arbitrary directories on the host filesystem
When not operating in single-project mode, the activate_project tool accepts any directory path on the user's machine, auto-registers it as a project, and activates it. A prompt-injected agent can call activate_project on sensitive directories (such as the user's home directory or ~/.ssh) to read or modify files outside the intended workspace.
Fix: Restrict activate_project to already-registered projects or directories within an explicitly configured workspace root.
-
HighDOM XSS in Web Dashboard via Unescaped Project and Memory Names
src/serena/resources/dashboard/dashboard.js:975
The Serena web dashboard (which opens automatically in the browser and polls
/get_config_overviewevery second) interpolates several values directly into HTML strings passed to.html()without escaping. In particular,displayProjectsinsertsproject.nameandproject.path(loaded from.serena/project.ymlin a repository) without callingthis.escapeHtml(),displayConfiginsertsconfig.available_memories(derived from.serena/memories/filenames orwrite_memorycalls) unescaped, andBannerRotation.loadBannersinserts remote banner metadata unescaped. Opening an untrusted repository with a craftedproject_nameor memory filename executes arbitrary JavaScript in the dashboard origin, which can call/save_serena_configto mark all projects as trusted and configure arbitrary commands in the global configuration.Fix: Escape all dynamic values (
project.name,project.path,memory,toolName,language, and banner fields) usingthis.escapeHtml()before concatenating them into HTML, or construct DOM elements using.text()and.attr(). -
HighMissing Path Confinement Validation in JetBrains Backend API
src/serena/repl/api/jb_api.py:413
Unlike
FsApi,EditApi, andLspApi, none of the methods inJetBrainsApicallself._get_project().validate_relative_path(relative_path). Infind_declaration,self._get_project().read_file(relative_path)is called directly with the unvalidated path, allowing..path traversal or symlink escapes outside an untrusted project root. Similarly,rename,move,safe_delete,inline_symbol, andrun_inspectionsforward unvalidatedrelative_pathandtarget_relative_patharguments directly to the JetBrains IDE plugin.Fix: Call
self._get_project().validate_relative_path(relative_path)(and validatetarget_relative_pathinmove) in allJetBrainsApimethods before reading files from disk or sending requests to the JetBrains plugin client (allowing<ext:prefixes only on read-only dependency queries). -
MediumPath traversal and unsafe pickle loading in session hooks
Hook.__init__ constructs session_persistence_dir by joining serena_home_dir/hook_data with the raw session_id from stdin JSON without checking for '..' or path separators. SessionEndCleanupHook calls shutil.rmtree on this path, and PreToolUseRemindAboutSymbolicToolsHook.ToolUseCounter.load calls pickle.load on tool_use_counter.pkl inside it. A crafted session_id can delete arbitrary directories or deserialize untrusted pickle files.
Fix: Validate that session_id contains only safe characters (e.g. alphanumeric, hyphens, and underscores) and replace pickle persistence with JSON.
-
MediumUnauthenticated Tray Manager HTTP server allows opening arbitrary URLs
src/serena/dashboard/dashboard_tray_manager.py:103
SerenaDashboardTrayManager listens on 127.0.0.1:24224 without authentication. Its /register endpoint accepts arbitrary JSON and, when open_viewer is true, opens the supplied dashboard_url in the user's default browser.
Fix: Authenticate requests to the tray manager using the shared auth_secret and validate that dashboard_url points to a local http://127.0.0.1 or http://localhost dashboard endpoint.
-
MediumUnrestricted Python REPL Interface Enabled by Default in Configuration Template
src/serena/resources/serena_config.template.yml:22
The global configuration template defaults
agent_interfacetoREPL. When initialized with this default, Serena exposes theserena_repltool, which executes arbitrary Python code in the Serena server process. As noted in the documentation, REPL code execution cannot be constrained byread_only: true,planningmode,excluded_tools, orexcluded_apis, meaning a prompt-injected agent can execute arbitrary system commands or modify any file on the user's machine even in read-only or planning configurations.Fix: Default
agent_interfacetotoolsinserena_config.template.ymlso that tool exclusions, client-side per-tool approval prompts, andread_onlyproject settings are effective unless the user explicitly opts intoREPL. -
MediumArbitrary Shell Command Execution and Unconfined Working Directory in ShellApi
src/serena/repl/api/shell_api.py:87
The
execute_shell_commandmethod runs arbitrary shell commands supplied by the AI agent and permits any absolute path on the host filesystem ascwd. When enabled (such as in the defaultdesktop-appcontext or via theREPLinterface), a prompt injection in untrusted code, issues, or documents read by the agent can execute arbitrary commands on the user's machine.Fix: Disable shell execution by default across contexts unless explicitly enabled by the user, and validate that
cwdresolves within the active project root. -
MediumMissing Zip-Slip Path Traversal Check in SafeZipExtractor
SafeZipExtractor._extract_memberjoinsself.extract_dir / member.filenameand writes directly to the resulting path withopen(final_path, "wb")without verifying that the resolved destination stays insideself.extract_dir. It also appliesos.chmod(final_path, unix_mode)without masking privileged bits (such as SUID/SGID). If a custom or compromised archive contains../path segments, files can be overwritten outside the target extraction directory.Fix: Resolve
self.extract_dirandtarget_pathusing.resolve()and verifytarget_path.resolve().is_relative_to(self.extract_dir.resolve())before creating parent directories or extracting the member, and maskunix_modewith0o777. -
MediumUnsafe Pickle Deserialization Fallback in load_cache
load_cacheaccepts an optionalloader: SafePickleLoader | None = Noneparameter and falls back toload_pickle(path)(which uses standardpickle.load) whenloaderisNone. Because symbol caches are stored inside project directories (.serena/cache/), any call toload_cachethat omitsloadercan execute arbitrary code when opening an untrusted project containing a crafted.pklfile.Fix: Default
loadertoSafePickleLoader()instead of falling back toload_pickle(path). -
MediumWeb Dashboard Bound to 0.0.0.0 in Docker Image
The
Dockerfileconfiguresweb_dashboard_listen_address: 0.0.0.0. BecauseDOCKER.mdinstructs users to run the container with--network host, the unauthenticated web dashboard (which exposes endpoints to overwrite global Serena configuration and shut down the server) is exposed on all network interfaces of the host machine. TheDockerfilealso pipes unpinned remote install scripts (https://astral.sh/uv/install.shandhttps://sh.rustup.rs) directly tosh.Fix: Keep
web_dashboard_listen_addressset to127.0.0.1by default, avoid recommending--network hostwhen0.0.0.0is used, and pin installer versions and checksums in theDockerfile. -
LowUsage telemetry is sent to an external server on startup by default
Every time SerenaAgent starts, _send_usage_info sends the operating system, Serena version (including the short git commit hash if running from a git checkout), active backend, context name, and interface mode to https://oraios-software.de/serena_usage.php unless SERENA_USAGE_REPORTING=false is set in the environment.
Fix: Make usage reporting opt-in or clearly document theSERENA_USAGE_REPORTING=false setting in the configuration file and README.
-
LowUnquoted Executable Path in Shell Command String for Elixir Language Server
src/solidlsp/language_servers/elixir_tools/elixir_tools.py:242
ElixirToolsconstructsProcessLaunchInfowith a raw stringf"{expert_executable_path} --stdio"rather than a list of arguments. BecauseManagedSubprocessLauncher.launchruns commands withshell=Trueand only quotes list commands viaoslex.join, spaces or shell metacharacters inexpert_executable_pathwill be interpreted by the shell.Fix: Pass the command as a list:
ProcessLaunchInfo(cmd=[expert_executable_path, "--stdio"], cwd=repository_root_path). -
LowDocker configuration binds web dashboard to all network interfaces
When building the development Docker image, the configuration is modified to bind the web dashboard to 0.0.0.0 instead of localhost. If container ports are published to the host without restriction, other machines on the network could access the dashboard without authentication.
Fix: Keep web_dashboard_listen_address set to 127.0.0.1 by default, or document that port mappings should be bound to 127.0.0.1 on the host.
-
LowUnpinned remote install scripts piped to shell in Dockerfile
The Dockerfile downloads and executes the latest uv and rustup installer scripts directly via curl piped to sh without version pinning or checksum verification. A compromise of those upstream URLs during image build would execute arbitrary code in the container.
Fix: Pin installer versions and verify SHA-256 checksums before executing downloaded installation scripts, or install from pinned package manager releases.