> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sovseal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Defending SOC Agents from Memory Poisoning

> Verify every restored threat-intel snapshot before an agent acts on it — fail closed on tamper or substitution, page a human.

Security operations center (SOC) agents that read threat intelligence from a replication endpoint face a specific adversarial risk: an attacker who gains write access to the endpoint can silently replace your stored indicators of compromise (IOCs) with forged ones. The agent then operates on poisoned data — suppressing real threats or flagging friendly IPs — with no visible error. This cookbook applies the [Verified Semantic Recall](/platform/core-concepts/verified-semantic-recall) pattern to close that gap: every restored snapshot is cryptographically checked before your agent code ever sees it.

## The attack scenario

Your agent checkpoints threat intelligence (malicious IPs, file hashes, attack patterns) via `client.snapshot()` and restores it on other devices or after a restart. Memory poisoning happens when an adversary modifies the replication database to inject false IOCs:

1. **False negatives** — the attacker replaces a `CRITICAL` record for a known command-and-control IP with a benign entry, so the agent ignores traffic that should alert.
2. **Denial of service** — the attacker flags legitimate internal IPs as malicious, halting automated routing.

Because a substituted record can be fully valid ciphertext (something you genuinely encrypted at an earlier point), an auth-tag check alone is not enough — a hash comparison against what you expect is required too.

## Implementation

<Warning>
  Neither SDK verifies this automatically. You call `restore()`, get back ciphertext and a receipt, and decide what "fail closed" means for your agent. See [Verified Semantic Recall](/platform/core-concepts/verified-semantic-recall) for why.
</Warning>

Create `soc_agent.ts`. Each threat indicator is one agent identity, so `sequence_number` gives you a natural update history per IOC, and `agent_id` is a stable hash of the IP you're tracking — never the raw IP itself, since `agent_id` is visible to the server.

```typescript soc_agent.ts theme={null}
import {
  AgentStateClient,
  decryptJson,
  canonicalize,
  CryptoService,
} from "@sovseal/sdk";

const client = new AgentStateClient({
  endpoint: process.env.SOVSEAL_ENDPOINT!,
  apiKey: process.env.SOVSEAL_API_KEY!,
});

interface ThreatIndicator {
  ipAddress: string;
  threatLevel: "CRITICAL" | "HIGH" | "MEDIUM";
  signatureHash: string;
  notes: string;
}

async function agentIdFor(ip: string): Promise<string> {
  return CryptoService.sha256Hex(new TextEncoder().encode(`threat_intel:${ip}`));
}

/** Checkpoint a threat indicator. */
async function recordThreat(
  ip: string,
  indicator: ThreatIndicator,
  key: CryptoKey,
  sequenceNumber: number,
  parentSnapshot: string | null,
) {
  const agentId = await agentIdFor(ip);
  return client.snapshot({
    key,
    payload: {
      agent_id: agentId,
      sequence_number: sequenceNumber,
      parent_snapshot: parentSnapshot,
      policy_hash: "0".repeat(64),
      active_context: indicator,
      timestamp: new Date().toISOString(),
    },
  });
}

/**
 * Restore and cryptographically verify a threat indicator.
 * Throws on tamper or substitution. Returns null if nothing exists yet.
 */
async function queryThreatDatabase(
  ip: string,
  key: CryptoKey,
): Promise<ThreatIndicator | null> {
  const agentId = await agentIdFor(ip);
  const { receipt, ciphertextUrl } = await client.restore({ agentId });
  if (!ciphertextUrl) return null;

  const ciphertext = await fetch(ciphertextUrl).then((r) => r.text());

  let payload: unknown;
  try {
    payload = await decryptJson(ciphertext, key);
  } catch {
    triggerIncidentResponse({
      reason: "AUTH_TAG_MISMATCH",
      expected: receipt.client_payload_hash,
      received: "(decryption failed — ciphertext rejected)",
      ip,
    });
    throw new Error("Security alert: threat intelligence ciphertext failed authentication.");
  }

  const expected = await CryptoService.sha256Hex(
    new TextEncoder().encode(canonicalize(payload)),
  );
  if (expected !== receipt.client_payload_hash) {
    triggerIncidentResponse({
      reason: "HASH_MISMATCH",
      expected,
      received: receipt.client_payload_hash,
      ip,
    });
    throw new Error("Security alert: threat intelligence state integrity is compromised.");
  }

  return (payload as { active_context: ThreatIndicator }).active_context;
}

/** Fail closed: log forensic evidence, page the team. */
function triggerIncidentResponse(details: {
  reason: string;
  expected: string;
  received: string;
  ip: string;
}) {
  console.error("====================================================");
  console.error(" [CRITICAL INCIDENT] AGENT MEMORY INTEGRITY COMPROMISED");
  console.error("====================================================");
  console.error(`Reason:   ${details.reason}`);
  console.error(`Expected: ${details.expected}`);
  console.error(`Received: ${details.received}`);
  console.error(`Target:   ${details.ip}`);
  console.error("Action: Failing closed. Restricting automated routing.");
  console.error("====================================================");
  notifyPagerDuty(details);
}

function notifyPagerDuty(details: object) {
  // Drop in your PagerDuty / Slack integration here
  console.log("Notifying security triage team...");
}

// --- Example run ---

async function run(key: CryptoKey) {
  const knownMaliciousIP = "198.51.100.42";

  await recordThreat(
    knownMaliciousIP,
    {
      ipAddress: knownMaliciousIP,
      threatLevel: "CRITICAL",
      signatureHash: "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
      notes: "Known command & control node",
    },
    key,
    0,
    null,
  );

  const intel = await queryThreatDatabase(knownMaliciousIP, key);
  if (intel) {
    console.log(`IP ${intel.ipAddress} confirmed threat: ${intel.threatLevel}`);
  }
}
```

## Operational guidance for SOC environments

<Warning>
  **Treat any verification failure as a security incident, not a transient error.** It means either your replication endpoint has been compromised or an attacker is actively serving forged records. Do not retry. Do not degrade gracefully. Surface the error, quarantine the agent, and page a human immediately.
</Warning>

<Steps>
  <Step title="Fail closed — do not apply the record">
    The agent must not load the modified threat database or make routing decisions from unverified data. `queryThreatDatabase` above throws before returning — decision logic downstream never receives the poisoned payload.
  </Step>

  <Step title="Preserve the forensic trail">
    Log the expected and received hashes immediately — the delta is the primary evidence for pinpointing which replication log entry was compromised.
  </Step>

  <Step title="Recover via lineage rollback">
    Once the endpoint is secured, walk `client.lineage(agentId)` to find the last known-good `sequence_number` and resume writes from there.
  </Step>
</Steps>

## Attacks this catches

| Attack                                      | Caught by             | Mechanism                                               |
| ------------------------------------------- | --------------------- | ------------------------------------------------------- |
| Single bit flipped in ciphertext            | AES-GCM auth tag      | `decryptJson` throws                                    |
| Ciphertext truncated or extended            | AES-GCM auth tag      | `decryptJson` throws                                    |
| Two valid records swapped on the server     | Hash comparison       | `sha256(canonicalize(payload))` ≠ `client_payload_hash` |
| Old snapshot replayed in place of a new one | Hash comparison       | `parent_snapshot` is inside the hashed payload          |
| Record from a different project substituted | AES-GCM key isolation | Wrong key → `decryptJson` throws                        |

<Tip>
  This pattern only matters for state that crosses the network via `restore()`. If your SOC agent instead uses `store_memory` / `recall_memory` for free-text threat notes, those run entirely against the local index and are inside your trust boundary — see [Verified Semantic Recall § Where this applies](/platform/core-concepts/verified-semantic-recall#where-this-applies).
</Tip>
