Skip to content
Open
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 @@ -49,6 +49,9 @@
# Enable the browser environment
#enable_browser = true

# Skip TLS certificate verification for outbound HTTP requests (NOT recommended)
#insecure_skip_verify = false

# Maximum budget per task, 0.0 means no limit
#max_budget_per_task = 0.0

Expand Down
3 changes: 2 additions & 1 deletion docs/usage/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ These variables correspond to the `[core]` section in `config.toml`:
| `SAVE_TRAJECTORY_PATH` | string | `"./trajectories"` | Path to store conversation trajectories |
| `REPLAY_TRAJECTORY_PATH` | string | `""` | Path to load and replay a trajectory file |
| `FILE_STORE_PATH` | string | `"/tmp/file_store"` | File store directory path |
| `INSECURE_SKIP_VERIFY` | boolean | `false` | Skip TLS certificate verification for outbound HTTP requests (NOT recommended) |
| `FILE_STORE` | string | `"memory"` | File store type (`memory`, `local`, etc.) |
| `FILE_UPLOADS_MAX_FILE_SIZE_MB` | integer | `0` | Maximum file upload size in MB (0 = no limit) |
| `FILE_UPLOADS_RESTRICT_FILE_TYPES` | boolean | `false` | Whether to restrict file upload types |
Expand Down Expand Up @@ -248,4 +249,4 @@ export DEBUG_RUNTIME=true
docker run -e LLM_API_KEY="your-key" -e DEBUG=true openhands/openhands
```

6. **Validation**: Invalid environment variable values will be logged as errors and fall back to defaults.
6. **Validation**: Invalid environment variable values will be logged as errors and fall back to defaults.
5 changes: 3 additions & 2 deletions enterprise/integrations/jira/jira_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from openhands.server.shared import server_config
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
from openhands.server.user_auth.user_auth import UserAuth
from openhands.utils.http_session import httpx_verify_option

JIRA_CLOUD_API_URL = 'https://api.atlassian.com/ex/jira'

Expand Down Expand Up @@ -408,7 +409,7 @@ async def get_issue_details(
svc_acc_api_key: str,
) -> Tuple[str, str]:
url = f'{JIRA_CLOUD_API_URL}/{jira_cloud_id}/rest/api/2/issue/{job_context.issue_key}'
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.get(url, auth=(svc_acc_email, svc_acc_api_key))
response.raise_for_status()
issue_payload = response.json()
Expand Down Expand Up @@ -443,7 +444,7 @@ async def send_message(
f'{JIRA_CLOUD_API_URL}/{jira_cloud_id}/rest/api/2/issue/{issue_key}/comment'
)
data = {'body': message.message}
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(
url, auth=(svc_acc_email, svc_acc_api_key), json=data
)
Expand Down
5 changes: 3 additions & 2 deletions enterprise/integrations/jira_dc/jira_dc_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from openhands.server.shared import server_config
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
from openhands.server.user_auth.user_auth import UserAuth
from openhands.utils.http_session import httpx_verify_option


class JiraDcManager(Manager):
Expand Down Expand Up @@ -422,7 +423,7 @@ async def get_issue_details(
"""Get issue details from Jira DC API."""
url = f'{job_context.base_api_url}/rest/api/2/issue/{job_context.issue_key}'
headers = {'Authorization': f'Bearer {svc_acc_api_key}'}
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.get(url, headers=headers)
response.raise_for_status()
issue_payload = response.json()
Expand Down Expand Up @@ -452,7 +453,7 @@ async def send_message(
url = f'{base_api_url}/rest/api/2/issue/{issue_key}/comment'
headers = {'Authorization': f'Bearer {svc_acc_api_key}'}
data = {'body': message.message}
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(url, headers=headers, json=data)
response.raise_for_status()
return response.json()
Expand Down
3 changes: 2 additions & 1 deletion enterprise/integrations/linear/linear_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from openhands.server.shared import server_config
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
from openhands.server.user_auth.user_auth import UserAuth
from openhands.utils.http_session import httpx_verify_option


class LinearManager(Manager):
Expand Down Expand Up @@ -408,7 +409,7 @@ async def start_job(self, linear_view: LinearViewInterface):
async def _query_api(self, query: str, variables: Dict, api_key: str) -> Dict:
"""Query Linear GraphQL API."""
headers = {'Authorization': api_key}
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(
self.api_url,
headers=headers,
Expand Down
9 changes: 5 additions & 4 deletions enterprise/server/auth/token_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

from openhands.core.config import load_openhands_config
from openhands.integrations.service_types import ProviderType
from openhands.utils.http_session import httpx_verify_option

# Create a function to get config to avoid circular imports
_config = None
Expand Down Expand Up @@ -201,7 +202,7 @@ async def get_idp_tokens_from_keycloak(
access_token: str,
idp: ProviderType,
) -> dict[str, str | int]:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
base_url = KEYCLOAK_SERVER_URL_EXT if self.external else KEYCLOAK_SERVER_URL
url = f'{base_url}/realms/{KEYCLOAK_REALM_NAME}/broker/{idp.value}/token'
headers = {
Expand Down Expand Up @@ -359,7 +360,7 @@ async def _refresh_github_token(self, refresh_token: str) -> dict[str, str | int
'refresh_token': refresh_token,
'grant_type': 'refresh_token',
}
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(url, data=payload)
response.raise_for_status()
logger.info('Successfully refreshed GitHub token')
Expand All @@ -385,7 +386,7 @@ async def _refresh_gitlab_token(self, refresh_token: str) -> dict[str, str | int
'refresh_token': refresh_token,
'grant_type': 'refresh_token',
}
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(url, data=payload)
response.raise_for_status()
logger.info('Successfully refreshed GitLab token')
Expand Down Expand Up @@ -413,7 +414,7 @@ async def _refresh_bitbucket_token(
'refresh_token': refresh_token,
}

async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(url, data=data, headers=headers)
response.raise_for_status()
logger.info('Successfully refreshed Bitbucket token')
Expand Down
3 changes: 3 additions & 0 deletions enterprise/server/routes/api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from openhands.core.logger import openhands_logger as logger
from openhands.server.user_auth import get_user_id
from openhands.utils.async_utils import call_sync_from_async
from openhands.utils.http_session import httpx_verify_option


# Helper functions for BYOR API key management
Expand Down Expand Up @@ -67,6 +68,7 @@ async def generate_byor_key(user_id: str) -> str | None:

try:
async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={
'x-goog-api-key': LITE_LLM_API_KEY,
}
Expand Down Expand Up @@ -119,6 +121,7 @@ async def delete_byor_key_from_litellm(user_id: str, byor_key: str) -> bool:

try:
async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={
'x-goog-api-key': LITE_LLM_API_KEY,
}
Expand Down
5 changes: 3 additions & 2 deletions enterprise/server/routes/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from storage.user_settings import UserSettings

from openhands.server.user_auth import get_user_id
from openhands.utils.http_session import httpx_verify_option

stripe.api_key = STRIPE_API_KEY
billing_router = APIRouter(prefix='/api/billing')
Expand Down Expand Up @@ -78,7 +79,7 @@ def calculate_credits(user_info: LiteLlmUserInfo) -> float:
async def get_credits(user_id: str = Depends(get_user_id)) -> GetCreditsResponse:
if not stripe_service.STRIPE_API_KEY:
return GetCreditsResponse()
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
user_json = await _get_litellm_user(client, user_id)
credits = calculate_credits(user_json['user_info'])
return GetCreditsResponse(credits=Decimal('{:.2f}'.format(credits)))
Expand Down Expand Up @@ -390,7 +391,7 @@ async def success_callback(session_id: str, request: Request):
)
raise HTTPException(status.HTTP_400_BAD_REQUEST)

async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
# Update max budget in litellm
user_json = await _get_litellm_user(client, billing_session.user_id)
amount_subtotal = stripe_session.amount_subtotal or 0
Expand Down
5 changes: 3 additions & 2 deletions enterprise/server/routes/github_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from server.logger import logger

from openhands.server.shared import config
from openhands.utils.http_session import httpx_verify_option

GITHUB_PROXY_ENDPOINTS = bool(os.environ.get('GITHUB_PROXY_ENDPOINTS'))

Expand Down Expand Up @@ -87,7 +88,7 @@ async def access_token(request: Request, subdomain: str):
]
body = urlencode(query_params, doseq=True)
url = 'https://github.com/login/oauth/access_token'
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(url, content=body)
return Response(
response.content,
Expand All @@ -101,7 +102,7 @@ async def post_proxy(request: Request, subdomain: str, path: str):
logger.info(f'github_proxy_post:1:{path}')
body = await request.body()
url = f'https://github.com/{path}'
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
response = await client.post(url, content=body, headers=request.headers)
return Response(
response.content,
Expand Down
5 changes: 5 additions & 0 deletions enterprise/server/saas_nested_conversation_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from openhands.utils.import_utils import get_impl
from openhands.utils.shutdown_listener import should_continue
from openhands.utils.utils import create_registry_and_conversation_stats
from openhands.utils.http_session import httpx_verify_option

# Pattern for accessing runtime pods externally
RUNTIME_URL_PATTERN = os.getenv(
Expand Down Expand Up @@ -261,6 +262,7 @@ async def _start_conversation(
):
logger.info('starting_nested_conversation', extra={'sid': sid})
async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={
'X-Session-API-Key': session_api_key,
}
Expand Down Expand Up @@ -449,6 +451,7 @@ async def send_event_to_conversation(self, sid: str, data: dict):
raise ValueError(f'no_such_conversation:{sid}')
nested_url = self._get_nested_url_for_runtime(runtime['runtime_id'], sid)
async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={
'X-Session-API-Key': runtime['session_api_key'],
}
Expand Down Expand Up @@ -516,6 +519,7 @@ async def _get_runtime_status_from_nested_runtime(
return None

async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={
'X-Session-API-Key': session_api_key,
}
Expand Down Expand Up @@ -792,6 +796,7 @@ async def _create_runtime(
@contextlib.asynccontextmanager
async def _httpx_client(self):
async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={'X-API-Key': self.config.sandbox.api_key or ''},
timeout=_HTTP_TIMEOUT,
) as client:
Expand Down
2 changes: 2 additions & 0 deletions enterprise/storage/saas_settings_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from openhands.storage import get_file_store
from openhands.storage.settings.settings_store import SettingsStore
from openhands.utils.async_utils import call_sync_from_async
from openhands.utils.http_session import httpx_verify_option


@dataclass
Expand Down Expand Up @@ -216,6 +217,7 @@ async def update_settings_with_litellm_default(
)

async with httpx.AsyncClient(
verify=httpx_verify_option(),
headers={
'x-goog-api-key': LITE_LLM_API_KEY,
}
Expand Down
6 changes: 6 additions & 0 deletions openhands/core/config/openhands_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class OpenHandsConfig(BaseModel):
file_store_web_hook_url: Optional url for file store web hook
file_store_web_hook_headers: Optional headers for file_store web hook
enable_browser: Whether to enable the browser environment
insecure_skip_verify: When True, TLS certificate verification is disabled for outbound HTTP requests.
Defaults to None, meaning the system default (verify certificates).
save_trajectory_path: Either a folder path to store trajectories with auto-generated filenames, or a designated trajectory file path.
save_screenshots_in_trajectory: Whether to save screenshots in trajectory (in encoded image format).
replay_trajectory_path: Path to load trajectory and replay. If provided, trajectory would be replayed first before user's instruction.
Expand Down Expand Up @@ -74,6 +76,10 @@ class OpenHandsConfig(BaseModel):
file_store_web_hook_url: str | None = Field(default=None)
file_store_web_hook_headers: dict | None = Field(default=None)
file_store_web_hook_batch: bool = Field(default=False)
insecure_skip_verify: bool | None = Field(
default=None,
description='Disable TLS certificate verification when set to true. Defaults to verifying certificates.',
)
enable_browser: bool = Field(default=True)
save_trajectory_path: str | None = Field(default=None)
save_screenshots_in_trajectory: bool = Field(default=False)
Expand Down
6 changes: 6 additions & 0 deletions openhands/core/config/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from openhands.storage import get_file_store
from openhands.storage.files import FileStore
from openhands.utils.import_utils import get_impl
from openhands.utils.http_session import configure_http_session

JWT_SECRET = '.jwt_secret'
load_dotenv()
Expand Down Expand Up @@ -456,6 +457,11 @@ def finalize_config(cfg: OpenHandsConfig) -> None:
)
)

if cfg.insecure_skip_verify is None:
configure_http_session()
else:
configure_http_session(verify=not cfg.insecure_skip_verify)

# If CLIRuntime is selected, disable Jupyter for all agents
# Assuming 'cli' is the identifier for CLIRuntime
if cfg.runtime and cfg.runtime.lower() == 'cli':
Expand Down
3 changes: 2 additions & 1 deletion openhands/integrations/bitbucket/service/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
ResourceNotFoundError,
User,
)
from openhands.utils.http_session import httpx_verify_option


class BitBucketMixinBase(BaseGitService, HTTPClient):
Expand Down Expand Up @@ -83,7 +84,7 @@ async def _make_request(

"""
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
bitbucket_headers = await self._get_headers()
response = await self.execute_request(
client, url, bitbucket_headers, params, method
Expand Down
5 changes: 3 additions & 2 deletions openhands/integrations/github/service/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
UnknownException,
User,
)
from openhands.utils.http_session import httpx_verify_option


class GitHubMixinBase(BaseGitService, HTTPClient):
Expand Down Expand Up @@ -43,7 +44,7 @@ async def _make_request(
method: RequestMethod = RequestMethod.GET,
) -> tuple[Any, dict]: # type: ignore[override]
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
github_headers = await self._get_headers()

# Make initial request
Expand Down Expand Up @@ -83,7 +84,7 @@ async def execute_graphql_query(
self, query: str, variables: dict[str, Any]
) -> dict[str, Any]:
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
github_headers = await self._get_headers()

response = await client.post(
Expand Down
5 changes: 3 additions & 2 deletions openhands/integrations/gitlab/service/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
UnknownException,
User,
)
from openhands.utils.http_session import httpx_verify_option


class GitLabMixinBase(BaseGitService, HTTPClient):
Expand Down Expand Up @@ -41,7 +42,7 @@ async def _make_request(
method: RequestMethod = RequestMethod.GET,
) -> tuple[Any, dict]: # type: ignore[override]
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
gitlab_headers = await self._get_headers()

# Make initial request
Expand Down Expand Up @@ -99,7 +100,7 @@ async def execute_graphql_query(
if variables is None:
variables = {}
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
gitlab_headers = await self._get_headers()
# Add content type header for GraphQL
gitlab_headers['Content-Type'] = 'application/json'
Expand Down
3 changes: 2 additions & 1 deletion openhands/integrations/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
)
from openhands.microagent.types import MicroagentContentResponse, MicroagentResponse
from openhands.server.types import AppMode
from openhands.utils.http_session import httpx_verify_option


class ProviderToken(BaseModel):
Expand Down Expand Up @@ -174,7 +175,7 @@ async def _get_latest_provider_token(
) -> SecretStr | None:
"""Get latest token from service"""
try:
async with httpx.AsyncClient() as client:
async with httpx.AsyncClient(verify=httpx_verify_option()) as client:
resp = await client.get(
self.REFRESH_TOKEN_URL,
headers={
Expand Down
Loading
Loading