Skip to content
Draft
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Changed

- Identify Socket API configuration requests and scan uploads as
`socket-basics/<version>` using the Basics version.

## [3.4.1] - 2026-10-21

### Changed
Expand Down
4 changes: 3 additions & 1 deletion socket_basics/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from pathlib import Path
from typing import Dict, Any, List, Optional

from ..user_agent import USER_AGENT

logger = logging.getLogger(__name__)

TRUE_STRINGS = ('true', '1', 'yes', 'on')
Expand Down Expand Up @@ -1017,7 +1019,7 @@ def load_socket_basics_config(org_slug_override: str | None = None) -> Dict[str,
from socketdev import socketdev

# Initialize SDK
sdk = socketdev(token=api_key, timeout=100)
sdk = socketdev(token=api_key, timeout=100, user_agent=USER_AGENT)

# Get organizations and find the right one or auto-discover
orgs = sdk.org.get()
Expand Down
4 changes: 3 additions & 1 deletion socket_basics/socket_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,13 @@
from .core.config import Config, parse_cli_args, create_config_from_args
from .core.connector import ConnectorManager
from .core.notification.manager import NotificationManager
from .user_agent import USER_AGENT
except ImportError:
# Absolute imports for when run as a script (best-effort fallback)
from socket_basics.core.config import Config, parse_cli_args, create_config_from_args
from socket_basics.core.connector import ConnectorManager
from socket_basics.core.notification.manager import NotificationManager
from socket_basics.user_agent import USER_AGENT

# Configure logger (basicConfig will be applied in main)
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -272,7 +274,7 @@ def submit_socket_facts(self, socket_facts_path: Path, results: Dict[str, Any])

# Initialize SDK
logger.debug("Initializing socketdev SDK")
sdk = socketdev(token=socket_api_key, timeout=100)
sdk = socketdev(token=socket_api_key, timeout=100, user_agent=USER_AGENT)
logger.debug("✓ socketdev SDK initialized")

# Prepare full scan parameters with proper defaults
Expand Down
4 changes: 4 additions & 0 deletions socket_basics/user_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .version import __version__

USER_AGENT_NAME = "socket-basics"
USER_AGENT = f"{USER_AGENT_NAME}/{__version__}"
80 changes: 80 additions & 0 deletions tests/test_user_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import json

import pytest
import requests

from socket_basics.core.config import Config, load_socket_basics_config
from socket_basics.socket_basics import SecurityScanner
from socket_basics.version import __version__


@pytest.fixture
def captured_requests(monkeypatch):
captured = []

def send(session, request, **kwargs):
captured.append(request)
if request.path_url == "/v0/organizations":
organization = {"slug": "test-org", "plan": "enterprise"}
body = {"organizations": {"test-org": organization}}
elif request.method == "POST":
body = {
"id": "test-scan",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"organization_id": "test-org",
"repository_id": "test-repo",
"branch": "main",
"html_report_url": "https://socket.dev/test-scan",
}
else:
body = {}
response = requests.Response()
response.status_code = 201 if request.method == "POST" else 200
response.headers["Content-Type"] = "application/json"
response._content = json.dumps(body).encode()
return response

monkeypatch.setattr(requests.sessions.Session, "send", send)
monkeypatch.setenv("SOCKET_SECURITY_API_KEY", "synthetic-test-token")
monkeypatch.delenv("SKIP_SOCKET_SUBMISSION", raising=False)
return captured


def test_dashboard_configuration_requests_identify_basics(captured_requests):
config = load_socket_basics_config(org_slug_override="test-org")

assert config["socket_org"] == "test-org"
assert [request.path_url for request in captured_requests] == [
"/v0/organizations",
"/v0/orgs/test-org/settings/socket-basics",
]
assert all(
request.headers["User-Agent"] == f"socket-basics/{__version__}"
for request in captured_requests
)


def test_scan_upload_identifies_basics(captured_requests, tmp_path):
facts = tmp_path / ".socket.facts.json"
components = [{"type": "file", "name": "test"}]
facts.write_text(json.dumps({"components": components}))
config = Config(
{
"workspace": str(tmp_path),
"socket_api_key": "synthetic-test-token",
"socket_org": "test-org",
"repo": "test-repo",
"branch": "main",
}
)

SecurityScanner(config).submit_socket_facts(facts, {})

assert len(captured_requests) == 1
request = captured_requests[0]
assert request.method == "POST"
assert request.path_url.startswith("/v0/orgs/test-org/full-scans?")
assert request.headers["User-Agent"] == f"socket-basics/{__version__}"
assert request.headers["Authorization"].startswith("Basic ")
assert request.headers["Content-Type"].startswith("multipart/form-data;")
Loading