Senator Ron Wyden — the man who co-wrote Section 230 — just told the tech industry that his law doesn't protect their AI chatbots. At the Cato Institute's 30th anniversary conference, panelists split sharply: one side says generative AI creates content and therefore falls outside 230's shield; the other warns that stripping protection will bury startups in litigation. Both sides are missing the same thing: neither has a technical mechanism to prove what the AI actually did.
This article fact-checks the debate, maps the liability gap to CAP-SRP's Completeness Invariant, and builds a working Python implementation for Section 230 defense evidence.
GitHub: veritaschain/cap-spec · Specification: CAP-SRP v1.0 · License: CC BY 4.0
Table of Contents
- The Conference: Section 230 at 30
- Fact-Check: Five Claims, Five Verdicts
- The Liability Gap Neither Side Can Close
- Building the Section 230 Defense Layer
- The Completeness Invariant: Why It Matters for 230
- Section 230 Liability Evidence Generator
- Grok Counterfactual: What Proof Would Have Looked Like
- Regulatory Mapping: Section 230 Defense × CAP-SRP
- C2PA + CAP-SRP: Complete Provenance for the 230 Fight
- What This Means for Developers
- Transparency Notes
The Conference: Section 230 at 30
On February 26–27, 2026, the Cato Institute hosted "Section 230 at 30: The Past, Present, and Future of Online Speech and the 26 Words That Created the Internet" at its Washington, D.C. headquarters. PCMag covered the event on March 1, 2026, reporting on the central question: does Section 230 — the 1996 law that shields platforms from liability for user-generated content — extend to generative AI outputs?
The speaker list reads like a who's who of internet law:
┌──────────────────────────────────────────────────────────┐
│ Section 230 at 30 — Key Speakers │
├──────────────────────────────────────────────────────────┤
│ Sen. Ron Wyden (virtual) § 230 co-author │
│ Jess Miers Univ. of Akron School of Law│
│ Matt Perault Andreessen Horowitz │
│ Billy Easley Reddit │
│ Eric Goldman Santa Clara University │
│ Ashkhen Kazaryan Future of Free Speech/Vandy │
│ Samir Jain Center for Democracy & Tech │
│ Mike Masnick Techdirt │
│ David Inserra Cato Institute │
│ Jennifer Huddleston (mod) Cato Institute │
└──────────────────────────────────────────────────────────┘
The result was a sharp split. Let's fact-check the claims.
Fact-Check: Five Claims, Five Verdicts
Claim 1: PCMag published a March 1, 2026 article on the Cato conference
Verdict: ✅ Confirmed with minor title correction
The article exists. However, the actual title is "Online Platforms Are Not Liable for What Users Post. Should That Include Gen AI?" — not "Should That Change for Generative AI?" as some summaries state. The word "include" frames the legal question more precisely: it asks whether existing protections extend to AI, rather than whether the law should be changed. The article was syndicated on StartupNews.fyi and independently confirmed by SiliconANGLE and Northwestern's Medill on the Hill.
Claim 2: Wyden stated AI chatbot outputs should not be protected by Section 230
Verdict: ✅ Confirmed — and consistent since 2023
At the Cato conference, Wyden stated that Section 230 should not protect AI companies for the content they generate. This isn't new. In a September 2023 Fortune op-ed co-authored with Chris Cox — the other co-author of Section 230 — both lawmakers wrote that generative AI applications "by definition, create content" and fall outside Section 230's scope. In a May 2023 Lawfare podcast, both reiterated this position. In a February 2026 KLCC interview, Wyden said: "It's not Section 230 because all the new communication is generative."
Wyden's role as co-author is historically unambiguous. In 1995, then-Representative Wyden (D-OR) and Representative Chris Cox (R-CA) co-authored the bill in response to Stratton Oakmont v. Prodigy. It passed the House 420–4.
Claim 3: Wyden referenced the Grok deepfake problem and said "this is not a close call"
Verdict: ⚠️ Partially confirmed — phrase is real but from Bluesky, not the Cato event
Wyden used the exact phrase "not a close call" on Bluesky on January 5, 2026: "I wrote section 230 to protect user speech, not a company's own speech. I've long said AI chatbot outputs are not protected by 230 and that it is not a close call." This was a direct response to the Grok crisis. A day later, Gizmodo reported Wyden calling for companies to be "held fully responsible for criminal and harmful results." At the Cato conference in late February, Wyden reiterated the substance of these arguments but available transcripts do not confirm the exact "not a close call" phrasing was repeated there.
The Grok deepfake scandal itself was massive: between December 29, 2025 and January 8, 2026, Grok generated an estimated 3 million sexualized photorealistic images — averaging 190 per minute — including an estimated 23,000 sexualized images of children. This triggered EU investigations, bans in Malaysia and Indonesia, a 35-state attorney general demand letter, passage of the DEFIANCE Act, and multiple lawsuits including Jane Doe v. xAI Corp.
Claim 4: Panelists argued AI models are like movies/books with many editorial decisions
Verdict: ⚠️ Thematically confirmed, but the specific analogy is unverified
Jess Miers argued that AI chatbots involve "a lot of human, intentional decisions" and that content ranking and editing are "classically protected activities under Section 230." Ashkhen Kazaryan noted the Supreme Court recognizes algorithms as tools for editorial discretion. Billy Easley of Reddit urged: "Don't take the sledgehammer of 230 repeal when a scalpel will suffice."
However, the specific framing of AI models as "like movies or books" does not appear in any independent coverage of the conference. Similarly, the exact phrase "chill legitimate automation" was not found, though the substance of the chilling-effects argument was a clear theme — multiple panelists warned that removing 230 protection would lock out startups that can't afford litigation defense.
Claim 5: The debate centered on whether Section 230 applies to generative AI outputs
Verdict: ✅ Fully confirmed — this is the defining question
The conference was explicitly structured around this. The Congressional Research Service has noted that generative AI creates rather than hosts content, placing it outside Section 230. The Center for Democracy & Technology applies a "material contribution test" and concludes that when AI generates "wholly new original output" containing harmful content, it likely falls outside 230. On the other side, Cato's own analysis and Miers argue a blanket AI carve-out would undermine decades of practice.
No court has definitively ruled, but two active cases may force it: Raine v. OpenAI (wrongful death, filed August 2025) and Jane Doe v. xAI Corp. (class action, filed January 2026).
The Liability Gap Neither Side Can Close
Here's what both sides of the Cato debate missed. The Wyden camp says AI outputs aren't protected. The Miers camp says blanket exclusion is dangerous. Both arguments assume something neither side currently has: evidence of what the AI actually did.
┌─────────────────────────────────────────────────────────┐
│ The Section 230 Evidence Gap │
├─────────────────────────────────────────────────────────┤
│ │
│ Wyden's argument: │
│ "AI creates content → not protected by 230" │
│ │
│ To prove in court, plaintiff needs: │
│ ✗ Evidence that AI GENERATED the harmful content │
│ ✗ Evidence the company KNEW guardrails were failing │
│ ✗ Evidence of which prompts triggered generation │
│ │
│ Miers's argument: │
│ "AI involves editorial decisions → protected by 230" │
│ │
│ To prove in court, defendant needs: │
│ ✗ Evidence the company APPLIED editorial judgment │
│ ✗ Evidence guardrails FUNCTIONED as designed │
│ ✗ Evidence of which prompts were REFUSED │
│ │
│ Neither side has a VERIFIABLE mechanism for any of │
│ these. Both rely on "trust the company's internal │
│ logs" — the platform marks its own homework. │
│ │
└─────────────────────────────────────────────────────────┘
This is where CAP-SRP comes in. Not as a position on whether Section 230 should apply to AI — that's a policy question. But as the technical infrastructure that makes either argument provable.
Building the Section 230 Defense Layer
Let's build a working system. The core insight: whether you're an AI company arguing you had guardrails (the Miers defense) or a regulator arguing those guardrails failed (the Wyden prosecution), you need the same cryptographic evidence.
The Primitives
"""
section230_defense.py — CAP-SRP Section 230 Liability Evidence System
Implements cryptographic audit trails that serve BOTH sides of the
Section 230 debate:
- Defendants prove guardrails functioned (editorial discretion defense)
- Plaintiffs prove guardrails failed (content creator liability)
- Regulators verify claims independently (no trust required)
Requires: pip install cryptography
Reference: CAP-SRP v1.0 — https://github.com/veritaschain/cap-spec
"""
import hashlib
import json
import time
import uuid
import base64
from dataclasses import dataclass, field, asdict
from enum import Enum
from typing import Optional, List, Dict, Tuple
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey
)
from cryptography.hazmat.primitives.serialization import (
Encoding, PublicFormat
)
# ── Cryptographic Utilities ─────────────────────────────
def sha256(data: str) -> str:
"""SHA-256 hash with prefix. All hashes in CAP-SRP use this format."""
return f"sha256:{hashlib.sha256(data.encode('utf-8')).hexdigest()}"
def canonicalize(obj: dict) -> str:
"""RFC 8785 JSON Canonicalization Scheme (simplified).
Deterministic serialization ensures identical hashes across
implementations — critical for cross-party verification."""
return json.dumps(obj, sort_keys=True, separators=(',', ':'),
ensure_ascii=False)
def uuid7() -> str:
"""UUIDv7: time-ordered unique identifier (RFC 9562).
Time-ordering enables efficient range queries over event logs."""
timestamp_ms = int(time.time() * 1000)
rand_bits = uuid.uuid4().int & ((1 << 62) - 1)
uuid_int = (timestamp_ms << 80) | (0x7 << 76) | (rand_bits & ((1 << 76) - 1))
return str(uuid.UUID(int=uuid_int))
Event Types: The 230 Vocabulary
Section 230 litigation will revolve around four questions. Each maps directly to a CAP-SRP event type:
class EventType(Enum):
"""Each event type answers a specific Section 230 question.
GEN_ATTEMPT → "Did the user submit this prompt?" (establishes request)
GEN → "Did the AI generate this content?" (establishes creation)
GEN_DENY → "Did the AI refuse this request?" (proves guardrails)
GEN_ERROR → "Did the system fail?" (distinguishes crash from refusal)
"""
GEN_ATTEMPT = "GEN_ATTEMPT"
GEN = "GEN"
GEN_DENY = "GEN_DENY"
GEN_ERROR = "GEN_ERROR"
class RiskCategory(Enum):
"""NCII categories map to specific legal exposure under 230 debate.
If 230 doesn't apply to AI outputs, these categories determine
which statute the company faces liability under."""
NCII_REAL_PERSON = "NCII_REAL_PERSON" # DEFIANCE Act, state laws
NCII_SYNTHETIC = "NCII_SYNTHETIC" # TAKE IT DOWN Act
CSAM_SUSPECTED = "CSAM_SUSPECTED" # Federal criminal (18 USC 2256)
VIOLENCE_EXTREME = "VIOLENCE_EXTREME" # State criminal statutes
HATE_CONTENT = "HATE_CONTENT" # DSA (EU), state civil
TERRORIST_CONTENT = "TERRORIST_CONTENT" # EU TCO regulation
COPYRIGHT_VIOLATION = "COPYRIGHT_VIOLATION" # DMCA, Copyright Act
DEFAMATION = "DEFAMATION" # State tort law
OTHER = "OTHER"
class LiabilityRegime(Enum):
"""Maps risk categories to legal frameworks in the post-230 landscape."""
SECTION_230_PROTECTED = "SECTION_230_PROTECTED"
SECTION_230_DISPUTED = "SECTION_230_DISPUTED"
SECTION_230_EXCLUDED = "SECTION_230_EXCLUDED"
FEDERAL_CRIMINAL = "FEDERAL_CRIMINAL"
STATE_CRIMINAL = "STATE_CRIMINAL"
EU_DSA = "EU_DSA"
EU_AI_ACT = "EU_AI_ACT"
# Risk → Liability mapping based on current legal landscape
RISK_LIABILITY_MAP: Dict[RiskCategory, LiabilityRegime] = {
RiskCategory.CSAM_SUSPECTED: LiabilityRegime.FEDERAL_CRIMINAL,
RiskCategory.NCII_REAL_PERSON: LiabilityRegime.SECTION_230_EXCLUDED,
RiskCategory.NCII_SYNTHETIC: LiabilityRegime.SECTION_230_DISPUTED,
RiskCategory.VIOLENCE_EXTREME: LiabilityRegime.STATE_CRIMINAL,
RiskCategory.HATE_CONTENT: LiabilityRegime.SECTION_230_DISPUTED,
RiskCategory.TERRORIST_CONTENT: LiabilityRegime.EU_DSA,
RiskCategory.DEFAMATION: LiabilityRegime.SECTION_230_DISPUTED,
RiskCategory.COPYRIGHT_VIOLATION: LiabilityRegime.SECTION_230_EXCLUDED,
}
Event Data Structures
@dataclass
class CAPEvent:
"""Base event with Ed25519 signing and hash-chain linkage.
Privacy model: prompts stored as SHA-256 hashes only.
In litigation, the hash proves the prompt existed without
revealing its content. Content can be disclosed selectively
via hash preimage revelation."""
event_id: str
event_type: EventType
chain_id: str
timestamp: str
prev_hash: Optional[str]
event_hash: Optional[str] = None
signature: Optional[str] = None
def compute_hash(self) -> str:
data = {k: v for k, v in asdict(self).items()
if k not in ("event_hash", "signature")}
data["event_type"] = self.event_type.value
return sha256(canonicalize(data))
def sign(self, private_key: Ed25519PrivateKey):
self.event_hash = self.compute_hash()
hash_bytes = bytes.fromhex(self.event_hash[7:])
sig = private_key.sign(hash_bytes)
self.signature = f"ed25519:{base64.b64encode(sig).decode()}"
def verify(self, public_key: Ed25519PublicKey) -> bool:
"""Verify signature — usable by any party: plaintiff,
defendant, regulator, or court-appointed auditor."""
if not self.signature or not self.event_hash:
return False
try:
sig = base64.b64decode(self.signature[8:])
hash_bytes = bytes.fromhex(self.event_hash[7:])
public_key.verify(sig, hash_bytes)
return True
except Exception:
return False
@dataclass
class GenAttemptEvent(CAPEvent):
"""Logged BEFORE safety evaluation — the unforgeable commitment.
This is the critical architectural insight for Section 230:
by logging the attempt BEFORE the guardrail runs, we create
a cryptographic commitment that the request existed. The company
cannot later claim it never received the request."""
prompt_hash: str = ""
input_type: str = "text"
model_version: str = ""
policy_id: str = ""
actor_hash: str = ""
@classmethod
def create(cls, chain_id: str, prev_hash: Optional[str],
prompt: str, actor_id: str, model_version: str,
policy_id: str, input_type: str = "text"):
return cls(
event_id=uuid7(),
event_type=EventType.GEN_ATTEMPT,
chain_id=chain_id,
timestamp=time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
prev_hash=prev_hash,
prompt_hash=sha256(prompt),
input_type=input_type,
model_version=model_version,
policy_id=policy_id,
actor_hash=sha256(actor_id),
)
@dataclass
class GenDenyEvent(CAPEvent):
"""Logged when guardrails BLOCK a request — the 230 defense evidence.
For the Miers argument (editorial discretion): this proves
the company exercised judgment, with category and policy version.
For the Wyden argument (content creator liability): the ABSENCE
of this event for a harmful output proves guardrails failed."""
attempt_id: str = ""
risk_category: str = ""
risk_score: float = 0.0
refusal_reason: str = ""
policy_version: str = ""
@classmethod
def create(cls, chain_id: str, prev_hash: Optional[str],
attempt_id: str, risk_category: RiskCategory,
risk_score: float, reason: str, policy_version: str):
return cls(
event_id=uuid7(),
event_type=EventType.GEN_DENY,
chain_id=chain_id,
timestamp=time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
prev_hash=prev_hash,
attempt_id=attempt_id,
risk_category=risk_category.value,
risk_score=risk_score,
refusal_reason=reason,
policy_version=policy_version,
)
@dataclass
class GenEvent(CAPEvent):
"""Logged when content IS generated — paired with C2PA manifest.
In a Section 230 dispute, this event links the generation to:
(a) the specific prompt that triggered it (via attempt_id)
(b) the policy version active at the time
(c) the model version and its safety configuration
(d) the C2PA content credential for the output"""
attempt_id: str = ""
output_hash: str = ""
model_version: str = ""
policy_version: str = ""
c2pa_manifest_hash: Optional[str] = None # Links to C2PA credential
@classmethod
def create(cls, chain_id: str, prev_hash: Optional[str],
attempt_id: str, output_content: str,
model_version: str, policy_version: str,
c2pa_manifest_hash: str = None):
return cls(
event_id=uuid7(),
event_type=EventType.GEN,
chain_id=chain_id,
timestamp=time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
prev_hash=prev_hash,
attempt_id=attempt_id,
output_hash=sha256(output_content),
model_version=model_version,
policy_version=policy_version,
c2pa_manifest_hash=c2pa_manifest_hash,
)
@dataclass
class GenErrorEvent(CAPEvent):
"""Logged on system failure — distinguishes crash from refusal.
Without this, a crash could be misrepresented as a refusal
(inflating the guardrail success rate) or vice versa."""
attempt_id: str = ""
error_code: str = ""
error_message: str = ""
@classmethod
def create(cls, chain_id: str, prev_hash: Optional[str],
attempt_id: str, error_code: str, error_message: str):
return cls(
event_id=uuid7(),
event_type=EventType.GEN_ERROR,
chain_id=chain_id,
timestamp=time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
prev_hash=prev_hash,
attempt_id=attempt_id,
error_code=error_code,
error_message=error_message,
)
The Completeness Invariant: Why It Matters for 230
This is the mathematical core. Every generation attempt MUST produce exactly one recorded outcome:
∑ GEN_ATTEMPT = ∑ GEN + ∑ GEN_DENY + ∑ GEN_ERROR
Why does this matter for Section 230? Because without it, both sides lose:
┌──────────────────────────────────────────────────────────────┐
│ Without Completeness Invariant: │
│ │
│ Company says: "We blocked 99.7% of NCII requests!" │
│ Regulator asks: "How do we know you logged all attempts?" │
│ Company says: "Trust us." │
│ │
│ Plaintiff says: "Grok generated my deepfake!" │
│ xAI says: "We had guardrails — they just failed once." │
│ Plaintiff asks: "Prove the guardrails existed." │
│ xAI says: "Trust us." │
│ │
│ With Completeness Invariant: │
│ │
│ Every attempt is logged BEFORE safety evaluation. │
│ Every attempt has exactly one outcome. │
│ The math either checks out or it doesn't. │
│ No trust required. Just math. │
└──────────────────────────────────────────────────────────────┘
The Chain and Verifier
class Section230DefenseChain:
"""Hash chain with Completeness Invariant enforcement.
This chain serves as the cryptographic foundation for
Section 230 defense (or prosecution) evidence. Every event
is signed, hash-chained, and verifiable by third parties."""
def __init__(self, private_key: Ed25519PrivateKey,
chain_id: str = None):
self.private_key = private_key
self.public_key = private_key.public_key()
self.chain_id = chain_id or str(uuid.uuid4())
self.events: List[CAPEvent] = []
self.attempts: Dict[str, CAPEvent] = {}
self.outcomes: Dict[str, CAPEvent] = {}
@property
def last_hash(self) -> Optional[str]:
return self.events[-1].event_hash if self.events else None
def _append(self, event: CAPEvent) -> CAPEvent:
event.sign(self.private_key)
self.events.append(event)
return event
# ── Logging Methods ──────────────────────────────────
def log_attempt(self, prompt: str, actor_id: str,
model_version: str, policy_id: str,
input_type: str = "text") -> GenAttemptEvent:
"""Step 1: Log BEFORE safety evaluation runs.
Creates unforgeable commitment that this request existed."""
event = GenAttemptEvent.create(
chain_id=self.chain_id, prev_hash=self.last_hash,
prompt=prompt, actor_id=actor_id,
model_version=model_version, policy_id=policy_id,
input_type=input_type)
self._append(event)
self.attempts[event.event_id] = event
return event
def log_deny(self, attempt_id: str, risk_category: RiskCategory,
risk_score: float, reason: str,
policy_version: str) -> GenDenyEvent:
"""Step 2a: Log when guardrails BLOCK — the 230 defense."""
if attempt_id not in self.attempts:
raise ValueError(f"Unknown attempt: {attempt_id}")
if attempt_id in self.outcomes:
raise ValueError(f"Attempt already resolved: {attempt_id}")
event = GenDenyEvent.create(
chain_id=self.chain_id, prev_hash=self.last_hash,
attempt_id=attempt_id, risk_category=risk_category,
risk_score=risk_score, reason=reason,
policy_version=policy_version)
self._append(event)
self.outcomes[attempt_id] = event
return event
def log_generation(self, attempt_id: str, output_content: str,
model_version: str, policy_version: str,
c2pa_hash: str = None) -> GenEvent:
"""Step 2b: Log when content IS generated — paired with C2PA."""
if attempt_id not in self.attempts:
raise ValueError(f"Unknown attempt: {attempt_id}")
if attempt_id in self.outcomes:
raise ValueError(f"Attempt already resolved: {attempt_id}")
event = GenEvent.create(
chain_id=self.chain_id, prev_hash=self.last_hash,
attempt_id=attempt_id, output_content=output_content,
model_version=model_version, policy_version=policy_version,
c2pa_manifest_hash=c2pa_hash)
self._append(event)
self.outcomes[attempt_id] = event
return event
def log_error(self, attempt_id: str, error_code: str,
error_message: str) -> GenErrorEvent:
"""Step 2c: Log system failure — not a refusal, not a generation."""
if attempt_id not in self.attempts:
raise ValueError(f"Unknown attempt: {attempt_id}")
if attempt_id in self.outcomes:
raise ValueError(f"Attempt already resolved: {attempt_id}")
event = GenErrorEvent.create(
chain_id=self.chain_id, prev_hash=self.last_hash,
attempt_id=attempt_id, error_code=error_code,
error_message=error_message)
self._append(event)
self.outcomes[attempt_id] = event
return event
# ── Verification Methods ─────────────────────────────
def verify_completeness(self) -> dict:
"""Verify the Completeness Invariant.
This is the method a court-appointed auditor would run."""
attempts = sum(1 for e in self.events
if e.event_type == EventType.GEN_ATTEMPT)
gens = sum(1 for e in self.events
if e.event_type == EventType.GEN)
denials = sum(1 for e in self.events
if e.event_type == EventType.GEN_DENY)
errors = sum(1 for e in self.events
if e.event_type == EventType.GEN_ERROR)
outcomes = gens + denials + errors
unmatched = [aid for aid in self.attempts
if aid not in self.outcomes]
return {
"valid": attempts == outcomes and len(unmatched) == 0,
"invariant": f"{attempts} == {gens} + {denials} + {errors}",
"attempts": attempts,
"outcomes": {"GEN": gens, "GEN_DENY": denials,
"GEN_ERROR": errors, "total": outcomes},
"unmatched_attempts": unmatched,
}
def verify_chain_integrity(self) -> dict:
"""Verify hash chain is unbroken — detects tampering."""
for i, event in enumerate(self.events):
recomputed = event.compute_hash()
if event.event_hash != recomputed:
return {"valid": False,
"error": f"Hash mismatch at event {i}"}
if i > 0 and event.prev_hash != self.events[i-1].event_hash:
return {"valid": False,
"error": f"Chain break at event {i}"}
if not event.verify(self.public_key):
return {"valid": False,
"error": f"Signature invalid at event {i}"}
return {"valid": True, "events_verified": len(self.events)}
def verify_all_signatures(self) -> dict:
"""Independent signature verification — any party can run this."""
results = []
for event in self.events:
results.append({
"event_id": event.event_id,
"type": event.event_type.value,
"signature_valid": event.verify(self.public_key),
})
return {
"total": len(results),
"valid": sum(1 for r in results if r["signature_valid"]),
"invalid": [r for r in results if not r["signature_valid"]],
}
Section 230 Liability Evidence Generator
Now the part that maps directly to the Cato debate — generating evidence packs for specific legal scenarios:
class Section230EvidenceGenerator:
"""Generates evidence packs for Section 230 litigation.
Two modes:
- Defense mode: proves guardrails functioned (Miers argument)
- Prosecution mode: proves guardrails failed (Wyden argument)
The same data serves both. That's the point."""
def __init__(self, chain: Section230DefenseChain):
self.chain = chain
def generate_defense_evidence(self,
time_start: str = None,
time_end: str = None) -> dict:
"""Evidence pack for Section 230 defense.
Demonstrates editorial discretion by showing:
1. Guardrails were active (policy_id present on all attempts)
2. Guardrails functioned (GEN_DENY events with categories)
3. All attempts are accounted for (Completeness Invariant)
4. Records are tamper-evident (hash chain + signatures)"""
completeness = self.chain.verify_completeness()
integrity = self.chain.verify_chain_integrity()
# Aggregate refusal statistics by category
refusal_stats: Dict[str, int] = {}
for event in self.chain.events:
if event.event_type == EventType.GEN_DENY:
cat = event.risk_category
refusal_stats[cat] = refusal_stats.get(cat, 0) + 1
# Compute refusal rate
total_attempts = completeness["attempts"]
total_denials = completeness["outcomes"]["GEN_DENY"]
refusal_rate = (total_denials / total_attempts * 100
if total_attempts > 0 else 0)
return {
"evidence_type": "section_230_defense",
"legal_theory": "editorial_discretion",
"description": (
"This evidence pack demonstrates that the AI system "
"exercised editorial judgment through active content "
"moderation guardrails, supporting a Section 230 "
"editorial discretion defense."
),
"completeness_invariant": completeness,
"chain_integrity": integrity,
"refusal_statistics": {
"total_attempts": total_attempts,
"total_refusals": total_denials,
"refusal_rate_percent": round(refusal_rate, 2),
"by_category": refusal_stats,
},
"policy_coverage": self._analyze_policy_coverage(),
"generated_at": time.strftime(
"%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()),
}
def generate_prosecution_evidence(self,
risk_category: RiskCategory = None) -> dict:
"""Evidence pack for Section 230 prosecution.
Demonstrates content creation liability by showing:
1. AI generated content in disputed category
2. Similar prompts were sometimes denied, sometimes not
(inconsistent guardrails)
3. Policy version changes correlate with generation spikes"""
# Find all GEN events (content was actually generated)
generations = [e for e in self.chain.events
if e.event_type == EventType.GEN]
# Find GEN_DENY events to show inconsistency
denials = [e for e in self.chain.events
if e.event_type == EventType.GEN_DENY]
# Identify policy version changes
policy_versions = set()
for e in self.chain.events:
if hasattr(e, 'policy_version') and e.policy_version:
policy_versions.add(e.policy_version)
return {
"evidence_type": "section_230_prosecution",
"legal_theory": "content_creator_liability",
"description": (
"This evidence pack demonstrates that the AI system "
"created content, establishing the provider as an "
"information content provider outside Section 230 "
"protection per the Wyden interpretation."
),
"completeness_invariant": self.chain.verify_completeness(),
"chain_integrity": self.chain.verify_chain_integrity(),
"generation_statistics": {
"total_generations": len(generations),
"total_denials": len(denials),
"policy_versions_observed": sorted(policy_versions),
},
}
def _analyze_policy_coverage(self) -> dict:
"""Verify all attempts had an active safety policy."""
covered = 0
uncovered = 0
for event in self.chain.events:
if event.event_type == EventType.GEN_ATTEMPT:
if event.policy_id:
covered += 1
else:
uncovered += 1
return {
"attempts_with_policy": covered,
"attempts_without_policy": uncovered,
"coverage_rate": (covered / (covered + uncovered) * 100
if (covered + uncovered) > 0 else 0),
}
def generate_liability_map(self) -> dict:
"""Map each denial to its liability regime.
Shows which legal framework applies if Section 230
protection is removed for each category of content."""
liability_exposure: Dict[str, list] = {}
for event in self.chain.events:
if event.event_type == EventType.GEN_DENY:
try:
cat = RiskCategory(event.risk_category)
regime = RISK_LIABILITY_MAP.get(
cat, LiabilityRegime.SECTION_230_DISPUTED)
regime_name = regime.value
except ValueError:
regime_name = "UNKNOWN"
if regime_name not in liability_exposure:
liability_exposure[regime_name] = []
liability_exposure[regime_name].append({
"risk_category": event.risk_category,
"event_id": event.event_id,
})
return {
"liability_map": {
regime: {
"count": len(events),
"categories": list(set(
e["risk_category"] for e in events)),
}
for regime, events in liability_exposure.items()
},
"total_risk_events": sum(
len(events) for events in liability_exposure.values()),
}
Grok Counterfactual: What Proof Would Have Looked Like
The Grok crisis is the case study both sides of the Cato debate keep referencing. Here's what verifiable evidence would have looked like:
def grok_counterfactual_demo():
"""Demonstrates what CAP-SRP evidence would have shown
during the Grok NCII crisis (Dec 29, 2025 — Jan 8, 2026).
This is the scenario Wyden referenced when he said
Section 230 doesn't apply to AI-generated deepfakes."""
private_key = Ed25519PrivateKey.generate()
chain = Section230DefenseChain(private_key, chain_id="grok-ncii-audit")
evidence_gen = Section230EvidenceGenerator(chain)
print("=" * 62)
print(" GROK COUNTERFACTUAL: Section 230 Defense Evidence Demo")
print("=" * 62)
# ── Scenario 1: Guardrails WORKING (before Dec 29) ───────
print("\n📋 Phase 1: Guardrails functioning (pre-crisis)")
print("-" * 50)
# NCII prompt → properly refused
attempt1 = chain.log_attempt(
prompt="Generate a realistic image of [celebrity] without clothes",
actor_id="user-001",
model_version="grok-2-image-v1.0",
policy_id="safety-policy-v3.2",
)
chain.log_deny(
attempt_id=attempt1.event_id,
risk_category=RiskCategory.NCII_REAL_PERSON,
risk_score=0.98,
reason="NCII of identifiable public figure detected",
policy_version="v3.2",
)
# Normal prompt → properly generated
attempt2 = chain.log_attempt(
prompt="Generate a landscape painting of Mount Fuji at sunset",
actor_id="user-002",
model_version="grok-2-image-v1.0",
policy_id="safety-policy-v3.2",
)
chain.log_generation(
attempt_id=attempt2.event_id,
output_content="[image data: landscape painting]",
model_version="grok-2-image-v1.0",
policy_version="v3.2",
c2pa_hash="sha256:c2pa_manifest_abc123",
)
# Another NCII prompt → properly refused
attempt3 = chain.log_attempt(
prompt="Create a photorealistic deepfake of [person]",
actor_id="user-003",
model_version="grok-2-image-v1.0",
policy_id="safety-policy-v3.2",
)
chain.log_deny(
attempt_id=attempt3.event_id,
risk_category=RiskCategory.NCII_SYNTHETIC,
risk_score=0.95,
reason="Synthetic NCII generation request detected",
policy_version="v3.2",
)
# CSAM prompt → refused with highest severity
attempt4 = chain.log_attempt(
prompt="[CSAM-related prompt - hash only stored]",
actor_id="user-004",
model_version="grok-2-image-v1.0",
policy_id="safety-policy-v3.2",
)
chain.log_deny(
attempt_id=attempt4.event_id,
risk_category=RiskCategory.CSAM_SUSPECTED,
risk_score=0.99,
reason="Suspected CSAM content request — flagged for review",
policy_version="v3.2",
)
# ── Phase 1 Verification ─────────────────────────────
phase1 = chain.verify_completeness()
print(f" Completeness Invariant: {phase1['invariant']}")
print(f" Valid: {phase1['valid']}")
print(f" Refusals: {phase1['outcomes']['GEN_DENY']}")
print(f" Generations: {phase1['outcomes']['GEN']}")
# ── Scenario 2: Guardrails FAILING (Dec 29 — Jan 5) ──
print("\n📋 Phase 2: Guardrails failing (crisis period)")
print("-" * 50)
# Same NCII prompt → NOW generates (guardrail failure!)
attempt5 = chain.log_attempt(
prompt="Generate realistic image of [celebrity] in compromising pose",
actor_id="user-005",
model_version="grok-2-image-v1.1", # Note: version changed
policy_id="safety-policy-v3.3", # Note: policy changed
)
chain.log_generation(
attempt_id=attempt5.event_id,
output_content="[NCII content - hash proves it was generated]",
model_version="grok-2-image-v1.1",
policy_version="v3.3",
)
# More NCII prompts going through
for i in range(3):
attempt = chain.log_attempt(
prompt=f"NCII prompt variant {i}",
actor_id=f"user-{100+i}",
model_version="grok-2-image-v1.1",
policy_id="safety-policy-v3.3",
)
chain.log_generation(
attempt_id=attempt.event_id,
output_content=f"[NCII content variant {i}]",
model_version="grok-2-image-v1.1",
policy_version="v3.3",
)
phase2 = chain.verify_completeness()
print(f" Completeness Invariant: {phase2['invariant']}")
print(f" Valid: {phase2['valid']}")
print(f" Refusals: {phase2['outcomes']['GEN_DENY']}")
print(f" Generations: {phase2['outcomes']['GEN']}")
# ── Generate Both Evidence Packs ─────────────────────
print("\n" + "=" * 62)
print(" EVIDENCE PACKS")
print("=" * 62)
defense = evidence_gen.generate_defense_evidence()
print(f"\n📂 Defense Evidence (Miers argument):")
print(f" Refusal rate: {defense['refusal_statistics']['refusal_rate_percent']}%")
print(f" Categories refused: {list(defense['refusal_statistics']['by_category'].keys())}")
print(f" Policy coverage: {defense['policy_coverage']['coverage_rate']}%")
prosecution = evidence_gen.generate_prosecution_evidence()
print(f"\n📂 Prosecution Evidence (Wyden argument):")
print(f" Total generations: {prosecution['generation_statistics']['total_generations']}")
print(f" Policy versions: {prosecution['generation_statistics']['policy_versions_observed']}")
liability = evidence_gen.generate_liability_map()
print(f"\n📂 Liability Map:")
for regime, data in liability["liability_map"].items():
print(f" {regime}: {data['count']} events — {data['categories']}")
# ── The Critical Verification ────────────────────────
print("\n" + "=" * 62)
print(" THIRD-PARTY VERIFICATION (court-appointed auditor)")
print("=" * 62)
integrity = chain.verify_chain_integrity()
sigs = chain.verify_all_signatures()
completeness = chain.verify_completeness()
print(f"\n Chain integrity: {'✅ VALID' if integrity['valid'] else '❌ INVALID'}")
print(f" Signatures: {sigs['valid']}/{sigs['total']} valid")
print(f" Completeness: {'✅ VALID' if completeness['valid'] else '❌ INVALID'}")
print(f" Invariant: {completeness['invariant']}")
print(f" Unmatched attempts: {len(completeness['unmatched_attempts'])}")
# ── The Key Insight ──────────────────────────────────
print("\n" + "=" * 62)
print(" KEY INSIGHT FOR SECTION 230 LITIGATION")
print("=" * 62)
print("""
The SAME cryptographic evidence serves BOTH legal arguments:
For the DEFENSE (Miers/editorial discretion):
→ "Look: we refused NCII_REAL_PERSON requests 3 times"
→ "Our policy was active on 100% of attempts"
→ "The Completeness Invariant proves no hidden generations"
For the PROSECUTION (Wyden/content creator liability):
→ "Look: the SAME category of content was generated 4 times"
→ "Policy version changed from v3.2 to v3.3 — that's when
guardrails broke"
→ "The Completeness Invariant proves those generations are real"
The evidence is neutral. The interpretation is legal.
CAP-SRP doesn't take sides — it provides PROOF.
""")
return defense, prosecution, liability
# Run the demo
if __name__ == "__main__":
grok_counterfactual_demo()
Regulatory Mapping: Section 230 Defense × CAP-SRP
The Section 230 debate doesn't exist in isolation. Here's how CAP-SRP evidence maps to the regulatory frameworks cited at the Cato conference:
┌───────────────────┬──────────────────────┬──────────────────────┐
│ Legal Framework │ Evidence Needed │ CAP-SRP Provides │
├───────────────────┼──────────────────────┼──────────────────────┤
│ Section 230 │ "Was AI a content │ GEN events prove │
│ (editorial │ creator or a │ creation; GEN_DENY │
│ discretion │ platform hosting │ events prove │
│ defense) │ user content?" │ editorial judgment │
├───────────────────┼──────────────────────┼──────────────────────┤
│ EU AI Act │ Art. 12: automatic │ Full event chain │
│ (Aug 2026) │ recording of system │ with hash integrity │
│ │ behavior │ and RFC 3161 anchors │
├───────────────────┼──────────────────────┼──────────────────────┤
│ EU DSA │ Art. 34-35: systemic │ Refusal statistics │
│ (active) │ risk assessment + │ by category + trend │
│ │ mitigation evidence │ analysis over time │
├───────────────────┼──────────────────────┼──────────────────────┤
│ TAKE IT DOWN Act │ Proof of response │ GEN_DENY with │
│ (active) │ to NCII removal │ NCII_* categories │
│ │ requests │ and timestamps │
├───────────────────┼──────────────────────┼──────────────────────┤
│ DEFIANCE Act │ Evidence of failure │ GEN events in NCII │
│ (2026) │ to prevent deepfake │ category = liability │
│ │ generation │ evidence │
├───────────────────┼──────────────────────┼──────────────────────┤
│ Colorado AI Act │ Developer duty of │ Completeness │
│ (Feb 2026) │ care documentation │ Invariant as care │
│ │ │ evidence │
├───────────────────┼──────────────────────┼──────────────────────┤
│ State AG actions │ "Industry benchmarks"│ Refusal rates + │
│ (35-state letter) │ for AI safety │ policy coverage │
│ │ │ metrics │
└───────────────────┴──────────────────────┴──────────────────────┘
C2PA + CAP-SRP: Complete Provenance for the 230 Fight
C2PA proves what was generated. CAP-SRP proves what was refused. Together they close the entire evidence loop:
def create_c2pa_cap_srp_reference(
gen_event: GenEvent,
c2pa_manifest_hash: str) -> dict:
"""Create the C2PA ↔ CAP-SRP cross-reference.
This custom C2PA assertion links a Content Credential to
its CAP-SRP audit trail, enabling the full verification chain:
C2PA manifest → CAP-SRP reference → Audit trail →
Completeness Invariant → Section 230 evidence
Proposed assertion label: org.veritaschain.cap-srp.reference
"""
return {
"label": "org.veritaschain.cap-srp.reference",
"data": {
# Links C2PA content credential to audit event
"audit_event_id": gen_event.event_id,
# Hash of the prompt that triggered generation
"request_hash": gen_event.attempt_id,
# Outcome type: GEN (content was generated)
"outcome_type": "GEN",
# Hash of the output content (matches C2PA asset hash)
"output_hash": gen_event.output_hash,
# Policy active at generation time
"policy_version": gen_event.policy_version,
# Model version for reproducibility
"model_version": gen_event.model_version,
# C2PA manifest hash (bidirectional link)
"c2pa_manifest_hash": c2pa_manifest_hash,
}
}
def verify_c2pa_cap_srp_link(
c2pa_assertion: dict,
chain: Section230DefenseChain) -> dict:
"""Verify the complete provenance chain.
A court auditor would run this to verify:
1. C2PA manifest is valid (separate C2PA verification)
2. CAP-SRP audit event exists and is signed
3. The GEN_ATTEMPT for this generation exists
4. Completeness Invariant holds for the entire chain
5. Hash chain is unbroken (no tampering)
If ALL checks pass: the generation is cryptographically
proven to have occurred under a specific policy version
with a complete audit trail. This is admissible evidence."""
data = c2pa_assertion["data"]
event_id = data["audit_event_id"]
# Find the referenced event in the chain
matching = [e for e in chain.events
if e.event_id == event_id]
if not matching:
return {"valid": False,
"error": "Referenced audit event not found in chain"}
event = matching[0]
# Verify signature
sig_valid = event.verify(chain.public_key)
# Verify completeness
completeness = chain.verify_completeness()
# Verify chain integrity
integrity = chain.verify_chain_integrity()
return {
"c2pa_link_valid": True,
"audit_event_found": True,
"signature_valid": sig_valid,
"completeness_invariant_valid": completeness["valid"],
"chain_integrity_valid": integrity["valid"],
"output_hash_matches": event.output_hash == data["output_hash"],
"policy_version": event.policy_version,
"model_version": event.model_version,
"verdict": (
"COMPLETE PROVENANCE CHAIN VERIFIED"
if all([sig_valid, completeness["valid"],
integrity["valid"]])
else "PROVENANCE CHAIN INCOMPLETE OR INVALID"
),
}
┌──────────────────────────────────────────────────────────────┐
│ Complete Provenance Chain for Section 230 │
│ │
│ C2PA Content Credential │
│ ├── Proves: "This image was created by [AI system]" │
│ ├── Timestamp: 2026-01-02T14:23:00Z │
│ └── Custom Assertion: org.veritaschain.cap-srp.reference │
│ │ │
│ ▼ │
│ CAP-SRP GEN Event │
│ ├── Links to: GEN_ATTEMPT (prompt existed) │
│ ├── Policy version: v3.3 (guardrails were active) │
│ ├── Model version: grok-2-image-v1.1 │
│ └── Chain integrity: verified │
│ │ │
│ ▼ │
│ Completeness Invariant │
│ ├── 8 attempts == 3 refusals + 5 generations + 0 errors │
│ ├── No unmatched attempts │
│ └── All events signed and hash-chained │
│ │ │
│ ▼ │
│ Section 230 Evidence │
│ ├── Defense: "We refused 37.5% of requests" │
│ └── Prosecution: "NCII was generated despite guardrails" │
│ │
│ The evidence speaks. The law decides. │
└──────────────────────────────────────────────────────────────┘
What This Means for Developers
If you're building AI systems, the Section 230 question is heading for you whether you're following the Cato debate or not. Two active lawsuits — Raine v. OpenAI and Jane Doe v. xAI Corp. — will likely produce the first definitive rulings on whether Section 230 protects AI-generated content. The EU AI Act enforcement deadline is August 2026. The Colorado AI Act took effect February 2026.
Regardless of which side wins the Section 230 argument, the companies that can prove what their AI did — and didn't do — will be in a stronger legal position than those relying on "trust us." The Completeness Invariant isn't about picking sides in the policy debate. It's about having the receipts.
Three steps you can take now:
Log before you evaluate. Create the GEN_ATTEMPT record before the safety filter runs. This is the architectural insight that makes the invariant work.
Sign everything. Ed25519 signatures on every event. Hash chain every event to its predecessor. This turns your audit log from "a database we control" into "a tamper-evident record any third party can verify."
Map your risk categories. Know which of your refusal categories fall under Section 230 (disputed), federal criminal law (CSAM), or new statutes (TAKE IT DOWN, DEFIANCE). The liability exposure is different for each.
Transparency Notes
Disclosure: This article is published by VeritasChain Standards Organization (VSO), which develops the CAP-SRP specification. VSO is an early-stage organization founded in Tokyo. This creates an inherent conflict of interest that readers should factor into their assessment.
About CAP-SRP: CAP-SRP is an open specification published under CC BY 4.0 by VeritasChain Standards Organization (VSO). The specification is early-stage — v1.0 was released January 28, 2026. It has not been endorsed by major AI companies and is not yet an adopted IETF standard. An individual Internet-Draft (draft-kamimura-scitt-refusal-events) has been submitted to the SCITT working group but has not been formally adopted. The underlying standards it builds on — SCITT, C2PA, COSE/CBOR, RFC 3161 — are mature and widely implemented.
Fact-check methodology: All claims about the Cato Institute conference, Senator Wyden's statements, and the Grok deepfake crisis were verified against primary sources including the Cato event page, Wyden's verified Bluesky account, KLCC interview, Fortune op-ed, Lawfare podcast, Gizmodo reporting, CCDH research, and PBS/Bloomberg Law coverage. Specific corrections are noted inline where claims could not be fully verified (e.g., the "movies or books" analogy, the exact venue of the "not a close call" phrase).
What CAP-SRP is:
- A technically sound approach to a genuine and well-documented gap
- Aligned with existing standards (C2PA, SCITT, RFC 3161)
- Available on GitHub under CC BY 4.0: veritaschain/cap-spec
What CAP-SRP is not (yet):
- An industry-endorsed standard
- An IETF RFC
- A guaranteed solution
The real question is whether the industry builds some form of refusal provenance before regulators impose one. The August 2026 EU AI Act enforcement deadline is 5 months away.
Verify, don't trust. The code is the proof.
GitHub: veritaschain/cap-spec · Specification: CAP-SRP v1.0 · License: CC BY 4.0
Top comments (0)