feat: implement core tool categories and granular sub-constraints#1101
feat: implement core tool categories and granular sub-constraints#1101
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. OpenSSF Scorecard
Scanned Files
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1101 +/- ##
==========================================
+ Coverage 89.00% 89.03% +0.02%
==========================================
Files 802 824 +22
Lines 46993 47903 +910
Branches 4715 4821 +106
==========================================
+ Hits 41828 42650 +822
- Misses 4285 4352 +67
- Partials 880 901 +21 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request introduces new tool categories for web, database, and terminal operations, including robust SSRF prevention for web tools and granular sub-constraint enforcement for all tools. My review identified a high-severity DNS rebinding vulnerability in the HTTP request tool, a medium-severity path injection risk in the SQL query tool's URI construction, and a performance concern regarding connection pooling in the HTTP request tool.
| async with httpx.AsyncClient() as client: | ||
| response = await client.request( | ||
| method=method, | ||
| url=url, | ||
| headers=headers, | ||
| content=body, | ||
| timeout=timeout, | ||
| follow_redirects=False, | ||
| ) |
There was a problem hiding this comment.
The current implementation is vulnerable to DNS rebinding (TOCTOU). While _validate_url performs DNS resolution and checks the resulting IPs against the blocklist, httpx.AsyncClient().request will perform its own DNS resolution when it receives the hostname. An attacker could return a public IP during validation and a private/internal IP during the actual request. To mitigate this, the tool should pin the request to the validated IP addresses found in the validation object (e.g., by using a custom transport or rewriting the URL to use the IP and setting the Host header).
| ) -> ToolExecutionResult: | ||
| """Execute the query against SQLite (inner coroutine for timeout wrapping).""" | ||
| if self._config.read_only: | ||
| db_uri = f"file:{self._config.database_path}?mode=ro" |
There was a problem hiding this comment.
The SQLite URI construction is vulnerable to path injection or breakage if database_path contains special characters like ? or #. Since uri=True is passed to aiosqlite.connect, the path component must be properly URI-encoded to ensure it is interpreted correctly as a file path rather than part of the query string.
| db_uri = f"file:{self._config.database_path}?mode=ro" | |
| import urllib.parse | |
| db_uri = f"file:{urllib.parse.quote(str(self._config.database_path))}?mode=ro" |
| A ``ToolExecutionResult`` with the response. | ||
| """ | ||
| try: | ||
| async with httpx.AsyncClient() as client: |
There was a problem hiding this comment.
Instantiating a new httpx.AsyncClient for every request is inefficient as it prevents connection pooling. If this tool is invoked frequently by an agent, the overhead of establishing new TCP/TLS connections for every call will significantly impact performance. Consider managing a persistent client instance or reusing one from a shared context.
There was a problem hiding this comment.
Pull request overview
Implements core tool categories (web/database/terminal) and adds a granular “sub-constraints” layer to gate tool invocation beyond category-level permissions, including shared SSRF defenses and new observability domains.
Changes:
- Added built-in Web tools (HTTP requests, HTML parsing, provider-backed search) plus shared SSRF network validation.
- Added Database tools (SQLite query + schema inspection) and Terminal tool (sandboxed shell execution) with new config wiring and event domains.
- Implemented sub-constraint models + enforcement and integrated them into
ToolPermissionCheckerandToolInvoker, with extensive unit tests.
Reviewed changes
Copilot reviewed 53 out of 57 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Adds httpx dependency lock entry. |
| pyproject.toml | Pins httpx==0.28.1. |
| docs/design/operations.md | Updates design doc to reflect sub-constraint enforcement layer. |
| CLAUDE.md | Updates repo documentation to include new tool categories and events. |
| src/synthorg/tools/network_validator.py | New shared SSRF/DNS/IP validation utilities and NetworkPolicy. |
| src/synthorg/tools/web/base_web_tool.py | Introduces common WEB tool base with URL validation helper. |
| src/synthorg/tools/web/http_request.py | Adds HTTP request tool using httpx + SSRF validation + truncation. |
| src/synthorg/tools/web/html_parser.py | Adds HTML parsing/extraction tool (text/links/metadata). |
| src/synthorg/tools/web/web_search.py | Adds provider-agnostic web search tool and protocol. |
| src/synthorg/tools/web/config.py | Adds web tools configuration model. |
| src/synthorg/tools/web/init.py | Exposes web tool public API exports. |
| src/synthorg/tools/database/base_db_tool.py | Adds common DATABASE tool base. |
| src/synthorg/tools/database/config.py | Adds database configuration models. |
| src/synthorg/tools/database/sql_query.py | Adds SQLite query execution tool with read-only defaults. |
| src/synthorg/tools/database/schema_inspect.py | Adds schema inspection tool (list tables / describe table). |
| src/synthorg/tools/database/init.py | Exposes database tool public API exports. |
| src/synthorg/tools/terminal/base_terminal_tool.py | Adds common TERMINAL tool base with allow/blocklist logic. |
| src/synthorg/tools/terminal/config.py | Adds terminal tool configuration model. |
| src/synthorg/tools/terminal/shell_command.py | Adds sandbox-delegated shell command tool. |
| src/synthorg/tools/terminal/init.py | Exposes terminal tool public API exports. |
| src/synthorg/tools/sub_constraints.py | Adds sub-constraint enums, model, and per-access-level defaults. |
| src/synthorg/tools/sub_constraint_enforcer.py | Adds enforcement logic for sub-constraints on invocations. |
| src/synthorg/tools/permissions.py | Integrates sub-constraint enforcement into permission checking. |
| src/synthorg/tools/invoker.py | Enforces sub-constraints in the invocation pipeline. |
| src/synthorg/tools/factory.py | Adds tool builders for new categories and config-based wiring. |
| src/synthorg/tools/git_url_validator.py | Reuses shared SSRF blocklist from network_validator. |
| src/synthorg/tools/init.py | Re-exports new tools/configs in top-level tools package. |
| src/synthorg/observability/events/web.py | Adds web event constants. |
| src/synthorg/observability/events/database.py | Adds database event constants. |
| src/synthorg/observability/events/terminal.py | Adds terminal event constants. |
| src/synthorg/observability/events/sub_constraint.py | Adds sub-constraint event constants. |
| src/synthorg/core/agent.py | Adds per-agent ToolPermissions.sub_constraints field. |
| src/synthorg/config/schema.py | Adds optional web/database/terminal config fields to RootConfig. |
| src/synthorg/config/defaults.py | Adds default entries for new config fields. |
| tests/unit/observability/test_events.py | Extends event-domain discovery expectations. |
| tests/unit/tools/test_sub_constraints.py | Tests sub-constraint enums/defaults/resolution. |
| tests/unit/tools/test_sub_constraint_enforcer.py | Tests enforcement logic for each constraint dimension. |
| tests/unit/tools/test_permissions_sub_constraints.py | Tests permission checker integration with sub-constraints. |
| tests/unit/tools/test_invoker_sub_constraints.py | Tests invoker pipeline sub-constraint enforcement behavior. |
| tests/unit/tools/test_factory.py | Updates expected tool names list. |
| tests/unit/tools/test_factory_sandbox_wiring.py | Updates expected tool counts for new categories. |
| tests/unit/tools/test_factory_new_categories.py | Adds factory tests for web/database/terminal categories. |
| tests/unit/tools/test_network_validator.py | Adds comprehensive SSRF/network validator tests. |
| tests/unit/tools/web/conftest.py | Adds shared fixtures for web tool tests. |
| tests/unit/tools/web/test_http_request.py | Adds HttpRequestTool unit tests. |
| tests/unit/tools/web/test_html_parser.py | Adds HtmlParserTool unit tests. |
| tests/unit/tools/web/test_web_search.py | Adds WebSearchTool unit tests. |
| tests/unit/tools/terminal/conftest.py | Adds shared fixtures for terminal tool tests. |
| tests/unit/tools/terminal/test_config.py | Adds terminal config unit tests. |
| tests/unit/tools/terminal/test_shell_command.py | Adds ShellCommandTool unit tests. |
| tests/unit/tools/database/conftest.py | Adds shared fixtures for database tool tests. |
| tests/unit/tools/database/test_config.py | Adds database config unit tests. |
| tests/unit/tools/database/test_sql_query.py | Adds SqlQueryTool unit tests. |
| tests/unit/tools/database/test_schema_inspect.py | Adds SchemaInspectTool unit tests. |
| tests/unit/tools/web/init.py | Test package init. |
| tests/unit/tools/terminal/init.py | Test package init. |
| tests/unit/tools/database/init.py | Test package init. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Write protection | ||
| is_write = keyword in _WRITE_PREFIXES | ||
| if is_write and self._config.read_only: | ||
| logger.warning( | ||
| DB_WRITE_BLOCKED, | ||
| keyword=keyword, | ||
| database=self._config.database_path, | ||
| ) |
There was a problem hiding this comment.
Read-only enforcement is bypassable for statements like PRAGMA/WITH (and any unclassified keyword) because is_write only checks _WRITE_PREFIXES. This contradicts the comment above and allows potentially mutating PRAGMAs in read-only mode. Consider treating any statement not explicitly in a small read-only allowlist (e.g., SELECT/EXPLAIN) as write/blocked when read_only=True, or explicitly classify PRAGMA/WITH as write-unsafe.
| super().__init__( | ||
| name="sql_query", | ||
| description=( | ||
| "Execute SQL queries against a database. " | ||
| "Read-only by default; write queries require " | ||
| "explicit configuration." | ||
| ), | ||
| parameters_schema=dict(_PARAMETERS_SCHEMA), | ||
| action_type=ActionType.DB_QUERY, | ||
| config=config, | ||
| ) |
There was a problem hiding this comment.
The tool’s action_type is always ActionType.DB_QUERY, but the docstring says write queries should use ActionType.DB_MUTATE for escalation. As written, write operations won’t be classified as db:mutate, which can undermine approval/risk policy. Consider setting the action type based on the classified statement (or otherwise surfacing a per-invocation action_type to the security pipeline).
| async def _list_tables(self) -> ToolExecutionResult: | ||
| """List all tables in the database.""" | ||
| async with aiosqlite.connect(self._config.database_path) as db: | ||
| cursor = await db.execute( | ||
| "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name" | ||
| ) | ||
| rows = await cursor.fetchall() |
There was a problem hiding this comment.
SchemaInspectTool claims “Always read-only”, but it connects with aiosqlite.connect(self._config.database_path) which can create a missing DB file and opens it writable even when the connection config is read-only. Consider using SQLite URI mode=ro (and failing if the file doesn’t exist) to make the read-only guarantee enforceable.
| if url.startswith("-"): | ||
| return False | ||
| url_lower = url.lower() |
There was a problem hiding this comment.
is_allowed_http_scheme() checks url.startswith("-") without stripping leading whitespace, so inputs like " -http://…" won’t be caught by the flag-injection guard (and leading whitespace may also affect subsequent parsing/validation). Consider normalizing with lstrip() (or rejecting leading whitespace/control chars) before scheme checks.
| if url.startswith("-"): | |
| return False | |
| url_lower = url.lower() | |
| normalized_url = url.lstrip() | |
| if normalized_url.startswith("-"): | |
| return False | |
| url_lower = normalized_url.lower() |
| normalized = hostname.lower() | ||
| is_https = url.startswith("https://") | ||
|
|
||
| port: int | None = None | ||
| try: | ||
| raw_port = urlparse(url).port | ||
| except ValueError: | ||
| logger.warning( | ||
| WEB_SSRF_BLOCKED, | ||
| hostname=normalized, | ||
| reason="malformed_port", | ||
| ) | ||
| return f"Invalid port in URL: {url!r}" | ||
| if raw_port is not None and raw_port <= 0: | ||
| logger.warning( | ||
| WEB_SSRF_BLOCKED, | ||
| hostname=normalized, | ||
| port=raw_port, | ||
| reason="invalid_port", | ||
| ) | ||
| return f"Invalid port in URL: {raw_port!r}" | ||
| if raw_port is not None: | ||
| port = raw_port | ||
| elif is_https: | ||
| port = 443 | ||
| else: | ||
| port = 80 | ||
|
|
There was a problem hiding this comment.
validate_url_host() determines HTTPS via url.startswith("https://"), which is case-sensitive. Since is_allowed_http_scheme() allows uppercase schemes (it lowercases), URLs like "HTTPS://example.com" will be treated as HTTP (wrong port and is_https). Consider using urlparse(url).scheme.casefold() for scheme detection and default port selection.
| """Enforce network mode constraint. | ||
|
|
||
| ``NONE`` blocks all tools in network-requiring categories. | ||
| ``ALLOWLIST_ONLY`` is enforced at the transport level (not here), | ||
| but tools in network-requiring categories are allowed through. | ||
| """ |
There was a problem hiding this comment.
NetworkMode.ALLOWLIST_ONLY is documented as “enforced at the transport level”, but there’s no implementation using ToolSubConstraints.network_allowlist (it’s currently unused in the codebase). As a result, RESTRICTED mode still allows any public host. Consider wiring network_allowlist into the web transport (e.g., build a NetworkPolicy allowlist from it) or enforcing host:port checks here before allowing WEB tools.
src/synthorg/tools/factory.py
Outdated
| all_tools: list[BaseTool] = [ | ||
| *_build_file_system_tools(workspace=workspace), | ||
| *_build_git_tools( | ||
| workspace=workspace, | ||
| git_clone_policy=git_clone_policy, | ||
| sandbox=sandbox, | ||
| ), | ||
| ) | ||
| *_build_web_tools( | ||
| network_policy=web_network_policy, | ||
| search_provider=web_search_provider, | ||
| ), | ||
| *_build_terminal_tools( |
There was a problem hiding this comment.
RootConfig.web is documented as “None = no web tools”, but build_default_tools_from_config() always builds web tools regardless of whether config.web is set (it only uses it to optionally supply a policy). This makes the configuration contract ambiguous. Consider either (a) skipping web tool creation when config.web is None, or (b) updating the schema field description/defaults to reflect that web tools are enabled by default.
src/synthorg/config/schema.py
Outdated
| ) | ||
| web: WebToolsConfig | None = Field( | ||
| default=None, | ||
| description="Web tool configuration (None = no web tools)", |
There was a problem hiding this comment.
The web field description says “None = no web tools”, but the tool factory currently constructs web tools even when this is None. Either the description should be updated (e.g., “None = default web config”) or the factory should treat None as a disable flag to match this docstring.
| description="Web tool configuration (None = no web tools)", | |
| description="Web tool configuration (None = default web config)", |
src/synthorg/tools/web/__init__.py
Outdated
| from synthorg.tools.web.config import WebToolsConfig | ||
| from synthorg.tools.web.html_parser import HtmlParserTool | ||
| from synthorg.tools.web.http_request import HttpRequestTool | ||
| from synthorg.tools.web.web_search import SearchResult, WebSearchProvider, WebSearchTool |
There was a problem hiding this comment.
This import line likely exceeds the configured Ruff line length (88) and will fail linting. Consider splitting the imported names across multiple lines (or using parentheses) to satisfy the formatter/linter.
| from synthorg.tools.web.web_search import SearchResult, WebSearchProvider, WebSearchTool | |
| from synthorg.tools.web.web_search import ( | |
| SearchResult, | |
| WebSearchProvider, | |
| WebSearchTool, | |
| ) |
| command: str = arguments["command"] | ||
| working_dir: str | None = arguments.get("working_directory") | ||
| timeout: float = arguments.get("timeout") or self._config.default_timeout | ||
|
|
||
| if not command.strip(): | ||
| return ToolExecutionResult( | ||
| content="Empty command", | ||
| is_error=True, |
There was a problem hiding this comment.
working_directory is documented as “relative to workspace”, but the implementation passes Path(working_dir) directly to the sandbox without enforcing that it’s relative (or within the workspace) at the tool layer. If a sandbox backend is configured without strict workspace enforcement, this could allow escaping the intended directory boundary. Consider rejecting absolute paths and normalizing/validating that the resolved path stays within the sandbox/workspace root before calling the backend.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Caution Review failedPull request was closed or merged during review WalkthroughAdds three new tool categories—web, database, and terminal—implementing HTTP requests (using Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/unit/tools/test_factory.py (1)
45-45:⚠️ Potential issue | 🟡 MinorUpdate docstring to reflect new tool count.
The docstring says "11 built-in tools" but
_EXPECTED_TOOL_NAMESnow contains 14 tools.📝 Proposed fix
- """Factory returns all 11 built-in tools sorted by name.""" + """Factory returns all 14 built-in tools sorted by name."""🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/tools/test_factory.py` at line 45, Update the docstring in tests/unit/tools/test_factory.py to match the current expected tool count: change the phrase "Factory returns all 11 built-in tools sorted by name." to reflect 14 built-in tools (or use a count derived from _EXPECTED_TOOL_NAMES); locate the test function or module containing that docstring and update it to "14 built-in tools" or make it dynamic by referencing _EXPECTED_TOOL_NAMES so the docstring remains accurate when the list changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/database/config.py`:
- Around line 44-51: Add validation to ensure the default_connection value
exists in the connections mapping: implement a pydantic root_validator (or
`@validator` that accesses other fields) for the model that checks that the
'default_connection' string is one of the keys of the 'connections' dict and
raises a ValueError if not (handle the case where connections is empty
appropriately). Use the existing field names 'connections' and
'default_connection' in the validator so misconfiguration is caught at model
creation rather than later in tool resolution.
In `@src/synthorg/tools/database/schema_inspect.py`:
- Around line 112-125: Change the broad "except Exception" in the schema
inspection block to catch database-specific errors only (use "except
aiosqlite.Error as exc") so only SQLite-related failures are handled; update
imports to include aiosqlite if missing, keep the existing logger.warning call
(DB_SCHEMA_INSPECT_FAILED, action=action, error=str(exc)) and the
ToolExecutionResult return unchanged, and do not swallow system exceptions like
KeyboardInterrupt or SystemExit.
In `@src/synthorg/tools/database/sql_query.py`:
- Around line 254-262: Remove the redundant list() wrapping around await
cursor.fetchall() in the async function that assigns to rows; change the
assignment to use rows = await cursor.fetchall() so rows is directly the list
returned by cursor.fetchall(), leaving the subsequent logger.info and
ToolExecutionResult code (keyword, row_count handling) unchanged and referencing
the same rows variable.
In `@src/synthorg/tools/factory.py`:
- Around line 298-318: The code rebuilds sandbox backends multiple times when
sandbox_backends is None (calls to build_sandbox_backends before resolving
different categories); refactor by computing a single local backends variable
once (e.g., compute resolved_backends = sandbox_backends or
build_sandbox_backends(config=config.sandboxing, workspace=workspace)) and then
pass resolved_backends into resolve_sandbox_for_category for both the version
control and terminal resolution paths (references: sandbox_backends,
build_sandbox_backends, resolve_sandbox_for_category, config.sandboxing,
terminal_sandbox) so backends are built at most once per call.
In `@src/synthorg/tools/invoker.py`:
- Around line 199-204: The call to
self._permission_checker.check_sub_constraints passes dict(tool_call.arguments)
which is only a shallow copy and may allow nested mutable values to be mutated
by sub-constraint evaluation; replace the shallow copy with a deep copy (use
copy.deepcopy) of tool_call.arguments before passing it to
_permission_checker.check_sub_constraints so mutations cannot leak into later
validation/execution, keeping the call signature and arguments (tool.name,
tool.category, tool.action_type) unchanged.
In `@src/synthorg/tools/sub_constraint_enforcer.py`:
- Around line 124-138: The current tuple eagerly evaluates all checks by calling
self._check_network, self._check_terminal, self._check_git, and
self._check_requires_approval up front; change this to lazy evaluation so each
check is invoked only as you iterate. Replace the tuple of direct calls with a
generator/iterable of callables or a generator expression that calls each check
(e.g., yield or call each lambda) and iterate over it, invoking each check in
turn and returning the first non-None violation; refer to the methods
_check_network, _check_terminal, _check_git, and _check_requires_approval and
the local variable checks/violation to locate and update the code.
In `@src/synthorg/tools/sub_constraints.py`:
- Around line 236-241: The code directly indexes _LEVEL_SUB_CONSTRAINTS with
access_level which can raise a KeyError if a new ToolAccessLevel isn't present;
change this to fetch with a safe default (e.g.,
_LEVEL_SUB_CONSTRAINTS.get(access_level, DEFAULT_SUB_CONSTRAINTS or an empty
list)) and add a defensive logger.warning or logger.debug indicating an unknown
access_level before falling back, then continue to call
logger.debug(SUB_CONSTRAINT_RESOLVED, access_level=access_level.value,
source="level_default") as before; update references to _LEVEL_SUB_CONSTRAINTS,
access_level, and SUB_CONSTRAINT_RESOLVED in the same block.
In `@src/synthorg/tools/terminal/base_terminal_tool.py`:
- Around line 66-85: The command blocklist currently uses substring matching via
the _is_command_blocked method (it checks if pattern.lower() in normalized), so
update the docstring for the command_blocklist field in the terminal config (the
command_blocklist declaration) to explicitly state patterns are matched as
substrings anywhere in the command (not just as prefixes), note this differs
from the allowlist's prefix matching, and add a brief example and warning about
single-word patterns like "halt"/"shutdown"/"reboot" potentially matching
unintended strings; keep the wording concise and include guidance to use more
specific patterns if exact command matching is desired.
In `@src/synthorg/tools/terminal/config.py`:
- Around line 27-40: Update the command_blocklist in config.py (symbol:
command_blocklist) to include broader/regex-safe patterns and additional
dangerous commands: replace simple substrings like "rm -rf /" with regex
patterns that account for flags (e.g.,
r"\\brm\\s+(-[\\w-\\s]+\\s*)*/?\\s*/\\b"), add patterns for recursive
chmod/chown (e.g., r"\\bchmod\\s+-R\\b", r"\\bchown\\s+-R\\b"), mv to /dev/null
(e.g., r"\\bmv\\s+/.+\\s+/dev/null\\b"), and other dangerous variants (mkfs, dd,
fork bomb, shutdown/reboot/poweroff variants); ensure the Field default uses
these regex strings and update any matching/validation code to use re.search on
each pattern rather than simple substring checks so the new patterns are
effective, and keep in mind to adopt an allowlist approach for production
deployments.
In `@src/synthorg/tools/terminal/shell_command.py`:
- Around line 198-206: The truncation currently slices output to
self._config.max_output_bytes then appends a marker, which can push the result
past the limit; fix by precomputing the truncation marker string (e.g.,
trunc_marker = f"\n\n[Truncated: output exceeded
{self._config.max_output_bytes:,} bytes]"), then when len(output) >
self._config.max_output_bytes set truncated = True and slice the original output
to self._config.max_output_bytes - len(trunc_marker) (use max(0, ...) to avoid
negative slice lengths) and append trunc_marker so the final output length never
exceeds self._config.max_output_bytes; update references to output, truncated,
and self._config.max_output_bytes accordingly.
In `@src/synthorg/tools/web/html_parser.py`:
- Around line 169-179: HtmlParserTool.__init__ currently doesn't accept or
forward network_policy and request_timeout to BaseWebTool; add optional
parameters network_policy=None and request_timeout=None to
HtmlParserTool.__init__ and pass them through in the super().__init__ call (or
alternatively change the base class to a simpler BaseTool and update the
docstring to state this tool makes no HTTP requests) so the behavior is
explicit; update the constructor signature and the super call referencing
HtmlParserTool.__init__ and BaseWebTool to reflect the chosen approach and
adjust the docstring to note whether network settings are ignored.
In `@src/synthorg/tools/web/http_request.py`:
- Around line 209-227: The code currently uses response.text and len(content)
which counts characters and decodes the entire body into memory, so change the
logic in the HTTP request handling to read and truncate by bytes: stream the
response bytes (e.g., using response.iter_content(chunk_size=4096) or access
response.content up to _max_response_bytes) accumulate at most
self._max_response_bytes bytes, set truncated=True if more bytes exist, then
decode only the collected bytes to a string (use errors='replace' or 'ignore')
for logging and use the byte length for content_length; update uses of
response.text, content, truncated, and the logger.info(WEB_REQUEST_SUCCESS, ...)
call accordingly so truncation and the "[Truncated: response exceeded ...
bytes]" message reflect bytes not characters and avoid loading the full body
into memory.
In `@src/synthorg/tools/web/web_search.py`:
- Line 119: Replace the shallow copy used when constructing parameters_schema
with a deep, read-only copy: use copy.deepcopy(_PARAMETERS_SCHEMA) and wrap the
result with types.MappingProxyType so nested dicts like "properties" aren't
shared or mutable; update the import to include copy and MappingProxyType and
change the construction site where parameters_schema=dict(_PARAMETERS_SCHEMA) is
used (referencing _PARAMETERS_SCHEMA and the parameters_schema argument) to
create MappingProxyType(copy.deepcopy(_PARAMETERS_SCHEMA)).
In `@tests/unit/tools/database/test_sql_query.py`:
- Around line 34-35: Add the missing pytest unit marker to the test by
decorating the test_classify method with `@pytest.mark.unit` (and ensure pytest is
imported if not already); this mirrors other tests in the file and keeps the
test suite markers consistent while the test still asserts
_classify_statement(query) == expected.
In `@tests/unit/tools/test_factory_new_categories.py`:
- Around line 48-60: The test test_custom_network_policy builds a custom
NetworkPolicy and passes it to build_default_tools but never asserts the policy
was applied; update the test to fetch the http_request tool (as done via
next(...) and HttpRequestTool) and add an assertion that the tool's
network_policy (or the appropriate property on the HttpRequestTool instance)
equals the policy instance or has the same settings (e.g.,
block_private_ips=False) to verify build_default_tools applied the custom
NetworkPolicy.
In `@tests/unit/tools/test_network_validator.py`:
- Around line 36-39: Replace the broad pytest.raises(Exception) in test_frozen
with the specific pydantic ValidationError: update the test case (test_frozen)
to expect ValidationError from pydantic when attempting to mutate
NetworkPolicy.block_private_ips, import ValidationError from pydantic at the top
of the test module, and make the same replacement for the similar assertions
around lines 57-60 so all Pydantic field validation checks use
pytest.raises(ValidationError) instead of Exception.
In `@tests/unit/tools/test_sub_constraint_enforcer.py`:
- Around line 40-44: The test_frozen currently expects a generic Exception when
mutating the frozen Pydantic model; update test_frozen to expect
pydantic.errors.ValidationError specifically by replacing
pytest.raises(Exception) with pytest.raises(ValidationError) and add the
appropriate import for ValidationError from pydantic at the top of the test
file; keep the test target SubConstraintViolation and the mutation line
unchanged so the failure still arises from the frozen behavior.
- Around line 161-174: The test test_local_only_blocks_clone currently calls
enforcer.check with ActionType.VCS_COMMIT which is incorrect for a clone/read
operation; update the enforcer.check invocation in test_local_only_blocks_clone
to use ActionType.VCS_READ (or add a concise inline comment explaining why
VCS_COMMIT is intentionally used) so the action type matches the semantic intent
for a clone, keeping references to SubConstraintEnforcer,
ToolSubConstraints(git=GitAccess.LOCAL_ONLY), and the "git_clone" call
consistent.
---
Outside diff comments:
In `@tests/unit/tools/test_factory.py`:
- Line 45: Update the docstring in tests/unit/tools/test_factory.py to match the
current expected tool count: change the phrase "Factory returns all 11 built-in
tools sorted by name." to reflect 14 built-in tools (or use a count derived from
_EXPECTED_TOOL_NAMES); locate the test function or module containing that
docstring and update it to "14 built-in tools" or make it dynamic by referencing
_EXPECTED_TOOL_NAMES so the docstring remains accurate when the list changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: db74c08f-e17e-49b5-8366-737b9c304970
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (56)
CLAUDE.mddocs/design/operations.mdpyproject.tomlsrc/synthorg/config/defaults.pysrc/synthorg/config/schema.pysrc/synthorg/core/agent.pysrc/synthorg/observability/events/database.pysrc/synthorg/observability/events/sub_constraint.pysrc/synthorg/observability/events/terminal.pysrc/synthorg/observability/events/web.pysrc/synthorg/tools/__init__.pysrc/synthorg/tools/database/__init__.pysrc/synthorg/tools/database/base_db_tool.pysrc/synthorg/tools/database/config.pysrc/synthorg/tools/database/schema_inspect.pysrc/synthorg/tools/database/sql_query.pysrc/synthorg/tools/factory.pysrc/synthorg/tools/git_url_validator.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/permissions.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/terminal/__init__.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/terminal/shell_command.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/web/base_web_tool.pysrc/synthorg/tools/web/config.pysrc/synthorg/tools/web/html_parser.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/web/web_search.pytests/unit/observability/test_events.pytests/unit/tools/database/__init__.pytests/unit/tools/database/conftest.pytests/unit/tools/database/test_config.pytests/unit/tools/database/test_schema_inspect.pytests/unit/tools/database/test_sql_query.pytests/unit/tools/terminal/__init__.pytests/unit/tools/terminal/conftest.pytests/unit/tools/terminal/test_config.pytests/unit/tools/terminal/test_shell_command.pytests/unit/tools/test_factory.pytests/unit/tools/test_factory_new_categories.pytests/unit/tools/test_factory_sandbox_wiring.pytests/unit/tools/test_invoker_sub_constraints.pytests/unit/tools/test_network_validator.pytests/unit/tools/test_permissions_sub_constraints.pytests/unit/tools/test_sub_constraint_enforcer.pytests/unit/tools/test_sub_constraints.pytests/unit/tools/web/__init__.pytests/unit/tools/web/conftest.pytests/unit/tools/web/test_html_parser.pytests/unit/tools/web/test_http_request.pytests/unit/tools/web/test_web_search.py
| ) | ||
|
|
||
| rows = list(await cursor.fetchall()) | ||
| if not rows: | ||
| logger.info(DB_QUERY_SUCCESS, keyword=keyword, row_count=0) | ||
| return ToolExecutionResult( | ||
| content="Query returned no results.", | ||
| metadata={"keyword": keyword, "row_count": 0}, | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Minor: Redundant list() wrapping.
cursor.fetchall() already returns a list. The list() wrapper on line 256 is unnecessary.
♻️ Remove redundant list()
- rows = list(await cursor.fetchall())
+ rows = await cursor.fetchall()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ) | |
| rows = list(await cursor.fetchall()) | |
| if not rows: | |
| logger.info(DB_QUERY_SUCCESS, keyword=keyword, row_count=0) | |
| return ToolExecutionResult( | |
| content="Query returned no results.", | |
| metadata={"keyword": keyword, "row_count": 0}, | |
| ) | |
| ) | |
| rows = await cursor.fetchall() | |
| if not rows: | |
| logger.info(DB_QUERY_SUCCESS, keyword=keyword, row_count=0) | |
| return ToolExecutionResult( | |
| content="Query returned no results.", | |
| metadata={"keyword": keyword, "row_count": 0}, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/database/sql_query.py` around lines 254 - 262, Remove the
redundant list() wrapping around await cursor.fetchall() in the async function
that assigns to rows; change the assignment to use rows = await
cursor.fetchall() so rows is directly the list returned by cursor.fetchall(),
leaving the subsequent logger.info and ToolExecutionResult code (keyword,
row_count handling) unchanged and referencing the same rows variable.
| checks = ( | ||
| self._check_network(tool_name, category), | ||
| self._check_terminal(tool_name, category), | ||
| self._check_git(tool_name, action_type), | ||
| self._check_requires_approval(tool_name, action_type), | ||
| ) | ||
| for violation in checks: | ||
| if violation is not None: | ||
| logger.warning( | ||
| SUB_CONSTRAINT_DENIED, | ||
| tool_name=tool_name, | ||
| constraint=violation.constraint, | ||
| reason=violation.reason, | ||
| ) | ||
| return violation |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider lazy evaluation for minor efficiency gain.
All _check_* methods are called eagerly when constructing the tuple (lines 124-128), even though iteration stops at the first violation. This is functionally correct but performs unnecessary work when an early check fails.
♻️ Lazy evaluation alternative
- checks = (
- self._check_network(tool_name, category),
- self._check_terminal(tool_name, category),
- self._check_git(tool_name, action_type),
- self._check_requires_approval(tool_name, action_type),
- )
- for violation in checks:
- if violation is not None:
+ check_fns = (
+ lambda: self._check_network(tool_name, category),
+ lambda: self._check_terminal(tool_name, category),
+ lambda: self._check_git(tool_name, action_type),
+ lambda: self._check_requires_approval(tool_name, action_type),
+ )
+ for check_fn in check_fns:
+ violation = check_fn()
+ if violation is not None:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| checks = ( | |
| self._check_network(tool_name, category), | |
| self._check_terminal(tool_name, category), | |
| self._check_git(tool_name, action_type), | |
| self._check_requires_approval(tool_name, action_type), | |
| ) | |
| for violation in checks: | |
| if violation is not None: | |
| logger.warning( | |
| SUB_CONSTRAINT_DENIED, | |
| tool_name=tool_name, | |
| constraint=violation.constraint, | |
| reason=violation.reason, | |
| ) | |
| return violation | |
| check_fns = ( | |
| lambda: self._check_network(tool_name, category), | |
| lambda: self._check_terminal(tool_name, category), | |
| lambda: self._check_git(tool_name, action_type), | |
| lambda: self._check_requires_approval(tool_name, action_type), | |
| ) | |
| for check_fn in check_fns: | |
| violation = check_fn() | |
| if violation is not None: | |
| logger.warning( | |
| SUB_CONSTRAINT_DENIED, | |
| tool_name=tool_name, | |
| constraint=violation.constraint, | |
| reason=violation.reason, | |
| ) | |
| return violation |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/sub_constraint_enforcer.py` around lines 124 - 138, The
current tuple eagerly evaluates all checks by calling self._check_network,
self._check_terminal, self._check_git, and self._check_requires_approval up
front; change this to lazy evaluation so each check is invoked only as you
iterate. Replace the tuple of direct calls with a generator/iterable of
callables or a generator expression that calls each check (e.g., yield or call
each lambda) and iterate over it, invoking each check in turn and returning the
first non-None violation; refer to the methods _check_network, _check_terminal,
_check_git, and _check_requires_approval and the local variable checks/violation
to locate and update the code.
There was a problem hiding this comment.
Actionable comments posted: 18
♻️ Duplicate comments (1)
src/synthorg/tools/web/http_request.py (1)
200-227:⚠️ Potential issue | 🟠 MajorThe response cap still doesn't cap memory usage.
Switching to
response.contentfixed the byte/char accounting, butAsyncClient.request()still buffers the entire body before this code truncates it. A large response can therefore exhaust memory even when_max_response_bytesis small. Use streaming (client.stream(...)/response.aiter_bytes()) and stop reading once the budget is hit.In httpx 0.28.1, does AsyncClient.request() buffer the full response body by default, and what async streaming API should be used to stop after N bytes (for example, client.stream(...) with response.aiter_bytes())?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/web/http_request.py` around lines 200 - 227, The current implementation uses httpx.AsyncClient.request which buffers the entire response before you truncate, risking OOM; replace the request call with httpx.AsyncClient.stream and iterate response.aiter_bytes() (or aiter_raw() chunks) inside the same async with, accumulate up to self._max_response_bytes and stop reading further once the budget is hit, then decode the accumulated bytes to produce content; keep the same exception handling (TimeoutException/HTTPError) and return ToolExecutionResult as before, and ensure the stream/response is properly closed when you break early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/database/config.py`:
- Around line 46-49: The connections mapping currently allows blank/whitespace
keys; update its type to use NotBlankStr for keys so invalid names are rejected
at model creation: change the annotated type from dict[str,
DatabaseConnectionConfig] to dict[NotBlankStr, DatabaseConnectionConfig] and
import NotBlankStr from core.types, keeping the Field(...) as-is; ensure this
aligns with the existing default_connection constraint and run validations to
confirm blank keys are now rejected.
In `@src/synthorg/tools/database/schema_inspect.py`:
- Around line 127-177: Both _list_tables and _describe_table open and query the
DB without honoring DatabaseConnectionConfig.query_timeout, so slow/locked DBs
can hang; modify these methods to wrap the async DB operations (the
aiosqlite.connect + execute + fetchall sequence) in an asyncio.wait_for using
the configured query_timeout (same pattern used by SqlQueryTool) and log/return
a ToolExecutionResult error on timeout; ensure you reference
self._config.query_timeout and keep the URI construction and safety check
(_SAFE_IDENTIFIER_RE) but move the actual DB calls into the timed block and
handle asyncio.TimeoutError similarly to other DB errors.
In `@src/synthorg/tools/database/sql_query.py`:
- Around line 39-53: The current _WRITE_PREFIXES tuple permits file-affecting
SQLite statements (e.g., ATTACH, DETACH, VACUUM) whenever read_only=False, which
allows arbitrary DB files to be touched; create a separate blacklist constant
(e.g., _FILE_AFFECTING_PREFIXES) containing "ATTACH", "DETACH", and "VACUUM"
(and any variants like "VACUUM INTO") and update the validation logic that
inspects SQL prefixes (the code paths that currently consult _WRITE_PREFIXES) to
always reject statements starting with any _FILE_AFFECTING_PREFIXES regardless
of the read_only flag; ensure both places where prefix checks occur (the
_WRITE_PREFIXES definition and the other occurrence around lines 158-175) are
updated to reference the new blacklist so file-oriented statements remain
blocked even when writable.
In `@src/synthorg/tools/factory.py`:
- Around line 120-142: The factory _build_database_tools currently builds tools
only for DatabaseConfig.default_connection (or the first entry), ignoring other
entries in DatabaseConfig.connections; update it to iterate over all
config.connections items and instantiate a SqlQueryTool and SchemaInspectTool
for each connection (using each connection's DatabaseConnectionConfig) so every
configured connection is exposed as tools; ensure returned tuple includes one
pair per connection (optionally include the connection name in tool construction
or wrapping if the tools support/need it) rather than only the default.
- Around line 270-274: The current assignment uses truthiness so an explicitly
provided empty mapping for sandbox_backends gets ignored; change the logic to
honor any supplied sandbox_backends (including {} or MappingProxyType({})) by
checking if sandbox_backends is not None and only calling
build_sandbox_backends(config.sandboxing, workspace) when sandbox_backends is
None; update the assignment that sets resolved_backends (and any related uses)
to prefer the provided sandbox_backends when sandbox_backends is not None,
otherwise call build_sandbox_backends.
In `@src/synthorg/tools/invoker.py`:
- Around line 207-221: The code treats requires_approval like a hard deny by
returning early instead of parking the escalation and letting the normal
security flow run; change the branch handling violation.requires_approval to
append a structured record to self._pending_escalations (including tool_call,
violation, tool_call.id/name and any correlation id) and produce a non-fatal
ToolResult (e.g., is_error=False or a special pending flag) or otherwise avoid
returning early so that _check_security() still executes; keep the existing
logger.warning(TOOL_SECURITY_ESCALATED, ...) but remove the immediate return and
ensure whatever caller logic expects a pending escalation can resume the tool
call from the entry points that inspect _pending_escalations and call
_check_security().
- Around line 199-204: Replace the direct copy.deepcopy call used when calling
_permission_checker.check_sub_constraints with the guarded helper
_safe_deepcopy_args so deepcopy failures are handled consistently; specifically,
before invoking _permission_checker.check_sub_constraints(tool.name,
tool.category, tool.action_type, ...), call self._safe_deepcopy_args(tool_call)
(or otherwise obtain the safely-copied arguments via that helper), check for a
ToolResult indicating TOOL_INVOKE_DEEPCOPY_ERROR and return it if present, and
only pass the successful deep-copied dict to
_permission_checker.check_sub_constraints to avoid raising on non-copyable
values.
In `@src/synthorg/tools/network_validator.py`:
- Around line 103-109: The current model_validator method _normalize_allowlist
mutates a frozen instance with object.__setattr__; instead implement
normalization before instance construction by changing the validator to run in
"before" mode and operate on the input mapping: replace
`@model_validator`(mode="after") _normalize_allowlist with a
`@model_validator`(mode="before") function that accepts the incoming values dict,
lowercases and deduplicates values from the "hostname_allowlist" key into a
tuple, sets that tuple back into the values dict, and returns the modified
values; remove any use of object.__setattr__ so no mutation of the constructed
object occurs.
- Around line 350-441: validate_url_host is too large and mixes parsing, port
normalization, allowlist/master-switch checks, literal-IP handling, and DNS
resolution; split it into small helpers to keep the main function under 50
lines. Extract responsibilities into helper functions such as
parse_and_normalize_hostname(url) that calls extract_hostname and determines
is_https, determine_port(url, is_https) that validates and returns port or an
error string, check_allowlist_and_master_switch(normalized, policy, is_https,
port) that returns _ok or an error as appropriate, handle_literal_ip(normalized,
is_https, port) that uses ipaddress.ip_address and is_blocked_ip, and
perform_dns_check(normalized, timeout) that wraps resolve_and_check and returns
either the resolved IPs or an error string; then rewrite validate_url_host to
call those helpers in sequence and return early based on their results,
preserving uses of _ok, resolve_and_check, is_blocked_ip, extract_hostname, and
policy.hostname_allowlist / policy.block_private_ips /
policy.dns_resolution_timeout.
- Around line 408-410: The allowlist branch returns early without performing DNS
resolution, leaving resolved_ips empty and bypassing the module's
TOCTOU/rebinding protections; change the early-return logic in the block that
checks if normalized is in policy.hostname_allowlist to perform the same
hostname resolution used elsewhere in this module (populate resolved_ips using
the existing resolver code/path and apply any canonicalization the function
uses), then call _ok with the normalized name, port, is_https flag and the
populated resolved_ips (i.e., preserve the same resolved_ips handling as the
non-allowlist path) so allowlisted hostnames are still pinned to their resolved
IPs before returning.
In `@src/synthorg/tools/sub_constraint_enforcer.py`:
- Around line 102-129: The check() method only evaluates network, terminal, git,
and requires_approval and ignores ToolSubConstraints.file_system,
.code_execution, and .network_allowlist as well as the arguments param; fix by
reading the ToolSubConstraints for the invoked tool (use the existing tool_name
lookup that returns ToolSubConstraints), add/ call new helper methods (e.g.,
_check_filesystem(tool_name, category, arguments),
_enforce_code_execution(tool_name, category),
_enforce_network_allowlist(tool_name, category)) or extend check() to: (1)
enforce FileSystemScope by validating paths from arguments with PathValidator
and rejecting accesses outside WORKSPACE_ONLY/PROJECT_DIRECTORY/FULL semantics,
(2) enforce code_execution by selecting/forcing the appropriate runner or
rejecting non-containerized runs when code_execution == CONTAINERIZED, and (3)
enforce per-agent network_allowlist by passing the allowlist into the network
transport or rejecting disallowed destinations; update check() to include these
checks (and return SubConstraintViolation on failure) and remove the unused noqa
on arguments once implemented.
In `@src/synthorg/tools/sub_constraints.py`:
- Around line 193-222: When get_sub_constraints receives custom_constraints for
a built-in access_level, do not return it verbatim; instead load the built-in
defaults for that access_level (the existing spec defaults used for
SANDBOXED/PROJECT/ETC) and merge only the explicitly provided override fields
from custom_constraints into those defaults so unspecified fields keep the
built-in values. Update get_sub_constraints to detect when access_level !=
ToolAccessLevel.CUSTOM, perform a field-wise merge/copy from the default
ToolSubConstraints for that access_level with non-None/custom values overriding,
and adjust the logger call (e.g., use source="merged" or similar) before
returning the resulting ToolSubConstraints.
In `@src/synthorg/tools/terminal/shell_command.py`:
- Around line 205-221: The code currently truncates the combined `output` string
by character count instead of bytes; change the truncation to operate on UTF-8
encoded bytes: after combining `result.stdout` and `result.stderr` into
`output`, encode it to `out_bytes = output.encode("utf-8")`, compute `marker =
f"\n\n[Truncated: output exceeded {self._config.max_output_bytes:,} bytes]"` and
`marker_bytes = marker.encode("utf-8")`, check `if len(out_bytes) >
self._config.max_output_bytes:` then slice `out_bytes = out_bytes[: max(0,
self._config.max_output_bytes - len(marker_bytes))]`, decode back with `output =
out_bytes.decode("utf-8", errors="replace")`, append `marker` and set `truncated
= True`; update references to `output`, `marker`, `truncated`, and keep using
`self._config.max_output_bytes`.
- Around line 113-121: Allowlist check currently inspects the raw shell string,
which is bypassable via shell chaining; modify the validation in the execution
path that calls bash -c so it either (A) parses the command into argv (e.g., via
shlex.split) and validates the parsed executable/first argument against
self._config.command_allowlist (use the same logic as _is_command_allowed but
applied to the first token), or (B) if you must keep passing through a shell,
explicitly reject any shell metacharacters (like ;, &&, |, $, `, >, <, \\n) when
a command_allowlist is set; update the code paths around _is_command_allowed and
where the subprocess is invoked so ToolExecutionResult is returned with
is_error=True when validation fails.
- Around line 162-180: The code currently only rejects absolute
working_directory values but lets relative paths like "../../" through; update
the validation where working_dir is converted to cwd (the Path(working_dir)
block) to resolve the path against the workspace root and ensure it stays inside
the workspace before calling self._sandbox.execute. Concretely, compute resolved
= (workspace_root / working_dir).resolve() and compare to
workspace_root.resolve() using resolved.is_relative_to(workspace_root.resolve())
(or equivalent containment check) and return a ToolExecutionResult with
is_error=True and a clear message if the resolved path is outside the workspace;
keep the existing error for absolute paths, and then pass the validated cwd into
self._sandbox.execute.
In `@src/synthorg/tools/web/http_request.py`:
- Around line 272-278: In _pin_url, avoid emitting malformed IPv6 netlocs and
stop mutating the caller's headers: when building pinned_netloc, detect IPv6
literals (e.g., pinned_ip contains ':') and wrap them in brackets (use
f"[{pinned_ip}]" when needed) before appending port_suffix; do not modify the
incoming headers dict — create a new_headers = dict(headers) (or {} if headers
is None), set new_headers["Host"] = parsed.hostname or "" if missing, and return
the rewritten URL together with the new_headers (adjust the function
return/signature accordingly) so callers receive a fresh headers mapping.
In `@src/synthorg/tools/web/web_search.py`:
- Around line 144-151: The current broad "except Exception" around the call to
self._provider.search in the web search routine swallows system errors; change
it to explicitly handle provider-level errors and re-raise system-level
exceptions. Replace the bare except with either an except catching the
provider-specific exception type(s) raised by WebSearchProvider.search (e.g.,
ProviderError) or, if unknown, keep except Exception as exc but immediately
re-raise for critical system exceptions (MemoryError, RecursionError,
KeyboardInterrupt, SystemExit) before logging and returning the
ToolExecutionResult; reference self._provider.search, WEB_SEARCH_FAILED,
logger.warning and ToolExecutionResult when making the change.
In `@tests/unit/tools/database/test_sql_query.py`:
- Around line 15-36: Remove the duplicate pytest marker on the test method: keep
a single `@pytest.mark.unit` and delete the other one so the decorator list for
test_classify only contains one `@pytest.mark.unit` plus the existing
`@pytest.mark.parametrize`; ensure the test still calls _classify_statement(query)
== expected and that `@pytest.mark.parametrize` remains immediately above the def
test_classify definition.
---
Duplicate comments:
In `@src/synthorg/tools/web/http_request.py`:
- Around line 200-227: The current implementation uses httpx.AsyncClient.request
which buffers the entire response before you truncate, risking OOM; replace the
request call with httpx.AsyncClient.stream and iterate response.aiter_bytes()
(or aiter_raw() chunks) inside the same async with, accumulate up to
self._max_response_bytes and stop reading further once the budget is hit, then
decode the accumulated bytes to produce content; keep the same exception
handling (TimeoutException/HTTPError) and return ToolExecutionResult as before,
and ensure the stream/response is properly closed when you break early.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2c8698de-5ffe-4f09-a570-feb744d03e8d
📒 Files selected for processing (21)
src/synthorg/config/schema.pysrc/synthorg/tools/database/config.pysrc/synthorg/tools/database/schema_inspect.pysrc/synthorg/tools/database/sql_query.pysrc/synthorg/tools/factory.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/terminal/shell_command.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/web/web_search.pytests/integration/tools/test_factory_integration.pytests/unit/tools/database/test_sql_query.pytests/unit/tools/test_factory.pytests/unit/tools/test_factory_new_categories.pytests/unit/tools/test_network_validator.pytests/unit/tools/test_sub_constraint_enforcer.pytests/unit/tools/web/test_html_parser.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax:except A, B:(no parentheses)—ruff enforces this on Python 3.14.
Type hints: all public functions required, mypy strict mode enforced.
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules).
Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.
Use@computed_fieldfor derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).
Use NotBlankStr (from core.types) for all identifier/name fields—including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants—instead of manual whitespace validators.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Line length: 88 characters (ruff enforced).
Functions: < 50 lines, files < 800 lines.
Handle errors explicitly, never silently swallow.
Validate at system boundaries (user input, external APIs, config files).
Variable name: always logger (not _logger, not log).
All error paths must log at WARNING or ERROR with context before raising.
All state transitions must log at INFO.
DEBUG for object creation, internal flow, entry/exit of key functions.
Pure data models, enums, and re-exports do NOT need logging.
Files:
tests/integration/tools/test_factory_integration.pytests/unit/tools/test_factory.pysrc/synthorg/config/schema.pytests/unit/tools/web/test_html_parser.pysrc/synthorg/tools/invoker.pytests/unit/tools/database/test_sql_query.pytests/unit/tools/test_factory_new_categories.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/database/config.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/database/schema_inspect.pytests/unit/tools/test_network_validator.pysrc/synthorg/tools/database/sql_query.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/factory.pysrc/synthorg/tools/terminal/shell_command.pytests/unit/tools/test_sub_constraint_enforcer.pysrc/synthorg/tools/web/web_search.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/network_validator.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Markers:@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slow.
Async: asyncio_mode = "auto"—no manual@pytest.mark.asyncioneeded.
Timeout: 30 seconds per test (global in pyproject.toml—do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed).
Parametrize: Prefer@pytest.mark.parametrizefor testing similar cases.
Tests must use test-provider, test-small-001, etc. (not real vendor names).
Property-based testing: Python uses Hypothesis (@given+@settings).
Hypothesis profiles configured in tests/conftest.py: ci (deterministic, max_examples=10 + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Controlled via HYPOTHESIS_PROFILE env var.
Never skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).
Files:
tests/integration/tools/test_factory_integration.pytests/unit/tools/test_factory.pytests/unit/tools/web/test_html_parser.pytests/unit/tools/database/test_sql_query.pytests/unit/tools/test_factory_new_categories.pytests/unit/tools/test_network_validator.pytests/unit/tools/test_sub_constraint_enforcer.py
⚙️ CodeRabbit configuration file
Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare
@settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which@given() honors automatically.
Files:
tests/integration/tools/test_factory_integration.pytests/unit/tools/test_factory.pytests/unit/tools/web/test_html_parser.pytests/unit/tools/database/test_sql_query.pytests/unit/tools/test_factory_new_categories.pytests/unit/tools/test_network_validator.pytests/unit/tools/test_sub_constraint_enforcer.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, and observability/http_handler.py may use stdlib logging and print(..., file=sys.stderr).
Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events. import EVENT_CONSTANT
Structured kwargs: always logger.info(EVENT, key=value)—never logger.info("msg %s", val)
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.
RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError.
Non-retryable errors raise immediately without retry.
RetryExhaustedError signals that all retries failed—the engine layer catches this to trigger fallback chains.
Rate limiter respects RateLimitError.retry_after from providers—automatically pauses future requests.
Files:
src/synthorg/config/schema.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/database/config.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/database/schema_inspect.pysrc/synthorg/tools/database/sql_query.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/factory.pysrc/synthorg/tools/terminal/shell_command.pysrc/synthorg/tools/web/web_search.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/network_validator.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/config/schema.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/database/config.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/database/schema_inspect.pysrc/synthorg/tools/database/sql_query.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/factory.pysrc/synthorg/tools/terminal/shell_command.pysrc/synthorg/tools/web/web_search.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/network_validator.py
🧠 Learnings (47)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Dependency review: license allow-list (permissive + weak-copyleft), per-package GPL exemptions for dev-only tool deps, PR comment summaries.
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Set `RetryConfig` and `RateLimiterConfig` per-provider in `ProviderConfig`.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to src/synthorg/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/factory.py
📚 Learning: 2026-03-31T21:07:37.469Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.469Z
Learning: Applies to src/synthorg/providers/**/*.py : Set `RetryConfig` and `RateLimiterConfig` per-provider in `ProviderConfig`
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/invoker.pytests/unit/tools/test_sub_constraint_enforcer.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-04-02T09:04:54.709Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:04:54.709Z
Learning: Applies to tests/**/*.py : Use pytest markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to tests/**/*.py : Markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow.
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `allow_inf_nan=False` in all `ConfigDict` declarations to reject `NaN`/`Inf` in numeric fields at validation time
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to **/*.py : Config vs runtime state: use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).
Applied to files:
src/synthorg/tools/database/schema_inspect.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.
Applied to files:
src/synthorg/tools/database/schema_inspect.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions
Applied to files:
src/synthorg/tools/database/schema_inspect.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/tools/database/schema_inspect.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).
Applied to files:
tests/unit/tools/test_network_validator.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)
Applied to files:
tests/unit/tools/test_network_validator.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files).
Applied to files:
tests/unit/tools/test_network_validator.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
src/synthorg/tools/web/__init__.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Version locations: pyproject.toml ([tool.commitizen].version), src/synthorg/__init__.py (__version__).
Applied to files:
src/synthorg/tools/web/__init__.py
📚 Learning: 2026-04-02T07:18:02.381Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T07:18:02.381Z
Learning: Applies to {pyproject.toml,src/synthorg/__init__.py} : Update version in `pyproject.toml` (`[tool.commitizen].version`) and `src/synthorg/__init__.py` (`__version__`)
Applied to files:
src/synthorg/tools/web/__init__.py
📚 Learning: 2026-04-01T06:12:03.047Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T06:12:03.047Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Line length: 88 characters (enforced by ruff)
Applied to files:
src/synthorg/tools/web/__init__.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to docker/Dockerfile.sandbox : Docker sandbox: `synthorg-sandbox` — Python 3.14 + Node.js + git, non-root (UID 10001), agent code execution sandbox
Applied to files:
src/synthorg/tools/terminal/shell_command.py
🔇 Additional comments (10)
tests/unit/tools/test_sub_constraint_enforcer.py (1)
288-301: Good precedence coverage.This locks in the short-circuit order, so future constraint additions cannot silently change which policy wins.
src/synthorg/tools/terminal/config.py (2)
27-45: Blocklist uses substring matching with known bypass potential.The default blocklist patterns are vulnerable to minor variations as previously noted. For example,
"rm -rf /"won't matchrm -rf /(extra whitespace) orrm -r -f /(separated flags). The current implementation inbase_terminal_tool.pyuses simple substring matching.However, the docstring clearly documents the matching behavior, and the sandbox backend provides the primary security boundary. This blocklist serves as defense-in-depth rather than the sole protection layer.
8-56: Well-structured frozen config model with sensible defaults.The
TerminalConfigmodel follows project conventions:
frozen=Trueandallow_inf_nan=Falsein ConfigDict- Appropriate Field constraints (
gt=0,le=600.0)- Clear attribute documentation
- Reasonable defaults (1 MiB output limit, 30s timeout with 10-minute max)
src/synthorg/tools/web/web_search.py (1)
102-124: Constructor properly usescopy.deepcopyfor schema immutability.The
parameters_schema=copy.deepcopy(_PARAMETERS_SCHEMA)at line 120 correctly addresses the past review concern about shallow copying. The nested dicts are now properly isolated.tests/integration/tools/test_factory_integration.py (1)
15-15: Tool count correctly updated for new default tools.The increase from 11 to 14 accounts for the three new tools added by default:
http_request,html_parser, andshell_command. This aligns with the factory behavior whereweb_searchrequires an explicit provider and database tools requireDatabaseConfig.tests/unit/tools/test_factory.py (1)
19-34: Expected tool names correctly updated with alphabetical ordering.The new tools (
html_parser,http_request,shell_command) are correctly positioned in the alphabetically sorted tuple, and the docstring at line 45 accurately reflects the new count of 14.src/synthorg/tools/web/__init__.py (1)
1-21: Clean package re-exports with complete__all__definition.The package init properly exposes the web tools public API. The
__all__list matches all imports, providing a clear contract for consumers.src/synthorg/config/schema.py (1)
714-725: New tool configuration fields correctly added to RootConfig.The three new optional fields follow established patterns in the model. The distinction in default behavior is correctly documented:
webandterminal:None= use default configuration (tools enabled)database:None= no database tools (explicit opt-in required)This aligns with the factory behavior and security posture (database access requires explicit configuration).
tests/unit/tools/database/test_sql_query.py (1)
42-158: Comprehensive test coverage for SqlQueryTool.The test suite covers:
- Statement classification including edge cases (comments, whitespace)
- Read-only enforcement with parametrized write operations
- Write mode behavior
- Query execution including empty queries, no results, parameterized queries, and invalid SQL
Good use of
@pytest.mark.parametrizefor the write-blocked tests.tests/unit/tools/web/test_html_parser.py (1)
1-119: Test suite structure and fixture are correct.The test file is well-organized with comprehensive coverage across all three extraction modes (
text,links,metadata), edge cases, and default behavior. All tests are properly marked with@pytest.mark.unit, and thehtml_toolfixture is correctly defined intests/unit/tools/web/conftest.py.
| def _build_database_tools( | ||
| *, | ||
| config: DatabaseConfig, | ||
| ) -> tuple[BaseTool, ...]: | ||
| """Instantiate the built-in database tools for each configured connection.""" | ||
| from synthorg.tools.database import SchemaInspectTool, SqlQueryTool # noqa: PLC0415 | ||
|
|
||
| if not config.connections: | ||
| return () | ||
|
|
||
| # Use the default connection, or first available | ||
| conn_name = config.default_connection | ||
| conn_config: DatabaseConnectionConfig | None = config.connections.get(conn_name) | ||
| if conn_config is None and config.connections: | ||
| conn_name = next(iter(config.connections)) | ||
| conn_config = config.connections[conn_name] | ||
| if conn_config is None: | ||
| return () | ||
|
|
||
| return ( | ||
| SqlQueryTool(config=conn_config), | ||
| SchemaInspectTool(config=conn_config), | ||
| ) |
There was a problem hiding this comment.
Non-default database connections are silently ignored.
DatabaseConfig.connections is a mapping, but the factory only ever builds one SqlQueryTool/SchemaInspectTool pair for default_connection (or the first entry). Any additional configured connection is unreachable from the tool surface. Either emit one tool per connection or add a connection argument to the database tools.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/factory.py` around lines 120 - 142, The factory
_build_database_tools currently builds tools only for
DatabaseConfig.default_connection (or the first entry), ignoring other entries
in DatabaseConfig.connections; update it to iterate over all config.connections
items and instantiate a SqlQueryTool and SchemaInspectTool for each connection
(using each connection's DatabaseConnectionConfig) so every configured
connection is exposed as tools; ensure returned tuple includes one pair per
connection (optionally include the connection name in tool construction or
wrapping if the tools support/need it) rather than only the default.
| if working_dir: | ||
| cwd = Path(working_dir) | ||
| if cwd.is_absolute(): | ||
| return ToolExecutionResult( | ||
| content=( | ||
| f"Absolute working_directory not allowed: {working_dir!r}. " | ||
| "Use a path relative to the workspace." | ||
| ), | ||
| is_error=True, | ||
| ) | ||
| else: | ||
| cwd = None | ||
|
|
||
| try: | ||
| result = await self._sandbox.execute( | ||
| command="bash", | ||
| args=("-c", command), | ||
| cwd=cwd, | ||
| timeout=timeout, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -t f . src/synthorg/tools/sandbox src/synthorg/tools/terminal \
| xargs -r rg -n -C3 '\bcwd\b|\bworkspace\b|\bresolve\(\)|\bis_relative_to\b|\bdef execute\b'Repository: Aureliolo/synthorg
Length of output: 32301
🏁 Script executed:
# Find where ShellCommandTool gets initialized and what context it has access to
rg -n "class ShellCommandTool" src/synthorg/tools/terminal/shell_command.pyRepository: Aureliolo/synthorg
Length of output: 106
🏁 Script executed:
# Check SubprocessSandboxConfig and DockerSandboxConfig definitions
fd -t f . src/synthorg/tools/sandbox -name "*.py" | xargs rg -n "class.*SandboxConfig|workspace_only.*=" | head -30Repository: Aureliolo/synthorg
Length of output: 1334
🏁 Script executed:
# Check if shell_command.py has any workspace context or sandbox config access
rg -n "workspace|_sandbox" src/synthorg/tools/terminal/shell_command.py | head -20Repository: Aureliolo/synthorg
Length of output: 618
🏁 Script executed:
# Check if DockerSandboxConfig also has workspace_only
rg -A5 "class DockerSandboxConfig" src/synthorg/tools/sandbox/docker_config.pyRepository: Aureliolo/synthorg
Length of output: 274
🏁 Script executed:
# Check the BaseTerminalTool or ShellCommandTool constructor to see what context is available
rg -B5 -A15 "class ShellCommandTool\|def __init__" src/synthorg/tools/terminal/shell_command.py | head -50Repository: Aureliolo/synthorg
Length of output: 44
🏁 Script executed:
# Check if sandbox has workspace property exposed
rg -n "workspace" src/synthorg/tools/sandbox/protocol.pyRepository: Aureliolo/synthorg
Length of output: 282
🏁 Script executed:
# Check the SandboxBackend protocol or actual implementations for workspace property
rg -B2 -A2 "def workspace|property.*workspace" src/synthorg/tools/sandbox/Repository: Aureliolo/synthorg
Length of output: 772
🏁 Script executed:
# Check if ShellCommandTool can access sandbox.workspace
cat -n src/synthorg/tools/terminal/shell_command.py | sed -n '140,185p'Repository: Aureliolo/synthorg
Length of output: 1915
🏁 Script executed:
# Verify the actual validation happening in subprocess_sandbox.py _validate_cwd
cat -n src/synthorg/tools/sandbox/subprocess_sandbox.py | sed -n '321,342p'Repository: Aureliolo/synthorg
Length of output: 994
🏁 Script executed:
# Verify what happens when we pass a relative path with .. to the subprocess sandbox
# Check if resolve() properly handles relative paths
python3 << 'EOF'
from pathlib import Path
import tempfile
# Simulate workspace and user input
with tempfile.TemporaryDirectory() as tmpdir:
workspace = Path(tmpdir)
# Test case 1: Simple relative path (should be allowed)
user_input = "subdir"
cwd = Path(user_input)
try:
rel = cwd.resolve().relative_to(workspace.resolve())
print(f"✓ '{user_input}' resolves to: {rel} (ALLOWED)")
except ValueError as e:
print(f"✗ '{user_input}' rejected: {e}")
# Test case 2: Path with .. that escapes (should be rejected)
user_input = "../../../../etc"
cwd = Path(user_input)
try:
rel = cwd.resolve().relative_to(workspace.resolve())
print(f"✓ '{user_input}' resolves to: {rel} (ALLOWED)")
except ValueError as e:
print(f"✗ '{user_input}' rejected: CORRECTLY CAUGHT")
# Test case 3: Check what resolve() returns for relative paths without workspace context
user_input = "../../.."
cwd = Path(user_input)
print(f"Relative path '{user_input}' resolves to absolute: {cwd.resolve()}")
EOFRepository: Aureliolo/synthorg
Length of output: 301
🏁 Script executed:
# Check if DockerSandboxConfig inherits workspace_only or has its own default
cat -n src/synthorg/tools/sandbox/docker_config.py | head -50Repository: Aureliolo/synthorg
Length of output: 2325
🏁 Script executed:
# Check if DockerSandboxConfig has workspace_only setting
rg -n "workspace_only" src/synthorg/tools/sandbox/docker_config.pyRepository: Aureliolo/synthorg
Length of output: 44
🏁 Script executed:
# Look for test cases that verify .. escapes are blocked
rg -B3 -A3 "\.\./\.\." tests/unit/tools/ tests/integration/tools/Repository: Aureliolo/synthorg
Length of output: 6681
🏁 Script executed:
# Verify the actual flow: does shell_command pass unresolved Path or should it resolve first?
# Check how sandbox.execute is supposed to work with relative paths
rg -B5 -A10 "def execute" src/synthorg/tools/sandbox/protocol.pyRepository: Aureliolo/synthorg
Length of output: 575
🏁 Script executed:
# Check how GitStatusTool and other file system tools validate paths at the boundary
rg -B5 -A15 "escapes workspace" src/synthorg/tools/Repository: Aureliolo/synthorg
Length of output: 1851
🏁 Script executed:
# Check if there are any tests for shell_command.py path traversal
rg -n "ShellCommandTool\|test_shell_command\|working_dir" tests/unit/tools/terminal/ tests/integration/tools/terminal/Repository: Aureliolo/synthorg
Length of output: 135
🏁 Script executed:
# Check PathValidator - how do other tools do validation?
rg -B10 -A10 "class PathValidator" src/synthorg/tools/Repository: Aureliolo/synthorg
Length of output: 1592
🏁 Script executed:
# Check the full PathValidator.validate method to see the exact pattern
cat -n src/synthorg/tools/file_system/_path_validator.py | sed -n '1,80p'Repository: Aureliolo/synthorg
Length of output: 3311
🏁 Script executed:
# Verify the exact validation logic used
rg -B3 -A8 "def validate\(" src/synthorg/tools/file_system/_path_validator.pyRepository: Aureliolo/synthorg
Length of output: 444
🏁 Script executed:
# Check if workspace is accessible in ShellCommandTool
rg -B5 -A5 "def __init__|self._sandbox|self\._workspace" src/synthorg/tools/terminal/shell_command.pyRepository: Aureliolo/synthorg
Length of output: 1392
Validate working_directory against workspace boundary at the tool boundary.
This only rejects absolute paths. Relative paths like ../../.. bypass the check and are forwarded to the sandbox, making containment dependent on backend-specific configuration that may not be enforced consistently (e.g., DockerSandbox has no workspace_only flag).
Add path validation at this system boundary following the pattern used by file system tools: check that the resolved path remains within the workspace root using is_relative_to(), not just rejection of absolute paths.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/terminal/shell_command.py` around lines 162 - 180, The
code currently only rejects absolute working_directory values but lets relative
paths like "../../" through; update the validation where working_dir is
converted to cwd (the Path(working_dir) block) to resolve the path against the
workspace root and ensure it stays inside the workspace before calling
self._sandbox.execute. Concretely, compute resolved = (workspace_root /
working_dir).resolve() and compare to workspace_root.resolve() using
resolved.is_relative_to(workspace_root.resolve()) (or equivalent containment
check) and return a ToolExecutionResult with is_error=True and a clear message
if the resolved path is outside the workspace; keep the existing error for
absolute paths, and then pass the validated cwd into self._sandbox.execute.
| try: | ||
| results = await self._provider.search(query, max_results) | ||
| except Exception as exc: | ||
| logger.warning(WEB_SEARCH_FAILED, query=query, error=str(exc)) | ||
| return ToolExecutionResult( | ||
| content=f"Web search failed: {exc}", | ||
| is_error=True, | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider narrowing the exception type for provider errors.
The bare except Exception catch is broad. If the WebSearchProvider protocol implementations can raise specific exceptions, consider catching those explicitly or at minimum re-raising system errors.
♻️ Suggested refinement
try:
results = await self._provider.search(query, max_results)
- except Exception as exc:
+ except builtins.MemoryError, RecursionError:
+ raise
+ except Exception as exc:
logger.warning(WEB_SEARCH_FAILED, query=query, error=str(exc))
return ToolExecutionResult(
content=f"Web search failed: {exc}",
is_error=True,
)Per project conventions, system errors like MemoryError and RecursionError should propagate rather than being swallowed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/web/web_search.py` around lines 144 - 151, The current
broad "except Exception" around the call to self._provider.search in the web
search routine swallows system errors; change it to explicitly handle
provider-level errors and re-raise system-level exceptions. Replace the bare
except with either an except catching the provider-specific exception type(s)
raised by WebSearchProvider.search (e.g., ProviderError) or, if unknown, keep
except Exception as exc but immediately re-raise for critical system exceptions
(MemoryError, RecursionError, KeyboardInterrupt, SystemExit) before logging and
returning the ToolExecutionResult; reference self._provider.search,
WEB_SEARCH_FAILED, logger.warning and ToolExecutionResult when making the
change.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/synthorg/tools/database/config.py (2)
49-52:⚠️ Potential issue | 🟡 MinorUse
NotBlankStrfor connection-name keys.
connectionscurrently accepts blank/whitespace keys (dict[str, ...]). Line 49 should constrain keys toNotBlankStrso invalid identifiers are rejected during config validation.Suggested fix
- connections: dict[str, DatabaseConnectionConfig] = Field( + connections: dict[NotBlankStr, DatabaseConnectionConfig] = Field( default_factory=dict, description="Named database connections", )As per coding guidelines "Use NotBlankStr (from core.types) for all identifier/name fields—including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants—instead of manual whitespace validators."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/database/config.py` around lines 49 - 52, Change the type for the connections mapping to use NotBlankStr for keys so blank/whitespace connection names are rejected: replace dict[str, DatabaseConnectionConfig] with dict[NotBlankStr, DatabaseConnectionConfig] on the connections Field (and add/import NotBlankStr from core.types at top of the module) to ensure validation uses the canonical identifier type.
58-70:⚠️ Potential issue | 🟠 MajorFail fast when
default_connectionis not configured.Line 65 only logs a warning, but downstream tool construction can still hard-fail with
KeyErrorwhen indexingconfig.connections[selected_connection](seesrc/synthorg/tools/factory.py:120-140). This should be rejected at model validation time instead of deferred.Suggested fix
`@model_validator`(mode="after") def _validate_default_connection(self) -> Self: - """Warn when default_connection is missing from connections. - - Does not raise -- the tool factory falls back to the first - available connection when the default is not found. - """ + """Validate that default_connection exists in connections.""" if self.connections and self.default_connection not in self.connections: - logger.warning( - "database_config_default_missing", - default_connection=self.default_connection, - available=sorted(self.connections), - ) + logger.error( + "database_config_default_missing", + default_connection=self.default_connection, + available=sorted(self.connections), + ) + raise ValueError( + "default_connection must match one of the configured connections" + ) return selfAs per coding guidelines "Validate at system boundaries (user input, external APIs, config files)".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/database/config.py` around lines 58 - 70, The current _validate_default_connection (decorated with `@model_validator`(mode="after")) only logs a warning when default_connection is missing from connections, but this allows downstream KeyError; change the validator to reject the model by raising a validation error (e.g., ValueError or pydantic.ValidationError) when self.connections is set and self.default_connection is not present, including the invalid default_connection and available connections in the error message so model construction fails fast; update any tests/consumers expecting a warning accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/database/config.py`:
- Around line 66-67: Replace the literal event name
"database_config_default_missing" with the domain event constant (e.g.,
DATABASE_CONFIG_DEFAULT_MISSING) from synthorg.observability.events.database:
import the constant at top of src/synthorg/tools/database/config.py and pass
that constant to logger.warning in place of the raw string; ensure the import
uses the exact constant name (DATABASE_CONFIG_DEFAULT_MISSING) and update the
logger call in the same function where logger.warning currently uses the
literal.
---
Duplicate comments:
In `@src/synthorg/tools/database/config.py`:
- Around line 49-52: Change the type for the connections mapping to use
NotBlankStr for keys so blank/whitespace connection names are rejected: replace
dict[str, DatabaseConnectionConfig] with dict[NotBlankStr,
DatabaseConnectionConfig] on the connections Field (and add/import NotBlankStr
from core.types at top of the module) to ensure validation uses the canonical
identifier type.
- Around line 58-70: The current _validate_default_connection (decorated with
`@model_validator`(mode="after")) only logs a warning when default_connection is
missing from connections, but this allows downstream KeyError; change the
validator to reject the model by raising a validation error (e.g., ValueError or
pydantic.ValidationError) when self.connections is set and
self.default_connection is not present, including the invalid default_connection
and available connections in the error message so model construction fails fast;
update any tests/consumers expecting a warning accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 62e87347-5d5a-42a1-9010-3a73d8d36d68
📒 Files selected for processing (1)
src/synthorg/tools/database/config.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax:except A, B:(no parentheses)—ruff enforces this on Python 3.14.
Type hints: all public functions required, mypy strict mode enforced.
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules).
Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.
Use@computed_fieldfor derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).
Use NotBlankStr (from core.types) for all identifier/name fields—including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants—instead of manual whitespace validators.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Line length: 88 characters (ruff enforced).
Functions: < 50 lines, files < 800 lines.
Handle errors explicitly, never silently swallow.
Validate at system boundaries (user input, external APIs, config files).
Variable name: always logger (not _logger, not log).
All error paths must log at WARNING or ERROR with context before raising.
All state transitions must log at INFO.
DEBUG for object creation, internal flow, entry/exit of key functions.
Pure data models, enums, and re-exports do NOT need logging.
Files:
src/synthorg/tools/database/config.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, and observability/http_handler.py may use stdlib logging and print(..., file=sys.stderr).
Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events. import EVENT_CONSTANT
Structured kwargs: always logger.info(EVENT, key=value)—never logger.info("msg %s", val)
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.
RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError.
Non-retryable errors raise immediately without retry.
RetryExhaustedError signals that all retries failed—the engine layer catches this to trigger fallback chains.
Rate limiter respects RateLimitError.retry_after from providers—automatically pauses future requests.
Files:
src/synthorg/tools/database/config.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/tools/database/config.py
🧠 Learnings (18)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `allow_inf_nan=False` in all `ConfigDict` declarations to reject `NaN`/`Inf` in numeric fields at validation time
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : Use NotBlankStr (from core.types) for all identifier/name fields—including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants—instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/synthorg/tools/invoker.py (1)
210-224:⚠️ Potential issue | 🟠 MajorApproval-required sub-constraints still behave like hard denies.
The
requires_approval=Truebranch returns an errorToolResultwithout appending to_pending_escalations. Compare with_check_security()(lines 298-312), which appends anEscalationInfobefore returning when the verdict isESCALATE. Without this tracking, sub-constraint escalations cannot be parked and resumed through the approval flow.To align with existing security escalation behavior, append to
_pending_escalationswhenviolation.requires_approvalisTrue:Proposed fix
if violation.requires_approval: + from synthorg.engine.approval_gate_models import ( # noqa: PLC0415 + EscalationInfo, + ) + + # Generate a unique approval_id for sub-constraint escalations + approval_id = f"sub-constraint-{tool_call.id}" + self._pending_escalations.append( + EscalationInfo( + approval_id=approval_id, + tool_call_id=tool_call.id, + tool_name=tool_call.name, + action_type=tool.action_type, + risk_level=ApprovalRiskLevel.HIGH, + reason=violation.reason, + ), + ) logger.warning( TOOL_SECURITY_ESCALATED, tool_call_id=tool_call.id, tool_name=tool_call.name, reason=violation.reason, + approval_id=approval_id, ) return ToolResult( tool_call_id=tool_call.id, content=( f"Sub-constraint escalation: {violation.reason}. " "Human approval required." ), is_error=True, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/invoker.py` around lines 210 - 224, The branch handling violation.requires_approval currently logs and returns a ToolResult error but fails to record the escalation for later approval; update the block in invoker.py so that before returning you append an EscalationInfo to _pending_escalations (mirroring the behavior in _check_security), constructing the EscalationInfo with the tool_call (or tool_call.id/name), violation details/reason and any needed metadata, then return the error ToolResult as before; ensure you reference TOOL_SECURITY_ESCALATED/logger.warning and use the same EscalationInfo shape used elsewhere so this sub-constraint escalation can be parked and resumed via the approval flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/web/http_request.py`:
- Around line 62-67: The schema for the "timeout" property currently allows 0
which in httpx means "no timeout"; update the "timeout" schema (the "timeout"
property object) to either set "minimum" to a positive value (e.g., 1) or add
"exclusiveMinimum": 0 to forbid zero, and/or adjust the "description" to
explicitly state that 0 means "no timeout" if you intend to allow it; modify the
"timeout" object accordingly so callers aren't confused by 0's special meaning.
- Around line 146-153: Duplicate logging occurs because _validate_url(...)
already emits WEB_SSRF_BLOCKED; remove the extra
logger.warning(WEB_SSRF_BLOCKED, ...) call in the SSRF validation block inside
the function that returns the ToolExecutionResult so you only return the error
ToolExecutionResult (content=f"URL blocked: {validation}", is_error=True) and
rely on _validate_url to log the block; keep the rest of the block intact and do
not alter _validate_url itself.
---
Duplicate comments:
In `@src/synthorg/tools/invoker.py`:
- Around line 210-224: The branch handling violation.requires_approval currently
logs and returns a ToolResult error but fails to record the escalation for later
approval; update the block in invoker.py so that before returning you append an
EscalationInfo to _pending_escalations (mirroring the behavior in
_check_security), constructing the EscalationInfo with the tool_call (or
tool_call.id/name), violation details/reason and any needed metadata, then
return the error ToolResult as before; ensure you reference
TOOL_SECURITY_ESCALATED/logger.warning and use the same EscalationInfo shape
used elsewhere so this sub-constraint escalation can be parked and resumed via
the approval flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: db5568da-e808-4b86-ae21-8d1303bcd5a8
📒 Files selected for processing (4)
src/synthorg/tools/factory.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/web/http_request.pytests/unit/tools/database/test_sql_query.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Web
- GitHub Check: Build Backend
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations—Python 3.14 has PEP 649 native lazy annotations.
Use PEP 758 except syntax:except A, B:(no parentheses)—ruff enforces this on Python 3.14.
Type hints: all public functions required, mypy strict mode enforced.
Docstrings: Google style, required on public classes/functions (enforced by ruff D rules).
Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use allow_inf_nan=False in all ConfigDict declarations to reject NaN/Inf in numeric fields at validation time.
Use@computed_fieldfor derived values instead of storing + validating redundant fields (e.g. TokenUsage.total_tokens).
Use NotBlankStr (from core.types) for all identifier/name fields—including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants—instead of manual whitespace validators.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Line length: 88 characters (ruff enforced).
Functions: < 50 lines, files < 800 lines.
Handle errors explicitly, never silently swallow.
Validate at system boundaries (user input, external APIs, config files).
Variable name: always logger (not _logger, not log).
All error paths must log at WARNING or ERROR with context before raising.
All state transitions must log at INFO.
DEBUG for object creation, internal flow, entry/exit of key functions.
Pure data models, enums, and re-exports do NOT need logging.
Files:
tests/unit/tools/database/test_sql_query.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/factory.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Markers:@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slow.
Async: asyncio_mode = "auto"—no manual@pytest.mark.asyncioneeded.
Timeout: 30 seconds per test (global in pyproject.toml—do not add per-file pytest.mark.timeout(30) markers; non-default overrides like timeout(60) are allowed).
Parametrize: Prefer@pytest.mark.parametrizefor testing similar cases.
Tests must use test-provider, test-small-001, etc. (not real vendor names).
Property-based testing: Python uses Hypothesis (@given+@settings).
Hypothesis profiles configured in tests/conftest.py: ci (deterministic, max_examples=10 + derandomize=True), dev (1000 examples), fuzz (10,000 examples, no deadline), extreme (500,000 examples, no deadline). Controlled via HYPOTHESIS_PROFILE env var.
Never skip, dismiss, or ignore flaky tests—always fix them fully and fundamentally. For timing-sensitive tests, mock time.monotonic() and asyncio.sleep() to make them deterministic. For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number).
Files:
tests/unit/tools/database/test_sql_query.py
⚙️ CodeRabbit configuration file
Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare
@settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which@given() honors automatically.
Files:
tests/unit/tools/database/test_sql_query.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic MUST have: from synthorg.observability import get_logger then logger = get_logger(name)
Never use import logging / logging.getLogger() / print() in application code. Exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, and observability/http_handler.py may use stdlib logging and print(..., file=sys.stderr).
Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api). Import directly: from synthorg.observability.events. import EVENT_CONSTANT
Structured kwargs: always logger.info(EVENT, key=value)—never logger.info("msg %s", val)
All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.
RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError.
Non-retryable errors raise immediately without retry.
RetryExhaustedError signals that all retries failed—the engine layer catches this to trigger fallback chains.
Rate limiter respects RateLimitError.retry_after from providers—automatically pauses future requests.
Files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/factory.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/factory.py
🧠 Learnings (18)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-04-02T09:04:54.709Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T09:04:54.709Z
Learning: Applies to tests/**/*.py : Use pytest markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to tests/**/*.py : Markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow.
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Prefer `pytest.mark.parametrize` for testing similar cases.
Applied to files:
tests/unit/tools/database/test_sql_query.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-06T20:42:41.219Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T20:42:41.219Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/tools/factory.py
🔇 Additional comments (18)
tests/unit/tools/database/test_sql_query.py (5)
1-8: LGTM!Imports are appropriate. Testing the private
_classify_statementhelper directly is a reasonable approach for unit testing internal classification logic.
12-35: LGTM!Comprehensive parameterized test covering all SQL statement types, edge cases with comments, whitespace handling, and empty inputs. Good use of
@pytest.mark.parametrizeas recommended by guidelines.
41-78: LGTM!Tests correctly verify application-level read-only enforcement. The
test_select_allowedproperly sets up the database before querying, andtest_write_blocked_in_read_onlycovers all write statement types with appropriate assertion on the "blocked" message.
84-100: LGTM!Test correctly verifies that write operations succeed when
read_only=False. The assertion for "affected" in the content aligns with expected behavior for successful INSERT statements.
106-157: LGTM!Execution tests comprehensively cover:
- Empty query handling (returns error before DB connection)
- Empty result sets from valid queries
- Parameterized queries (SQL injection prevention)
- Invalid SQL error handling
Assertions correctly align with the implementation's error messages per the source code.
src/synthorg/tools/invoker.py (2)
424-427: LGTM!The integration correctly invokes sub-constraint checking after the permission check and before parameter validation, returning early on any violation. This placement ensures sub-constraints are enforced before any tool execution proceeds.
187-201: LGTM — deepcopy guard applied correctly.The method now uses
_safe_deepcopy_args()to safely copytool_call.arguments, handling non-copyable values gracefully and emittingTOOL_INVOKE_DEEPCOPY_ERRORon failure. This addresses the earlier concern about unguardedcopy.deepcopy()calls.src/synthorg/tools/factory.py (7)
120-142: Non-default database connections are still silently ignored.This function only builds tools for a single connection (the default or first available). Additional entries in
DatabaseConfig.connectionsproduce no tools and no warning. If this is intentional (e.g., to avoid tool name collisions), consider documenting it in the docstring or emitting a debug log noting skipped connections.
38-52: LGTM! New imports for web, database, and terminal tool support.TYPE_CHECKING imports properly guard runtime-only type hints.
92-117: LGTM! Web tools builder follows established patterns.Conditionally including
WebSearchToolonly when a provider is available is the right approach.
145-153: LGTM! Terminal tools builder is clean and simple.
156-228: LGTM! Extended function properly integrates new tool categories.The conditional database tool inclusion and the final sorting maintain deterministic behavior.
270-284: LGTM! Sandbox backend resolution refactored correctly.The
is not Nonecheck (line 273) properly handles explicit empty mappings, andresolved_backendsis computed once and reused for both VERSION_CONTROL and TERMINAL categories, addressing the previous review concerns.
286-302: LGTM! Graceful degradation for missing terminal sandbox.Catching
KeyErrorand logging a warning while allowing terminal tools to operate without a sandbox is appropriate for resilience.src/synthorg/tools/web/http_request.py (4)
1-29: LGTM! Module setup follows project conventions.Imports, logger setup, and event constant usage align with coding guidelines.
74-115: LGTM! Class initialization is well-documented and follows conventions.The constructor properly copies
_PARAMETERS_SCHEMAto avoid mutation, and the docstring includes a usage example.
222-228: Byte-based truncation is correctly implemented.The code now properly truncates by bytes using
response.contentand decodes witherrors="replace"to handle truncation at UTF-8 boundaries. This addresses the previous review concern about character vs byte counting.Note:
response.contentstill buffers the entire response before truncation. For very large responses, consider usingresponse.aiter_bytes()to stream and truncate without full buffering. However, with a 1MB default limit, this is likely acceptable.
254-296: LGTM! IPv6 handling and immutability concerns addressed.The method now correctly brackets IPv6 literals in the netloc (lines 281-288) and returns a copied headers dict (line 290) rather than mutating the caller's input. This addresses both concerns from the previous review.
| "timeout": { | ||
| "type": "number", | ||
| "description": "Request timeout in seconds", | ||
| "minimum": 0, | ||
| "maximum": 300, | ||
| }, |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider setting minimum to a positive value or exclusiveMinimum: 0.
A timeout of exactly 0 in httpx means "no timeout" rather than "instant timeout," which may confuse callers expecting 0 to mean immediate failure. If intentional, add a note in the description.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/web/http_request.py` around lines 62 - 67, The schema for
the "timeout" property currently allows 0 which in httpx means "no timeout";
update the "timeout" schema (the "timeout" property object) to either set
"minimum" to a positive value (e.g., 1) or add "exclusiveMinimum": 0 to forbid
zero, and/or adjust the "description" to explicitly state that 0 means "no
timeout" if you intend to allow it; modify the "timeout" object accordingly so
callers aren't confused by 0's special meaning.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/synthorg/tools/database/config.py (1)
66-69: 🛠️ Refactor suggestion | 🟠 MajorReplace raw event name with the database event constant.
Line 67 still logs a literal (
"database_config_default_missing"). Please import and useDATABASE_CONFIG_DEFAULT_MISSINGfromsynthorg.observability.events.databasefor consistency and centralized event governance.Proposed diff
from synthorg.core.types import NotBlankStr # noqa: TC001 from synthorg.observability import get_logger +from synthorg.observability.events.database import DATABASE_CONFIG_DEFAULT_MISSING @@ if self.connections and self.default_connection not in self.connections: logger.warning( - "database_config_default_missing", + DATABASE_CONFIG_DEFAULT_MISSING, default_connection=self.default_connection, available=sorted(self.connections), )As per coding guidelines "Always use event name constants from domain-specific modules under
synthorg.observability.events."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/database/config.py` around lines 66 - 69, Replace the literal event name string with the shared constant: add an import for DATABASE_CONFIG_DEFAULT_MISSING from synthorg.observability.events.database and use that constant in the logger.warning call (the call referencing self.default_connection and sorted(self.connections)); ensure the existing logger.warning parameters remain unchanged and only the event name argument is replaced with DATABASE_CONFIG_DEFAULT_MISSING.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/invoker.py`:
- Around line 440-443: Update the invoke() docstring step list to reflect the
new ordering where _invoke_single() performs sub-constraint checks before
parameter validation: mention the "sub-constraint" stage immediately prior to
"parameter validation" in the invoke() flow description and ensure any
numbered/ordered steps and examples mirror this new sequence so invoke() and
_invoke_single() stay in sync for runbooks/debugging.
---
Duplicate comments:
In `@src/synthorg/tools/database/config.py`:
- Around line 66-69: Replace the literal event name string with the shared
constant: add an import for DATABASE_CONFIG_DEFAULT_MISSING from
synthorg.observability.events.database and use that constant in the
logger.warning call (the call referencing self.default_connection and
sorted(self.connections)); ensure the existing logger.warning parameters remain
unchanged and only the event name argument is replaced with
DATABASE_CONFIG_DEFAULT_MISSING.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 48d44fff-3462-48f0-952c-7470987eea7b
📒 Files selected for processing (2)
src/synthorg/tools/database/config.pysrc/synthorg/tools/invoker.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations- Python 3.14 has PEP 649 native lazy annotations
Useexcept A, B:syntax (no parentheses) for exception handling in Python 3.14 per PEP 758 - ruff enforces this
Use 88-character line length (enforced by ruff) in Python code
Files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/database/config.py
src/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.py: Type hints required on all public functions; use mypy strict mode
Docstrings required on all public classes and functions using Google style
Create new objects, never mutate existing ones; for non-Pydantic internal collections, usecopy.deepcopy()at construction andMappingProxyTypewrapping for read-only enforcement
Fordict/listfields in frozen Pydantic models, usecopy.deepcopy()at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 withBaseModel,model_validator,computed_field, andConfigDict
Useallow_inf_nan=Falsein allConfigDictdeclarations to rejectNaN/Infin numeric fields at validation time
Use@computed_fieldfor derived values instead of storing and validating redundant fields (e.g.,TokenUsage.total_tokens)
UseNotBlankStr(fromcore.types) for all identifier/name fields - including optional and tuple variants - instead of manual whitespace validators
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code (e.g., multiple tool invocations, parallel agent calls); prefer structured concurrency over barecreate_task
Functions should be less than 50 lines; files should be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, and config files
Every module with business logic MUST importfrom synthorg.observability import get_loggerand uselogger = get_logger(__name__)
Never useimport logging/logging.getLogger()/print()in application code; exceptions allowed only inobservability/setup.py,observability/sinks.py,observability/syslog_handler.py, andobservability/http_handler.py
Use variable namelogger(not...
Files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/database/config.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/tools/invoker.pysrc/synthorg/tools/database/config.py
🧠 Learnings (38)
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-04-07T07:27:16.474Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T07:27:16.474Z
Learning: Applies to src/**/*.py : For `dict`/`list` fields in frozen Pydantic models, use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/api/**/*.py : Authentication uses JWT + API key. Approval gate integration for high-risk operations.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.
Applied to files:
src/synthorg/tools/invoker.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-07T07:27:16.474Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T07:27:16.474Z
Learning: Applies to src/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state that evolves; never mix static config fields with mutable runtime fields in one model
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `allow_inf_nan=False` in all `ConfigDict` declarations to reject `NaN`/`Inf` in numeric fields at validation time
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-01T09:37:49.451Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:37:49.451Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models with `model_copy(update=...)` for runtime state that evolves
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `NotBlankStr` from `core.types` for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-07T07:27:16.474Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T07:27:16.474Z
Learning: Applies to src/**/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields - including optional and tuple variants - instead of manual whitespace validators
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to **/*.py : Models: Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict). Use computed_field for derived values instead of storing + validating redundant fields. Use NotBlankStr (from core.types) for all identifier/name fields — including optional (NotBlankStr | None) and tuple (tuple[NotBlankStr, ...]) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to **/*.py : Use `NotBlankStr` (from `core.types`) for all identifier/name fields, including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants, instead of manual whitespace validators
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-31T16:09:24.320Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T16:09:24.320Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from `synthorg.observability.events.<domain>` modules (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly and use in structured logging
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-16T06:24:56.341Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T06:24:56.341Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from the domain-specific module under `synthorg.observability.events` in logging calls
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to src/synthorg/**/*.py : Use event constants from `synthorg.observability.events.<domain>` (e.g., `API_REQUEST_STARTED` from `events.api`); import directly and log with structured kwargs: `logger.info(EVENT, key=value)`, never interpolated strings
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`); import directly rather than using string literals
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-04-07T07:27:16.474Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T07:27:16.474Z
Learning: Applies to src/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`)
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-18T21:23:23.586Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-18T21:23:23.586Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from the domain-specific module under synthorg.observability.events (e.g., API_REQUEST_STARTED from events.api, TOOL_INVOKE_START from events.tool). Import directly from synthorg.observability.events.<domain>.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `API_REQUEST_STARTED` from `events.api`, `TOOL_INVOKE_START` from `events.tool`). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Always use event name constants from domain-specific modules under `synthorg.observability.events` (e.g., `PROVIDER_CALL_START` from `events.provider`); import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to src/synthorg/**/*.py : Event names: always use constants from domain-specific modules under synthorg.observability.events (e.g., PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: `from synthorg.observability.events.<domain> import EVENT_CONSTANT`.
Applied to files:
src/synthorg/tools/database/config.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use event name constants from synthorg.observability.events domain-specific modules (e.g., PROVIDER_CALL_START from events.provider). Import directly: from synthorg.observability.events.<domain> import EVENT_CONSTANT.
Applied to files:
src/synthorg/tools/database/config.py
🔇 Additional comments (3)
src/synthorg/tools/invoker.py (1)
187-252: Good hardening of sub-constraint checks and escalation tracking.Using
_safe_deepcopy_args()here and recordingEscalationInfobefore returning closes the unsafe-copy gap and preserves approval context for resume flows.Based on learnings: Use
copy.deepcopy()at system boundaries (tool execution, serialization, delegation, persistence).src/synthorg/tools/database/config.py (2)
22-56: Good use of strict frozen Pydantic config models.
ConfigDict(frozen=True, allow_inf_nan=False)andNotBlankStrusage here is aligned well for config safety and validation.
49-52:⚠️ Potential issue | 🟠 MajorAdd immutability enforcement to the
connectionsdict field.
frozen=Trueprevents attribute reassignment but not nested dict mutation. Add a field validator to convert the dict totypes.MappingProxyTypeor change the annotation toMappingwith appropriate validation, following the pattern already used elsewhere in the codebase (e.g.,base.pyfor_parameters_schema).⛔ Skipped due to learnings
Learnt from: CR Repo: Aureliolo/synthorg PR: 0 File: CLAUDE.md:0-0 Timestamp: 2026-03-16T23:05:29.577Z Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).Learnt from: CR Repo: Aureliolo/synthorg PR: 0 File: CLAUDE.md:0-0 Timestamp: 2026-03-15T19:14:27.144Z Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).Learnt from: CR Repo: Aureliolo/synthorg PR: 0 File: CLAUDE.md:0-0 Timestamp: 2026-04-07T07:27:16.474Z Learning: Applies to src/**/*.py : For `dict`/`list` fields in frozen Pydantic models, use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization)Learnt from: CR Repo: Aureliolo/synthorg PR: 0 File: CLAUDE.md:0-0 Timestamp: 2026-04-01T09:09:43.948Z Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)Learnt from: CR Repo: Aureliolo/synthorg PR: 0 File: CLAUDE.md:0-0 Timestamp: 2026-03-20T21:44:04.528Z Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
972d90f to
ff41cdd
Compare
- Use NotBlankStr for DatabaseConfig.connections keys per coding conventions (reject blank/whitespace connection names) - Track sub-constraint escalations in _pending_escalations so requires_approval violations can be parked and resumed through the approval flow (aligns with security interceptor behavior)
- Add DB_CONFIG_DEFAULT_MISSING constant to events/database.py - Replace literal event name string in DatabaseConfig validator - Update invoke() docstring steps to include sub-constraint check (step 3) matching _invoke_single() execution order
- Use copy.deepcopy() for all _PARAMETERS_SCHEMA copies in tool constructors (schema_inspect, sql_query, shell_command, http_request, html_parser) to prevent shared nested dict mutation - Fix test_read_and_branch_allows_clone to use VCS_READ (consistent with test_local_only_blocks_clone)
Design: - Document all 6 sub-constraint dimensions in operations.md Security: - Block ATTACH/DETACH/VACUUM unconditionally (filesystem escape) - Resolve DNS for allowlisted hosts (TOCTOU pinning) - Block git_clone when network=NONE in sub-constraint enforcer - Re-raise MemoryError/RecursionError in DNS resolver and web search - Normalize Host header on all request paths (not just pin path) Code quality: - Pre-construction allowlist normalization (no frozen model mutation) - Merge custom sub-constraints with level defaults (not replace) - Remove duplicate TOOL_PERMISSION_CHECKER_CREATED log emission - Cap web search results to max_results defensively - Simplify conftest fixtures (direct Path typing) - Fix blocklist test to use set membership instead of substring - Assert both database tools absent in empty-connections test
- Fix requires_approval prefix from 'deployment' to 'deploy' to match actual action taxonomy (deploy:staging, deploy:production) - Remove duplicate WEB_SSRF_BLOCKED log (base_web_tool already logs) - Return normalized_headers on ValueError path in _pin_url - Update test for new deploy prefix
- Fix _validate_working_dir return type to include Path - Import Path at module level in shell_command.py - Fix aiosqlite cursor.description type mismatch (always non-None in types but None at runtime for DML) - Replace asynccontextmanager mock with proper class for error path (avoids unreachable yield in async generator)
The leading comment regex now matches -- comments at EOF (without trailing newline) by making the newline optional: (?:\n|$).
- Move trailing \s* outside the alternation in _LEADING_COMMENT_RE so indentation between consecutive -- comments is consumed - Snapshot cursor.description and close cursor before db.commit() for row-returning write statements (SQLite best practice)
47ebf8b to
b27f3dc
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
♻️ Duplicate comments (4)
tests/unit/tools/web/test_http_request.py (1)
151-162:⚠️ Potential issue | 🟡 MinorUse
result.errorfor SSRF rejection assertions.These cases still inspect
result.content, but the SSRF validation path returns its message in the error field. The test is checking the wrong payload here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/tools/web/test_http_request.py` around lines 151 - 162, The tests assert SSRF rejection messages from HttpRequestTool.execute against result.content but the SSRF validation path sets the message on result.error; update both tests (test_private_ip_blocked and test_file_scheme_blocked) to assert on result.error (e.g., assert result.is_error is True and assert "blocked" in result.error.lower() / assert "scheme" in result.error.lower()) while leaving the HttpRequestTool.execute invocation and is_error check unchanged so the assertions target the correct field.src/synthorg/tools/web/web_search.py (1)
144-182:⚠️ Potential issue | 🟠 MajorValidate provider results before rendering them.
WebSearchProvideris an external boundary, but this code onlygetattr()s fields on returned items. Dict results lose their realtitle/url/snippet, malformed objects still produce a success response, andSearchResultvalidation is skipped entirely. Normalize the sliced results withSearchResult.model_validate(...)(or equivalent coercion) before formatting.As per coding guidelines "Validate at system boundaries: user input, external APIs, and config files."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/web/web_search.py` around lines 144 - 182, The code currently formats provider results directly after calling self._provider.search in WebSearchTool (or similar) which can accept dicts or malformed objects; before slicing/formatting, normalize each item using SearchResult.model_validate (or the project's equivalent coercion/constructor) to ensure title/url/snippet fields exist and are typed, replacing or skipping invalid entries; validate the results list returned by self._provider.search, build a new capped list of validated SearchResult instances (or skip/log any that fail validation), then use that validated list for the existing logging and ToolExecutionResult creation so WEB_SEARCH_SUCCESS only reflects well-formed results.src/synthorg/tools/sub_constraint_enforcer.py (1)
102-128:⚠️ Potential issue | 🟠 MajorThree declared sub-constraints are still inert in this path.
check()only evaluates network, terminal, git, and requires_approval.file_system,code_execution, andnetwork_allowlistnever participate in the allow/deny decision here, andargumentsstays unused, so those controls currently have no effect beforeSUB_CONSTRAINT_ENFORCEDis logged. Please add explicit coverage for those dimensions once they are wired in.As per coding guidelines "Validate at system boundaries: user input, external APIs, and config files."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/sub_constraint_enforcer.py` around lines 102 - 128, The check method currently only runs _check_network, _check_terminal, _check_git, and _check_requires_approval, leaving file_system, code_execution, and network_allowlist inert; update the check(self, ...) implementation to also call the corresponding validators (e.g., _check_filesystem(tool_name, category, arguments), _check_code_execution(tool_name, category, arguments) and _check_network_allowlist(tool_name, category, arguments) or whatever the existing helper names are), include the returned SubConstraintViolation in the existing for-tuple so the first non-None violation is returned, and ensure the unused arguments parameter is passed into those checks for path/argument validation before logging SUB_CONSTRAINT_ENFORCED.src/synthorg/tools/network_validator.py (1)
263-272: 🧹 Nitpick | 🔵 TrivialException handling uses
isinstancecheck instead of PEP 758 comma syntax.Lines 264-265 use
isinstance(exc, MemoryError | RecursionError)for the system-error re-raise pattern. This works correctly, but the project convention documented in coding guidelines isexcept MemoryError, RecursionError: raise. Consider aligning with the convention for consistency, though the current approach is functionally equivalent.♻️ Align with project convention
- except Exception as exc: - if isinstance(exc, MemoryError | RecursionError): - raise + except MemoryError, RecursionError: + raise + except Exception as exc: logger.error(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/network_validator.py` around lines 263 - 272, Replace the current runtime-type check inside the generic except Exception as exc block with an explicit except clause that re-raises system-level errors: add an earlier handler except (MemoryError, RecursionError): raise and keep the subsequent except Exception as exc to perform the logger.error(WEB_DNS_FAILED, hostname=hostname, reason=..., exc_info=True) and return DNS failure string; this preserves hostname, logger and WEB_DNS_FAILED usage while aligning with the project convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/config/defaults.py`:
- Around line 48-50: The code in build_default_tools_from_config currently
treats config.web and config.terminal being None as "disable" (skipping
WebSearchTool creation and terminal sandbox setup), which conflicts with the
schema comment that None should mean "use default config"; update
build_default_tools_from_config to treat None as a trigger to instantiate
defaults: when config.web is None call the default web factory (e.g.,
create_default_web_config()) and proceed to add WebSearchTool, and when
config.terminal is None call the default terminal factory (e.g.,
create_default_terminal_config()) and apply the sandbox/settings before adding
terminal tools; ensure references to WebSearchTool creation and terminal sandbox
setup in build_default_tools_from_config use the resulting config object
(defaulted or provided) rather than gating on is not None so defaults are
applied consistently.
In `@src/synthorg/config/schema.py`:
- Around line 714-725: The RootConfig class docstring is out of sync: the newly
added public attributes web, database, and terminal (defined as WebToolsConfig |
None, DatabaseConfig | None, and TerminalConfig | None) are not documented in
the RootConfig attribute list; update the RootConfig class docstring to add
brief entries for web ("Web tool configuration (None = default web config)"),
database ("Database tool configuration (None = no database tools)"), and
terminal ("Terminal tool configuration (None = default terminal config)") so the
class attribute list accurately reflects the fields.
In `@src/synthorg/tools/network_validator.py`:
- Around line 415-418: The allowlist branch currently swallows DNS resolution
failures by converting non-tuple results to an empty tuple, weakening IP-pinning
protection; update the block that calls resolve_and_check(normalized,
policy.dns_resolution_timeout) so that when the result is not a successful tuple
you log a warning (including the hostname normalized and the resolution
error/indicator) before returning _ok(normalized, port, is_https=is_https,
resolved_ips=()); use the existing module logger (or the file's standard logger)
and reference the symbols normalized, policy.hostname_allowlist,
resolve_and_check, and _ok to locate and change the code.
In `@src/synthorg/tools/sub_constraint_enforcer.py`:
- Around line 157-160: The _check_network() logic currently blocks all
ToolCategory.WEB tools when self._constraints.network == NetworkMode.NONE, which
also prevents non-networked tools like HtmlParserTool from running; modify the
check in sub_constraint_enforcer._check_network to test for tools that actually
perform external network actions (e.g., by checking a capability/flag or method
like has_external_network_access or an action enum on the tool instance) instead
of matching on ToolCategory.WEB, and keep the existing external-fetch checks
(using _NETWORK_CATEGORIES and _GIT_CLONE_TOOL_NAMES) for tools that truly
require outbound network access so HtmlParserTool and other purely local
web-parsing tools remain allowed under NetworkMode.NONE.
In `@src/synthorg/tools/sub_constraints.py`:
- Around line 163-170: The requires_approval entry for
ToolAccessLevel.RESTRICTED in ToolSubConstraints uses the prefix "deploy" which
is too broad; update the tuple to use "deploy:" (i.e., change "deploy" to
"deploy:") so that _check_requires_approval() which uses startswith(prefix) only
matches category-style actions like "deploy:staging" and not unrelated names;
locate the ToolSubConstraints initialization for ToolAccessLevel.RESTRICTED in
src/synthorg/tools/sub_constraints.py and update the requires_approval tuple
accordingly.
In `@src/synthorg/tools/terminal/base_terminal_tool.py`:
- Around line 79-84: The warning currently logs the full blocked command
(logger.warning with TERMINAL_COMMAND_BLOCKED and command=command); change these
calls to avoid emitting sensitive arguments by replacing the command=command
field with either the executable name (extract from the command string) or a
redacted fingerprint (e.g., hash or masked string), and include the matched
pattern/reason as-is; update all occurrences of logger.warning using
TERMINAL_COMMAND_BLOCKED (the call at the shown location plus the similar calls
around lines 114-118 and 124-128) to use the executable or fingerprint instead
of the raw command string so logs never contain full rejected command arguments.
- Around line 121-123: The current allowlist loop uses
normalized.startswith(prefix.lower()) which lets entries like "ls" match
"lscpu"; update the check to match command boundaries instead of raw prefixes:
for each prefix in self._config.command_allowlist, treat prefix_lower =
prefix.lower() and accept only if normalized equals prefix_lower or normalized
has prefix_lower followed by whitespace (or end-of-string). Implement this using
a boundary-aware test (e.g., a regex built with re.escape(prefix_lower) anchored
to the start and requiring (\s|$) after the escaped prefix) in the same method
where normalized and self._config.command_allowlist are used.
In `@src/synthorg/tools/web/base_web_tool.py`:
- Around line 41-42: The constructor for the web tool (the __init__ that accepts
request_timeout: float = 30.0) should validate request_timeout before assigning
it to the instance: check that request_timeout is a positive number (> 0) and
raise a ValueError with a clear message if not; update any assignment of
self.request_timeout in the class (e.g., in BaseWebTool.__init__) to occur only
after this validation so invalid timeouts fail fast at construction.
In `@tests/unit/tools/test_factory_new_categories.py`:
- Around line 13-16: The workspace fixture currently returns a non-existent
Path; modify the fixture function named workspace to create the directory before
returning it (e.g., call workspace_path.mkdir(parents=True, exist_ok=True) on
the Path you construct) so tests don't need to call workspace.mkdir()
themselves; keep the fixture signature (def workspace(tmp_path: Path) -> Path)
and return the created path.
---
Duplicate comments:
In `@src/synthorg/tools/network_validator.py`:
- Around line 263-272: Replace the current runtime-type check inside the generic
except Exception as exc block with an explicit except clause that re-raises
system-level errors: add an earlier handler except (MemoryError,
RecursionError): raise and keep the subsequent except Exception as exc to
perform the logger.error(WEB_DNS_FAILED, hostname=hostname, reason=...,
exc_info=True) and return DNS failure string; this preserves hostname, logger
and WEB_DNS_FAILED usage while aligning with the project convention.
In `@src/synthorg/tools/sub_constraint_enforcer.py`:
- Around line 102-128: The check method currently only runs _check_network,
_check_terminal, _check_git, and _check_requires_approval, leaving file_system,
code_execution, and network_allowlist inert; update the check(self, ...)
implementation to also call the corresponding validators (e.g.,
_check_filesystem(tool_name, category, arguments),
_check_code_execution(tool_name, category, arguments) and
_check_network_allowlist(tool_name, category, arguments) or whatever the
existing helper names are), include the returned SubConstraintViolation in the
existing for-tuple so the first non-None violation is returned, and ensure the
unused arguments parameter is passed into those checks for path/argument
validation before logging SUB_CONSTRAINT_ENFORCED.
In `@src/synthorg/tools/web/web_search.py`:
- Around line 144-182: The code currently formats provider results directly
after calling self._provider.search in WebSearchTool (or similar) which can
accept dicts or malformed objects; before slicing/formatting, normalize each
item using SearchResult.model_validate (or the project's equivalent
coercion/constructor) to ensure title/url/snippet fields exist and are typed,
replacing or skipping invalid entries; validate the results list returned by
self._provider.search, build a new capped list of validated SearchResult
instances (or skip/log any that fail validation), then use that validated list
for the existing logging and ToolExecutionResult creation so WEB_SEARCH_SUCCESS
only reflects well-formed results.
In `@tests/unit/tools/web/test_http_request.py`:
- Around line 151-162: The tests assert SSRF rejection messages from
HttpRequestTool.execute against result.content but the SSRF validation path sets
the message on result.error; update both tests (test_private_ip_blocked and
test_file_scheme_blocked) to assert on result.error (e.g., assert
result.is_error is True and assert "blocked" in result.error.lower() / assert
"scheme" in result.error.lower()) while leaving the HttpRequestTool.execute
invocation and is_error check unchanged so the assertions target the correct
field.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7be66645-3f62-4f93-8c51-1b80a41d045b
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (57)
CLAUDE.mddocs/design/operations.mdpyproject.tomlsrc/synthorg/config/defaults.pysrc/synthorg/config/schema.pysrc/synthorg/core/agent.pysrc/synthorg/observability/events/database.pysrc/synthorg/observability/events/sub_constraint.pysrc/synthorg/observability/events/terminal.pysrc/synthorg/observability/events/web.pysrc/synthorg/tools/__init__.pysrc/synthorg/tools/database/__init__.pysrc/synthorg/tools/database/base_db_tool.pysrc/synthorg/tools/database/config.pysrc/synthorg/tools/database/schema_inspect.pysrc/synthorg/tools/database/sql_query.pysrc/synthorg/tools/factory.pysrc/synthorg/tools/git_url_validator.pysrc/synthorg/tools/invoker.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/permissions.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/terminal/__init__.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/terminal/config.pysrc/synthorg/tools/terminal/shell_command.pysrc/synthorg/tools/web/__init__.pysrc/synthorg/tools/web/base_web_tool.pysrc/synthorg/tools/web/config.pysrc/synthorg/tools/web/html_parser.pysrc/synthorg/tools/web/http_request.pysrc/synthorg/tools/web/web_search.pytests/integration/tools/test_factory_integration.pytests/unit/observability/test_events.pytests/unit/tools/database/__init__.pytests/unit/tools/database/conftest.pytests/unit/tools/database/test_config.pytests/unit/tools/database/test_schema_inspect.pytests/unit/tools/database/test_sql_query.pytests/unit/tools/terminal/__init__.pytests/unit/tools/terminal/conftest.pytests/unit/tools/terminal/test_config.pytests/unit/tools/terminal/test_shell_command.pytests/unit/tools/test_factory.pytests/unit/tools/test_factory_new_categories.pytests/unit/tools/test_factory_sandbox_wiring.pytests/unit/tools/test_invoker_sub_constraints.pytests/unit/tools/test_network_validator.pytests/unit/tools/test_permissions_sub_constraints.pytests/unit/tools/test_sub_constraint_enforcer.pytests/unit/tools/test_sub_constraints.pytests/unit/tools/web/__init__.pytests/unit/tools/web/conftest.pytests/unit/tools/web/test_html_parser.pytests/unit/tools/web/test_http_request.pytests/unit/tools/web/test_web_search.py
| "web": None, | ||
| "database": None, | ||
| "terminal": None, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how config.web/database/terminal are interpreted by the factory.
rg -n -C4 'build_default_tools_from_config|config\.(web|database|terminal)|is not None|== None' src/synthorg/tools/factory.py
# Confirm new defaults in the config base layer.
rg -n -C2 '"web": None|"database": None|"terminal": None' src/synthorg/config/defaults.py
# Inspect schema defaults for these fields.
rg -n -C4 '\bweb\b|\bdatabase\b|\bterminal\b' src/synthorg/config/schema.pyRepository: Aureliolo/synthorg
Length of output: 4409
Inconsistency between schema documentation and factory implementation for web/terminal defaults.
The schema documents that config.web: None = "default web config" and config.terminal: None = "default terminal config", but build_default_tools_from_config() gates web policy extraction (line 309) and terminal sandbox setup (line 291) on is not None checks. When these are None, WebSearchTool is not added and terminal tools run without configured sandbox/settings. Clarify: should None values trigger a default factory configuration, or is the schema description inaccurate? (Database behavior is intentional per schema: None disables tools.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/config/defaults.py` around lines 48 - 50, The code in
build_default_tools_from_config currently treats config.web and config.terminal
being None as "disable" (skipping WebSearchTool creation and terminal sandbox
setup), which conflicts with the schema comment that None should mean "use
default config"; update build_default_tools_from_config to treat None as a
trigger to instantiate defaults: when config.web is None call the default web
factory (e.g., create_default_web_config()) and proceed to add WebSearchTool,
and when config.terminal is None call the default terminal factory (e.g.,
create_default_terminal_config()) and apply the sandbox/settings before adding
terminal tools; ensure references to WebSearchTool creation and terminal sandbox
setup in build_default_tools_from_config use the resulting config object
(defaulted or provided) rather than gating on is not None so defaults are
applied consistently.
| logger.warning( | ||
| TERMINAL_COMMAND_BLOCKED, | ||
| command=command, | ||
| pattern=pattern, | ||
| reason="blocklist_match", | ||
| ) |
There was a problem hiding this comment.
Don't log full blocked commands.
These warning events include the raw command string. Rejected commands often carry credentials or other sensitive args, so this leaks secrets into logs on the denial path. Log only the executable / matched rule, or a redacted fingerprint of the original command.
Also applies to: 114-118, 124-128
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/terminal/base_terminal_tool.py` around lines 79 - 84, The
warning currently logs the full blocked command (logger.warning with
TERMINAL_COMMAND_BLOCKED and command=command); change these calls to avoid
emitting sensitive arguments by replacing the command=command field with either
the executable name (extract from the command string) or a redacted fingerprint
(e.g., hash or masked string), and include the matched pattern/reason as-is;
update all occurrences of logger.warning using TERMINAL_COMMAND_BLOCKED (the
call at the shown location plus the similar calls around lines 114-118 and
124-128) to use the executable or fingerprint instead of the raw command string
so logs never contain full rejected command arguments.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
src/synthorg/tools/sub_constraint_enforcer.py (1)
124-128:⚠️ Potential issue | 🟠 MajorThese checks still leave three policy dimensions unenforced.
check()only runs network/terminal/git/requires_approval, and_check_network()only blocksNetworkMode.NONE.argumentsis still unused here, sofile_system,code_execution, andnetwork_allowlistnever participate in the decision;WORKSPACE_ONLY,ALLOWLIST_ONLY, and container-only policies are effectively declarative in this layer. Please either enforce those dimensions here or thread the relevant path/destination/runtime metadata into the downstream tool backends before returning success.As per coding guidelines, "Validate at system boundaries: user input, external APIs, and config files".
Also applies to: 146-168
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/sub_constraint_enforcer.py` around lines 124 - 128, The check() loop only calls _check_network, _check_terminal, _check_git, and _check_requires_approval and ignores the passed arguments, so policy dimensions file_system (WORKSPACE_ONLY), code_execution (container-only), and network_allowlist (ALLOWLIST_ONLY) are not enforced; update check() to either enforce those policies directly or propagate the needed runtime metadata to downstream backends: inspect the incoming arguments param and call or augment checks to validate file_system access (e.g., enforce WORKSPACE_ONLY by ensuring file paths are within workspace), code_execution constraints (disallow container-executed actions when not permitted), and network_allowlist (validate destinations against network_allowlist) before returning success, or include these fields in the object returned/passed to tool backends so methods like _check_network/_check_terminal/_check_git receive and enforce network_allowlist, file_system and code_execution constraints; ensure the functions named _check_network, _check_terminal, _check_git, and _check_requires_approval are updated to accept and use the relevant args/metadata.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/sub_constraints.py`:
- Around line 205-207: Update the param docstring for custom_constraints to
accurately describe the actual overlay semantics: state that when provided for a
built-in level the dict is merged/overlaid onto the level defaults (explicitly
set fields in custom_constraints override defaults while unspecified fields are
inherited), and that CUSTOM still requires a full constraints dict; reference
the custom_constraints parameter and the overlay behavior implemented in the
code around lines where defaults are combined (the overlay logic at the block
that applies fields onto level defaults) so the text matches the implementation;
if you intended full replacement instead, change the overlay logic to replace
rather than merge.
- Around line 119-148: The ToolSubConstraints model currently sets model_config
= ConfigDict(frozen=True, allow_inf_nan=False) which lets Pydantic drop unknown
fields; update this ConfigDict to include extra="forbid" so unknown/misspelled
sub-constraint keys are rejected (e.g., change ConfigDict(...) to include
extra="forbid"); adjust any tests or callers that pass through
get_sub_constraints() if they relied on silent ignoring to ensure they provide
valid fields.
In `@src/synthorg/tools/terminal/base_terminal_tool.py`:
- Line 77: Avoid calling command.split() twice: compute the split once into a
local variable (e.g., parts = command.split(maxsplit=1)) and then set executable
= parts[0] if parts else ""; apply the same change to the analogous occurrence
around line 112 (same pattern) so both places in BaseTerminalTool use a single
split call.
- Around line 89-92: The _SHELL_META_RE character-class is missing carriage
return and backslash which can allow obfuscation or line-continuation escapes;
update the regex in base_terminal_tool.py (symbol: _SHELL_META_RE) to include
'\r' and the backslash character inside the character class (properly escaped in
the pattern) so the compiled pattern also matches carriage returns and
backslashes alongside the existing metacharacters.
In `@src/synthorg/tools/web/web_search.py`:
- Around line 171-172: The bare except block labeled "except Exception: # noqa:
S112 -- skip malformed items" is silently dropping malformed provider items;
update that block to log the malformed item at DEBUG level before continuing
(preserve the continue behavior). Use the module/class logger (e.g.,
logger.debug) and include the exception info (exc_info=True or similar) and the
offending item payload/identifier so operators can inspect faulty provider data
while still skipping it. Ensure the log message is concise and placed inside the
same except block that currently swallows the error.
---
Duplicate comments:
In `@src/synthorg/tools/sub_constraint_enforcer.py`:
- Around line 124-128: The check() loop only calls _check_network,
_check_terminal, _check_git, and _check_requires_approval and ignores the passed
arguments, so policy dimensions file_system (WORKSPACE_ONLY), code_execution
(container-only), and network_allowlist (ALLOWLIST_ONLY) are not enforced;
update check() to either enforce those policies directly or propagate the needed
runtime metadata to downstream backends: inspect the incoming arguments param
and call or augment checks to validate file_system access (e.g., enforce
WORKSPACE_ONLY by ensuring file paths are within workspace), code_execution
constraints (disallow container-executed actions when not permitted), and
network_allowlist (validate destinations against network_allowlist) before
returning success, or include these fields in the object returned/passed to tool
backends so methods like _check_network/_check_terminal/_check_git receive and
enforce network_allowlist, file_system and code_execution constraints; ensure
the functions named _check_network, _check_terminal, _check_git, and
_check_requires_approval are updated to accept and use the relevant
args/metadata.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a16bd451-bda9-4b0c-a0fd-51431cb99857
📒 Files selected for processing (9)
src/synthorg/config/schema.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/web/base_web_tool.pysrc/synthorg/tools/web/web_search.pytests/unit/tools/test_factory_new_categories.pytests/unit/tools/test_sub_constraints.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: Socket Security: Pull Request Alerts
- GitHub Check: Deploy Preview
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Build Sandbox
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649
Useexcept A, B:syntax (no parentheses) for exception handling -- ruff enforces this on Python 3.14 (PEP 758)
All public functions require type hints; mypy strict mode enforcement
Docstrings must use Google style and are required on all public classes and functions (enforced by ruff D rules)
Use immutability patterns: create new objects, never mutate existing ones. For non-Pydantic internal collections, usecopy.deepcopy()at construction plusMappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state evolution. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Always useallow_inf_nan=Falsein ConfigDict; use@computed_fieldfor derived values; useNotBlankStrfor all identifier/name fields
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code. Prefer structured concurrency over barecreate_task
Maintain line length of 88 characters (enforced by ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, and config files
Variable name for logger must always belogger(not_logger, notlog)
All Python source files must use ruff for linting and formatting with line length 88 characters
Files:
src/synthorg/config/schema.pytests/unit/tools/test_sub_constraints.pysrc/synthorg/tools/web/base_web_tool.pysrc/synthorg/tools/terminal/base_terminal_tool.pytests/unit/tools/test_factory_new_categories.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/web/web_search.pysrc/synthorg/tools/sub_constraints.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging,logging.getLogger(), orprint()in application code (exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py may use stdlib logging and print for handler/bootstrap code)
Use event name constants fromsynthorg.observability.eventsdomain-specific modules (e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool). Import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Always use structured kwargs in logging:logger.info(EVENT, key=value)-- neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
Use DEBUG logging for object creation, internal flow, and entry/exit of key functions
All provider calls go throughBaseCompletionProviderwhich applies retry and rate limiting automatically. Never implement retry logic in driver subclasses or calling code
Maintain 80% minimum test coverage (enforced in CI)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names:example-provider,example-large-001,example-medium-001,example-small-001
All Python code in src/synthorg/ must pass mypy strict mode type-checking
Pure data models, enums, and re-exports in Python do NOT need logging setup
Files:
src/synthorg/config/schema.pysrc/synthorg/tools/web/base_web_tool.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/web/web_search.pysrc/synthorg/tools/sub_constraints.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/config/schema.pysrc/synthorg/tools/web/base_web_tool.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/network_validator.pysrc/synthorg/tools/web/web_search.pysrc/synthorg/tools/sub_constraints.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: All tests must use markers:@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slow
Useasyncio_mode = "auto"for async tests -- no manual@pytest.mark.asyncioneeded
Global timeout of 30 seconds per test (configured in pyproject.toml). Non-default overrides liketimeout(60)are allowed
Prefer@pytest.mark.parametrizefor testing similar cases in Python tests
Tests must usetest-provider,test-small-001, etc. instead of real vendor names
Use Hypothesis for property-based testing with@givenand@settings. Configure via HYPOTHESIS_PROFILE env var:ci(10 examples, deterministic),dev(1000 examples),fuzz(10,000 examples, no deadline),extreme(500,000 examples)
When Hypothesis finds a failure, it is a real bug -- fix it fully and fundamentally. Add an explicit@example(...)decorator to the test for permanent coverage in CI
Never skip, dismiss, or ignore flaky tests -- always fix them fully and fundamentally. Mocktime.monotonic()andasyncio.sleep()for determinism; useasyncio.Event().wait()for tasks that must block indefinitely
Test files and scripts/ may use relaxed ruff rules:
Files:
tests/unit/tools/test_sub_constraints.pytests/unit/tools/test_factory_new_categories.py
⚙️ CodeRabbit configuration file
Test files do not require Google-style docstrings on classes or functions -- ruff D rules are only enforced on src/. A bare
@settings() decorator with no arguments on Hypothesis property tests is a no-op and should not be suggested -- the HYPOTHESIS_PROFILE env var controls example counts via registered profiles, which@given() honors automatically.
Files:
tests/unit/tools/test_sub_constraints.pytests/unit/tools/test_factory_new_categories.py
🧠 Learnings (46)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/network_validator.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (via `model_copy(update=...)`) for runtime state that evolves
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-15T18:38:44.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:38:44.202Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; separate mutable-via-copy models (using `model_copy(update=...)`) for runtime state
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraint_enforcer.pysrc/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Set `RetryConfig` and `RateLimiterConfig` per-provider in `ProviderConfig`.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-17T06:30:14.180Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:30:14.180Z
Learning: Applies to src/synthorg/security/**/*.py : Security module includes SecOps agent, rule engine (soft-allow/hard-deny), audit log, output scanner, risk classifier, autonomy levels (4 strategies), timeout policies.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/tools/sub_constraint_enforcer.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/settings/**/*.py : Settings package (settings/): runtime-editable settings persistence (DB > env > YAML > code defaults), typed definitions (9 namespaces), Fernet encryption for sensitive values, config bridge (JSON serialization for Pydantic/collections), ConfigResolver (typed accessors), validation, registry, change notifications via message bus, SettingsSubscriber protocol, SettingsChangeDispatcher (polls `#settings` channel, routes to subscribers, restart_required filtering)
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-31T21:07:37.469Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T21:07:37.469Z
Learning: Applies to src/synthorg/providers/**/*.py : Set `RetryConfig` and `RateLimiterConfig` per-provider in `ProviderConfig`
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to src/synthorg/**/*.py : Maintain 80% minimum test coverage (enforced in CI)
Applied to files:
tests/unit/tools/test_factory_new_categories.py
📚 Learning: 2026-04-02T08:47:46.313Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T08:47:46.313Z
Learning: Run full pytest suite with coverage: `uv run python -m pytest tests/ -n auto --cov=synthorg --cov-fail-under=80`
Applied to files:
tests/unit/tools/test_factory_new_categories.py
📚 Learning: 2026-04-01T14:22:06.315Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T14:22:06.315Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Validate at system boundaries (user input, external APIs, config files)
Applied to files:
src/synthorg/tools/sub_constraint_enforcer.py
📚 Learning: 2026-03-16T20:14:00.937Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T20:14:00.937Z
Learning: Applies to **/*.py : Validate: at system boundaries (user input, external APIs, config files).
Applied to files:
src/synthorg/tools/sub_constraint_enforcer.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
Applied to files:
src/synthorg/tools/sub_constraint_enforcer.py
📚 Learning: 2026-03-19T07:13:44.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:13:44.964Z
Learning: Applies to **/*.py : Validate at system boundaries (user input, external APIs, config files)
Applied to files:
src/synthorg/tools/sub_constraint_enforcer.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to **/*.py : Validate at system boundaries: user input, external APIs, and config files
Applied to files:
src/synthorg/tools/sub_constraint_enforcer.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/network_validator.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to **/*.py : Functions must be less than 50 lines; files must be less than 800 lines
Applied to files:
src/synthorg/tools/network_validator.py
📚 Learning: 2026-04-01T06:12:03.047Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T06:12:03.047Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Keep functions under 50 lines and files under 800 lines
Applied to files:
src/synthorg/tools/network_validator.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Handle errors explicitly, never silently swallow. Validate at system boundaries (user input, external APIs, config files).
Applied to files:
src/synthorg/tools/network_validator.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to **/*.py : Use immutability patterns: create new objects, never mutate existing ones. For non-Pydantic internal collections, use `copy.deepcopy()` at construction plus `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors (`is_retryable=True`): `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-02T21:38:30.127Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T21:38:30.127Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-16T19:13:34.746Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:34.746Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry. RetryExhaustedError signals that all retries failed — the engine layer catches this to trigger fallback chains.
Applied to files:
src/synthorg/tools/web/web_search.py
🔇 Additional comments (29)
src/synthorg/config/schema.py (3)
40-45: LGTM!Type-only imports with
# noqa: TC001are appropriate here since these config models are used solely for Pydantic type annotations. The imports follow the existing pattern in the file.
579-583: Docstring now includes the new fields.The
RootConfigattribute list is updated to documentweb,database, andterminal. The descriptions accurately reflect the semantics:Noneenables default behavior for web/terminal tools, whileNonedisables database tools (requiring explicit opt-in). This security-conscious asymmetry aligns with the PR's SSRF and SQL injection mitigation goals.
719-730: LGTM!The three new configuration fields follow the established pattern:
- Frozen Pydantic model with
allow_inf_nan=False✓- Union type with
Nonefor optional config sections ✓- Descriptive
Field()definitions ✓- Defaults to
Nonealigning withdefaults.pyand factory wiring ✓The intentional distinction—
databaserequiring explicit configuration whilewebandterminalfall back to safe defaults—is a sound fail-closed posture for database tool access.tests/unit/tools/test_factory_new_categories.py (5)
13-17: Fixture setup is clean and self-contained.Creating the workspace inside the fixture keeps test setup consistent and removes per-test boilerplate.
23-62: Web factory coverage is strong.This block checks default inclusion, provider-dependent behavior, and custom network-policy wiring in a focused way.
67-105: Database-tool gating assertions look complete.Nice job covering default-off, configured-on, and empty-connections skip behavior for both
sql_queryandschema_inspect.
110-129: Terminal factory tests are well-targeted.Both default creation and
TerminalConfigpropagation are asserted clearly.
135-147: Top-level factory contract checks are good guardrails.Count and sorted-order assertions provide fast regression detection for default factory output.
src/synthorg/tools/terminal/base_terminal_tool.py (3)
1-21: LGTM!Module setup correctly uses
get_logger(__name__), imports the event constant from the appropriate domain module, and properly guards the type-only import behindTYPE_CHECKING.
24-65: LGTM!The class correctly extends
BaseToolwithToolCategory.TERMINAL, properly forwards all required parameters to the parent__init__, and theconfigproperty provides clean read-only access to the configuration.
124-134: Allowlist boundary handling looks correct.The prefix matching now properly uses word boundaries (
normalized == prefix_lower or normalized.startswith(prefix_lower + " ")), which prevents unintended matches like"ls"allowing"lscpu". The warning logging correctly outputs only the executable name rather than full command arguments.src/synthorg/tools/network_validator.py (7)
1-30: LGTM!Module setup is correct with proper imports, observability integration via
get_logger(__name__), and comprehensive module-level docstring explaining the SSRF prevention purpose.
33-61: Comprehensive SSRF blocklist coverage.The
BLOCKED_NETWORKStuple correctly covers all critical ranges: private (RFC 1918), loopback, link-local, CGNAT, test-nets, multicast, broadcast, and IPv6 equivalents including Teredo, 6to4, and ULA. The inclusion of64:ff9b::/96(NAT64) and2002::/16(6to4) is particularly important as these can encode IPv4 addresses that bypass naive checks.
67-114: LGTM!
NetworkPolicycorrectly uses frozen Pydantic model withallow_inf_nan=False, boundeddns_resolution_timeout, and propermode="before"validator for allowlist normalization without mutating the frozen instance.
145-172: Solid fail-closed IP validation with IPv6-mapped handling.The
is_blocked_ipfunction correctly:
- Blocks unparseable IPs (fail-closed)
- Unwraps IPv6-mapped IPv4 addresses before checking
- Logs blocked attempts with structured kwargs
263-272: System error propagation is now correctly implemented.The exception handling properly re-raises
MemoryErrorandRecursionErrorbefore the generic catch, following project conventions.
414-426: Allowlist DNS resolution now properly logs failures.The allowlist branch correctly resolves DNS for IP pinning and logs a warning when resolution fails for allowlisted hosts before returning an empty tuple. This provides observability while maintaining the bypass behavior.
437-451: Literal IP handling correctly bypasses DNS resolution.For literal IPs, the code skips DNS resolution (not needed) and directly checks against the blocklist. The try/except/else pattern cleanly separates the IP literal case from hostname resolution.
src/synthorg/tools/web/base_web_tool.py (3)
1-23: LGTM!Module setup is correct with proper imports, ABC for abstract base, and observability integration.
33-65: LGTM!Constructor properly validates
request_timeout > 0before use, sets the hardcodedToolCategory.WEB, and defaultsNetworkPolicy()when none provided. The validation addresses the system boundary principle.
72-94: Clean URL validation helper.The
_validate_urlmethod correctly:
- Checks scheme first via
is_allowed_http_scheme- Logs blocked attempts with structured kwargs and descriptive reason
- Delegates host/IP validation to the centralized
validate_url_hostsrc/synthorg/tools/web/web_search.py (7)
1-26: LGTM!Module setup is correct with proper imports,
copyfor deep copying,runtime_checkableProtocol, and observability integration.
28-42: LGTM!
SearchResultis a proper frozen Pydantic model withallow_inf_nan=False, suitable for representing immutable search results.
44-67: LGTM!
WebSearchProviderprotocol is correctly defined asruntime_checkablewith an asyncsearchmethod. The docstring properly documents the expected return type.
102-124: LGTM!Constructor properly uses
copy.deepcopy(_PARAMETERS_SCHEMA)to avoid shared nested dict mutation, and stores the injected provider.
144-153: Proper exception handling with system error propagation.The exception handling correctly re-raises
MemoryErrorandRecursionErrorbefore the generic catch, following project conventions for system error propagation.
162-179: Good boundary validation for external provider results.The code properly validates provider results at the system boundary:
- Caps results to
max_results- Coerces items to
SearchResultviamodel_validate- Silently skips malformed items (logged implicitly via empty validated list)
- Returns graceful "No results found." when all items fail validation
181-197: LGTM!Result formatting is clean with proper structured logging of success metrics and well-formatted multiline output.
tests/unit/tools/test_sub_constraints.py (1)
158-193: Good regression coverage for merged overrides.These assertions lock in the security-sensitive contract that built-in defaults survive partial overrides instead of being replaced wholesale.
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
src/synthorg/tools/web/web_search.py (1)
114-123: 🛠️ Refactor suggestion | 🟠 MajorMake the copied schema immutable.
copy.deepcopy()fixes nested aliasing, but the schema handed toBaseWebToolis still mutable. That leaves the tool contract open to runtime mutation, which is the exact collection pattern the repo asks us to lock down forBaseTool-owned state.Based on learnings: "For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction plus MappingProxyType wrapping for read-only enforcement."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/tools/web/web_search.py` around lines 114 - 123, The parameters_schema passed to BaseWebTool is currently a mutable deep copy of _PARAMETERS_SCHEMA; wrap the deep-copied schema with a read-only MappingProxyType before passing it to super() so the tool gets an immutable mapping, e.g., replace parameters_schema=copy.deepcopy(_PARAMETERS_SCHEMA) with parameters_schema=MappingProxyType(copy.deepcopy(_PARAMETERS_SCHEMA)); update imports to include MappingProxyType and reference the constructor call in the BaseWebTool subclass (the super().__init__ in this class) and the _PARAMETERS_SCHEMA symbol when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/synthorg/tools/terminal/base_terminal_tool.py`:
- Around line 67-88: Add DEBUG and INFO logs in _is_command_blocked to follow
the logging policy: log entry into the function with logger.debug including the
raw command and normalized/executable values, log the decision point for each
pattern check at DEBUG, and emit an INFO-level state transition when a command
is allowed or denied (e.g., on block: logger.info with executable, pattern,
reason="deny" before the existing logger.warning; on allow: logger.info with
executable, reason="allow" before returning False). Apply the same pattern to
the related validation functions referenced in the 95-135 range so all
entry/exit and allow/deny transitions use DEBUG for flow and INFO for state
changes.
- Around line 76-80: The blocklist comparison currently uses the raw normalized
string (variable normalized) so patterns with specific spacing like "rm -rf /"
can be bypassed with extra whitespace; update the normalization in the
BaseTerminalTool method to collapse all runs of whitespace
(spaces/tabs/newlines) into a single space (e.g., using a regex replace) after
strip(). Use that collapsed-normalized string for splitting into
parts/executable and for the loop that checks self._config.command_blocklist
(ensure you also lower-case both sides before the "in" check).
In `@src/synthorg/tools/web/web_search.py`:
- Around line 140-145: In execute(), validate and normalize max_results before
using it: ensure the value from arguments is an integer and within an acceptable
range (e.g. a positive non-zero upper bound), otherwise raise a clear ValueError
or coerce safely; then use the validated value (e.g. validated_max_results) when
calling self._provider.search(query, validated_max_results) and when slicing
list(results)[:validated_max_results] to avoid passing strings or negatives to
the provider or causing local TypeErrors in list slicing.
- Around line 142-152: The code currently logs and returns raw user query and
provider exception strings (see logger.info, logger.warning, and the
ToolExecutionResult content after calling self._provider.search); change these
to never emit raw PII/error text: replace logger.info(...) and
logger.warning(...) calls to log only a redacted fingerprint (e.g., hash or
truncated length) of query and the exception type (type(exc).__name__) plus any
safe metadata, and update returned ToolExecutionResult messages to a generic
failure message (no raw exc or query); apply the same redaction pattern to the
other occurrences noted (the other logger calls and ToolExecutionResult returns
referenced in the comment) so no raw query/error details are written to logs or
returned to callers.
- Around line 144-145: The tool calls self._provider.search(query, max_results)
without enforcing the provided network_policy; update the WebSearch tool to
preflight the network_policy (same deny/allow logic as in
src/synthorg/tools/web/http_request.py preflight) and block/raise before
invoking _provider.search so it fails closed, or change the provider interface
(e.g., provider.search) to accept a network_policy argument and ensure
implementations enforce it; specifically, perform the network_policy check in
the method that calls self._provider.search (or add a network_policy param to
Provider.search) and return/raise on deny so no outbound request can bypass the
policy.
---
Duplicate comments:
In `@src/synthorg/tools/web/web_search.py`:
- Around line 114-123: The parameters_schema passed to BaseWebTool is currently
a mutable deep copy of _PARAMETERS_SCHEMA; wrap the deep-copied schema with a
read-only MappingProxyType before passing it to super() so the tool gets an
immutable mapping, e.g., replace
parameters_schema=copy.deepcopy(_PARAMETERS_SCHEMA) with
parameters_schema=MappingProxyType(copy.deepcopy(_PARAMETERS_SCHEMA)); update
imports to include MappingProxyType and reference the constructor call in the
BaseWebTool subclass (the super().__init__ in this class) and the
_PARAMETERS_SCHEMA symbol when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8c905a34-aeef-4b30-b715-81d6193afffe
📒 Files selected for processing (3)
src/synthorg/tools/sub_constraints.pysrc/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/web/web_search.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Build Sandbox
- GitHub Check: Build Backend
- GitHub Check: Build Web
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: Nofrom __future__ import annotations-- Python 3.14 has PEP 649
Useexcept A, B:syntax (no parentheses) for exception handling -- ruff enforces this on Python 3.14 (PEP 758)
All public functions require type hints; mypy strict mode enforcement
Docstrings must use Google style and are required on all public classes and functions (enforced by ruff D rules)
Use immutability patterns: create new objects, never mutate existing ones. For non-Pydantic internal collections, usecopy.deepcopy()at construction plusMappingProxyTypewrapping for read-only enforcement
Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state evolution. Never mix static config fields with mutable runtime fields in one model
Use Pydantic v2 (BaseModel,model_validator,computed_field,ConfigDict). Always useallow_inf_nan=Falsein ConfigDict; use@computed_fieldfor derived values; useNotBlankStrfor all identifier/name fields
Preferasyncio.TaskGroupfor fan-out/fan-in parallel operations in new code. Prefer structured concurrency over barecreate_task
Maintain line length of 88 characters (enforced by ruff)
Functions must be less than 50 lines; files must be less than 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, and config files
Variable name for logger must always belogger(not_logger, notlog)
All Python source files must use ruff for linting and formatting with line length 88 characters
Files:
src/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/web/web_search.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must have:from synthorg.observability import get_loggerthenlogger = get_logger(__name__)
Never useimport logging,logging.getLogger(), orprint()in application code (exception: observability/setup.py, observability/sinks.py, observability/syslog_handler.py, observability/http_handler.py may use stdlib logging and print for handler/bootstrap code)
Use event name constants fromsynthorg.observability.eventsdomain-specific modules (e.g.,API_REQUEST_STARTEDfromevents.api,TOOL_INVOKE_STARTfromevents.tool). Import directly:from synthorg.observability.events.<domain> import EVENT_CONSTANT
Always use structured kwargs in logging:logger.info(EVENT, key=value)-- neverlogger.info("msg %s", val)
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
Use DEBUG logging for object creation, internal flow, and entry/exit of key functions
All provider calls go throughBaseCompletionProviderwhich applies retry and rate limiting automatically. Never implement retry logic in driver subclasses or calling code
Maintain 80% minimum test coverage (enforced in CI)
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names:example-provider,example-large-001,example-medium-001,example-small-001
All Python code in src/synthorg/ must pass mypy strict mode type-checking
Pure data models, enums, and re-exports in Python do NOT need logging setup
Files:
src/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/web/web_search.py
src/**/*.py
⚙️ CodeRabbit configuration file
This project uses Python 3.14+ with PEP 758 except syntax: "except A, B:" (comma-separated, no parentheses) is correct and mandatory -- do NOT flag it as a typo or suggest parenthesized form. The "except builtins.MemoryError, RecursionError: raise" pattern is intentional project convention for system-error propagation. When evaluating the 50-line function limit, count only the function body excluding the signature lines, decorators, and docstring. Functions 1-5 lines over due to docstrings or multi-line signatures should not be flagged. Do not suggest extracting single-use helper functions called exactly once -- this reduces readability without improving maintainability.
Files:
src/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraints.pysrc/synthorg/tools/web/web_search.py
🧠 Learnings (33)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies: disabled/weighted/per-category/milestone), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume).
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, and key function entry/exit
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-31T20:07:03.035Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-31T20:07:03.035Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising; all state transitions must log at INFO; DEBUG for object creation, internal flow, entry/exit of key functions
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-17T06:43:14.114Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T06:43:14.114Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising. All state transitions must log at INFO. DEBUG for object creation, internal flow, entry/exit of key functions. Pure data models, enums, and re-exports do NOT need logging.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-15T16:55:07.730Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T16:55:07.730Z
Learning: Applies to src/synthorg/**/*.py : All error paths must log at WARNING or ERROR with context before raising.
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/security/**/*.py : Security package (security/): SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered), risk classifier, risk tier classifier, action type registry, ToolInvoker security integration, progressive trust (4 strategies), autonomy levels (presets, resolver, change strategy), timeout policies (park/resume)
Applied to files:
src/synthorg/tools/terminal/base_terminal_tool.pysrc/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-04-02T11:49:21.202Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T11:49:21.202Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`) with `allow_inf_nan=False` in all `ConfigDict` declarations
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-04-01T15:36:39.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T15:36:39.993Z
Learning: Applies to src/**/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `allow_inf_nan=False` in all `ConfigDict` declarations to reject `NaN`/`Inf` in numeric fields at validation time
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to **/*.py : Use Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Always use `allow_inf_nan=False` in ConfigDict; use `computed_field` for derived values; use `NotBlankStr` for all identifier/name fields
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/sub_constraints.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-16T23:05:29.577Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T23:05:29.577Z
Learning: Applies to **/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/tools/sub_constraints.pysrc/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to **/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models for runtime state evolution. Never mix static config fields with mutable runtime fields in one model
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/tools/sub_constraints.py
📚 Learning: 2026-04-01T09:09:43.948Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:09:43.948Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at construction and `MappingProxyType` wrapping for read-only enforcement in non-Pydantic internal collections (registries, BaseTool)
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : For non-Pydantic internal collections (registries, `BaseTool`), use `copy.deepcopy()` at construction and wrap with `MappingProxyType` for read-only enforcement
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-01T09:58:27.410Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T09:58:27.410Z
Learning: Applies to **/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for dict/list fields in frozen Pydantic models
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to **/*.py : Immutability: create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement.
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to **/*.py : Use immutability patterns: create new objects, never mutate existing ones. For non-Pydantic internal collections, use `copy.deepcopy()` at construction plus `MappingProxyType` wrapping for read-only enforcement
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-01T17:49:14.133Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-01T17:49:14.133Z
Learning: Applies to src/synthorg/{providers,engine}/**/*.py : Retryable errors are `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`; non-retryable errors raise immediately; `RetryExhaustedError` signals all retries failed
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-07T13:20:05.336Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-07T13:20:05.336Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors (`is_retryable=True`): `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately without retry
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-04-02T21:38:30.127Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-02T21:38:30.127Z
Learning: Applies to {**/*.py,web/src/**/*.{ts,tsx}} : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to **/*.py : Handle errors explicitly—never silently swallow exceptions.
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to **/*.py : Handle errors explicitly; never silently swallow exceptions
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-14T15:43:05.601Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T15:43:05.601Z
Learning: Applies to **/*.py : Handle errors explicitly, never silently swallow exceptions
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-16T19:13:34.746Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:34.746Z
Learning: Applies to src/synthorg/providers/**/*.py : Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError. Non-retryable errors raise immediately without retry. RetryExhaustedError signals that all retries failed — the engine layer catches this to trigger fallback chains.
Applied to files:
src/synthorg/tools/web/web_search.py
📚 Learning: 2026-03-14T16:18:57.267Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-14T16:18:57.267Z
Learning: Applies to src/ai_company/!(observability)/**/*.py : Use DEBUG logging for object creation, internal flow, and entry/exit of key functions.
Applied to files:
src/synthorg/tools/web/web_search.py
🔇 Additional comments (8)
src/synthorg/tools/sub_constraints.py (6)
226-238: Inconsistent handling of unknown access levels in merge path remains.Line 228 falls back to
ToolSubConstraints()(permissive defaults) whenaccess_levelis not in the mapping, but lines 252-261 raiseValueErrorfor the same scenario without custom constraints. If a newToolAccessLevelenum value is added without updating_LEVEL_SUB_CONSTRAINTS, the merge path silently uses permissive defaults as the base—violating fail-closed security principles.🛡️ Proposed fix for consistent error handling
# Non-CUSTOM with overrides: merge into level defaults so # unset fields retain the secure baseline. - base = _LEVEL_SUB_CONSTRAINTS.get(access_level, ToolSubConstraints()) + base = _LEVEL_SUB_CONSTRAINTS.get(access_level) + if base is None: + msg = f"No default sub-constraints for access level: {access_level.value}" + logger.warning( + SUB_CONSTRAINT_RESOLVED, + access_level=access_level.value, + source="error", + error=msg, + ) + raise ValueError(msg) merged = { **base.model_dump(), **custom_constraints.model_dump(exclude_unset=True),
1-24: Module setup and imports look good.Correct use of
get_logger(__name__)for structured logging, proper event constant import from domain-specific module, and immutability support viaMappingProxyTypeandFinal. No forbidden patterns present.
27-95: Enum definitions are well-structured.All five constraint dimension enums follow StrEnum conventions with comprehensive docstrings. The value naming is consistent and the granularity matches the design spec.
101-148: Model configuration follows all guidelines.Properly configured with
frozen=True,allow_inf_nan=False, andextra="forbid"to reject unknown fields at this security boundary. UsingNotBlankStrfor identifier tuples ensures no empty/whitespace-only entries.
151-187: Level defaults mapping is well-designed.Immutable via
MappingProxyTypewithFinaltype hint. The constraint progression fromSANDBOXED(most restrictive) toELEVATED(least restrictive) follows a sensible security hierarchy. Therequires_approvalprefixes correctly use the"category:"format.
240-267: Error handling and default resolution paths are correct.The
CUSTOMlevel without constraints properly raisesValueErrorwith a clear message. Unknown access levels (without custom constraints) also correctly fail withValueError. Logging follows structured kwargs convention with appropriate severity levels (warning for errors, debug for successful resolution).src/synthorg/tools/terminal/base_terminal_tool.py (1)
117-134: Good improvement: blocked-command logs avoid raw command leakageUsing
executable(instead of full command) inTERMINAL_COMMAND_BLOCKEDmaterially reduces sensitive-arg exposure in denial logs. Nice hardening.src/synthorg/tools/web/web_search.py (1)
166-170: Nice defensive normalization of provider items.Coercing each provider item through
SearchResult.model_validate(..., from_attributes=True)is the right hardening layer here. It keeps malformed provider payloads from breaking the whole tool call.
| def _is_command_blocked(self, command: str) -> bool: | ||
| """Check if the command matches any blocklist pattern. | ||
|
|
||
| Args: | ||
| command: The command string to check. | ||
|
|
||
| Returns: | ||
| ``True`` if the command is blocked. | ||
| """ | ||
| normalized = command.strip().lower() | ||
| parts = normalized.split(maxsplit=1) | ||
| executable = parts[0] if parts else "" | ||
| for pattern in self._config.command_blocklist: | ||
| if pattern.lower() in normalized: | ||
| logger.warning( | ||
| TERMINAL_COMMAND_BLOCKED, | ||
| executable=executable, | ||
| pattern=pattern, | ||
| reason="blocklist_match", | ||
| ) | ||
| return True | ||
| return False |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add DEBUG/INFO logs for command-validation flow to match project logging policy
The key validation paths return without INFO/DEBUG flow logs on allowed outcomes. Add DEBUG for entry/decision flow and INFO for state transitions (allow/deny outcomes), not only WARNING on denials.
As per coding guidelines, "All state transitions must log at INFO level" and "Use DEBUG logging for object creation, internal flow, and entry/exit of key functions."
Also applies to: 95-135
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/terminal/base_terminal_tool.py` around lines 67 - 88, Add
DEBUG and INFO logs in _is_command_blocked to follow the logging policy: log
entry into the function with logger.debug including the raw command and
normalized/executable values, log the decision point for each pattern check at
DEBUG, and emit an INFO-level state transition when a command is allowed or
denied (e.g., on block: logger.info with executable, pattern, reason="deny"
before the existing logger.warning; on allow: logger.info with executable,
reason="allow" before returning False). Apply the same pattern to the related
validation functions referenced in the 95-135 range so all entry/exit and
allow/deny transitions use DEBUG for flow and INFO for state changes.
| max_results: int = arguments.get("max_results", 10) | ||
|
|
||
| logger.info(WEB_SEARCH_START, query=query, max_results=max_results) | ||
|
|
||
| try: | ||
| results = await self._provider.search(query, max_results) |
There was a problem hiding this comment.
Validate max_results inside execute().
max_results is trusted twice: it's forwarded to the provider at Line 145, then reused in list(results)[:max_results] at Line 164. If schema validation is bypassed and a caller passes "10" or -1, this either issues a bad outbound request or raises an uncaught TypeError locally. Re-check the type/range here instead of relying on schema metadata alone.
🛡️ Suggested hardening
- query: str = arguments["query"]
- max_results: int = arguments.get("max_results", 10)
+ query: str = arguments["query"]
+ raw_max_results = arguments.get("max_results", 10)
+ if type(raw_max_results) is not int or not 1 <= raw_max_results <= 100:
+ return ToolExecutionResult(
+ content="max_results must be an integer between 1 and 100.",
+ is_error=True,
+ )
+ max_results = raw_max_resultsAs per coding guidelines: "Validate at system boundaries: user input, external APIs, and config files."
Also applies to: 164-164
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/web/web_search.py` around lines 140 - 145, In execute(),
validate and normalize max_results before using it: ensure the value from
arguments is an integer and within an acceptable range (e.g. a positive non-zero
upper bound), otherwise raise a clear ValueError or coerce safely; then use the
validated value (e.g. validated_max_results) when calling
self._provider.search(query, validated_max_results) and when slicing
list(results)[:validated_max_results] to avoid passing strings or negatives to
the provider or causing local TypeErrors in list slicing.
| logger.info(WEB_SEARCH_START, query=query, max_results=max_results) | ||
|
|
||
| try: | ||
| results = await self._provider.search(query, max_results) | ||
| except MemoryError, RecursionError: | ||
| raise | ||
| except Exception as exc: | ||
| logger.warning(WEB_SEARCH_FAILED, query=query, error=str(exc)) | ||
| return ToolExecutionResult( | ||
| content=f"Web search failed: {exc}", | ||
| is_error=True, |
There was a problem hiding this comment.
Redact query/error details before logging or returning them.
query is raw user input and str(exc) comes from an external provider boundary. Writing those values to INFO/WARNING logs and echoing str(exc) back in Line 151 can leak PII, secrets, or provider internals into audit sinks and model-visible output. Log a redacted fingerprint/length plus error_type, and return a generic failure message instead.
Based on learnings: "Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, output scan response policies (redact/withhold/log-only/autonomy-tiered)."
Also applies to: 156-157, 172-176, 181-181, 194-198
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/web/web_search.py` around lines 142 - 152, The code
currently logs and returns raw user query and provider exception strings (see
logger.info, logger.warning, and the ToolExecutionResult content after calling
self._provider.search); change these to never emit raw PII/error text: replace
logger.info(...) and logger.warning(...) calls to log only a redacted
fingerprint (e.g., hash or truncated length) of query and the exception type
(type(exc).__name__) plus any safe metadata, and update returned
ToolExecutionResult messages to a generic failure message (no raw exc or query);
apply the same redaction pattern to the other occurrences noted (the other
logger calls and ToolExecutionResult returns referenced in the comment) so no
raw query/error details are written to logs or returned to callers.
| try: | ||
| results = await self._provider.search(query, max_results) |
There was a problem hiding this comment.
Apply network_policy before calling the provider.
This tool accepts a network_policy, but Line 145 delegates straight to the injected provider. Any provider that performs real HTTP calls can bypass the shared deny/allowlist logic here, unlike src/synthorg/tools/web/http_request.py:117-152, which preflights outbound traffic before making the request. Fail closed before invoking search(), or make the provider interface receive and enforce the policy explicitly.
Based on learnings: "Security: SecOps agent, rule engine (soft-allow/hard-deny, fail-closed)."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/tools/web/web_search.py` around lines 144 - 145, The tool calls
self._provider.search(query, max_results) without enforcing the provided
network_policy; update the WebSearch tool to preflight the network_policy (same
deny/allow logic as in src/synthorg/tools/web/http_request.py preflight) and
block/raise before invoking _provider.search so it fails closed, or change the
provider interface (e.g., provider.search) to accept a network_policy argument
and ensure implementations enforce it; specifically, perform the network_policy
check in the method that calls self._provider.search (or add a network_policy
param to Provider.search) and return/raise on deny so no outbound request can
bypass the policy.
🤖 I have created a release *beep* *boop* --- ## [0.6.4](v0.6.3...v0.6.4) (2026-04-07) ### Features * analytics and metrics runtime pipeline ([#226](#226), [#225](#225), [#227](#227), [#224](#224)) ([#1127](#1127)) ([ec57641](ec57641)) * engine intelligence -- quality signals, health monitoring, trajectory scoring, coordination metrics ([#1099](#1099)) ([aac2029](aac2029)), closes [#697](#697) [#707](#707) [#705](#705) [#703](#703) * enterprise-grade auth -- HttpOnly cookie sessions, CSRF, lockout, session limits ([#1102](#1102)) ([d3022c7](d3022c7)), closes [#1068](#1068) * implement core tool categories and granular sub-constraints ([#1101](#1101)) ([0611b53](0611b53)), closes [#1034](#1034) [#220](#220) * memory evolution -- GraphRAG/consistency research + SelfEditingMemoryStrategy ([#1036](#1036), [#208](#208)) ([#1129](#1129)) ([a9acda3](a9acda3)) * security hardening -- sandbox, risk override, SSRF self-heal, DAST fix ([#1100](#1100)) ([31e7273](31e7273)), closes [#1098](#1098) [#696](#696) [#222](#222) [#671](#671) ### Bug Fixes * harden agent identity versioning post-review ([#1128](#1128)) ([8eb2859](8eb2859)), closes [#1076](#1076) ### Documentation * engine architecture research ([#688](#688) [#690](#690) [#848](#848) [#687](#687)) ([#1114](#1114)) ([59b31f9](59b31f9)) ### Maintenance * add .claudeignore and split CLAUDE.md for token optimization ([#1112](#1112)) ([b0fbd18](b0fbd18)) * bump github.com/sigstore/protobuf-specs from 0.5.0 to 0.5.1 in /cli in the all group ([#1106](#1106)) ([73089c9](73089c9)) * bump jsdom from 29.0.1 to 29.0.2 in /site in the all group ([#1107](#1107)) ([8e99dce](8e99dce)) * bump jsdom from 29.0.1 to 29.0.2 in /web in the all group ([#1108](#1108)) ([ce8c749](ce8c749)) * bump python from `fb83750` to `6869258` in /docker/backend in the all group ([#1104](#1104)) ([4911726](4911726)) * bump python from `fb83750` to `6869258` in /docker/web in the all group ([#1103](#1103)) ([87bdf09](87bdf09)) * bump the all group across 1 directory with 4 updates ([#1111](#1111)) ([f702464](f702464)) * bump the all group in /docker/sandbox with 2 updates ([#1105](#1105)) ([05a91ca](05a91ca)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Implements #1034 (core tool categories) and #220 (granular tool access sub-constraints) as a single cohesive change. Adds 6 new tools across 3 categories, a shared SSRF prevention layer, and a granular sub-constraint enforcement pipeline integrated into the existing tool invoker.
Tool Categories (#1034)
Web tools (
tools/web/)NetworkPolicy, IP blocklist, DNS validation). Redirects disabled to prevent SSRF bypass. Response truncation at configurable max bytes.WebSearchProviderprotocol -- no concrete implementation shipped (inject via MCP bridge or custom provider).html.parser. Strips script/style tags. Operates on pre-fetched content (no HTTP).Database tools (
tools/database/)?mode=ro. Query timeout viaasyncio.wait_for. Table name validation regex for PRAGMA queries.list_tables(sqlite_master) +describe_table(PRAGMA table_info with safe identifier validation).Terminal tools (
tools/terminal/)SandboxBackenddelegation. Command allow/blocklist. Working directory support. Output truncation. Returns error when no sandbox configured.Shared infrastructure
network_validator.py: Extracted SSRF blocklist and DNS validation fromgit_url_validator(backward compatible).NetworkPolicymodel reusable across tool categories. Case-insensitive scheme validation.Granular Sub-Constraints (#220)
Models (
sub_constraints.py)FileSystemScope,NetworkMode,GitAccess,CodeExecutionIsolation,TerminalAccessToolSubConstraintsfrozen Pydantic model with per-level defaults matching operations.md section 11.2get_sub_constraints()resolution with custom override supportEnforcement (
sub_constraint_enforcer.py)SubConstraintEnforcerchecks network (blocks WEB when NONE), terminal (blocks TERMINAL when NONE), git (blocks push for LOCAL_ONLY/READ_AND_BRANCH, blocks clone for LOCAL_ONLY), and requires_approval (escalation for matching action type prefixes)ToolPermissionChecker(optionalsub_constraintsparam)ToolInvokerpipeline between permission check and param validationAgent model
ToolPermissionsgainssub_constraints: ToolSubConstraints | Nonefield for per-agent overridesIntegration
_build_web/database/terminal_toolsbuildersRootConfiggains optionalweb,database,terminalconfig fieldshttpx==0.28.1(async HTTP client)Security Highlights
Test Plan
Review Coverage
Closes #1034
Closes #220