mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
73 lines
4.3 KiB
Python
73 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Real native agent + MCP + shell under Sandpit, with a local scripted model.
|
|
|
|
No real model, credentials, relay, or production user data are used. This proves
|
|
native tool plumbing and confinement; it is not the live UI conversation test.
|
|
"""
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shlex
|
|
import subprocess
|
|
import tempfile
|
|
import threading
|
|
|
|
repo = Path(__file__).resolve().parents[1]
|
|
root = Path(tempfile.mkdtemp(prefix='buzz-native-security-', dir='/tmp')).resolve()
|
|
for name in ['home', 'work', 'protected', 'outside', 'control']:
|
|
(root / name).mkdir()
|
|
(root / 'protected/secret').write_text('synthetic')
|
|
(root / 'protected/keep').write_text('original')
|
|
(root / 'work/secret-link').symlink_to(root / 'protected/secret')
|
|
node = subprocess.check_output([str(repo / 'bin/node'), '-p', 'process.execPath'], text=True).strip()
|
|
probe_command = shlex.join([node, str(repo / 'scripts/fixtures/sandpit-acp-probe.cjs'), '--probe', '--root', str(root)])
|
|
requests = []
|
|
|
|
class Model(BaseHTTPRequestHandler):
|
|
def log_message(self, *_args):
|
|
pass
|
|
|
|
def do_POST(self):
|
|
request = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
|
|
requests.append(request)
|
|
if len(requests) == 1:
|
|
names = [tool['function']['name'] for tool in request['tools']]
|
|
shell = next(name for name in names if name.endswith('__shell') or name == 'shell')
|
|
message = {'role': 'assistant', 'content': None, 'tool_calls': [{'id': 'probe', 'type': 'function', 'function': {'name': shell, 'arguments': json.dumps({'command': probe_command, 'workdir': str(root / 'work')})}}]}
|
|
finish = 'tool_calls'
|
|
else:
|
|
message = {'role': 'assistant', 'content': 'Scripted model: probe complete.'}
|
|
finish = 'stop'
|
|
body = json.dumps({'id': 'local-scripted-model', 'object': 'chat.completion', 'model': 'scripted-test', 'choices': [{'index': 0, 'message': message, 'finish_reason': finish}]}).encode()
|
|
self.send_response(200)
|
|
self.send_header('Content-Type', 'application/json')
|
|
self.send_header('Content-Length', str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
server = ThreadingHTTPServer(('127.0.0.1', 0), Model)
|
|
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
values = {'BUZZ_AGENT_PROVIDER': 'openai', 'OPENAI_COMPAT_API_KEY': 'synthetic-test', 'OPENAI_COMPAT_MODEL': 'scripted-test', 'OPENAI_COMPAT_BASE_URL': f'http://127.0.0.1:{server.server_port}', 'BUZZ_AGENT_MAX_ROUNDS': '3', 'BUZZ_AGENT_NO_HINTS': '1', 'BUZZ_SANDBOX_FIXTURE': str(root)}
|
|
policy = {'schema_version': 1, 'writable_roots': [str(root / 'work')], 'denied_reads': [str(root / 'protected')], 'denied_writes': [str(root / 'protected')], 'network': {'mode': 'allowlist', 'destinations': [f'localhost:{server.server_port}']}, 'environment': list(values)}
|
|
source = root / 'control/policy.json'
|
|
source.write_text(json.dumps(policy))
|
|
status = root / 'control/protected'
|
|
env = {'HOME': str(root / 'home'), 'PATH': os.environ['PATH'], 'TMPDIR': str(root), 'BUZZ_ACP_SECURITY_STATUS': str(status), **values}
|
|
try:
|
|
result = subprocess.run([str(repo / 'target/debug/buzz-acp'), 'sandbox-chat', '--policy', str(source), '--agent-command', str(repo / 'target/debug/buzz-agent'), '--mcp-command', str(repo / 'target/debug/buzz-dev-mcp'), '--workspace', str(root / 'work'), '--prompt', 'Execute the disposable probe once.'], env=env, text=True, capture_output=True, timeout=60)
|
|
(root / 'transcript.txt').write_text(result.stdout + result.stderr)
|
|
(root / 'model-requests.json').write_text(json.dumps(requests, indent=2))
|
|
assert result.returncode == 0, result.stderr
|
|
report = json.loads((root / 'work/report.json').read_text())
|
|
assert len(requests) == 2, len(requests)
|
|
assert len(status.read_text()) == 64
|
|
assert report['allowed_write'] == 'allowed'
|
|
for key in ['read', 'write', 'outside_write', 'hardlink', 'symlink_read', 'policy_tamper', 'empty_environment_read', 'socket']:
|
|
assert report[key] in ['EACCES', 'EPERM'], (key, report)
|
|
assert (root / 'protected/keep').read_text() == 'original'
|
|
print(f'PASS native Buzz agent → local scripted model → MCP → real shell, with eight enforced denials.\nArtifacts: {root}')
|
|
finally:
|
|
server.shutdown()
|
|
server.server_close()
|