~/codigoagente
AI agents

Legal audit of a real blog: what we found on codigoagente and how we fixed it

In part one we covered which US, EU and Ecuador laws affect an AI-built app or site and put together an audit prompt. Here we apply it to a real case: this very blog, a Next.js 16 and Sanity site deployed on Vercel and built largely with Claude Code. You'll see the inventory, the curl checks, before and after screenshots and the code we changed. The screenshots show the Spanish version of the site.

The inventory: what the site actually does

Before fixing anything, we listed what the site does with visitors' data:

QuestionAnswer for codigoagente
Sign-up, forms or comments?No
Newsletter or marketing emails?No
Subscriptions or payments?No
Analytics, ads or session replay?No
Third-party resources?Images from `cdn.sanity.io` (Sanity's CDN)
Affiliate links?Yes, to Contabo in the Paymenter guide
Contact?One email address, `contacto@codigoagente.com`
Audience?Ecuador, the rest of Latin America, Spain and English-speaking readers
AI features for readers?No (content is written with AI assistance, but readers don't interact with any AI)

That rules several laws out: COPPA (not a children's service and no data collected), CIPA (no recording or chat), CAN-SPAM and Article 50 of Ecuador's Electronic Commerce Act (no emails), ROSCA (no subscriptions) and the DMCA (readers can't publish anything). Still in play: Ecuador's LOPDP, the GDPR because of readers in Spain, and the FTC's guidance on affiliate links.

Check 1: fonts and third-party resources

The site uses the Geist fonts via next/font/google. That module downloads the fonts at build time and serves them from the site's own domain, so the reader's browser never contacts Google. We checked it on the live site:

terminalsh
curl -s https://codigoagente.vercel.app/ | grep -c "fonts.googleapis.com\|fonts.gstatic.com"
# 0

curl -s https://codigoagente.vercel.app/ | grep -oE '[^"]*\.woff2' | head -2
# /_next/static/immutable/media/797e433ab948586e-s.p.1v5bejj26fx9h.woff2
# /_next/static/immutable/media/caa3a2e1cccd8315-s.p.0zr6hhvz-h9nw.woff2

Result: fine. Zero calls to Google; fonts come from /_next/static/.

Listing every external domain on an article with images turned up another third party:

terminalsh
curl -s https://codigoagente.vercel.app/blog/instalar-paymenter-vps-coolify-docker \
  | grep -oE '(src|href)="https?://[^"/]+' | sort | uniq -c
#   3 href="https://contabo.com
#   1 href="https://github.com
#   2 href="https://safebuildlabs.com
#   9 src="https://cdn.sanity.io

The hrefs are links the reader chooses to click. The srcs on cdn.sanity.io are not: the browser loads them automatically and Sanity receives the reader's IP, just like Google with fonts. There are two options: serve images through the site's own domain (for example with next/image, at the cost of Vercel image optimisation quota) or disclose it transparently. We chose the latter for now: Sanity was already a data processor, and the policy now states exactly what it receives.

Check 2: cookies

terminalsh
curl -sI https://codigoagente.vercel.app/ | grep -ci "set-cookie"
# 0

The public site sets no cookies and doesn't use localStorage (confirmed by searching src/ for document.cookie, localStorage and cookies()). The cookie policy said otherwise: it described a consent banner that doesn't exist, AdSense cookies that were never enabled, and left gaps like [PROVEEDOR PENDIENTE] ("provider pending").

codigoagente cookie policy before and after: the inventory goes from pending providers and AdSense to stating there are no cookies and images come from cdn.sanity.io
Top: the template-generated policy. Bottom: the policy that describes what the site does today · codigoagente

Fix: we rewrote src/app/(es)/cookies/page.tsx with a real inventory (pages and fonts from Vercel, images from Sanity, no analytics or ads) and explained why there is no banner. If analytics is added tomorrow, the page already says consent will be requested first.

Check 3: the privacy policy

Same problem, but more serious, because the LOPDP (Art. 12) requires fair and transparent information. The old version mentioned "aggregate analytics" and "advertising through Google AdSense" based on consent, a banner, and keeping logs "for a maximum of [N] days".

Privacy policy before and after: AdSense, analytics and the unfilled retention period disappear; the real providers Vercel and Sanity appear
Purposes, processors and retention sections, before and after · codigoagente

Fix in src/app/(es)/privacidad/page.tsx:

  • Data processed: only connection data and the emails we receive.
  • Named providers: Vercel (hosting), Sanity (content and image CDN) and the domain's email forwarding.
  • A concrete deadline for rights requests: 15 days, as set by the LOPDP.
  • The right to complain to the Superintendencia de Protección de Datos Personales.
  • A commitment to update the policy before adding analytics, ads or forms.

We also removed the "[N] business days" from the contact page and the mention of "advertising space" from the terms, because the site shows no ads.

Check 4: affiliate links

The Paymenter guide links to Contabo with an affiliate link. The site already marked those links with rel="sponsored" for Google and showed a "Tools mentioned" box with the disclosure, but at the end of the article. A reader clicking the link in the third paragraph had seen no notice, and the FTC expects the disclosure close to the link.

Fix 1: a notice at the top of every article with affiliate links.

Affiliate link notice at the top of a codigoagente article, below the guide's table of contents
The notice only appears on articles with affiliate links · codigoagente

Fix 2: an "affiliate" tag next to every paid link in the body.

Before and after of a Contabo link in the body text: afterwards it carries the affiliate tag next to it
The same paragraph before and after · codigoagente

The change is in src/components/organisms/post-body.tsx: the component that renders Portable Text links already knew whether a link was sponsored, so adding the tag next to it was enough:

src/components/organisms/post-body.tsxtsx
link: ({ value, children }) => {
  const href: string = value?.href ?? "#";
  const external = href.startsWith("http");
  const sponsored = Boolean(value?.sponsored);
  const rel = sponsored ? "sponsored noopener noreferrer" : "noopener noreferrer";
  return (
    <>
      <a href={href} {...(external ? { target: "_blank", rel } : {})}>
        {children}
      </a>
      {sponsored ? (
        <span className="ml-1 rounded border px-1 align-middle font-mono text-[0.7em] text-muted-foreground no-underline">
          {affiliateMark}
        </span>
      ) : null}
    </>
  );
},

The tag and notice text come from the src/lib/i18n.ts dictionary, so the English site shows "affiliate". The top notice is added in src/components/templates/post-template.tsx when the article has affiliateLinks.

What's still pending (and why we didn't fix it ourselves)

An honest audit also writes down what can't be closed in code:

  1. Controller details. The LOPDP (Art. 12(8)) requires disclosing the controller's address, phone and email. codigoagente is an anonymous brand and currently only publishes the email and city. Meeting this without exposing an individual means, for example, running the blog through a company or with a business address. That's a business decision, not a code change.
  2. English policies. The legal pages exist only in Spanish, and the English footer says so. For US and EU readers, an English version is worth having.
  3. Images from Sanity's CDN. It's disclosed, but if the site grows with EU readers, serving them from its own domain removes the issue entirely.
  4. AI-written content. The AI Act requires disclosing AI-generated text published to inform the public on matters of public interest, unless it undergoes human review with editorial responsibility. This blog is technical and reviewed before publishing, but a note on how articles are written adds transparency.

Summary of changes

RiskLawApplies?ChangeFile
Google FontsGDPRYesAlready compliant via `next/font``src/app/layout.tsx`
Third-party imagesGDPR / LOPDPYesDisclosed in privacy and cookie policies`privacidad/page.tsx`, `cookies/page.tsx`
Policy not matching realityLOPDP Art. 12YesRewritten`privacidad/page.tsx`, `cookies/page.tsx`
Gaps in contact page and termsLOPDPYes15-day deadline, no ads`contacto/page.tsx`, `terminos/page.tsx`
Affiliate links without nearby noticeFTCYesTop notice and tag next to each link`post-body.tsx`, `post-template.tsx`, `i18n.ts`
MinorsCOPPA / LOPDP Art. 21No——
Session replayCIPANo——
Marketing emailsCAN-SPAM / ECA Art. 50No——
SubscriptionsROSCA / California ARLNo——
User contentDMCANo——

Before pushing, pnpm lint and pnpm build passed without errors, and we checked on a local production server that the notice and tags appear only on articles with affiliate links.

FAQ

How long does an audit like this take?

For a blog like this one, the inventory and curl checks take under an hour. The code fixes, another hour. What takes longest is deciding the non-technical pending items.

Can I use the same prompt on my project?

Yes, it's the one from part one. The key is step 1: Claude takes inventory before touching anything and asks you what it can't infer.

Why not add a cookie banner "just in case"?

Because a banner with no cookies to manage confuses readers and adds a component to maintain. If there are no non-essential cookies, the right move is to say so in the policy.

Conclusion

Most issues weren't technical but about consistency: template-generated legal pages describing a site that doesn't exist. The fixes were small: two rewritten policies, two gaps removed and an affiliate notice where readers actually see it. If your project is AI-built, start with the inventory from part one and review your own legal pages with the same eye.

// implementation

Need this automation running in your business?
Don't spend weeks wiring servers, APIs and agents. Safe Build Labs, the agency that sponsors codigoagente, builds it for you, production-ready.
We tested mcp-builder, Anthropic's official skill for building MCP servers: one for a small shop in 99 seconds for 0.25 USD, plus two version bugs to avoid.
  • Skills for your agents
  • Claude Code
read article
We tested webapp-testing, Anthropic's official skill that tests your local web app with Playwright: setup, with_server.py, two real pitfalls and what it costs.
  • Skills for your agents
  • Claude Code
read article
video-shotcraft is an Apache-2.0 skill with 150 shots that lets Claude Code build your product's promo video in Remotion. How it works and what it costs.
  • Skills for your agents
  • Claude Code
read article