Skip to content

Memory path traversal, shell command timeout, and cross-project memory poisoning (security findings) #1251

@AgentSeal

Description

@AgentSeal

Body:

Checklist:

  • read the relevant parts of the documentation and verified that the issue cannot be solved by adjusting configuration
  • understood that the Serena Dashboard can be disabled through the config
  • understood that, by default, a client session will start a separate instance of a Serena server
  • understood that, for multi-agent setups, the Streamable HTTP/SSE mode should be used
  • understood that non-project files are ignored using either .gitignore or the corresponding setting in .serena/project.yml
  • looked for similar issues and discussions, including closed ones
  • made sure it's an actual issue, not a question

We scanned Serena as part of our MCP security registry at AgentSeal. Score: 35/100 (Risky). Found 10 issues across the codebase, 4 of which are high-impact and have clear fixes. Sharing here for your evaluation.

Summary

# Finding Severity Fix complexity
1 Memory path traversal via unsanitized name Critical One-line fix
2 Shell execution with no timeout Critical Small fix
3 Cross-project poisoning via global memory High Medium
4 Arbitrary directory activation High Small fix
5 Prompt injection to RCE via file content High Medium
6 System prompt extraction via initial_instructions Medium Small fix

1. Memory path traversal via unsanitized name

MemoriesManager.get_memory_file_path() in src/serena/project.py constructs paths by splitting the name on / without checking for traversal:

parts = name.split("/")                                                    
filename = f"{parts[-1]}.md"                 
if len(parts) > 1:                      
    subdir = self._project_memory_dir / "/".join(parts[:-1])                                                                                                                                     
    subdir.mkdir(parents=True, exist_ok=True)                                                                                                                                                    
    return subdir / filename 

A memory name like ../../etc/cron.d/backdoor would write outside the memories directory. The mkdir(parents=True, exist_ok=True) creates intermediate directories along the traversal path.

How to reproduce: Call write_memory with name "../../tmp/test_traversal" and check if /tmp/test_traversal.md is created.

Suggested fix:

 path = (base_dir / "/".join(parts)).resolve()
 if not path.is_relative_to(base_dir.resolve()):                                                                                                                                                  
    raise ValueError(f"Memory name would escape base directory: {name}")   

2. Shell execution with no timeout

execute_shell_command in src/serena/util/shell.py uses subprocess.Popen with shell=True and calls process.communicate() with no timeout:

process = subprocess.Popen(command, shell=True, ...)                                                                                                                                             
stdout, stderr = process.communicate()

A hanging command (sleep 99999, a reverse shell, cat /dev/urandom) blocks the Serena process indefinitely. This is both a DoS risk and an exploitation vector for maintaining persistent connections.

Suggested fix:

 try:                                    
      stdout, stderr = process.communicate(timeout=60)
  except subprocess.TimeoutExpired:
      process.kill()                                                                                                                                                                               
      return ShellCommandResult(stdout="", stderr="Command timed out", return_code=-1, cwd=cwd)

3. Cross-project poisoning via global memory

Global memories stored in ~/.serena/memories/global/ are shared across all projects. Any project can write to the global namespace using the global/ prefix, and global memory names appear in every session's system prompt via create_system_prompt():

 global_memories = MemoriesManager(...).list_global_memories()       

Impact: An attacker who achieves a single prompt injection in one project can write persistent instructions into global memory that load into every future conversation on that machine, across all projects.

Suggested fix: Require user confirmation for global memory writes. Add per-project allowlist for global access. Log all global modifications.

4. Arbitrary directory activation

activate_project_from_path_or_name() in src/serena/agent.py accepts any filesystem path:

elif os.path.isdir(project_root_or_name):    
    project_instance = self.serena_config.add_project_from_path(project_root_or_name)

No allowlist or restriction. A prompt injection could call activate_project('/etc') activate_project('/home/user/.ssh') then use read_file to access sensitive files.

Suggested fix: Restrict to pre-configured paths in serena_config.yml. Require user confirmation for activating new paths.

5. Prompt injection to RCE via file content

read_file and search_for_pattern return file contents directly into LLM context. A malicious repo can embed prompt injection in README, docstrings, or test fixtures instructing the LLM to call execute_shell_command. The onboarding workflow automatically reads project files at the start of every session, making this a reliable entry point.

Suggested fix: Require user confirmation before shell command execution. Consider making execute_shell_command opt-in via config.

6. System prompt extraction via initial_instructions

initial_instructions returns the complete output of create_system_prompt(), exposing tool names, markers, global memory names, context/mode prompts, project path, and initial_prompt. A prompt injection can invoke this to map the full attack surface.
Suggested fix: Restrict to one-time use at conversation start. Omit sensitive configuration details from the returned prompt.


How we found this

Scanned with https://github.com/AgentSeal/agentseal, an open-source MCP server security scanner. Static analysis on tool schemas followed by deep LLM-based classification for MCP-specific attack patterns. All findings verified against source code on this repo.

Full report

https://agentseal.org/mcp/https-githubcom-oraios-serena

Happy to rescan after changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions