Skip to content

Commit 230e95c

Browse files
authored
Merge pull request #432 from aojea/security-triage
Security triage: mcp 2.x, LangChain 1.x, Go floor, verified-token audience, npm lockfiles
2 parents 46dd156 + 82973b7 commit 230e95c

31 files changed

Lines changed: 5313 additions & 142 deletions

File tree

.github/workflows/deploy-github-pages.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ jobs:
4444
check-latest: true
4545

4646
- name: Install PostCSS dependencies
47-
run: |
48-
npm init --yes
49-
npm install --save-dev autoprefixer@10.6.0 postcss-cli@12.0.0 postcss@8.5.28
47+
run: npm ci --ignore-scripts --no-audit --no-fund
5048
working-directory: ./site
5149

5250
- name: Build Hugo Site

cmd/chaos-agent/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This provides a realistic workload for the SAM mesh scale testing experiment. Ra
88

99
1. It connects to the `sam-node` MCP endpoint via SSE (or `sam-box` proxy in the microVM).
1010
2. It dynamically converts all discovered MCP tools into LangChain `Tool` objects with JSON schemas.
11-
3. It initializes a LangChain `AgentExecutor` using the SAM mesh's OpenAI-compatible inference endpoint (`/v1/chat/completions`).
11+
3. It initializes a LangChain `create_agent` tool-calling agent using the SAM mesh's OpenAI-compatible inference endpoint (`/v1/chat/completions`).
1212
4. It is given an adversarial prompt to explore all tools, fuzz them with extreme inputs, and find bugs.
1313
5. The LangChain framework automatically handles the multi-turn reasoning and tool-calling loop until the agent achieves its goal or hits the max iteration limit.
1414

cmd/chaos-agent/agent.py

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from typing import Any, Dict
99

1010
from langchain_openai import ChatOpenAI
11-
from langchain.agents import AgentExecutor, create_tool_calling_agent
12-
from langchain_core.prompts import ChatPromptTemplate
11+
from langchain.agents import create_agent
12+
from langchain_core.messages import HumanMessage
1313
from langchain_core.tools import tool
1414
from openai import AsyncOpenAI
1515

@@ -22,6 +22,32 @@ def tool_schema(spec):
2222
return getattr(spec, "input_schema", None) or getattr(spec, "inputSchema", {})
2323

2424

25+
# AgentExecutor counted model+tool iterations; a LangGraph agent counts graph
26+
# steps, and one iteration is two of those.
27+
MAX_ITERATIONS = 500
28+
29+
30+
async def run_round(agent, prompt):
31+
"""Drive one agent run to completion, echoing each turn as it happens.
32+
33+
Streaming the updates is what verbose=True used to give us: the tool calls
34+
and their results are the whole point of a chaos run, so they are printed
35+
as they arrive rather than summarised at the end.
36+
"""
37+
final = ""
38+
async for step in agent.astream(
39+
{"messages": [HumanMessage(content=prompt)]},
40+
config={"recursion_limit": 2 * MAX_ITERATIONS},
41+
stream_mode="updates",
42+
):
43+
for update in step.values():
44+
for msg in (update or {}).get("messages", []):
45+
msg.pretty_print()
46+
if msg.type == "ai" and not getattr(msg, "tool_calls", None):
47+
final = msg.content
48+
return final
49+
50+
2551
async def pick_model(inference_url, requested):
2652
"""Ask the mesh what it will serve this agent rather than guessing a name.
2753
@@ -101,27 +127,21 @@ async def mcp_tool(arguments_json: str) -> str:
101127
print(f"mesh offered model: {model}", file=sys.stderr)
102128
llm = ChatOpenAI(
103129
model=model,
104-
openai_api_base=args.inference_url,
105-
openai_api_key=args.auth if args.auth else "none",
130+
base_url=args.inference_url,
131+
api_key=args.auth if args.auth else "none",
106132
default_headers=headers
107133
)
108134

109-
# Define the agent prompt
110135
# No system turn: Gemma and several other instruction-tuned models
111136
# reject the system role outright with a 400, and the mesh may hand
112137
# this agent any model at all. The persona goes in the human turn,
113138
# which every model accepts.
114-
prompt_template = ChatPromptTemplate.from_messages([
115-
("human", "You are an autonomous adversarial AI agent. You have access to tools.\n\n{input}"),
116-
("placeholder", "{agent_scratchpad}"),
117-
])
139+
prompt = f"You are an autonomous adversarial AI agent. You have access to tools.\n\n{args.prompt}"
118140

119-
# Create the LangChain Agent
141+
# create_agent runs the model/tool loop until the model stops
142+
# calling tools; it replaced AgentExecutor in LangChain 1.x.
120143
print("Initializing LangChain Tool-Calling Agent...")
121-
agent = create_tool_calling_agent(llm, lc_tools, prompt_template)
122-
123-
# AgentExecutor runs the ReAct/Tool loop automatically!
124-
agent_executor = AgentExecutor(agent=agent, tools=lc_tools, verbose=True, max_iterations=500)
144+
agent = create_agent(model=llm, tools=lc_tools)
125145

126146
print(f"\n--- Starting Chaos Monkey Agent Loop ---")
127147
print(f"Instruction: {args.prompt}\n")
@@ -132,9 +152,9 @@ async def mcp_tool(arguments_json: str) -> str:
132152
print(f"\n--- Round {round_num} ---")
133153
started = time.monotonic()
134154
try:
135-
result = await agent_executor.ainvoke({"input": args.prompt})
155+
output = await run_round(agent, prompt)
136156
print("\n--- Final Agent Result ---")
137-
print(result["output"])
157+
print(output)
138158
except Exception as e:
139159
# A crash is a data point, not a reason to stop: an agent
140160
# that dies on the first refusal stops testing anything.

cmd/chaos-agent/requirements.txt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
openai>=1.40,<3
1+
openai>=3.11.0,<4
22
# Pinned to a major version: 1.x spells the transport streamablehttp_client and
33
# 2.x spells it streamable_http_client, so an unpinned floor silently changes
44
# the API underneath a sandbox image.
5-
mcp>=2.0,<3
6-
# Pinned below 1.0 deliberately: LangChain 1.x removed AgentExecutor and
7-
# create_tool_calling_agent from langchain.agents, so an unpinned install
8-
# builds an image whose agent fails at import rather than at run time.
9-
langchain>=0.3,<1
10-
langchain-openai>=0.2,<1
5+
mcp>=2.2.0,<3
6+
# LangChain 1.x replaced AgentExecutor with create_agent, which is what the
7+
# agent is written against; 0.x no longer receives security fixes.
8+
langchain>=1.4.0,<2
9+
langchain-openai>=1.6.2,<2

cmd/nano-init/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/google/sam/cmd/nano-init
22

3-
go 1.26.5
3+
go 1.26.6
44

55
require (
66
github.com/aojea/agents.net/tun2connect v0.0.1

cmd/sam-a2a-bridge/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/google/sam/cmd/sam-a2a-bridge
22

3-
go 1.25.7
3+
go 1.25.13
44

55
require (
66
github.com/a2aproject/a2a-go/v2 v2.5.0

development/examples/agent-harness/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
# There is no proxy support here on purpose. The sandbox routes the agent's
66
# traffic to the boundary through tun0, so the agent neither configures a proxy
77
# nor knows there is one.
8-
openai>=1.40,<3
8+
openai>=3.11.0,<4
99
# Pinned to a major version: 1.x spells the transport streamablehttp_client and
1010
# 2.x spells it streamable_http_client, so an unpinned floor silently changes
1111
# the API underneath a sandbox image.
12-
mcp>=2.0,<3
12+
mcp>=2.2.0,<3

development/examples/calc-mcp/calc_server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Calculator MCP backend exposed by node B in the local dev mesh."""
2-
from mcp.server.fastmcp import FastMCP
2+
from mcp.server.mcpserver import MCPServer
33

4-
mcp = FastMCP("calculator", host="0.0.0.0", port=7777)
4+
mcp = MCPServer("calculator")
55

66

77
@mcp.tool()
@@ -17,4 +17,4 @@ def multiply(a: float, b: float) -> float:
1717

1818

1919
if __name__ == "__main__":
20-
mcp.run(transport="streamable-http")
20+
mcp.run(transport="streamable-http", host="0.0.0.0", port=7777)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
mcp[cli]>=1.0.0,<2.0.0
1+
mcp[cli]>=2.2.0,<3
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
a2a-sdk>=1.0
2-
google-genai>=1.0
3-
httpx>=0.27
4-
sse-starlette>=2.0
5-
starlette>=0.40
6-
uvicorn>=0.30
1+
a2a-sdk>=1.1.2
2+
google-genai>=2.22.0
3+
httpx>=0.28.1
4+
sse-starlette>=3.4.11
5+
starlette>=1.6.0
6+
uvicorn>=0.52.4

0 commit comments

Comments
 (0)