From 8fb2855e4bbf5e6c5cb031e3265b5fd85895f805 Mon Sep 17 00:00:00 2001 From: a2a-code-editor Date: Mon, 13 Jul 2026 12:11:07 +0000 Subject: [PATCH] code editor: Agent Studio improvement iteration 1 for `customer-integration-engineer` --- .a2a-builder-state.json | 2 +- a2a.yaml | 2 +- agent.py | 26 +++++++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.a2a-builder-state.json b/.a2a-builder-state.json index 6fce500..8efe956 100644 --- a/.a2a-builder-state.json +++ b/.a2a-builder-state.json @@ -4,5 +4,5 @@ "repo_head_sha": "fa5803c0bdad221329bdc8410fcb2a193675c938", "source": "agent-builder", "updated_at": 1783944412, - "version": "0.1.5" + "version": "0.1.6" } \ No newline at end of file diff --git a/a2a.yaml b/a2a.yaml index aac123c..47f3154 100644 --- a/a2a.yaml +++ b/a2a.yaml @@ -1,5 +1,5 @@ name: customer-integration-engineer -version: 0.1.5 +version: 0.1.6 entrypoint: agent:CustomerIntegrationEngineer expose: public: false diff --git a/agent.py b/agent.py index 17a07c0..2aeb8d1 100644 --- a/agent.py +++ b/agent.py @@ -410,7 +410,7 @@ class CustomerIntegrationEngineer(A2AAgent[CustomerIntegrationEngineerConfig, No "Guides and validates customer integrations from requirements through a tested handoff while " "keeping credentials and production changes under customer control." ) - version = "0.1.5" + version = "0.1.6" config_model = CustomerIntegrationEngineerConfig auth_model = NoAuth @@ -980,24 +980,40 @@ def test_idempotency_key_stable(): def _typescript_connector(req: IntegrationRequirements, allowlist: list[str], digest: str) -> str: hosts = json.dumps(allowlist) return f'''// Minimal connector scaffold for {req.integration_id}. Plan digest: {digest} +import {{ lookup }} from "node:dns/promises"; +import net from "node:net"; + const ALLOWED_HOSTS = new Set({hosts}); const MAX_BYTES = {MAX_JSON_BYTES}; -export function validateUrl(raw: string): URL {{ +function isPublicAddress(address: string): boolean {{ + if (net.isIPv4(address)) {{ + const [a, b] = address.split(".").map(Number); + return !(a === 0 || a === 10 || a === 127 || a >= 224 || (a === 100 && b >= 64 && b <= 127) || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168)); + }} + const lower = address.toLowerCase(); + return net.isIPv6(address) === 6 && !(lower === "::" || lower === "::1" || lower.startsWith("fe80:") || lower.startsWith("fc") || lower.startsWith("fd")); +}} + +export async function validateUrl(raw: string): Promise {{ const url = new URL(raw); if (url.protocol !== "https:") throw new Error("connector only permits https URLs"); - if (!ALLOWED_HOSTS.has(url.hostname.toLowerCase())) throw new Error("host is not allowlisted"); + const host = url.hostname.toLowerCase(); + if (!ALLOWED_HOSTS.has(host)) throw new Error("host is not allowlisted"); + const records = await lookup(host, {{ all: true }}); + if (records.length === 0 || records.some(record => !isPublicAddress(record.address))) throw new Error("host resolved to unsafe address"); return url; }} export async function sendJson(rawUrl: string, payload: unknown, options: {{ dryRun?: boolean, secretRef?: string }} = {{}}) {{ - const url = validateUrl(rawUrl); + const url = await validateUrl(rawUrl); const body = JSON.stringify(payload); if (new TextEncoder().encode(body).length > MAX_BYTES) throw new Error("payload too large"); const idem = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body)); const idempotencyKey = Array.from(new Uint8Array(idem)).map(b => b.toString(16).padStart(2, "0")).join(""); if (options.dryRun !== false) return {{ status: "skipped", reason: "dry_run", idempotencyKey, url: url.toString() }}; - const response = await fetch(url, {{ method: "POST", body, headers: {{ "Content-Type": "application/json", "Idempotency-Key": idempotencyKey }} }}); + const response = await fetch(url, {{ method: "POST", redirect: "manual", body, headers: {{ "Content-Type": "application/json", "Idempotency-Key": idempotencyKey }} }}); + if (response.status >= 300 && response.status < 400) throw new Error("redirects are blocked"); return {{ statusCode: response.status, idempotencyKey }}; }} '''