Python: LangChain and LangGraph
Level 0 is complete once the runtime resolves through a Securd policy. This page covers Level 1: tool code that distinguishes a held destination from an unavailable service and escalates instead of retrying.
Configuration and reference code for Python: LangChain and LangGraph
The guard
Held and blocked names resolve to the policy's block page address. The guard resolves the hostname and compares the answer against that address.
# securd_guard.py
# Held and blocked names resolve to the policy's block page address.
# Put that address (from the console, Sites > your site) in SECURD_BLOCK_ADDRS.
import os
import socket
import logging
SECURD_BLOCK_ADDRS = {a for a in os.getenv("SECURD_BLOCK_ADDRS", "").split(",") if a}
log = logging.getLogger("securd")
class SecurdHeld(Exception):
"""Destination is held at the Greywall or blocked by the policy for this agent role."""
def securd_guard(hostname: str, agent_role: str) -> None:
try:
answers = {info[4][0] for info in socket.getaddrinfo(hostname, 443)}
except socket.gaierror as exc:
log.warning("securd.unresolved", extra={"agent_role": agent_role, "host": hostname})
raise SecurdHeld(f"{hostname} did not resolve inside {agent_role}") from exc
if answers & SECURD_BLOCK_ADDRS:
log.warning("securd.held", extra={"agent_role": agent_role, "host": hostname})
raise SecurdHeld(f"{hostname} is held or blocked for {agent_role}")
Wrap a tool
Call the guard at the start of any tool that reaches the network. Raise a typed exception so the agent loop can escalate to a human instead of retrying.
from urllib.parse import urlparse
import httpx
from langchain_core.tools import tool
from securd_guard import securd_guard, SecurdHeld
AGENT_ROLE = "research-agent"
@tool
def fetch_page(url: str) -> str:
"""Fetch a web page the agent has been approved to read."""
host = urlparse(url).hostname or ""
try:
securd_guard(host, AGENT_ROLE)
except SecurdHeld as held:
# Return a message the model can act on. Do not retry.
return f"BLOCKED: {held}. Ask an operator to approve {host} for {AGENT_ROLE}."
return httpx.get(url, timeout=20).text[:8000]
LangGraph: route a hold to a human node
Route a held destination to a human review node rather than back to the model. The hold is already recorded in the Securd log; repeated attempts add no information.
from langgraph.graph import StateGraph, END
def should_continue(state):
last = state["messages"][-1].content
return "needs_approval" if last.startswith("BLOCKED:") else "continue"
graph = StateGraph(dict)
graph.add_node("agent", call_model)
graph.add_node("tools", run_tools)
graph.add_node("needs_approval", ask_operator) # posts to your review channel
graph.add_conditional_edges("tools", should_continue, {
"continue": "agent",
"needs_approval": END,
})
Initial allow list
Import the OpenAI or Anthropic scope template, plus the LangSmith template if tracing is enabled. All other destinations the agent resolves during initial deployment appear in the review queue.
Evaluate Agent DNS with your own agent traffic
Deploy on a single policy in learning mode and review the recorded destinations with your team.