Cómo hacer un registro que delate manipulaciones
Un registro de auditoría es un historial de quién cambió qué y cuándo. Un registro que cualquiera puede editar no es una prueba, porque nadie sabe si lo cambiaron.
Importa porque una decisión sobre un cambio de IA vale lo que vale su registro. Una cadena de hashes hace que cualquier edición del registro se note.
Al final de esta lección
Nota
Puedo llevar un registro que muestre si lo cambiaron.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Solo añadir | se añaden entradas nuevas y las viejas no se editan | |
| Hash | una huella corta de un texto | 1a47e90b |
| Cadena | entradas que guardan cada una el hash de la anterior | |
| Manipular | cambiar un registro sin permiso | |
| Ancla | el último hash, guardado en otro sitio |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un hash es la huella de un texto
Un hash convierte cualquier texto en un código corto. El mismo texto da siempre el mismo código. Cambia una letra y el código cambia por completo.
El hash de esta lección es pequeño y está hecho para enseñar. No es seguro. Un registro real usa un hash fuerte de una biblioteca, y la idea es la misma.
Ejemplo resuelto: tres huellas
| Texto | Hash |
|---|---|
| (nada) | 811c9dc5 |
| a | e40c292c |
| abc | 1a47e90b |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Una cadena enlaza cada entrada con la anterior
Cada entrada guarda su texto, el hash de la entrada anterior y su propio hash. Su propio hash se hace con el hash anterior, una barra y su texto.
Edita la entrada 2 y su propio hash ya no coincide con su texto. Calcula un hash nuevo para disimularlo y la entrada 3 sigue guardando el viejo, así que se rompe el eslabón siguiente.
Ejemplo resuelto: cinco entradas
| Entrada | Texto | Hash anterior | Hash propio |
|---|---|---|---|
| 0 | modelo fijado a chat-v3 | 00000000 | 7e0a2f67 |
| 1 | canario al 5 por ciento | 7e0a2f67 | e5858133 |
| 2 | canario al 20 por ciento | e5858133 | 14be4e02 |
| 3 | promovido a todos los usuarios | 14be4e02 | 1b816758 |
| 4 | modelo antiguo retirado | 1b816758 | 454d87c4 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un error frecuente
Advertencia
Mucha gente cree que una cadena de hashes impide cambiar el registro. No lo impide. Solo muestra un cambio. Quien pueda escribir todo el archivo puede rehacer cada hash. Guarda el último hash, el ancla, donde no lleguen, y compáralo.
Escribe el ayudante del modelo, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// Los tokens de una respuesta, o null si no trae ninguno.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el ayudante del modelo, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"estado \" + res.status);' ' const d = await res.json();' ' return { text: d.choices[0].message.content, inTokens: count(d, \"prompt_tokens\"), outTokens: count(d, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Muestra grabada: ningún modelo respondió (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de auditoría, parte 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const START = \"00000000\";' 'function hash(text) {' ' let x = 2166136261;' ' for (let i = 0; i < text.length; i++) {' ' x = x ^ text.charCodeAt(i);' ' x = Math.imul(x, 16777619) >>> 0;' ' }' ' return (\"00000000\" + x.toString(16)).slice(-8);' '}' > audit.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de auditoría, parte 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function add(chain, text) {' ' const prev = chain.length > 0 ? chain[chain.length - 1].hash : START;' ' return chain.concat([{ text: text, prev: prev, hash: hash(prev + \"|\" + text) }]);' '}' 'function firstBroken(chain) {' ' let prev = START;' ' for (let i = 0; i < chain.length; i++) {' ' const e = chain[i];' ' if (e.prev === prev && e.hash === hash(e.prev + \"|\" + e.text)) prev = e.hash;' ' else return i;' ' }' ' return -1;' '}' >> audit.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de auditoría, parte 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const notes = [\"modelo fijado a chat-v3\", \"canario al 5 por ciento\", \"canario al 20 por ciento\",' ' \"promovido a todos los usuarios\", \"modelo antiguo retirado\"];' 'let chain = [];' 'notes.forEach(function (t) { chain = add(chain, t); });' 'const anchor = chain[4].hash;' 'const forged = chain.map(function (e) { return { text: e.text, prev: e.prev, hash: e.hash }; });' 'forged[2].text = \"canario al 90 por ciento\";' 'let again = [];' 'forged.forEach(function (e) { again = add(again, e.text); });' >> audit.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el script de auditoría, parte 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' console.log(\"hash de abc: \" + hash(\"abc\"));' ' console.log(\"primer eslabón roto en el registro real: \" + firstBroken(chain));' ' console.log(\"primer eslabón roto tras una edición: \" + firstBroken(forged));' ' console.log(\"el registro reconstruido es coherente: \" + (firstBroken(again) === -1));' ' console.log(\"el registro reconstruido coincide con el ancla: \" + (again[4].hash === anchor));' ' const q = \"En una frase corta, ¿por qué se nota una entrada editada en una cadena de hashes?\";' ' const sample = \"Cada entrada guarda la huella de la anterior, así que un cambio rompe todos los eslabones siguientes.\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 60);' ' console.log(\"el modelo dijo: \" + reply.text);' '}' 'main();' >> audit.js"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta el registro de auditoría
docker exec -w /home/node/project agent node audit.jsSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Lo que imprime el Run
| Línea | Qué muestra |
|---|---|
| hash de abc: 1a47e90b | La misma huella siempre |
| primer eslabón roto en el registro real: -1 | Ningún eslabón roto |
| primer eslabón roto tras una edición: 2 | La edición se halla en la entrada 2 |
| el registro reconstruido es coherente: true | Una reconstrucción completa pasa la comprobación de eslabones |
| el registro reconstruido coincide con el ancla: false | El ancla sigue mostrando el cambio |
| el modelo dijo: ... | Una frase del laboratorio. Una muestra grabada si ningún modelo responde |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.