Procházet zdrojové kódy

chore: clean up sandbox and ssh related configs (#3301)

* clean up sandbox and ssh related stuff

* remove ssh hostname

* remove ssh hostname

* remove ssh password

* update config

* fix typo that breaks the test
Xingyao Wang před 1 rokem
rodič
revize
a5195b0e65

+ 0 - 1
.github/workflows/review-pr.yml

@@ -53,7 +53,6 @@ jobs:
       env:
         LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
         LLM_MODEL: ${{ vars.LLM_MODEL }}
-        SANDBOX_BOX_TYPE: ssh
       run: |
         # Append path to launch poetry
         export PATH="/github/home/.local/bin:$PATH"

+ 0 - 1
.github/workflows/solve-issue.yml

@@ -45,7 +45,6 @@ jobs:
         ISSUE_BODY: ${{ github.event.issue.body }}
         LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
         OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
-        SANDBOX_BOX_TYPE: ssh
       run: |
         # Append path to launch poetry
         export PATH="/github/home/.local/bin:$PATH"

+ 1 - 1
agenthub/micro/commit_writer/README.md

@@ -3,7 +3,7 @@
 CommitWriterAgent can help write git commit message. Example:
 
 ```bash
-WORKSPACE_MOUNT_PATH="`PWD`" SANDBOX_BOX_TYPE="ssh" \
+WORKSPACE_MOUNT_PATH="`PWD`" \
   poetry run python opendevin/core/main.py -t "dummy task" -c CommitWriterAgent -d ./
 ```
 

+ 1 - 15
config.template.toml

@@ -55,22 +55,11 @@ workspace_base = "./workspace"
 # Path to rewrite the workspace mount path to
 #workspace_mount_rewrite = ""
 
-
 # Run as devin
 #run_as_devin = true
 
 # Runtime environment
-#runtime = "server"
-
-# SSH hostname for the sandbox
-#ssh_hostname = "localhost"
-
-# SSH password for the sandbox
-#ssh_password = ""
-
-# SSH port for the sandbox
-#ssh_port = 63710
-
+#runtime = "eventstream"
 
 # Name of the default agent
 #default_agent = "CodeActAgent"
@@ -181,9 +170,6 @@ llm_config = 'gpt3'
 # Sandbox timeout in seconds
 #timeout = 120
 
-# Sandbox type (ssh, e2b, local)
-#box_type = "ssh"
-
 # Sandbox user ID
 #user_id = 1000
 

+ 0 - 1
containers/app/Dockerfile

@@ -38,7 +38,6 @@ ENV RUN_AS_DEVIN=true
 # A random number--we need this to be different from the user's UID on the host machine
 ENV OPENDEVIN_USER_ID=42420
 ENV USE_HOST_NETWORK=false
-ENV SSH_HOSTNAME=host.docker.internal
 ENV WORKSPACE_BASE=/opt/workspace_base
 ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION
 RUN mkdir -p $WORKSPACE_BASE

+ 0 - 2
docs/i18n/fr/docusaurus-plugin-content-docs/current/usage/intro.mdx

@@ -72,8 +72,6 @@ WORKSPACE_BASE=$(pwd)/workspace
 docker run -it \
     --pull=always \
     -e SANDBOX_USER_ID=$(id -u) \
-    -e PERSIST_SANDBOX="true" \
-    -e SSH_PASSWORD="make something up here" \
     -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
     -v $WORKSPACE_BASE:/opt/workspace_base \
     -v /var/run/docker.sock:/var/run/docker.sock \

+ 0 - 2
docs/i18n/zh-Hans/docusaurus-plugin-content-docs/current/usage/intro.mdx

@@ -72,8 +72,6 @@ WORKSPACE_BASE=$(pwd)/workspace
 docker run -it \
     --pull=always \
     -e SANDBOX_USER_ID=$(id -u) \
-    -e PERSIST_SANDBOX="true" \
-    -e SSH_PASSWORD="make something up here" \
     -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
     -v $WORKSPACE_BASE:/opt/workspace_base \
     -v /var/run/docker.sock:/var/run/docker.sock \

+ 0 - 2
docs/modules/usage/openshift-example.md

@@ -158,8 +158,6 @@ spec:
     env:
     - name: SANDBOX_USER_ID
       value: "1000"
-    - name: SANDBOX_BOX_TYPE
-      value: 'local'
     - name: WORKSPACE_MOUNT_PATH
       value: "/opt/workspace_base"
     volumeMounts:

+ 0 - 18
opendevin/core/config.py

@@ -145,7 +145,6 @@ class SandboxConfig(metaclass=Singleton):
     """Configuration for the sandbox.
 
     Attributes:
-        box_type: The type of sandbox to use. Options are: ssh, e2b, local.
         container_image: The container image to use for the sandbox.
         user_id: The user ID for the sandbox.
         timeout: The timeout for the sandbox.
@@ -165,7 +164,6 @@ class SandboxConfig(metaclass=Singleton):
             Default is None for general purpose browsing. Check evaluation/miniwob and evaluation/webarena for examples.
     """
 
-    box_type: str = 'ssh'
     container_image: str = (
         'ubuntu:22.04'  # default to ubuntu:22.04 for eventstream runtime
     )
@@ -226,7 +224,6 @@ class AppConfig(metaclass=Singleton):
         max_iterations: The maximum number of iterations.
         max_budget_per_task: The maximum budget allowed per task, beyond which the agent will stop.
         e2b_api_key: The E2B API key.
-        ssh_hostname: The SSH hostname.
         disable_color: Whether to disable color. For terminals that don't support color.
         debug: Whether to enable debugging.
         enable_cli_session: Whether to enable saving and restoring the session when run from CLI.
@@ -255,10 +252,7 @@ class AppConfig(metaclass=Singleton):
     max_iterations: int = _MAX_ITERATIONS
     max_budget_per_task: float | None = None
     e2b_api_key: str = ''
-    ssh_hostname: str = 'localhost'
     disable_color: bool = False
-    ssh_port: int = 63710
-    ssh_password: str | None = None
     jwt_secret: str = uuid.uuid4().hex
     debug: bool = False
     enable_cli_session: bool = False
@@ -330,7 +324,6 @@ class AppConfig(metaclass=Singleton):
                 'e2b_api_key',
                 'github_token',
                 'jwt_secret',
-                'ssh_password',
             ]:
                 attr_value = '******' if attr_value else None
 
@@ -426,11 +419,6 @@ def load_from_env(cfg: AppConfig, env_or_toml_dict: dict | MutableMapping[str, s
                         f'Error setting env var {env_var_name}={value}: check that the value is of the right type'
                     )
 
-    if 'SANDBOX_TYPE' in env_or_toml_dict:
-        logger.opendevin_logger.error(
-            'SANDBOX_TYPE is deprecated. Please use SANDBOX_BOX_TYPE instead.'
-        )
-        env_or_toml_dict['SANDBOX_BOX_TYPE'] = env_or_toml_dict.pop('SANDBOX_TYPE')
     # Start processing from the root of the config object
     set_attr_from_env(cfg)
 
@@ -520,8 +508,6 @@ def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml'):
         keys_to_migrate = [key for key in core_config if key.startswith('sandbox_')]
         for key in keys_to_migrate:
             new_key = key.replace('sandbox_', '')
-            if new_key == 'type':
-                new_key = 'box_type'
             if new_key in sandbox_config.__annotations__:
                 # read the key in sandbox and remove it from core
                 setattr(sandbox_config, new_key, core_config.pop(key))
@@ -548,10 +534,6 @@ def finalize_config(cfg: AppConfig):
         cfg.workspace_mount_path = os.path.abspath(cfg.workspace_base)
     cfg.workspace_base = os.path.abspath(cfg.workspace_base)
 
-    # In local there is no sandbox, the workspace will have the same pwd as the host
-    if cfg.sandbox.box_type == 'local' and cfg.workspace_mount_path is not None:
-        cfg.workspace_mount_path_in_sandbox = cfg.workspace_mount_path
-
     if cfg.workspace_mount_rewrite:  # and not config.workspace_mount_path:
         # TODO why do we need to check if workspace_mount_path is None?
         base = cfg.workspace_base or os.getcwd()

+ 0 - 1
opendevin/core/logger.py

@@ -87,7 +87,6 @@ class SensitiveDataFilter(logging.Filter):
             'e2b_api_key',
             'github_token',
             'jwt_secret',
-            'ssh_password',
         ]
 
         # add env var names

+ 0 - 2
opendevin/core/schema/config.py

@@ -36,11 +36,9 @@ class ConfigType(str, Enum):
     MAX_ITERATIONS = 'MAX_ITERATIONS'
     AGENT = 'AGENT'
     E2B_API_KEY = 'E2B_API_KEY'
-    SANDBOX_BOX_TYPE = 'SANDBOX_BOX_TYPE'
     SANDBOX_USER_ID = 'SANDBOX_USER_ID'
     SANDBOX_TIMEOUT = 'SANDBOX_TIMEOUT'
     USE_HOST_NETWORK = 'USE_HOST_NETWORK'
-    SSH_HOSTNAME = 'SSH_HOSTNAME'
     DISABLE_COLOR = 'DISABLE_COLOR'
     DEBUG = 'DEBUG'
     FILE_UPLOADS_MAX_FILE_SIZE_MB = 'FILE_UPLOADS_MAX_FILE_SIZE_MB'

+ 0 - 5
tests/integration/regenerate.sh

@@ -56,7 +56,6 @@ cd "$PROJECT_ROOT" || exit 1
 mkdir -p $WORKSPACE_BASE
 
 # use environmental variable if exists, otherwise use "ssh"
-SANDBOX_BOX_TYPE="${SANDBOX_TYPE:-ssh}"
 TEST_RUNTIME="${TEST_RUNTIME:-eventstream}"  # can be server or eventstream
 # TODO: set this as default after ServerRuntime is deprecated
 if [ "$TEST_RUNTIME" == "eventstream" ] && [ -z "$SANDBOX_CONTAINER_IMAGE" ]; then
@@ -64,7 +63,6 @@ if [ "$TEST_RUNTIME" == "eventstream" ] && [ -z "$SANDBOX_CONTAINER_IMAGE" ]; th
 fi
 
 MAX_ITERATIONS=15
-echo "SANDBOX_BOX_TYPE: $SANDBOX_BOX_TYPE"
 echo "TEST_RUNTIME: $TEST_RUNTIME"
 
 agents=(
@@ -112,7 +110,6 @@ run_test() {
 
   env SCRIPT_DIR="$SCRIPT_DIR" \
     PROJECT_ROOT="$PROJECT_ROOT" \
-    SANDBOX_BOX_TYPE="$SANDBOX_BOX_TYPE" \
     WORKSPACE_BASE=$WORKSPACE_BASE \
     WORKSPACE_MOUNT_PATH=$WORKSPACE_MOUNT_PATH \
     MAX_ITERATIONS=$MAX_ITERATIONS \
@@ -183,7 +180,6 @@ regenerate_without_llm() {
   set -x
   env SCRIPT_DIR="$SCRIPT_DIR" \
       PROJECT_ROOT="$PROJECT_ROOT" \
-      SANDBOX_BOX_TYPE="$SANDBOX_BOX_TYPE" \
       WORKSPACE_BASE=$WORKSPACE_BASE \
       WORKSPACE_MOUNT_PATH=$WORKSPACE_MOUNT_PATH \
       MAX_ITERATIONS=$MAX_ITERATIONS \
@@ -213,7 +209,6 @@ regenerate_with_llm() {
     env SCRIPT_DIR="$SCRIPT_DIR" \
       PROJECT_ROOT="$PROJECT_ROOT" \
       DEBUG=true \
-      SANDBOX_BOX_TYPE="$SANDBOX_BOX_TYPE" \
       WORKSPACE_BASE=$WORKSPACE_BASE \
       WORKSPACE_MOUNT_PATH=$WORKSPACE_MOUNT_PATH \
       DEFAULT_AGENT=$agent \

+ 3 - 23
tests/integration/test_agent.py

@@ -29,7 +29,6 @@ CONFIG = AppConfig(
     workspace_base=os.getenv('WORKSPACE_BASE'),
     workspace_mount_path=os.getenv('WORKSPACE_MOUNT_PATH'),
     sandbox=SandboxConfig(
-        box_type=os.getenv('SANDBOX_BOX_TYPE', 'ssh'),
         use_host_network=True,
     ),
 )
@@ -80,8 +79,7 @@ def validate_final_state(final_state: State | None, test_name: str):
     (
         os.getenv('DEFAULT_AGENT') == 'CodeActAgent'
         or os.getenv('DEFAULT_AGENT') == 'CodeActSWEAgent'
-    )
-    and os.getenv('SANDBOX_BOX_TYPE', '').lower() != 'ssh',
+    ),
     reason='CodeActAgent/CodeActSWEAgent only supports ssh sandbox which is stateful',
 )
 @pytest.mark.skipif(
@@ -118,18 +116,13 @@ def test_write_simple_script(current_test_name: str) -> None:
     (
         os.getenv('DEFAULT_AGENT') == 'CodeActAgent'
         or os.getenv('DEFAULT_AGENT') == 'CodeActSWEAgent'
-    )
-    and os.getenv('SANDBOX_BOX_TYPE', '').lower() != 'ssh',
+    ),
     reason='CodeActAgent/CodeActSWEAgent only supports ssh sandbox which is stateful',
 )
 @pytest.mark.skipif(
     os.getenv('DEFAULT_AGENT') == 'PlannerAgent',
     reason='We only keep basic tests for PlannerAgent',
 )
-@pytest.mark.skipif(
-    os.getenv('SANDBOX_BOX_TYPE') == 'local',
-    reason='local sandbox shows environment-dependent absolute path for pwd command',
-)
 def test_edits(current_test_name: str):
     # Copy workspace artifacts to workspace_base location
     source_dir = os.path.join(os.path.dirname(__file__), 'workspace/test_edits/')
@@ -163,10 +156,6 @@ Enjoy!
     and os.getenv('DEFAULT_AGENT') != 'CodeActSWEAgent',
     reason='currently only CodeActAgent and CodeActSWEAgent have IPython (Jupyter) execution by default',
 )
-@pytest.mark.skipif(
-    os.getenv('SANDBOX_BOX_TYPE') != 'ssh',
-    reason='Currently, only ssh sandbox supports stateful tasks',
-)
 def test_ipython(current_test_name: str):
     # Execute the task
     task = "Use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'. Do not ask me for confirmation at any point."
@@ -191,10 +180,6 @@ def test_ipython(current_test_name: str):
     os.getenv('DEFAULT_AGENT') != 'ManagerAgent',
     reason='Currently, only ManagerAgent supports task rejection',
 )
-@pytest.mark.skipif(
-    os.getenv('SANDBOX_BOX_TYPE') == 'local',
-    reason='FIXME: local sandbox does not capture stderr',
-)
 def test_simple_task_rejection(current_test_name: str):
     # Give an impossible task to do: cannot write a commit message because
     # the workspace is not a git repo
@@ -211,10 +196,6 @@ def test_simple_task_rejection(current_test_name: str):
     and os.getenv('DEFAULT_AGENT') != 'CodeActSWEAgent',
     reason='currently only CodeActAgent and CodeActSWEAgent have IPython (Jupyter) execution by default',
 )
-@pytest.mark.skipif(
-    os.getenv('SANDBOX_BOX_TYPE') != 'ssh',
-    reason='Currently, only ssh sandbox supports stateful tasks',
-)
 def test_ipython_module(current_test_name: str):
     # Execute the task
     task = "Install and import pymsgbox==1.0.9 and print it's version in /workspace/test.txt. Do not ask me for confirmation at any point."
@@ -245,8 +226,7 @@ def test_ipython_module(current_test_name: str):
     (
         os.getenv('DEFAULT_AGENT') == 'CodeActAgent'
         or os.getenv('DEFAULT_AGENT') == 'CodeActSWEAgent'
-    )
-    and os.getenv('SANDBOX_BOX_TYPE', '').lower() != 'ssh',
+    ),
     reason='CodeActAgent/CodeActSWEAgent only supports ssh sandbox which is stateful',
 )
 def test_browse_internet(http_server, current_test_name: str):

+ 1 - 40
tests/unit/test_config.py

@@ -52,7 +52,6 @@ def test_compat_env_to_config(monkeypatch, setup_env):
     monkeypatch.setenv('AGENT_MEMORY_MAX_THREADS', '4')
     monkeypatch.setenv('AGENT_MEMORY_ENABLED', 'True')
     monkeypatch.setenv('DEFAULT_AGENT', 'CodeActAgent')
-    monkeypatch.setenv('SANDBOX_TYPE', 'local')
     monkeypatch.setenv('SANDBOX_TIMEOUT', '10')
 
     config = AppConfig()
@@ -67,7 +66,6 @@ def test_compat_env_to_config(monkeypatch, setup_env):
     assert config.get_agent_config().memory_max_threads == 4
     assert config.get_agent_config().memory_enabled is True
     assert config.default_agent == 'CodeActAgent'
-    assert config.sandbox.box_type == 'local'
     assert config.sandbox.timeout == 10
 
 
@@ -120,7 +118,6 @@ timeout = 1
 [core]
 workspace_base = "/opt/files2/workspace"
 default_agent = "TestAgent"
-sandbox_type = "local"
 """
         )
 
@@ -150,12 +147,8 @@ sandbox_type = "local"
     assert default_config.get_agent_config('BrowsingAgent').memory_enabled is False
 
     assert default_config.workspace_base == '/opt/files2/workspace'
-    assert default_config.sandbox.box_type == 'local'
     assert default_config.sandbox.timeout == 1
 
-    # default config doesn't have a field sandbox_type
-    assert not hasattr(default_config, 'sandbox_type')
-
     # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
     assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
     assert (
@@ -170,7 +163,7 @@ sandbox_type = "local"
     assert default_config.workspace_mount_path == '/opt/files2/workspace'
 
 
-def test_compat_load_sandbox_from_toml(default_config, temp_toml_file):
+def test_compat_load_sandbox_from_toml(default_config: AppConfig, temp_toml_file: str):
     # test loading configuration from a new-style TOML file
     # uses a toml file with sandbox_vars instead of a sandbox section
     with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
@@ -184,7 +177,6 @@ memory_enabled = true
 
 [core]
 workspace_base = "/opt/files2/workspace"
-sandbox_type = "local"
 sandbox_timeout = 500
 sandbox_container_image = "node:14"
 sandbox_user_id = 1001
@@ -199,7 +191,6 @@ default_agent = "TestAgent"
     assert default_config.default_agent == 'TestAgent'
     assert default_config.get_agent_config().memory_enabled is True
     assert default_config.workspace_base == '/opt/files2/workspace'
-    assert default_config.sandbox.box_type == 'local'
     assert default_config.sandbox.timeout == 500
     assert default_config.sandbox.container_image == 'node:14'
     assert default_config.sandbox.user_id == 1001
@@ -208,7 +199,6 @@ default_agent = "TestAgent"
     finalize_config(default_config)
 
     # app config doesn't have fields sandbox_*
-    assert not hasattr(default_config, 'sandbox_type')
     assert not hasattr(default_config, 'sandbox_timeout')
     assert not hasattr(default_config, 'sandbox_container_image')
     assert not hasattr(default_config, 'sandbox_user_id')
@@ -229,7 +219,6 @@ api_key = "toml-api-key"
 
 [core]
 workspace_base = "/opt/files3/workspace"
-sandbox_type = "local"
 disable_color = true
 sandbox_timeout = 500
 sandbox_user_id = 1001
@@ -237,7 +226,6 @@ sandbox_user_id = 1001
 
     monkeypatch.setenv('LLM_API_KEY', 'env-api-key')
     monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED')
-    monkeypatch.setenv('SANDBOX_TYPE', 'e2b')
     monkeypatch.setenv('SANDBOX_TIMEOUT', '1000')
     monkeypatch.setenv('SANDBOX_USER_ID', '1002')
 
@@ -262,7 +250,6 @@ sandbox_user_id = 1001
     assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
     assert default_config.workspace_mount_path == 'UNDEFINED'
 
-    assert default_config.sandbox.box_type == 'e2b'
     assert default_config.disable_color is True
     assert default_config.sandbox.timeout == 1000
     assert default_config.sandbox.user_id == 1002
@@ -285,14 +272,12 @@ api_key = "toml-api-key"
 workspace_base = "/opt/files3/workspace"
 
 [sandbox]
-box_type = "e2b"
 timeout = 500
 user_id = 1001
 """)
 
     monkeypatch.setenv('LLM_API_KEY', 'env-api-key')
     monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED')
-    monkeypatch.setenv('SANDBOX_TYPE', 'local')
     monkeypatch.setenv('SANDBOX_TIMEOUT', '1000')
     monkeypatch.setenv('SANDBOX_USER_ID', '1002')
 
@@ -303,7 +288,6 @@ user_id = 1001
 
     # before load_from_env, values are set to the values from the toml file
     assert default_config.get_llm_config().api_key == 'toml-api-key'
-    assert default_config.sandbox.box_type == 'e2b'
     assert default_config.sandbox.timeout == 500
     assert default_config.sandbox.user_id == 1001
 
@@ -314,7 +298,6 @@ user_id = 1001
     assert default_config.get_llm_config().model == 'test-model'
     assert default_config.get_llm_config().api_key == 'env-api-key'
 
-    assert default_config.sandbox.box_type == 'local'
     assert default_config.sandbox.timeout == 1000
     assert default_config.sandbox.user_id == 1002
 
@@ -335,7 +318,6 @@ workspace_base = "/opt/files/workspace"
 model = "test-model"
 
 [sandbox]
-box_type = "local"
 timeout = 1
 container_image = "custom_image"
 user_id = 1001
@@ -347,7 +329,6 @@ user_id = 1001
     finalize_config(default_config)
 
     assert default_config.get_llm_config().model == 'test-model'
-    assert default_config.sandbox.box_type == 'local'
     assert default_config.sandbox.timeout == 1
     assert default_config.sandbox.container_image == 'custom_image'
     assert default_config.sandbox.user_id == 1001
@@ -374,7 +355,6 @@ def test_defaults_dict_after_updates(default_config):
         defaults_after_updates['workspace_mount_path']['default']
         is UndefinedString.UNDEFINED
     )
-    assert defaults_after_updates['sandbox']['box_type']['default'] == 'ssh'
     assert defaults_after_updates['sandbox']['timeout']['default'] == 120
     assert (
         defaults_after_updates['sandbox']['container_image']['default']
@@ -393,7 +373,6 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
 
     load_from_toml(default_config)
     load_from_env(default_config, os.environ)
-    default_config.ssh_password = None  # prevent leak
     default_config.jwt_secret = None  # prevent leak
     for llm in default_config.llms.values():
         llm.api_key = None  # prevent leak
@@ -405,13 +384,8 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
 def test_finalize_config(default_config):
     # Test finalize config
     assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
-    default_config.sandbox.box_type = 'local'
     finalize_config(default_config)
 
-    assert (
-        default_config.workspace_mount_path_in_sandbox
-        == default_config.workspace_mount_path
-    )
     assert default_config.workspace_mount_path == os.path.abspath(
         default_config.workspace_base
     )
@@ -426,16 +400,6 @@ def test_workspace_mount_path_default(default_config):
     )
 
 
-def test_workspace_mount_path_in_sandbox_local(default_config):
-    assert default_config.workspace_mount_path_in_sandbox == '/workspace'
-    default_config.sandbox.box_type = 'local'
-    finalize_config(default_config)
-    assert (
-        default_config.workspace_mount_path_in_sandbox
-        == default_config.workspace_mount_path
-    )
-
-
 def test_workspace_mount_rewrite(default_config, monkeypatch):
     default_config.workspace_base = '/home/user/project'
     default_config.workspace_mount_rewrite = '/home/user:/sandbox'
@@ -512,14 +476,11 @@ def test_api_keys_repr_str():
         agents={'agent': agent_config},
         e2b_api_key='my_e2b_api_key',
         jwt_secret='my_jwt_secret',
-        ssh_password='my_ssh_password',
     )
     assert "e2b_api_key='******'" in repr(app_config)
     assert "e2b_api_key='******'" in str(app_config)
     assert "jwt_secret='******'" in repr(app_config)
     assert "jwt_secret='******'" in str(app_config)
-    assert "ssh_password='******'" in repr(app_config)
-    assert "ssh_password='******'" in str(app_config)
 
     # Check that no other attrs in AppConfig have 'key' or 'token' in their name
     # This will fail when new attrs are added, and attract attention