Change default access mode to RESTRICTED and warn on UNRESTRICTED startup - #193
joergmichno wants to merge 2 commits into
Conversation
The server defaulted to UNRESTRICTED, exposing execute_sql with full write access to the LLM. Combined with unfiltered SQL pass-through, prompt-injection payloads in agent-read content can reach the database verbatim (crystaldba#164). - --access-mode now defaults to restricted (module default aligned) - explicit --access-mode unrestricted logs a startup warning (destructive SQL + prompt-injection risk) - README: document restricted as the default mode Users passing --access-mode=unrestricted explicitly (as in all README examples) see no functional change beyond the warning.
jssmith
left a comment
There was a problem hiding this comment.
Review by OpenHands agent — concise
Approve. Good security posture improvement.
- Defaulting to
RESTRICTEDis the right call — the prompt-injection →execute_sqlpath is real and the existingSafeSqlDriveralready mitigates it. - Module-level default aligned with argparse default — consistent, and safe if
current_access_modeis read beforemain()runs. - Startup warning for
UNRESTRICTEDis clear and actionable. - README update is accurate; existing examples that pass
--access-mode=unrestrictedexplicitly keep working.
Suggestion (non-blocking)
- Add a test verifying that omitting
--access-moderesults inRESTRICTED(the existingtest_command_line_parsingpasses the flag explicitly, so the new default behavior isn't covered). A simplesys.argvwithout--access-modeassertingcurrent_access_mode == AccessMode.RESTRICTEDwould lock in the core behavior change.
This review was posted by an AI agent (OpenHands) on behalf of the repository maintainers.
Review — conciseVerdict: Request changes. The security rationale is sound and the default flip is the right direction, but the warning message has an encoding issue that will garble output in some terminals. Default change — correct
Startup warning — encoding problemlogger.warning(
"⚠️ UNRESTRICTED mode is active: ..."The README update — good
Nits (non-blocking)
SummaryThe only blocking issue is the emoji in the log message. Fix that and this is ready to merge. This review was created by an AI agent (OpenHands) on behalf of @jssmith. |
jssmith
left a comment
There was a problem hiding this comment.
Review: Change default access mode to RESTRICTED
Verdict: Approve with discussion point.
Assessment
Changing the default from UNRESTRICTED to RESTRICTED is a good security-by-default decision. The prompt-injection risk described in the PR body (content the agent reads reaching execute_sql unfiltered) is real.
Strengths
- The startup warning for UNRESTRICTED mode is well-written — concise, names the specific risks (destructive SQL + prompt injection), and points to the fix.
- README update is clear — documents restricted as default, notes the warning, and existing examples that pass
--access-mode=unrestrictedstill work. - Test suite passes — existing mode tests pass the flag explicitly so they're unaffected.
Discussion point
This is a breaking behavior change for users who start the server without --access-mode. Any user currently relying on the implicit unrestricted default will find their agent can no longer write to the database after upgrading. The PR acknowledges this, but consider:
- A migration note in the README or CHANGELOG would help users understand why their agent suddenly can't write.
- The warning text uses an emoji (
⚠️ ) which may render oddly in some log formats. Minor.
This is the right direction — secure defaults matter. The breakage is acceptable given the security improvement.
This review was created by an AI agent (OpenHands) on behalf of @jssmith.
jssmith
left a comment
There was a problem hiding this comment.
Review — concise
Verdict: Request changes. Good security-first direction, but this is a breaking change that needs migration considerations.
Strengths
- Changing the default to RESTRICTED follows the principle of least privilege. Good security posture.
- Startup warning for UNRESTRICTED mode is well-written — mentions prompt-injection risk from external content.
- README updated to reflect the new default and warning.
- Help text updated to reflect new default.
Issues to address
1. Breaking change for existing users (blocking)
Every existing deployment that doesn't explicitly set --access-mode=unrestricted will silently switch from full read/write to read-only. Users who rely on write operations (INSERT/UPDATE/DELETE/DDL) will see their agent suddenly unable to modify data, with no error at startup — only tool call failures at runtime. This needs:
- A prominent changelog/release note
- Consider a deprecation cycle: log a warning when
--access-modeis not explicitly set and default to UNRESTRICTED with a deprecation notice, then switch the default in the next major version - Or at minimum, a clear startup log message when running in the new default RESTRICTED mode that says "Running in restricted (read-only) mode. Pass --access-mode=unrestricted for write access."
2. No test updates (blocking)
The test_command_line_parsing test in test_access_mode.py asserts the default mode. Verify it still passes — if it expected UNRESTRICTED as default, it needs updating.
3. Warning message uses emoji (non-blocking)
The ⚠️ emoji in the log warning may not render correctly in all log sinks (syslog, journald, file logs in some encodings). Consider using a text-only prefix like [WARNING] or [SECURITY].
4. AccessMode enum order (non-blocking)
The enum still lists UNRESTRICTED first, RESTRICTED second. Consider reordering to list RESTRICTED first to reflect the new default priority. Not blocking but improves readability.
This review was created by an AI agent (OpenHands) on behalf of @jssmith.
- Log a startup info line when running in restricted mode so a silent read-only downgrade is visible (review request by jssmith) - Replace the emoji warning prefix with a syslog-safe [SECURITY] prefix - Order the AccessMode enum RESTRICTED-first to reflect the new default - README: add a prominent breaking-change note under Access Mode - tests: cover that omitting --access-mode resolves to RESTRICTED
|
@jssmith Thanks for the thorough review — both blocking points are addressed in f20a7cc:
Also picked up two nits while the branch was open: the warning prefix is now syslog-safe ([SECURITY]\ instead of the emoji), and the \AccessMode\ enum lists RESTRICTED first to match the new default. Full suite green: \pytest tests/unit\ -> 168 passed, 24 skipped, 1 xfailed. Ready for re-review. |
Motivation
Following up on #164: the server currently defaults to
UNRESTRICTED, whichexposes
execute_sqlwith full write capabilities to the LLM. Because thesqlparameter is passed through without filtering, any prompt-injectionpayload in content the agent reads (web pages, Confluence, emails) can reach
the database verbatim — a cross-tool injection path from one poisoned page to
DROP TABLE/DELETEon a production database.The existing
SafeSqlDriver(force_readonly=True) already solves this well;it just isn't the default.
Changes
--access-modenow defaults torestricted(module-level defaultaligned in
server.py).--access-mode unrestrictedprints a startup warning coveringdestructive SQL and the prompt-injection risk, and points to the restricted
mode.
default (the configuration examples already pass
--access-mode=unrestrictedexplicitly, so they keep working unchanged).
Backwards compatibility
This is a deliberate behavior change for users who relied on the implicit
default. Users who start the server with
--access-mode=unrestricted(as inall README examples) see no functional change beyond the new startup warning.
Tests
pytest tests/unit(Windows, Python 3.12): 167 passed, 24 skipped(DB-dependent), 1 xfailed — including all access-mode tests and the
Python-3.12-only
tests/unit/index/test_dta_calc.py(PEP 701).(same suite minus the 3.12-only file).
Out of scope (happy to follow up)
e.g. blocking DROP/ALTER/TRUNCATE unless explicitly enabled) deserves its
own PR with a proper design discussion.
Static analysis only; no live databases were tested.