Skip to content

security: comprehensive hardening for external deployment#3055

Closed
Skyfly2 wants to merge 62 commits into
HKUDS:mainfrom
JFamo:security/hardening
Closed

security: comprehensive hardening for external deployment#3055
Skyfly2 wants to merge 62 commits into
HKUDS:mainfrom
JFamo:security/hardening

Conversation

@Skyfly2
Copy link
Copy Markdown

@Skyfly2 Skyfly2 commented Apr 11, 2026

Summary

Comprehensive security hardening across nanobot to prepare for external deployment where untrusted users may attempt prompt injection, command execution, or network pivoting.

Critical

  • Tool confirmation flow: Implement PendingAction confirmation in AgentRunner._run_tool() — tools with requires_confirmation=True now queue for user approval instead of executing immediately when confirmation_supported=True
  • Execute-tool endpoint: Block dangerous tools (exec, write_file, edit_file, read_file, MCP tools) from direct invocation via /agent/execute-tool

High

  • Gateway auth required: NANOBOT_API_KEY is now mandatory for gateway mode (exit 1 if unset)
  • Constant-time auth: Use hmac.compare_digest for API key comparison
  • Exec disabled by default: ExecToolConfig.enable defaults to False
  • Workspace restriction: restrict_to_workspace defaults to True
  • Sandbox hardening: Add --unshare-net to bwrap for network isolation; minimize /dev mount

Medium

  • Container security: Remove SYS_ADMIN + seccomp/apparmor=unconfined defaults from docker-compose; bind gateway to 127.0.0.1
  • SSRF hardening: validate_resolved_url now denies on DNS resolution failure; validate_url_target returns pinned IPs for DNS rebinding protection
  • PII redaction: Redact user message content from API request logs

Test plan

  • Verify gateway refuses to start without NANOBOT_API_KEY
  • Verify /agent/execute-tool returns 403 for exec, write_file, MCP tools
  • Verify requires_confirmation tools queue PendingAction when confirmation_supported=True
  • Verify exec tool is disabled by default in fresh config
  • Run existing test suite to check for regressions

Made with Cursor

JFamo and others added 30 commits February 28, 2026 01:02
The identity preamble previously claimed 'You are nanobot, a helpful AI
assistant' which conflicted with the personalized SOUL.md content (e.g.
'I am Weasel 🔧'). The LLM received two competing identity statements,
causing confusion on welcome and potential name-prompting behaviour.

Replace with a neutral preamble that explicitly tells the agent its name
and personality live in SOUL.md, and to use that as the sole authority.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… set

When the coordinator invokes the agent via docker exec, enable full
DEBUG logging to a persistent file in the nanobot volume. Inspect with:

  docker exec <container> tail -f /root/.nanobot/agent.log

stderr is also retained so the coordinator can capture it via demux.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --api-key / NANOBOT_API_KEY option to the gateway command. When set,
starts an aiohttp server on the configured port with a single route:
POST /agent/run (requires Authorization: Bearer <key>).

Requests route through the live agent loop and in-memory CronService,
fixing the cron job scheduling bug caused by docker exec subprocesses
writing to jobs.json without notifying the gateway's running scheduler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs in on_cron_job:
1. Used `cron:{job.id}` as session key — a fresh isolated session with no
   personality or history, causing the agent to not know who it is.
   Now uses `{channel}:{chat_id}` (e.g. telegram_relay:7775855639) for
   deliver jobs so the agent has full context.

2. Passed job.payload.message raw as user input — the agent treated
   "go outside" as a command, not a reminder to deliver. Now framed as:
   "[SCHEDULED REMINDER] ... Deliver this message to the user in your own
   voice: {message}"

Also tightened CronTool descriptions to clarify:
- message should be the notification text ("Time to go outside!", not "go outside")
- every_seconds is for recurring tasks only, never one-time reminders
- one-time reminders always use `at` with a computed ISO datetime

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
logger was only imported inside the `if verbose:` block, causing a
NameError when `verbose=False` and the HTTP API server tried to log
its startup message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… acting

The previous guideline "State intent before tool calls" caused the model
to narrate planned actions to the user without actually making the tool
call. Replaced with a directive to call tools immediately and report
results naturally after they return.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Authorization: Bearer <NANOBOT_API_KEY> header to:
- Google Workspace tool HTTP calls (GoogleBaseTool._post)
- X/Twitter tool HTTP calls (XBaseTool._post)
- Telegram relay channel outbound messages (TelegramRelayChannel.send)

The coordinator now validates this header on all internal endpoints.
Falls back gracefully (no header) if the env var is not set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add coordinator/client.py with auth_headers() and post() that automatically
include Authorization: Bearer <NANOBOT_API_KEY> on every outbound
coordinator call.

- GoogleBaseTool._post and XBaseTool._post delegate to coordinator.client.post
- TelegramRelayChannel constructs its persistent httpx.AsyncClient with
  auth_headers() as default headers — auth is baked in, retry loop unchanged

New tools/channels calling the coordinator just use coordinator.client.post()
or pass auth_headers() to their client — no need to think about the token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Skyfly2 and others added 24 commits March 15, 2026 22:11
Prevents commit template file from being tracked.

Co-authored-by: Joshua Famous <joshua.famous@gmail.com>
- Fix _build_personality_learning_guidance() to reference memory tools
  (update_bot_identity, update_user_profile, save_memory) instead of
  edit_file/read_file for SOUL.md and USER.md, eliminating conflict with
  coordinator-generated AGENTS.md instructions
- Preserve <follow_ups> block from suppressed final content in
  process_direct() when MessageTool was used, so the coordinator can
  extract suggestions from the done SSE event

Made-with: Cursor
fix: resolve setup mode tool conflicts and preserve follow-ups
Merge upstream main branch into our fork, adopting:
- Native provider registry (replacing litellm)
- AgentRunner architecture with hooks, checkpoints, mid-turn injection
- Dream memory consolidation system
- AutoCompact session management
- New tools: Grep, Glob, NotebookEdit, Sandbox
- Channel auto-discovery registry
- Command router with slash commands
- Streaming support via hooks (on_stream, on_stream_end)

Re-integrated fork-specific features on top of upstream:
- Coordinator client (coordinator/client.py)
- Usage tracking (providers/tracked.py) wrapping upstream's retry APIs
- OAuth scope sync (scope_registry, sync_scoped_tools, _fetch_and_sync_scopes)
- Google/X/GitHub API tools with coordinator proxy
- Telegram relay and web relay channels
- SSE streaming gateway endpoints (/agent/run/stream)
- Custom gateway HTTP API (/agent/run, /agent/sync-scopes,
  /agent/consolidate-session, /agent/identity, /agent/execute-tool)
- Web relay auto-enable from COORDINATOR_URL/BOT_ID env vars
- process_direct enhanced with on_message, confirmation, AgentResponse
- MessageTool._sent_contents tracking for SSE content capture
- Memory tools (UpdateBotIdentity, UpdateUserProfile, SaveMemory)
  with coordinator identity notification
- TelegramRelayConfig and WebRelayConfig in config schema

Made-with: Cursor
The upstream AgentRunner handles tool execution internally, so the
fork's inline bot_name_updated event emission was lost in the merge.
Wire on_stream_event from process_direct() to UpdateBotIdentityTool
so the event flows through the SSE stream to the coordinator and
wizard during setup.

Made-with: Cursor
- Add requires_confirmation property to Tool base class (default False)
  and override to True on ExecTool, WriteFileTool, EditFileTool
- Update test_restart_command to expect AgentResponse instead of
  OutboundMessage from process_direct()
- Fix test_message_tool_suppress to mock chat_with_retry (not chat)
  and use non-blank final response to avoid runner empty-retry

Made-with: Cursor
feat: merge upstream HKUDS/nanobot v0.1.5
- Implement PendingAction confirmation flow in runner/loop for
  requires_confirmation tools (exec, write_file, edit_file)
- Block dangerous tools from /agent/execute-tool direct invocation
- Require NANOBOT_API_KEY for gateway mode; use constant-time comparison
- Default exec tool to disabled; default restrict_to_workspace to true
- Add --unshare-net to bwrap sandbox for network isolation
- Minimize /dev mount in bwrap to /dev/null only
- Bind gateway port to 127.0.0.1 in docker-compose
- Remove SYS_ADMIN and seccomp/apparmor=unconfined from defaults
- Fix SSRF: validate_resolved_url now denies on resolution failure
- Return pinned resolved IPs from validate_url_target for DNS pinning
- Update all callers for new validate_url_target signature
- Redact user message content from API logs

Made-with: Cursor
@JackLuguibin
Copy link
Copy Markdown
Contributor

Safety issues are indeed very important.

@Skyfly2 Skyfly2 closed this Apr 12, 2026
@Skyfly2 Skyfly2 deleted the security/hardening branch April 12, 2026 00:29
@Skyfly2
Copy link
Copy Markdown
Author

Skyfly2 commented Apr 12, 2026

sorry, my agent accidentally opened this PR on this repo instead of my fork. a lesson to always check the commands i let my agents run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants