Fue una conversación de Telegram con un amigo desarrollador: me contó que había encontrado su propio código en un repositorio público de GitHub, casi sin modificaciones. El autor había cambiado los nombres de variables y el README pero la lógica era idéntica, incluyendo un bug muy específico que él mismo había introducido y corregido meses antes.
La semana siguiente construí Sentinel.
La idea
Sentinel es un watchdog que corre automáticamente cada lunes y busca fragmentos de mis proyectos en el código público de GitHub. Si encuentra algo nuevo que no había visto antes, me avisa por Telegram. No es una solución perfecta, pero es mucho mejor que enterarse por casualidad.
Cómo busca
La estrategia no es comparar archivos completos — demasiado caro para la API gratuita — sino buscar fragmentos de código suficientemente específicos para no generar falsos positivos. Strings únicos de mi código: nombres de variables en español dentro de JavaScript (como setStation newNombre newLogo), combinaciones de constantes que no tienen equivalente genérico, comentarios propios dentro de código técnico.
La GitHub Code Search API tiene un límite de 30 requests por minuto. Para no gastar la quota, los fragmentos tienen que producir menos de 10 resultados. Si un fragmento produce 0 resultados, perfecto. Si produce más de 20, es probablemente demasiado genérico y lo saco de la lista.
La GitHub Code Search API
La API de búsqueda de código de GitHub es diferente de la API REST general. Las queries tienen su propia sintaxis y limitaciones. Por ejemplo, no se pueden buscar strings de menos de 3 caracteres, y algunas combinaciones de operadores están restringidas:
GET https://api.github.com/search/code
?q=setStation+newNombre+newLogo+language:javascript
&per_page=5
Authorization: Bearer ghp_XXXXX
La respuesta incluye los repositorios que contienen el fragmento, el archivo y el path. Si el repositorio no es ninguno de los míos y el fragmento matchea, es una coincidencia que hay que revisar.
La gestión de resultados vistos
Para no notificar dos veces por el mismo resultado, Sentinel mantiene una tabla SQLite local con los repositorios ya vistos para cada fingerprint:
CREATE TABLE seen_results (
fingerprint_id TEXT NOT NULL,
repo_full_name TEXT NOT NULL,
first_seen TEXT NOT NULL DEFAULT (date('now')),
PRIMARY KEY (fingerprint_id, repo_full_name)
);
Al ejecutarse cada lunes, compara los resultados actuales contra los que ya están en seen_results. Solo notifica los nuevos. Los ya conocidos se ignoran aunque sigan apareciendo. Con el tiempo, la base de vistos crece y el ruido disminuye.
Lo que encontré (y lo que no)
En las primeras semanas, Sentinel encontró tres coincidencias:
Una era efectivamente código mío: un snippet de PHP para parsear tokens que había publicado en un comentario de Stack Overflow y que alguien había incluido en su proyecto sin atribución. No me molestó mucho — lo había publicado en público — pero ilustra que el sistema funciona.
Las otras dos eran falsos positivos: coincidencias con código que se parece al mío porque ambos seguimos las mismas convenciones de PHP. Parte esperable del ajuste fino.
El formato de notificación
Cuando Sentinel encuentra algo nuevo, el mensaje de Telegram tiene este formato:
🔍 *Sentinel — coincidencia nueva*
*Fingerprint:* setStation newNombre newLogo
*Repositorio:* usuario/repo-externo
*Archivo:* src/player.js
*Link:* https://github.com/usuario/repo-externo/blob/main/src/player.js
Revisá si es uso legítimo o copia no atribuida.
El link va directamente al archivo en GitHub. La revisión manual tarda 2 minutos: abrir el link, ver el contexto del fragmento, decidir si es relevante. Si no lo es, se agrega el repo a una lista de ignorados.
El límite honesto
Sentinel sólo busca en código público de GitHub. Repositorios privados, GitLab, Bitbucket — invisible para él. Si alguien copia el código y lo mantiene privado, no lo vas a encontrar así. Y si el código fue refactorizado significativamente o traducido a otro lenguaje, las búsquedas de texto tampoco van a matchear.
Para eso necesitarías análisis de similitud semántica o embeddings de código — un problema mucho más difícil. Por ahora, con detectar los casos obvios me alcanza.
Por qué el repo es privado
El repo de Sentinel es privado — precisamente porque publicar los fingerprints que uso sería darle a alguien la guía de cómo evitar que lo detecte. Si un copión sabe exactamente qué strings busco, simplemente cambia esos strings y Sentinel no lo encuentra más. El README describe el enfoque general sin revelar las queries específicas.
Es el mismo principio que los sistemas de seguridad: la arquitectura puede ser pública, las claves no.
El ajuste fino de fingerprints
El mayor trabajo de mantenimiento de Sentinel no es el código sino el catálogo de fingerprints. Cada proyecto nuevo requiere identificar qué strings son suficientemente únicos para servir como huella. El criterio es que la búsqueda devuelva menos de 10 resultados en GitHub — si devuelve más, es demasiado genérico.
El proceso de selección de un buen fingerprint es iterativo. Tomás un fragmento candidato, lo buscás manualmente en GitHub, ves cuántos resultados hay y si alguno no es tuyo. Si el ratio de falsos positivos es alto, elegís un fragmento más específico. Un buen fingerprint es una combinación de términos que solo alguien que copió tu código exacto va a tener.
Límites de la GitHub Code Search API gratuita
La API de búsqueda de código de GitHub, incluso para repositorios públicos, tiene límites que no están bien documentados. El límite documentado es 30 requests por minuto autenticado. Pero también hay un límite no documentado de resultados por query (máximo 1000 results por búsqueda) y restricciones en qué operadores se pueden combinar.
Sentinel distribuye sus queries a lo largo de varios minutos para no golpear el rate limit. Con 15 fingerprints activos, el proceso completo de cada lunes toma ~8 minutos. No es urgente — corre en segundo plano y notifica al terminar si encontró algo.
Si el volumen de proyectos crece significativamente, la GitHub API no va a escalar. En ese punto la alternativa sería integrar con Sourcegraph o con un servicio de indexado de código que no tenga los mismos límites de rate. Por ahora, para el caso personal, la API gratuita es más que suficiente.
Comentarios
Todavía no hay comentarios. Sé el primero.
Dejá tu comentario
Los comentarios son moderados antes de publicarse.