galletas4all
v1.0.3
Published
Lightweight, self-hosted cookie consent widget for Chilean nDPL compliance. Logs consent to your own Cloudflare D1 database.
Maintainers
Readme
Galletas4all
A lightweight cookie consent widget for the Chilean nDPL (Ley 21.719) and GDPR. About 10 KB minified. Self-hosted on Cloudflare D1 — no third-party trackers, no SaaS fees, no shared database.
Live demo: https://galletas4all.pages.dev · Docs: ARCHITECTURE.md · Security: SECURITY.md
Why this exists
Chile's new Personal Data Protection Law (Ley 21.719) takes effect in late 2026. Most cookie consent SaaS tools charge per-site fees, share a tracker across every customer, or both. For small Chilean sites, that's the wrong shape of solution.
Galletas4all is what I would use on my own sites: a small widget that asks for consent honestly, blocks third-party scripts until the visitor agrees, and writes the audit log to a D1 table I own. Cloudflare's free tier covers the math for sites well into the tens of thousands of visitors per day.
Quick start — script tag (zero build step)
<script
defer
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/widget.js"
integrity="sha384-xth1tc/hMyIJV6UohIWoDvGBZbYC7WcQlwGVQPjCxIfBR8VL/EJhmWLeRoYMalzD"
crossorigin="anonymous"
data-api-url="https://your-worker.workers.dev"
data-lang="es"
></script>About integrity: the SRI hash pins the file. If the CDN is ever compromised, your browser refuses to execute the tampered bundle. The trade-off is that the snippet is version-locked — bumping to a new release means copying the new hash from the release notes (published on every GitHub release).
If you prefer auto-updates over SRI, drop both attributes and use @1 (moving tag). For a consent/compliance widget, we recommend keeping SRI on.
Mark any third-party script you want to gate:
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXX" data-consent-category="analytics"></script>The widget blocks tagged scripts until the visitor accepts the matching category, then swaps them back in.
Quick start — npm
npm install galletas4allimport { mount, onConsentChange } from 'galletas4all';
const teardown = mount({ apiUrl: 'https://your-worker.workers.dev' });
onConsentChange((record) => {
console.log('User decided:', record);
});mount() returns a teardown function. Call it on SPA route teardown to disconnect the script-blocking observer.
Deploy the Worker
git clone https://github.com/felipemillan/Galletas4all
cd Galletas4all/worker
cp wrangler.toml.example wrangler.toml
# edit wrangler.toml: set database_id and ALLOWED_ORIGIN
npx wrangler d1 create galletas4all-db
npx wrangler d1 execute galletas4all-db --remote --file=./schema.sql
npx wrangler deployALLOWED_ORIGIN is a comma-separated list of sites permitted to POST consent logs. Set * only for local development.
Optional: enable the GET /stats endpoint with npx wrangler secret put STATS_TOKEN. Without STATS_TOKEN set, /stats returns 503 (default-deny — prevents accidental exposure of consent volume).
What the widget actually does
| Step | Where | Notes |
|---|---|---|
| 1. Show banner | Browser | Reads localStorage first — no banner if already decided. |
| 2. Block scripts | Browser | MutationObserver rewrites type="text/plain" until consent. |
| 3. Persist decision | Browser | localStorage["galletas4all_consent"]. |
| 4. Send audit log | Browser → Worker | navigator.sendBeacon first; fetch({keepalive:true}) fallback. |
| 5. Write to D1 | Worker | One row per decision. Parameterized insert. |
Full data-flow diagrams in ARCHITECTURE.md.
Configuration
| Attribute | Default | What |
|---|---|---|
| data-api-url | "" (local-only mode) | URL of your Worker. Omit to skip audit logging. |
| data-lang | auto-detected | "es" or "en". Otherwise from ?lang=, then navigator.language. |
| data-policy-version | "" | Optional version label of your cookie policy (e.g. "2026-06"). Stamped onto each consent log so you can prove which policy text the visitor saw. |
| data-consent-category on a <script> | — | "essential", "analytics", or "marketing". Untagged scripts are not blocked. |
Worker env vars: ALLOWED_ORIGIN (required for production), STATS_TOKEN (required to enable GET /stats).
Browser support
Modern Chromium, Firefox, Safari (incl. iOS), Edge. Uses MutationObserver, navigator.sendBeacon, crypto.randomUUID with a getRandomValues fallback.
Limitations
Audit rows are admin-mutable. Anyone with D1 access can edit or delete rows. There is no tamper-proofing or signed ledger yet — that is planned for v2.0 (see docs/ROADMAP.md). For now, treat the D1 log as an operational audit trail, not a cryptographically verifiable record.
visitor_idlives inlocalStorage. The key isgalletas4all_vid. If the visitor clears their browser storage, uses a different device, or opens a private/incognito window, they get a new ID. The link between new and old consents is lost. There is no cross-device or cross-browser identity here by design (that would require account login, which is out of scope).The
Originheader is spoofable. The Worker uses theOriginheader to enforce theALLOWED_ORIGINallow-list. A non-browser client can forge that header. The allow-list reduces casual abuse; it does not stop a determined attacker. There is also no rate limiting on the Worker. Don't rely onALLOWED_ORIGINas a security boundary — it is a footgun-prevention measure.
DSAR runbook
Chilean Ley 21.719 (and GDPR Art. 15–17) gives visitors the right to access and erase their data. Here is how to handle both requests.
Step 1 — The visitor finds their ID.
Ask the requester to open their browser console on your site and run:
localStorage.getItem('galletas4all_vid')The value looks like vid_.... That is the visitor_id you will query.
Step 2 — Access request (deliver the data).
Run against your D1 database and deliver the resulting rows as JSON (or a CSV export):
npx wrangler d1 execute galletas4all-db --remote \
--command "SELECT * FROM consent_logs WHERE visitor_id = 'vid_...';"Replace vid_... with the actual value the visitor reported. The --remote flag is required — without it, wrangler queries your local dev database, not production D1, and you will get no rows.
Step 3 — Erasure request (right to be forgotten).
npx wrangler d1 execute galletas4all-db --remote --yes \
--command "DELETE FROM consent_logs WHERE visitor_id = 'vid_...';"The --remote flag is required — without it the DELETE runs against your local dev database and never touches production data. --yes skips the interactive confirmation for this destructive command; omit it if you want to confirm the production write manually.
Confirm the delete returned at least one row. If it returned zero, the ID was not in the production database (visitor may have already been purged, or they used a different browser).
For scheduled bulk purges based on age, see docs/RETENTION.md.
Contributing
Issues and PRs welcome. Read SECURITY.md before reporting vulnerabilities (use email, not the issue tracker).
The codebase is small and TypeScript-strict. To work locally:
npm install
npm test # vitest + jsdom + Miniflare
npm run build # IIFE + ESM + .d.ts
npm run dev # serve index.html on :3000License
MIT — see LICENSE.
Versión en español
Widget de consentimiento de cookies para la nueva Ley 21.719 de Chile y el GDPR europeo. ~10 KB minificado. Auto-alojado en Cloudflare D1: sin trackers de terceros, sin tarifas SaaS, sin base de datos compartida.
Demo en vivo: https://galletas4all.pages.dev · Arquitectura: ARCHITECTURE.md
Uso rápido — etiqueta script
<script
defer
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/widget.js"
integrity="sha384-xth1tc/hMyIJV6UohIWoDvGBZbYC7WcQlwGVQPjCxIfBR8VL/EJhmWLeRoYMalzD"
crossorigin="anonymous"
data-api-url="https://tu-worker.workers.dev"
data-lang="es"
></script>El atributo integrity (SRI) ancla el archivo. Si el CDN llegara a estar comprometido, el navegador rechaza el código alterado. A cambio, debes actualizar el hash al cambiar de versión — lo publicamos en cada release de GitHub.
Marca cualquier script de terceros que quieras controlar:
<script src="..." data-consent-category="analytics"></script>El widget bloquea los scripts marcados hasta que el visitante acepte la categoría correspondiente.
Despliegue del Worker
Mismas instrucciones que la sección en inglés. Necesitas una cuenta gratuita de Cloudflare. El esquema D1 es idempotente.
