[object Object]

← back to AbramsEgo

a2a-client: fix 3 Contrarian-flagged defects (TK-10381)

779ad4012d079f4cd02ffa183bc1a6acf05f738e · 2026-08-09 04:19:34 -0700 · steve@designerwallcoverings.com

- Add ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, REPLICATE_API_TOKEN
  to BANNED_PAYLOAD_PATTERNS (these are the actual high-value secrets on this
  box; original linter only blocked Shopify/GH tokens)
- Fix card validation: !card.name && !card.url → || so a card missing only
  name OR only url correctly throws instead of silently passing
- Fix dead rpcUrl ternary: both branches produced identical strings; now uses
  card.url (the A2A spec's declared JSON-RPC endpoint) with base-URL fallback
- Expand lintPayload tests to 9 assertions covering the 4 new AI key patterns

Files touched

Diff

commit 779ad4012d079f4cd02ffa183bc1a6acf05f738e
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Sun Aug 9 04:19:34 2026 -0700

    a2a-client: fix 3 Contrarian-flagged defects (TK-10381)
    
    - Add ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, REPLICATE_API_TOKEN
      to BANNED_PAYLOAD_PATTERNS (these are the actual high-value secrets on this
      box; original linter only blocked Shopify/GH tokens)
    - Fix card validation: !card.name && !card.url → || so a card missing only
      name OR only url correctly throws instead of silently passing
    - Fix dead rpcUrl ternary: both branches produced identical strings; now uses
      card.url (the A2A spec's declared JSON-RPC endpoint) with base-URL fallback
    - Expand lintPayload tests to 9 assertions covering the 4 new AI key patterns
---
 lib/a2a-client.js      | 10 ++++++----
 lib/a2a-client.test.js |  7 ++++++-
 memos/a2a-rails.md     |  1 +
 3 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/lib/a2a-client.js b/lib/a2a-client.js
index 853c3249..08d45985 100644
--- a/lib/a2a-client.js
+++ b/lib/a2a-client.js
@@ -32,6 +32,10 @@ const BANNED_PAYLOAD_PATTERNS = [
   /sk_live_/,
   /pk_live_/,
   /ghp_[A-Za-z0-9]{36}/,
+  /ANTHROPIC_API_KEY/i,
+  /OPENAI_API_KEY/i,
+  /GEMINI_API_KEY/i,
+  /REPLICATE_API_TOKEN/i,
 ];
 
 /** Throw if the outgoing question contains banned patterns (secrets, fleet internals). */
@@ -84,7 +88,7 @@ async function fetchAgentCard(baseUrl) {
   if (!res.ok) throw new Error(`a2a: agent card fetch failed ${res.status} from ${cardUrl}`);
   const card = await res.json();
   if (!card || typeof card !== 'object') throw new Error('a2a: invalid agent card (not an object)');
-  if (!card.name && !card.url) throw new Error('a2a: agent card missing name/url fields');
+  if (!card.name || !card.url) throw new Error('a2a: agent card missing name/url fields');
   cardCache[cacheKey] = { card, fetchedAt: Date.now() };
   return card;
 }
@@ -119,9 +123,7 @@ function buildGetTask(taskId) {
  * Reads the endpoint from the Agent Card or falls back to baseUrl + '/'.
  */
 async function rpcCall(baseUrl, card, body, authHeader) {
-  const rpcUrl = card?.defaultInputModes?.length
-    ? `${baseUrl.replace(/\/$/, '')}/`
-    : `${baseUrl.replace(/\/$/, '')}/`;
+  const rpcUrl = card?.url || `${baseUrl.replace(/\/$/, '')}/`;
 
   const headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
   if (authHeader) headers['Authorization'] = authHeader;
diff --git a/lib/a2a-client.test.js b/lib/a2a-client.test.js
index e94050bb..65a5989e 100644
--- a/lib/a2a-client.test.js
+++ b/lib/a2a-client.test.js
@@ -40,9 +40,14 @@ assert.throws(() => lintPayload('my SHOPIFY_ADMIN_TOKEN=abc123'), /blocked/);
 assert.throws(() => lintPayload('DATABASE_URL=postgres://...'), /blocked/);
 // use split string to avoid triggering gitleaks on the test itself
 assert.throws(() => lintPayload('key=' + 'sk_live_' + 'abcdefghijklmnop'), /blocked/);
+// AI keys — the actual high-value secrets on this box must also be blocked
+assert.throws(() => lintPayload('my ANTHROPIC_API_KEY=sk-ant-xyz'), /blocked/);
+assert.throws(() => lintPayload('export OPENAI_API_KEY=sk-proj-abc'), /blocked/);
+assert.throws(() => lintPayload('GEMINI_API_KEY=AIza...'), /blocked/);
+assert.throws(() => lintPayload('REPLICATE_API_TOKEN=r8_abc123'), /blocked/);
 lintPayload('What is the best approach to rate limiting?'); // clean
 lintPayload('How should I structure my agent for A2A?'); // clean
-console.log('  lintPayload: 5 assertions pass');
+console.log('  lintPayload: 9 assertions pass');
 
 // --- loadAllowlist -----------------------------------------------------------
 // The real file is data/a2a-agents.json (starts empty — [] by spec).
diff --git a/memos/a2a-rails.md b/memos/a2a-rails.md
index f03a640d..3bbd8729 100644
--- a/memos/a2a-rails.md
+++ b/memos/a2a-rails.md
@@ -8,6 +8,7 @@
 
 2. **Payload linter** — `lintPayload()` blocks outbound text containing:
    - `SHOPIFY_ADMIN_TOKEN`, `DATABASE_URL`, `sk_live_`, `pk_live_`, `ghp_…` (GitHub PATs)
+   - `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `REPLICATE_API_TOKEN` (AI keys)
    - No fleet-snapshot fields ever leave the box.
 
 3. **15 s timeout + no redirects** — `fetchSafe()` aborts after 15 s and sets `redirect: 'error'`

← aee628c5 feat: A2A client Phases A-C (TK-10381)  ·  back to AbramsEgo  ·  auto-data-snapshot: 2026-08-09T04:37:25 (1 data files) — dat c04a7bc7 →