Skip to content

SDK alignment & security hardening: preset system prompt, result logging, rm restrictions (#2) - #242

Open
arkaccess wants to merge 3 commits into
AutoForgeAI:masterfrom
arkaccess:swarm/issue-2-ken
Open

arkaccess wants to merge 3 commits into
AutoForgeAI:masterfrom
arkaccess:swarm/issue-2-ken

Conversation

@arkaccess

Copy link
Copy Markdown

Closes #2

Goes through the SDK alignment and security items in #2. Some were already fixed on master, some are done here, and the rest I skipped on purpose (reasons below).

Changes

#1 Version pinning: claude-agent-sdk>=0.1.39,<0.2.0 was already on master. Added mcp>=1.0.0,<2.0.0 to both requirements files. mcp only came in through the SDK, so a fresh install picked up mcp 2.x. 2.x renamed FastMCP, and python -m mcp_server.feature_mcp crashed on import (ModuleNotFoundError: No module named 'mcp.server.fastmcp'). I reproduced this in a fresh venv.

#2 System prompt preset (client.py): now {"type": "preset", "preset": "claude_code", "append": <our role>} instead of a plain string that replaced Claude Code's built-in prompt.

#3 Message types (agent.py): run_agent_session now also handles:

  • SystemMessage: prints the subtype; init is skipped as noise.
  • ResultMessage: prints subtype, turn count and cost. On an error stop (e.g. error_max_turns, API failure) it also prints the errors/result detail.

This is logging only; the return status and retry flow are unchanged. (ErrorMessage/ProgressMessage don't exist in the Python SDK. rate_limit_event is already handled by the existing MessageParseError retry.)

#4 MCP type: added "type": "stdio" to the features server.

#8 Configurable max_turns: a new AUTOFORGE_MAX_TURNS env var (positive integer) overrides the per-agent-type defaults (300/100/300). An invalid value is ignored with a warning.

#9 MCP readiness (mcp_server/feature_mcp.py): at startup the lifespan now runs SELECT COUNT(*) FROM features. If init, migration or that query fails, it prints [feature_mcp] FATAL: could not initialize feature database for PROJECT_DIR=...: <error> to stderr and exits. Before, the agent got a cryptic "Database not initialized" error on every tool call.

#10 rm hardening (security.py): new validate_rm_command, run on every rm in the command string (including after &&/;/|).

Blocked:

  • targets outside the project dir: /, /*, ~, ~/x, .., ../x, src/../.., absolute paths elsewhere
  • the project root itself (., ./)
  • bare wildcards at the root (*, .*)
  • .git and .autoforge
  • targets that use $VAR or `cmd` expansion
  • --no-preserve-root

It also follows cd in the same command, so cd .. && rm -rf proj and cd / && rm -rf tmp are blocked. After cd $X or cd - the target can't be checked, so rm is blocked there.

Still allowed: rm -rf node_modules, rm -rf dist/*, rm *.log, cd src && rm -rf old, rm -rf .autoforge/tmp.

Not changed (deliberately)

  • Launch multiple subagents #5 Hook signature: the Python SDK (0.1.81) still calls hooks as (input, tool_use_id, context), the same as HookCallback in claude_agent_sdk.types. The current signature is correct, and context is optional anyway.
  • feat: Add parallel subagent execution support #6 Extra hook types: without a concrete use, they would only add noise. PreToolUse and PreCompact cover what we need today.
  • fix: Windows command line length limit errors #7 File checkpointing: agents already commit to git for every feature, which serves as our rollback mechanism. Checkpointing would add overhead, and nothing here calls rewind_files().

Verification

  • python test_security.py: 205 passed, 0 failed. That includes the new test_validate_rm (33 direct cases plus 3 run through the hook with a project context).
  • python test_security_integration.py: all passed.
  • pytest test_client.py test_dependency_resolver.py test_rate_limit_utils.py: 55 passed.
  • ruff check .: clean.
  • mypy .: no new errors. The client.py "unused type: ignore" on effort= was already on master.
  • Started the feature MCP server over stdio. With a valid project dir it answered initialize and created features.db. With a broken one (.autoforge as a file) it printed the FATAL line and exited.
  • No UI changes, so I didn't run the browser check.

Assumptions / notes

  • The claude_code preset is much longer than the old one-line prompt. Very small local models (Ollama) may notice the extra tokens. It's still the SDK-recommended setup, so I didn't add a per-provider special case.
  • Pushing to AutoForgeAI/autoforge returned 403 for this account, so the branch is on the arkaccess fork. This is a cross-repo PR.

🤖 Generated with Claude Code

zwanski-start and others added 3 commits October 22, 2026 03:34
Block rm targets outside the project (/, ~, .., absolute paths), the
project root itself, bare wildcards at the root, .git/.autoforge,
unverifiable $VAR/`cmd` targets and --no-preserve-root. Every rm in a
compound command is checked and preceding cd commands are tracked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Use the claude_code system prompt preset with our role appended
- Declare the features MCP server transport type explicitly (stdio)
- Allow overriding max_turns via AUTOFORGE_MAX_TURNS
- Log SystemMessage and ResultMessage (turns, cost, error stops)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arkaccess

Copy link
Copy Markdown
Author

🔍 QA report: ✅ Passed

Tester: Barbara (cubefarm agent, in a fresh session) · Round: 1 · Author: Ken

Every item from #2 is either done or deliberately skipped with a reason. The preset system prompt, stdio MCP type, AUTOFORGE_MAX_TURNS, SystemMessage/ResultMessage logging, MCP fail-fast and the mcp<2 pin all work as described in my independent probes. The new rm validator blocks every listed dangerous case without breaking normal in-project deletes, and ruff, mypy and pytest show no regressions against master (the 24 mypy errors and 21 pytest failures are the same on master). Non-blocking follow-ups: brace expansion (rm -rf {..,x}) slips past the rm check, bash -c bypasses it as it already did on master, and the contents of .git/.autoforge (rm -rf .git/*) are not protected.

Check Details
✅ #10 rm restricted to project dir (validator + hook) About 60 probes through validate_rm_command and bash_security_hook with project_dir context. Blocked: /, /, ~, ~root, .., ../, ., ./, , ., ?, /, **, .git, .git/, ./.git, src/../.git, .autoforge, .autoforge/, $HOME, pwd, --no-preserve-root, cd .. && rm -rf proj, cd / && rm -rf tmp, cd - && rm, cd; rm x, /bin/rm -rf /, FOO=1 rm -rf /, quoted '/', the absolute project root. Allowed: node_modules, dist/, *.log, .autoforge/tmp, absolute path inside the project, rm -- -x.
✅ rm validator gaps (non-blocking) rm -rf {..,x} and src/{..,..}/../x are allowed because braces are not expanded. rm -rf .git/* and .autoforge/* are allowed. bash -c 'rm -rf /' is allowed, but master allows it too. cd src && rm -rf old is allowed by the validator but blocked by the hook because cd is not in the global allowlist, which is also master behaviour.
✅ #2 system prompt preset create_client with ClaudeSDKClient mocked: system_prompt == {type: preset, preset: claude_code, append: }, matching SDK 0.1.81 SystemPromptPreset.
✅ #4 MCP stdio type mcp_servers['features']['type'] == 'stdio', matching McpStdioServerConfig.
✅ #8 AUTOFORGE_MAX_TURNS Unset gives 300 coding / 100 testing. '42' and ' 7 ' override both. '0', '-3' and 'abc' print a warning and keep the defaults.
✅ #3 SystemMessage/ResultMessage logging Fed real SDK message objects into run_agent_session: init not printed, compact_boundary printed, error_max_turns prints turns, cost and error detail, a success result with no cost omits the cost. Return value is still ('continue', '').
✅ #9 MCP fail-fast readiness With a valid PROJECT_DIR the server answers the initialize JSON-RPC and creates features.db. With .autoforge as a file it prints '[feature_mcp] FATAL: could not initialize feature database ... FileExistsError' and exits with code 1.
✅ #1 mcp<2 pin Fresh Python 3.12 venv from requirements.txt resolves mcp 1.30.0 and claude-agent-sdk 0.1.81, and the feature MCP server imports and runs.
✅ No regressions vs master (mypy/pytest) Ran both on a throwaway master worktree. mypy shows the same 24 errors (only a line-number shift in client.py). pytest shows the same 21 failures in test_devserver_security.py on master and on the PR; the PR adds one passing test.
✅ Server boot + UI smoke test uvicorn on 127.0.0.1:5443: /api/health 200, /api/projects 200, invalid project 404, / 200. The home page renders with 0 console errors. No UI files changed, so e2e was skipped.
🧪 Commands run
Command Result
uv venv -p 3.12 && uv pip install -r requirements.txt ok (mcp 1.30.0, claude-agent-sdk 0.1.81)
ruff check . All checks passed
mypy . 24 errors, same as origin/master (pre-existing)
python test_security.py 205 passed, 0 failed
python test_security_integration.py 9 passed, 0 failed
python -m pytest -q 21 failed, 145 passed; master gives 21 failed, 144 passed with the same failures in test_devserver_security.py
PYTHONPATH=. python probe.py / probe2.py (rm validator + hook probes) all listed dangerous cases blocked; brace-expansion gap noted
PYTHONPATH=. python probe3.py (create_client options + run_agent_session messages) expected behaviour
PROJECT_DIR=<good|bad> python -m mcp_server.feature_mcp (stdio initialize) good: initialize OK + features.db; bad: FATAL line, exit 1
npm --prefix ui run build success
npm --prefix ui run lint 0 errors, 6 warnings
uvicorn server.main:app --port 5443 + curl health 200, projects 200, invalid project 404, / 200

No browser screenshots were taken in this round.

Posted by cubefarm · merges automatically once GitHub's checks pass

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.

SDK Alignment & Security Improvements (Claude Agent SDK 0.1.x)

2 participants