Skip to content

Commit 56377ad

Browse files
committed
Clean up obsolete prompts, document sandbox configurations, and add GCE prerequisites
- Delete 4 obsolete prompts in reference/prompts/ while retaining system-researcher.md as canonical standalone prompt example. - Document no-skill / custom system_prompt alternative in reference/README.md. - Document golden-image prerequisites (passwordless sudo and python3 for isolation probe) in docs/gce_sandbox_setup.md. - Document static-only, gvisor, microsandbox, and gce sandbox configurations with a comparison table in reference/README.md. - Increase sandbox timeout_seconds default to 600s across workflow.json and documentation. - Add fast preflight failure check in GceEnvironment for unconfigured default project placeholders (e.g. YOUR_PROJECT_ID). - Add unit tests for no-skill prompt loading and placeholder project validation in reference/test_suite.py. TAG=agy CONV=3fecc2dd-b200-4a0c-b4c0-3a0868b5cc2a Change-Id: If77748bc775a84411406604b750415de138dc933
1 parent b0de7ca commit 56377ad

9 files changed

Lines changed: 248 additions & 53 deletions

File tree

‎reference/README.md‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,31 @@ This directory contains a reference implementation of Mantis built directly on
44
top of the **Agent Development Kit (ADK)** using the full suite of canonical
55
**Mantis Skills** and **isolated sandboxed execution environments**.
66

7+
## Getting Started
8+
9+
First, install python3-venv such as with `sudo apt install python3-venv`, then
10+
run the install script, and the Mantis pipeline as below. Before you do that,
11+
consider whether you want to use the default isolated GCE VM reproduction
12+
pipeline or whether you'd prefer another mechanism like gVisor/microsandbox or
13+
even static. Update `workflow.json` based on your choices. Ask a coding agent
14+
like antigravity or opencode or anything else to help you write a workflow
15+
configuration that works for you.
16+
17+
```bash
18+
cd reference && ./install.sh
19+
export GOOGLE_CLOUD_PROJECT=your-gcp-project # required: default model is vertex_ai/*
20+
gcloud auth application-default login # if using ADC credentials
21+
./run.sh path/to/code # a file or a directory
22+
```
23+
24+
Once you have run it you can add the mantis-advise skill to your favorite coding
25+
agent and use that while developing your code to have your coding agent attempt
26+
to create fewer vulnerabilities. To try it manually you can run the script:
27+
28+
```
29+
python3 scripts/advise.py --file path/to/file.py # query accumulated knowledge
30+
```
31+
732
## Core Pipeline Stages
833

934
The pipeline in `workflow.json` orchestrates 16 canonical Mantis skills across
@@ -56,6 +81,80 @@ The reference harness implements ADK's `BaseEnvironment` interface:
5681
Networkless (`--network=none`), container-isolated filesystem at `/workspace`.
5782
- **`StaticOnlyEnvironment`**: Safe no-op environment for static-only scans.
5883

84+
### Configuring the Sandbox Backend in `workflow.json`
85+
86+
To change the sandbox backend, update the `"config.sandbox"` block in
87+
[`workflow.json`](workflow.json):
88+
89+
#### 1. Static-Only (`"static-only"`)
90+
91+
Zero dependencies. Dynamic exploit execution and patch testing are skipped.
92+
93+
```json
94+
"sandbox": {
95+
"type": "static-only"
96+
}
97+
```
98+
99+
#### 2. gVisor (`"gvisor"`)
100+
101+
Local OCI container isolation via Docker/Podman with gVisor `runsc` and
102+
`--network=none`.
103+
104+
```json
105+
"sandbox": {
106+
"type": "gvisor",
107+
"options": {
108+
"image": "mantis-sandbox:latest",
109+
"runtime": "runsc",
110+
"timeout_seconds": 600
111+
}
112+
}
113+
```
114+
115+
#### 3. MicroSandbox (`"microsandbox"`)
116+
117+
In-process hardware microVM isolation via `libkrun` and `Network.none()`.
118+
119+
```json
120+
"sandbox": {
121+
"type": "microsandbox",
122+
"options": {
123+
"image": "mantis-sandbox:latest",
124+
"timeout_seconds": 600
125+
}
126+
}
127+
```
128+
129+
#### 4. Hardened GCE VM (`"gce"`)
130+
131+
Ephemeral cloud VM in an isolated VPC with link-local DNS blackholing.
132+
133+
```json
134+
"sandbox": {
135+
"type": "gce",
136+
"options": {
137+
"project": "YOUR_PROJECT_ID",
138+
"zone": "us-central1-b",
139+
"image": "mantis-sandbox-image",
140+
"subnet": "mantis-isolated-subnet",
141+
"workdir": "/workspace",
142+
"tunnel_through_iap": true,
143+
"no_service_account": true,
144+
"no_external_ip": true,
145+
"verify_isolation": true,
146+
"timeout_seconds": 600
147+
}
148+
}
149+
```
150+
151+
| Sandbox Type | Dynamic Execution | Prerequisites |
152+
| :------------------- | :---------------: | :-------------------------------------------------- |
153+
| **`"static-only"`** | ❌ | None |
154+
| **`"gvisor"`** | ✅ | Docker/Podman + `runsc` runtime |
155+
| **`"microsandbox"`** | ✅ | Hardware virtualization (`/dev/kvm`) |
156+
| **`"gce"`** | ✅ | GCP Project, Isolated VPC/Subnet, Custom Disk Image |
157+
59158
### Quickstart: Isolated GCE Sandbox Setup
60159

61160
An automated setup script is provided at
@@ -164,3 +263,23 @@ tools:
164263
When compiled by `core/graph_loader.py`, each skill is loaded via
165264
`google.adk.skills.load_skill_from_dir` and attached to the agent as a
166265
`SkillToolset` connected to the active sandboxed environment.
266+
267+
### No-Skill / Custom System Prompt Alternative
268+
269+
As an alternative to loading a canonical Mantis skill directory,
270+
`core/graph_loader.py` also supports configuring an agent node with a custom
271+
markdown prompt file via `system_prompt` (such as
272+
[`prompts/system-researcher.md`](prompts/system-researcher.md)):
273+
274+
```json
275+
{
276+
"id": "researcher",
277+
"type": "agent",
278+
"system_prompt": "prompts/system-researcher.md",
279+
"tools": ["read_file", "write_file", "list_files", "report_findings", "get_findings"]
280+
}
281+
```
282+
283+
When `system_prompt` is specified instead of `skill`, `core/graph_loader.py`
284+
loads the agent's instructions directly from the given file and attaches the
285+
specified tools directly to the agent without instantiating a `SkillToolset`.

‎reference/core/environments/gce_env.py‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,24 @@ async def preflight(self) -> None:
183183
f"sandbox type 'gce' requires '{self.gcloud_bin}' on PATH. "
184184
"Install Google Cloud SDK or set sandbox.type to 'static-only'."
185185
)
186+
if self.project and self.project.strip().upper() in {"YOUR_PROJECT_ID", "YOUR_PROJECT", "<YOUR_PROJECT_ID>"}:
187+
raise ValueError(
188+
f"GCE sandbox project is set to default placeholder '{self.project}'. "
189+
"Update 'options.project' in workflow.json with your actual GCP Project ID, or configure sandbox.type to 'static-only' or 'microsandbox'."
190+
)
186191
if not self.project:
187192
rc, out = self._run_gcloud(["config", "get-value", "project"])
188193
if rc != 0 or not out.strip() or "unset" in out:
189194
raise ValueError(
190195
"GCP project not specified for GCE VM sandbox. Set options.project or GOOGLE_CLOUD_PROJECT env."
191196
)
192-
self.project = out.strip()
197+
resolved_proj = out.strip()
198+
if resolved_proj.upper() in {"YOUR_PROJECT_ID", "YOUR_PROJECT", "<YOUR_PROJECT_ID>"}:
199+
raise ValueError(
200+
f"GCE sandbox project is set to default placeholder '{resolved_proj}'. "
201+
"Update 'options.project' in workflow.json with your actual GCP Project ID, or configure sandbox.type to 'static-only' or 'microsandbox'."
202+
)
203+
self.project = resolved_proj
193204

194205
rc, out = self._run_gcloud(["auth", "list", "--filter=status:ACTIVE", "--format=value(account)"])
195206
if rc != 0 or not out.strip():

‎reference/docs/gce_sandbox_setup.md‎

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,56 @@ export DEV_BUILD_VM="my-dev-build-vm"
3232

3333
______________________________________________________________________
3434

35-
## 1. Assessment Disk Image Creation
35+
## 1. Assessment Disk Image Creation & Golden Image Prerequisites
36+
37+
Before capturing your golden assessment disk image from the source build VM,
38+
ensure the following prerequisites are installed and configured:
39+
40+
### Golden Image Prerequisites
41+
42+
1. **Passwordless `sudo` (`NOPASSWD`)**:
43+
44+
- During instance initialization, `GceEnvironment` provisions the guest
45+
workspace directory via SSH (`core/environments/gce_env.py:269`):
46+
```bash
47+
sudo mkdir -p /workspace && sudo chown -R $(whoami) /workspace
48+
```
49+
- The SSH user connecting to the sandbox VM must have passwordless `sudo`
50+
rights in `/etc/sudoers` or `/etc/sudoers.d/` (e.g. standard
51+
`%sudo ALL=(ALL) NOPASSWD:ALL` or `%admin ALL=(ALL) NOPASSWD:ALL`).
52+
Standard GCP Ubuntu LTS images configure this by default for the
53+
provisioning user.
54+
55+
2. **`python3` Interpreter**:
56+
57+
- `GceEnvironment` runs automated active in-guest security isolation audits
58+
(`_verify_guest_isolation()` in `core/environments/gce_env.py:338`) by
59+
piping a base64-encoded isolation verification script directly to
60+
`python3`:
61+
```bash
62+
echo '<probe_script>' | base64 -d | python3 -
63+
```
64+
- Ensure `python3` (or `python3-minimal`) is installed on the image and
65+
available in `PATH` (`/usr/bin/python3`). Without `python3`, instance
66+
startup fails fail-closed during guest isolation verification.
67+
68+
3. **Pre-Warmed Build Tools & Dependencies**:
69+
70+
- Because the isolated VPC has zero external internet routing and no Cloud
71+
NAT, all compilers, build runtimes, package manager caches, test
72+
frameworks, and dependencies needed to compile the target repository and
73+
reproduce vulnerabilities must be pre-installed and pre-built on the VM
74+
before capturing the disk image.
75+
76+
### Image Capture Workflow
3677

3778
1. Provision a development VM with all necessary build runtimes, compilers,
3879
package managers, and test suites.
39-
2. Build your target repository on the VM to warm all local build caches,
80+
2. Confirm that passwordless `sudo` and `python3` are configured as described
81+
above.
82+
3. Build your target repository on the VM to warm all local build caches,
4083
dependencies, and artifacts.
41-
3. Capture a custom disk image from the source build VM:
84+
4. Capture a custom disk image from the source build VM:
4285

4386
```bash
4487
gcloud compute images create "${IMAGE_NAME}" \

‎reference/prompts/system-calibrator.md‎

Lines changed: 0 additions & 10 deletions
This file was deleted.

‎reference/prompts/system-patcher.md‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎reference/prompts/system-reproducer.md‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎reference/prompts/system-reviewer.md‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎reference/test_suite.py‎

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,15 +960,22 @@ async def test_gce_sandbox_lifecycle_and_security_hardening(self):
960960
await sb_no_proj.preflight()
961961
self.assertIn("GCP project not specified", str(ctx_proj.exception))
962962

963-
# 2c. Fails when no active gcloud authentication
963+
# 2c. Fails when GCP project is set to default placeholder (e.g. YOUR_PROJECT_ID)
964+
sb_placeholder = GceSandbox(project="YOUR_PROJECT_ID", gcloud_bin="/usr/bin/gcloud")
965+
with patch("shutil.which", return_value="/usr/bin/gcloud"):
966+
with self.assertRaises(ValueError) as ctx_ph:
967+
await sb_placeholder.preflight()
968+
self.assertIn("default placeholder 'YOUR_PROJECT_ID'", str(ctx_ph.exception))
969+
970+
# 2d. Fails when no active gcloud authentication
964971
sb_auth_fail = GceSandbox(project="test-proj", gcloud_bin="/usr/bin/gcloud")
965972
sb_auth_fail._run_gcloud = MagicMock(return_value=(0, ""))
966973
with patch("shutil.which", return_value="/usr/bin/gcloud"):
967974
with self.assertRaises(RuntimeError) as ctx_auth:
968975
await sb_auth_fail.preflight()
969976
self.assertIn("No active Google Cloud authentication found", str(ctx_auth.exception))
970977

971-
# 2d. Preflight passes when active account is found
978+
# 2e. Preflight passes when active account is found
972979
sb_pass = GceSandbox(project="test-proj", gcloud_bin="/usr/bin/gcloud")
973980
sb_pass._run_gcloud = MagicMock(return_value=(0, "user@example.com\n"))
974981
with patch("shutil.which", return_value="/usr/bin/gcloud"):
@@ -2434,6 +2441,67 @@ def test_advise_cli_script_execution(self):
24342441
finally:
24352442
shutil.rmtree(temp_dir)
24362443

2444+
def test_no_skill_system_prompt_loading_and_execution(self):
2445+
"""Validates loading an agent configured with system_prompt (prompts/system-researcher.md) without a skill."""
2446+
temp_dir = tempfile.mkdtemp()
2447+
try:
2448+
prompt_src = os.path.join(os.path.dirname(__file__), "prompts", "system-researcher.md")
2449+
self.assertTrue(os.path.exists(prompt_src), "prompts/system-researcher.md must exist as canonical no-skill example")
2450+
2451+
prompts_dir = os.path.join(temp_dir, "prompts")
2452+
os.makedirs(prompts_dir, exist_ok=True)
2453+
shutil.copy(prompt_src, os.path.join(prompts_dir, "system-researcher.md"))
2454+
2455+
workflow_def = {
2456+
"name": "no_skill_workflow",
2457+
"nodes": [
2458+
{
2459+
"id": "researcher",
2460+
"type": "agent",
2461+
"system_prompt": "prompts/system-researcher.md",
2462+
"tools": ["read_file", "report_findings"]
2463+
}
2464+
],
2465+
"edges": [
2466+
{"from": "START", "to": "researcher"}
2467+
]
2468+
}
2469+
wf_path = os.path.join(temp_dir, "workflow.json")
2470+
with open(wf_path, "w") as f:
2471+
json.dump(workflow_def, f)
2472+
2473+
with patch.dict(os.environ, {"VERTEXAI_PROJECT": "test-project"}):
2474+
wf, cfg = load_workflow_from_json(wf_path)
2475+
self.assertIsNotNone(wf)
2476+
self.assertEqual(len(wf.edges), 1)
2477+
self.assertEqual(wf.edges[0].to_node.name, "researcher")
2478+
with open(prompt_src, "r", encoding="utf-8") as f:
2479+
expected_instructions = f.read()
2480+
self.assertEqual(wf.edges[0].to_node.instruction, expected_instructions)
2481+
2482+
# Test fail-fast when system_prompt points to a missing file
2483+
bad_wf_def = {
2484+
"nodes": [
2485+
{
2486+
"id": "researcher_bad",
2487+
"type": "agent",
2488+
"system_prompt": "prompts/non_existent.md"
2489+
}
2490+
],
2491+
"edges": [
2492+
{"from": "START", "to": "researcher_bad"}
2493+
]
2494+
}
2495+
bad_wf_path = os.path.join(temp_dir, "bad_workflow.json")
2496+
with open(bad_wf_path, "w") as f:
2497+
json.dump(bad_wf_def, f)
2498+
2499+
with self.assertRaises(ValueError) as ctx:
2500+
load_workflow_from_json(bad_wf_path)
2501+
self.assertIn("System prompt not found", str(ctx.exception))
2502+
finally:
2503+
shutil.rmtree(temp_dir)
2504+
24372505

24382506
if __name__ == "__main__":
24392507
unittest.main()

‎reference/workflow.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"no_service_account": true,
1919
"no_external_ip": true,
2020
"verify_isolation": true,
21-
"timeout_seconds": 60
21+
"timeout_seconds": 600
2222
}
2323
}
2424
},

0 commit comments

Comments
 (0)