Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat extra security for hosts #5836

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config.template.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ workspace_base = "./workspace"
# List of allowed file extensions for uploads
#file_uploads_allowed_extensions = [".*"]

# Approved hostnames for incoming requests. Use "*" for any
#approved_hostnames = ["localhost"]

#################################### LLM #####################################
# Configuration for LLM models (group name starts with 'llm')
# use 'llm' for the default LLM config
Expand Down
3 changes: 3 additions & 0 deletions openhands/core/config/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class AppConfig:
input is read line by line. When enabled, input continues until /exit command.
"""

approved_hostnames: list[str] = field(
default_factory=lambda: ['localhost', '127.0.0.1']
)
Comment on lines +49 to +51
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not rely on the uvicorn --host flag?

Copy link
Collaborator Author

@tofarr tofarr Dec 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The uvicorn host flag is currently set to 0.0.0.0 by the default docker command (so that the server is accessible from outside the docker container). This means that unless docker is firewalled, anything within is it accessible on any network on which the machine is running. So, for example, if a person was running on a wifi hotspot at an internet cafe, somebody at a nearby table could access their server and use their LLM credits assuming there were not other security precautions in place and that they could figure out the IP (Maybe just search the subnet for an open port 3000). Or if somebody did not secure their home WIFI correctly, then their neighbor may be able to use their LLM credits.

llms: dict[str, LLMConfig] = field(default_factory=dict)
agents: dict = field(default_factory=dict)
default_agent: str = OH_DEFAULT_AGENT
Expand Down
6 changes: 5 additions & 1 deletion openhands/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import openhands.agenthub # noqa F401 (we import this to get the agents registered)
from openhands.server.middleware import (
AttachConversationMiddleware,
HostCheckMiddleware,
InMemoryRateLimiter,
LocalhostCORSMiddleware,
NoCacheMiddleware,
Expand All @@ -26,7 +27,7 @@
from openhands.server.routes.public import app as public_api_router
from openhands.server.routes.security import app as security_api_router
from openhands.server.routes.settings import app as settings_router
from openhands.server.shared import openhands_config, session_manager
from openhands.server.shared import config, openhands_config, session_manager
from openhands.utils.import_utils import get_impl


Expand All @@ -48,6 +49,9 @@ async def _lifespan(app: FastAPI):
app.add_middleware(
RateLimitMiddleware, rate_limiter=InMemoryRateLimiter(requests=10, seconds=1)
)
approved_hostnames = config.approved_hostnames
if '*' not in config.approved_hostnames:
app.add_middleware(HostCheckMiddleware, approved_hostnames=set(approved_hostnames))


@app.get('/health')
Expand Down
24 changes: 24 additions & 0 deletions openhands/server/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,27 @@ async def __call__(self, request: Request, call_next: Callable):
await self._detach_session(request)

return response


class HostCheckMiddleware(BaseHTTPMiddleware):
"""
Middleware to prevent connections with unapproved hosts.
"""

approved_hostnames: set[str]

def __init__(self, app, approved_hostnames: set[str]):
super().__init__(app)
self.approved_hostnames = approved_hostnames

async def dispatch(self, request, call_next):
hostname = request.base_url.hostname
if hostname not in self.approved_hostnames:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={
'error': f'Unapproved host: {hostname}. To permit this host, add either "{hostname}" or "*" to core.approved_hostnames in your config.toml.'
},
)
response = await call_next(request)
return response
Loading