Loading repository data…
Loading repository data…
IngenieroSeed / repository
Marca de agua destructiva y 100% local para tu DNI. La marca no se superpone: sustituye los píxeles, así que quitarla no es recuperar el documento, es inventárselo. Ata cada copia a un propósito declarado y destruye el color de forma irreversible. Un archivo HTML, cero dependencias, cero red.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
Marca de agua destructiva, local y offline para copias de tu DNI, pasaporte o facturas.
Un solo archivo HTML. Sin dependencias. Sin build. Sin servidor. Sin red.
Cuando un hotel te pide una foto del DNI para una reserva, le acabas mandando por WhatsApp un fichero que sirve para cualquier cosa: abrir una cuenta, contratar una línea, firmar un préstamo al día siguiente. Esa copia sobrevive a tu estancia, se reenvía, acaba en el correo personal de alguien y, con suerte, en una carpeta compartida.
SafeDocument convierte esa copia en un documento atado a un único propósito declarado y físicamente degradado: el propósito se imprime miles de veces por encima —y por debajo— de tus datos, el color se destruye de forma irreversible, y lo que no necesite el destinatario lo tapas antes de marcar.
[!IMPORTANT] La marca no se superpone: sustituye los píxeles originales. Quitarla no es recuperar el documento, es inventárselo. Y un DNI inventado ya no es tu DNI: es un documento falsificado, con las consecuencias que eso tiene para quien lo presente. Ésa —y no la criptografía— es la defensa principal de esta herramienta.
| SafeDocument | |
|---|---|
| ✅ Es | Una marca de agua destructiva que ata una copia a un propósito concreto |
| ✅ Es | Una herramienta de redacción (tapar campos) irreversible sobre el original |
| ✅ Es | Un disuasorio semántico y legal: reutilizar la copia es falsificar |
| ✅ Es | 100 % local y auditable en una tarde: 860 líneas, un archivo, cero dependencias |
| ❌ No es | Cifrado. El destinatario ve tus datos: ése es el objetivo |
| ❌ No es | Anonimato. Tu nombre y tu número siguen ahí, legibles |
| ❌ No es | Un DRM. Nada impide fotografiar la pantalla o teclear los datos a mano |
| ❌ No es | Infalible. Lee el modelo de amenazas antes de confiar en ello |
[!CAUTION] Un DNI marcado sigue conteniendo todos tus datos personales. SafeDocument no te hace anónimo ni sustituye al criterio de no enviar el documento cuando no es legalmente exigible. Antes de mandar nada, la mejor protección sigue siendo preguntar si de verdad hace falta.
No hay npm install. No hay build. No hay servidor.
git clone https://github.com/TU-USUARIO/safedocument.git
cd safedocument
Y abre SafeDocument.html con doble clic. Eso es todo.
SafeDocument.htmlCtrl+S)Es el modo recomendado y funciona idénticamente:
SafeDocument.htmlLa cabecera de la app te confirma el estado (✓ sin conexión). Si funciona sin red, no hay debate posible sobre a dónde van tus datos: no van a ninguna parte.
Flujo de trabajo:
Ctrl+V, o clic para elegir)[!TIP] El propósito es el corazón de la herramienta, no un adorno. «Copia» no sirve de nada. «Copia válida únicamente para la reserva del Hotel ABCDE del 3 al 7 de agosto de 2026» convierte el documento en auto-limitado: dice a quién se entregó, para qué, y deja constancia de que cualquier otro uso es fraudulento. Cuanto más específico, más fuerte es la protección.
La promesa de «tus datos no salen de tu equipo» no es una nota en el README: está impuesta por el navegador mediante una Content Security Policy declarada en la primera línea del documento.
default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';
img-src data: blob:; font-src data:; connect-src 'none'; form-action 'none'; base-uri 'none'
| Directiva | Qué impide | Consecuencia |
|---|---|---|
connect-src 'none' | fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon | Ninguna petición de red es posible. Ni por error, ni por una dependencia comprometida, ni por código malicioso inyectado |
default-src 'none' | Cualquier carga de recurso no permitida explícitamente | Nada externo puede entrar |
form-action 'none' | Envío de formularios | No hay exfiltración por <form> |
base-uri 'none' | Reescritura de la base de URLs relativas | No se puede redirigir el origen de nada |
img-src data: blob: | Imágenes remotas | Solo píxeles que ya están en memoria |
[!NOTE] Esto es cualitativamente distinto de «confía en mí». Aunque el código intentara enviar tu DNI a un servidor, el navegador rechazaría la petición y lo registraría como violación de CSP en la consola. Puedes comprobarlo tú mismo: abre las DevTools (
F12), pestaña Red, y usa la herramienta. La lista estará vacía.
flowchart LR
subgraph tu["🖥️ Tu equipo · aquí ocurre absolutamente todo"]
direction TB
A["📄 Archivo local"] --> B["createImageBitmap()"]
B --> C["Canvas 2D · ImageData"]
C --> D["⚙️ Pipeline en JS puro<br/>(sin dependencias)"]
D --> E["💾 toBlob() → carpeta Descargas"]
end
tu -. "fetch · XHR · WebSocket · sendBeacon<br/><b>❌ BLOQUEADO POR LA CSP</b>" .-> net["🌐 Internet"]
style net fill:#2b1216,stroke:#ff6b6b,stroke-width:2px,color:#ff6b6b
style tu fill:#0e2b23,stroke:#31d0aa,stroke-width:2px
Además, y como consecuencia del diseño:
createImageBitmap() y se re-codifica desde un <canvas>. La geolocalización, el modelo de móvil, la fecha de captura y el número de serie de la cámara no llegan al archivo de salida. La orientación EXIF sí se respeta (imageOrientation: 'from-image') antes de descartarse.localStorage, ni IndexedDB, ni cookies, ni Service Worker. Al cerrar la pestaña no queda rastro.Y todo esto es verificable en 10 segundos sin fiarte de nadie: busca http en el archivo. No hay ni una sola URL. Busca fetch, XMLHttpRequest o localStorage. No aparecen. Es la ventaja de que quepa entero en una pantalla de tu editor.
Todo el procesamiento es una cadena de ocho etapas sobre Uint8ClampedArray, sin librerías gráficas.
flowchart TD
A["📄 Imagen original<br/>DNI · pasaporte · factura"] --> B{"¿Has tapado<br/>algún campo?"}
B -->|"Sí"| C["<b>1 · Redacción destructiva</b><br/>negro sólido o pixelado<br/>a resolución completa"]
B -->|"No"| D["<b>2 · Lienzo y escalado</b><br/>maxDim · márgenes · k = max(w,h)/1000"]
C --> D
D --> E["<b>3 · Luminancia local</b><br/>Rec. 601 + box blur separable"]
D --> F["<b>4 · Capa de texto ondulado</b><br/>glifos sobre curvas multi-seno"]
F --> G["<b>5 · Warp multi-lente</b><br/>Poisson-disk + mapa inverso bilineal"]
E --> H["<b>6 · Mezcla con contraste bloqueado</b><br/>polaridad garantizada + halo bipolar"]
G --> H
H --> I["<b>7 · Pie con el propósito</b><br/>destinatario · fecha · ID de copia"]
I --> J["<b>8 · Destrucción del color</b><br/>fBm + marching squares → isolíneas"]
J --> K["✅ <b>Copia protegida</b><br/>PNG sin pérdida o JPEG q=92"]
style A fill:#12161d,stroke:#5b8cff,color:#e6e9ef
style K fill:#0e2b23,stroke:#31d0aa,color:#31d0aa
style C fill:#2b1216,stroke:#ff6b6b,color:#e6e9ef
style J fill:#2b2416,stroke:#ffb454,color:#e6e9ef
Todas las decisiones aleatorias del pipeline salen de un único PRNG determinista sembrado con semilla | propósito | destinatario | fecha. Mismas entradas ⇒ imagen idéntica bit a bit.
Antes de marcar nada, las cajas que dibujas se aplican sobre el original a resolución completa (applyRedactions()), no sobre la vista previa. El resultado alimenta el resto del pipeline, así que lo tapado no queda debajo de la marca: deja de existir.
fillRect con #000 sólido. Irreversible. No hay nada que recuperar porque no hay nada.max(6, max(w,h)/9) px con imageSmoothingEnabled = false.[!CAUTION] El pixelado no es seguro para texto. Existen ataques prácticos de despixelado (p. ej. Depix) que reconstruyen texto pixelado por fuerza bruta cuando la fuente y el formato son conocidos —y el formato de un número de soporte de un DNI es perfectamente conocido—. Si el campo importa, usa negro. El pixelado es solo para cosas que no comprometen nada.
La imagen se escala para que su lado mayor no supere maxDim (1400 px por defecto) y se centra sobre un lienzo blanco con márgenes: 20k a los lados, 20k arriba y 110k abajo (el espacio del pie).
La clave del diseño es el factor de escala:
$$k = \frac{\max(\text{ancho}, \text{alto})}{1000}$$
Absolutamente todo se dimensiona en función de k: el cuerpo de letra, el grosor del trazo, el radio del desenfoque, los radios de las lentes, la separación de Poisson, el paso de las isolíneas. Consecuencia: el resultado es visualmente idéntico a cualquier resolución, solo cambia el número de píxeles. No hay ni un solo valor en píxeles absolutos hardcodeado en el pipeline.
Se calcula la luminancia Rec. 601 de cada píxel del fondo:
$$L = 0.299R + 0.587G + 0.114B$$
y se desenfoca con un box blur separable de radio 6k. La implementación (boxBlur) usa una ventana deslizante: en cada paso suma el píxel que entra y resta el que sale, así que el coste es O(W·H) independientemente del radio. Dos pasadas (horizontal y vertical) sobre Float32Array.
Este mapa L es el que decide, píxel a píxel, de qué color se pinta la marca. Ver etapa 6.
Cada carácter del pro