feat(cybersecurity): auto-commit con 48 cambios

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 23:44:39 +02:00
parent efc9911925
commit 729921e16e
48 changed files with 3765 additions and 8 deletions
+38
View File
@@ -0,0 +1,38 @@
package core
import (
"regexp"
"strings"
)
// ansiCSI matches CSI sequences: ESC [ ... <final byte>
// Covers colors (SGR), cursor movement, erase, etc.
var ansiCSI = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`)
// ansiOSC matches OSC sequences: ESC ] ... <BEL or ST>
// Used for window titles, hyperlinks, etc.
var ansiOSC = regexp.MustCompile(`\x1b\][^\x07\x1b]*(\x07|\x1b\\)`)
// ansiEsc matches other two-character escape sequences: ESC <char>
// Covers ESC c (reset), ESC ( B, ESC ) 0, etc.
var ansiEsc = regexp.MustCompile(`\x1b[@-Z\\-_]|\x1b[()][0-9A-Za-z]`)
// StripANSI removes ANSI/VT100 terminal escape sequences from s and filters
// non-printable control characters, preserving newlines (\n), tabs (\t) and
// carriage returns (\r).
func StripANSI(s string) string {
s = ansiCSI.ReplaceAllString(s, "")
s = ansiOSC.ReplaceAllString(s, "")
s = ansiEsc.ReplaceAllString(s, "")
return strings.Map(func(r rune) rune {
// Preserve printable characters, \n (0x0A), \t (0x09), \r (0x0D).
if r == '\n' || r == '\t' || r == '\r' {
return r
}
// Drop C0 control characters (0x00-0x1F) and DEL (0x7F).
if r < 0x20 || r == 0x7F {
return -1
}
return r
}, s)
}
+55
View File
@@ -0,0 +1,55 @@
---
name: strip_ansi
kind: function
lang: go
domain: core
version: "1.0.0"
purity: pure
signature: "func StripANSI(s string) string"
description: "Elimina secuencias de escape ANSI/VT100 de un string y filtra caracteres de control no imprimibles, preservando \\n, \\t y \\r."
tags: ["terminal", "ansi", "string", "sanitize", "terminal-capture"]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: ["regexp", "strings"]
params:
- name: s
desc: "String que puede contener secuencias de escape de terminal (CSI, OSC, escapes simples) y/o caracteres de control."
output: "String limpio: sin secuencias ANSI ni caracteres de control, preservando saltos de línea (\\n), tabulaciones (\\t) y retornos de carro (\\r)."
tested: true
tests:
- "golden: color SGR codes"
- "edge OSC titulo de ventana"
- "edge movimientos de cursor"
- "edge string sin escapes preserva saltos de linea"
- "edge string vacio"
- "edge preserva tabs"
test_file_path: "functions/core/strip_ansi_test.go"
file_path: "functions/core/strip_ansi.go"
---
## Ejemplo
```go
// Limpiar output de terminal con color rojo
raw := "\x1b[31mError:\x1b[0m archivo no encontrado"
clean := core.StripANSI(raw)
// clean == "Error: archivo no encontrado"
// Limpiar título de ventana OSC
raw2 := "\x1b]0;mi titulo\x07contenido real"
clean2 := core.StripANSI(raw2)
// clean2 == "contenido real"
```
## Cuando usarla
Cuando captures output de un PTY/TUI/subprocess y necesites texto plano: antes de indexar logs con ANSI en un buscador, antes de difar output de terminal, o cuando muestres salida de comando en un contexto sin soporte de escape (UI web, archivo, base de datos).
## Gotchas
- Preserva `\n`, `\t` y `\r` a propósito: el output de terminales suele tener CRLF y tabulaciones con semántica propia.
- Cubre CSI, OSC y escapes simples de dos caracteres. Secuencias DCS o PM (rarísimas) no se eliminan; si las necesitas, añade una regex adicional antes de llamar a esta función.
- Las regexes están precompiladas a nivel de paquete: no hay coste de compilación por llamada.
+53
View File
@@ -0,0 +1,53 @@
package core
import "testing"
func TestStripANSI(t *testing.T) {
t.Run("golden: color SGR codes", func(t *testing.T) {
got := StripANSI("\x1b[31mhola\x1b[0m mundo")
want := "hola mundo"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("edge OSC titulo de ventana", func(t *testing.T) {
got := StripANSI("\x1b]0;mi titulo\x07texto")
want := "texto"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("edge movimientos de cursor", func(t *testing.T) {
got := StripANSI("linea1\x1b[2K\x1b[1Glinea2")
want := "linea1linea2"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("edge string sin escapes preserva saltos de linea", func(t *testing.T) {
got := StripANSI("plano\ncon\nlineas")
want := "plano\ncon\nlineas"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("edge string vacio", func(t *testing.T) {
got := StripANSI("")
want := ""
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("edge preserva tabs", func(t *testing.T) {
got := StripANSI("a\tb")
want := "a\tb"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
}
+23
View File
@@ -0,0 +1,23 @@
package core
// PrefixDelta returns the portion of curr that follows the longest common
// prefix (LCP) shared with prev, comparing rune-by-rune to avoid splitting
// multi-byte characters.
//
// In the monotone streaming case (curr = prev + new), this returns exactly
// the new suffix. When the text diverges mid-way (reflow), it returns
// everything from the point of divergence to the end of curr.
func PrefixDelta(prev, curr string) string {
prevRunes := []rune(prev)
currRunes := []rune(curr)
common := 0
for common < len(prevRunes) && common < len(currRunes) {
if prevRunes[common] != currRunes[common] {
break
}
common++
}
return string(currRunes[common:])
}
+63
View File
@@ -0,0 +1,63 @@
---
name: text_prefix_delta
kind: function
lang: go
domain: core
version: "1.0.0"
purity: pure
signature: "func PrefixDelta(prev, curr string) string"
description: "Calcula el delta de streaming entre dos versiones de un texto: devuelve la porción de curr que sigue al prefijo común más largo con prev, comparando runa a runa para no partir caracteres multibyte."
tags: [string, diff, streaming, delta, terminal-capture]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
params:
- name: prev
desc: "Versión anterior del texto acumulativo (snapshot anterior del stream)."
- name: curr
desc: "Versión actual del texto acumulativo (snapshot actual, normalmente extiende a prev)."
output: "La porción de curr que sigue al prefijo común con prev (el 'delta' de streaming). Devuelve cadena vacía si curr no añade nada nuevo tras el prefijo común."
tested: true
tests:
- "monotono append normal"
- "prev vacio devuelve curr completo"
- "sin cambios devuelve vacio"
- "divergencia en medio devuelve desde divergencia"
- "curr mas corto que prev devuelve vacio"
- "multibyte cafe streaming"
- "multibyte prefijo parcial antes de acento"
- "ambos vacios devuelve vacio"
- "prev no vacio curr vacio devuelve vacio"
- "determinismo misma entrada misma salida"
test_file_path: "functions/core/text_prefix_delta_test.go"
file_path: "functions/core/text_prefix_delta.go"
---
## Ejemplo
```go
// Bucle de streaming por snapshots acumulativos:
prev := ""
snapshots := []string{"Hola", "Hola, mun", "Hola, mundo!"}
for _, curr := range snapshots {
delta := PrefixDelta(prev, curr)
if delta != "" {
fmt.Print(delta) // emite solo la parte nueva
}
prev = curr
}
// Output: Hola, mundo!
```
## Cuando usarla
Cuando hagas streaming por snapshots acumulativos y necesites emitir solo la parte nueva de cada snapshot. Caso típico: consumir `pty_capture_stream_go_infra` donde cada captura de la TUI es un snapshot que extiende al anterior, y quieres emitir eventos `text_delta` estilo SSE/streaming sin reenviar texto ya enviado.
## Gotchas
- Compara por prefijo común, no por diff completo. Si el texto cambia en medio (reflow, borrado, sobreescritura de terminal), el delta incluye todo desde el punto de divergencia hasta el final de curr — puede re-emitir texto ya visto. Adecuado para append monótono; en streaming de TUI con reflow es heurístico, no exacto.
- Trabaja sobre runas (no bytes) para no partir caracteres UTF-8 multibyte como 'é', '中', '→'. El offset de corte siempre cae en un límite de runa válido.
+87
View File
@@ -0,0 +1,87 @@
package core
import "testing"
func TestPrefixDelta(t *testing.T) {
tests := []struct {
name string
prev string
curr string
want string
}{
{
name: "monotono append normal",
prev: "PON",
curr: "PONG",
want: "G",
},
{
name: "prev vacio devuelve curr completo",
prev: "",
curr: "abc",
want: "abc",
},
{
name: "sin cambios devuelve vacio",
prev: "abc",
curr: "abc",
want: "",
},
{
name: "divergencia en medio devuelve desde divergencia",
prev: "abc",
curr: "abXY",
want: "XY",
},
{
name: "curr mas corto que prev devuelve vacio",
prev: "abcdef",
curr: "abc",
want: "",
},
{
name: "multibyte cafe streaming",
prev: "café",
curr: "café con leche",
want: " con leche",
},
{
name: "multibyte prefijo parcial antes de acento",
prev: "ca",
curr: "café",
want: "fé",
},
{
name: "ambos vacios devuelve vacio",
prev: "",
curr: "",
want: "",
},
{
name: "prev no vacio curr vacio devuelve vacio",
prev: "hola",
curr: "",
want: "",
},
{
name: "determinismo misma entrada misma salida",
prev: "hello world",
curr: "hello world!",
want: "!",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := PrefixDelta(tc.prev, tc.curr)
if got != tc.want {
t.Errorf("PrefixDelta(%q, %q) = %q, want %q", tc.prev, tc.curr, got, tc.want)
}
// Verificar determinismo: segunda llamada produce el mismo resultado.
got2 := PrefixDelta(tc.prev, tc.curr)
if got != got2 {
t.Errorf("no determinista: primera=%q segunda=%q", got, got2)
}
})
}
}