Zenzic — Lacune Architetturali & Roadmap
"Ciò che non è documentato, non esiste; ciò che è documentato male, è un'imboscata."
Questa pagina traccia i gap chiusi durante il ciclo v0.7.0 e quelli ancora aperti nella roadmap v0.8.0. È un documento vivente — aggiornato ad ogni sprint.
Aperti — Target v0.8.0
GAP-001 — Auto-Fix Engine
Componente: cli/_check.py, nuovo core/fixer.py
Descrizione: Zenzic rileva ma non ripara. Un contributore che riceve un finding Z501
(placeholder) o Z502 (contenuto troppo breve) deve individuare e modificare il file
manualmente. Un Auto-Fix engine applicherebbe patch sicure e reversibili direttamente
ai file sorgente — sostituendo i token placeholder, arricchendo le sezioni corte, e
riportando cosa è stato modificato.
Semantica pianificata:
zenzic fix all # dry-run per default: mostra il diff, non scrive nulla
zenzic fix all --apply # scrive le modifiche; staged via git diff
zenzic fix links # corregge solo Z101/Z104 (link rotti) — rinomina o crea stub
Vincoli di design:
-
L'Auto-Fix non deve mai toccare i file che hanno attivato Z201 (Shield secret) —
quelli richiedono giudizio umano.
-
La semantica dei codici di uscita rimane invariata:
--applyesce comunque con 1se rimangono finding non corretti.
-
Python puro, nessun subprocess (Pillar 2).
Stato: Fase di design. Nessun codice integrato.
GAP-002 — Supporto Plugin Navbar/Footer Dinamici
Componente: core/adapters/_docusaurus.py, _parse_config_navigation()
Descrizione: Docusaurus supporta voci navbar dichiarate tramite plugin
@docusaurus/plugin-* (es. plugin-content-docs multi-istanza, componenti navbar
personalizzati). Quando la navbar è popolata dinamicamente al build time, il parser
regex statico di Zenzic non riesce ad estrarre quei percorsi — il fallback tratta
tutti i file come REACHABLE.
Impatto: Basso rischio di falsi positivi (il fallback è conservativo), ma alcuni veri orfani potrebbero non essere rilevati in configurazioni con molti plugin.
Risoluzione pianificata: Un avviso strutturato (annotazione ::warning in modalità
CI) quando Zenzic rileva plugin navbar dinamici, indicando che il rilevamento degli
orfani potrebbe essere incompleto. L'utente può sopprimerlo con
dynamic_nav_plugins = true in zenzic.toml.
Stato: Tracciato. RFC aperta.
Chiusi in v0.7.0 — Operazione Obsidian Stress
Prima del rilascio di v0.7.0, quattro agenti AI sono stati istruiti per violare lo Shield di Zenzic (scanner di credenziali) utilizzando tecniche di bypass realistiche. Hanno trovato quattro vettori reali. Tutti sono stati chiusi prima del rilascio stabile.
Vedi il post-mortem tecnico completo: AI Red Team Attacks Code Linter
ZRT-001 — Bypass per Normalizzazione Unicode (Shield)
Identificato da: Agente AI "Alpha" durante l'Operazione Obsidian Stress
Componente: core/shield.py, scan_lines_with_lookback()
Descrizione: I pattern regex dello Shield corrispondevano a forme di credenziali
ASCII. Un attaccante che controllava un file Markdown poteva inserire un carattere
Unicode simile (es. ghp_… usando lettere latine a larghezza intera) in quello che
sembrava un token. Lo Shield non si attivava perché la sequenza di byte non corrispondeva
al pattern ASCII.
Risoluzione: La normalizzazione Unicode (NFKC) viene applicata ad ogni riga prima
del pattern matching. unicodedata.normalize("NFKC", line) unifica caratteri a larghezza
intera, apici, racchiusi e altri lookalike Unicode nella loro forma canonica ASCII.
Il contenuto originale della riga viene preservato per l'output; solo la copia
normalizzata viene confrontata con i pattern.
Lezione: Il rilevamento di credenziali basato su regex deve normalizzare l'input. La superficie di attacco non è il pattern — è la codifica.
Chiuso in: sprint v0.7.0 D038.
ZRT-002 — Fuga dal Buffer di Lookback (Shield)
Identificato da: Agente AI "Bravo" durante l'Operazione Obsidian Stress
Componente: core/shield.py, scan_lines_with_lookback()
Descrizione: Il buffer di lookback dello Shield era usato per rilevare costrutti
di credenziali multi-riga (es. chiave password: su una riga, valore sulla successiva).
L'agente Bravo ha inserito un blocco "filler" sufficientemente lungo (> dimensione buffer)
tra la riga chiave e quella valore. Il buffer si svuotava prima che la riga valore venisse
scansionata, spezzando l'associazione e sopprimendo il finding Z201.
Risoluzione: La dimensione del buffer di lookback è stata validata rispetto alla
lunghezza massima del pattern di credenziali multi-riga nel registry (codes.py).
Il buffer garantisce ora di coprire la finestra massima del pattern. Inoltre, il buffer
viene svuotato solo ai confini del file — mai a metà file.
Lezione: Il rilevamento basato su buffer richiede dimensionamento formale rispetto al pattern peggiore. Un buffer informalmente "abbastanza grande" non è una garanzia di sicurezza.
Chiuso in: sprint v0.7.0 D039.
ZRT-003 — Offuscamento con Entità HTML (Shield)
Identificato da: Agente AI "Charlie" durante l'Operazione Obsidian Stress
Componente: core/shield.py
Descrizione: Lo Shield scansionava i byte Markdown grezzi. L'agente Charlie ha
usato la codifica di entità HTML (ghp_… per ghp_…) all'interno di blocchi
di codice delimitati. I pattern dello Shield non corrispondevano alla forma codificata
con entità, permettendo a una credenziale falsa di passare inosservata.
Risoluzione: Un decodificatore leggero di entità HTML viene applicato ad ogni
riga prima del pattern matching dello Shield (dopo la normalizzazione NFKC). Il
decodificatore gestisce entità numeriche (g) e nominali (&). I riferimenti
di carattere XML/HTML sono normalizzati ai loro codepoint Unicode prima che la regex
venga eseguita.
Lezione: La difesa multi-encoding richiede normalizzazione a strati. Un singolo passaggio di normalizzazione (solo NFKC) è insufficiente quando il rendering HTML fa parte della pipeline dei contenuti.
Chiuso in: sprint v0.7.0 D040.
ZRT-004 — Confusione Scope Blocco Delimitato (Shield)
Identificato da: Agente AI "Delta" durante l'Operazione Obsidian Stress
Componente: core/shield.py, macchina a stati dei blocchi delimitati
Descrizione: Lo Shield saltava originariamente la scansione all'interno dei blocchi
delimitati con tripli backtick, ragionando che gli esempi di codice non sono segreti
attivi. L'agente Delta ha incorporato un pattern ghp_ all'interno di un blocco
delimitato bash. Lo Shield non si attivava.
Risoluzione dopo deliberazione: L'euristica "salta i blocchi delimitati" è stata
invertita. Lo Shield ora scansiona tutte le righe, compresi i blocchi di codice
delimitati. La motivazione: un file di documentazione che espone una credenziale reale
all'interno di un blocco bash sta comunque esponendo una credenziale reale. La natura
di esempio del blocco è irrilevante per l'esito di sicurezza.
Un commento # zenzic: ignore-next-line è il meccanismo autorizzato per gli autori
che devono includere una stringa con la forma di una credenziale in un esempio
documentato (es. mostrare il formato di un token GitHub senza usarne uno reale).
I fixture examples/matrix/red-team/ dimostrano questo pattern.
Lezione: Le esclusioni di scope euristiche che riducono i falsi positivi spesso creano falsi negativi in condizioni avversariali. I passaggi critici per la sicurezza devono per default scansionare tutto, autorizzare le eccezioni esplicitamente.
Chiuso in: sprint v0.7.0 D041.
Chiusi in Precedenza (Pre-v0.7.0)
ZRT-005 — Bootstrap Paradox
Componente: core/scanner.py
Descrizione: zenzic init andava in crash con un errore di configurazione quando
invocato in una directory vuota. La funzione find_repo_root() non aveva fallback,
rendendo impossibile inizializzare un progetto che non aveva ancora un marker .git
o zenzic.toml.
Risoluzione: Parametro fallback_to_cwd=True aggiunto a find_repo_root(), usato
esclusivamente dal comando init. Vedi ADR 003.
Chiuso in: v0.6.0a4.
ZRT-006 — Bypass VSM: Link con Slug Assoluti Saltati Silenziosamente
Componente: core/validator.py — ciclo di validazione link in Fase 2
Descrizione: Quando un progetto Docusaurus dichiara prefissi di proprietà del
routeBasePath (es. /blog/) tramite get_absolute_url_prefixes(), il validator
sopprime Z105 (ABSOLUTE_PATH) per i link che iniziano con quei prefissi. La
soppressione era implementata come un continue nudo, che usciva dall'iterazione
per-link prima del lookup nella VSM — rendendo impossibile a Z001 di attivarsi
su link con prefisso assoluto di proprietà del progetto.
Un secondo problema composto: DocusaurusAdapter.set_slug_map() non veniva mai
chiamato durante validate_links_async(), quindi la mappa degli slug era vuota al
momentodella costruzione della VSM. I post del blog che dichiaravano slug: mio-post
nel frontmatter venivano instradati tramite derivazione dal filename (es.
2026-04-29-mio-post → /blog/mio-post/), producendo una VSM che divergeva
dagli URL effettivamente serviti da Docusaurus.
Effetto combinato: Un link /blog/slug-sbagliato dove il vero slug era
/blog/slug-corretto non produceva alcun finding da Zenzic, mentre
docusaurus build falliva con un errore di link non trovato. Il sentinel era cieco
al modo di failure più comune dopo una ridenominazione.
Risoluzione: Due fix coordinati in core/validator.py:
-
Ordinamento del ciclo di vita —
adapter.set_slug_map(md_contents)viene ora chiamato (tramite guardiahasattrper sicurezza cross-engine) immediatamente prima dibuild_vsm(). La VSM è costruita sull'identità virtuale corretta, non sul filename fisico. -
Lookup VSM con scope — Dopo la soppressione di Z105, il validator verifica se il prefisso trovato ha almeno una route nella VSM (
_scanned_vsm_prefixes). In caso affermativo, esegue un lookupdict.get()e riportaFILE_NOT_FOUNDquando la route è assente. I prefissi senza voci nella VSM (plugin sorelle il cui markdown è fuori dallo scope di scansione) mantengono il bypass incondizionato — invariante Zero-Config preservata.
Impatto cross-engine: Gli adapter MkDocs, Zensical e Standalone non implementano
set_slug_map(). La guardia hasattr rende la chiamata un no-op per questi motori
— nessun cambiamento di comportamento.
Lock di regressione: tests/test_docusaurus_blog_vsm.py — classe
TestAbsoluteSlugMismatch — due nuovi test:
test_absolute_broken_blog_link_is_detected— slug sbagliato sollevaFILE_NOT_FOUNDtest_correct_absolute_slug_link_is_clean— slug corretto non produce errori
Chiuso in: v0.7.0.
D100 — Migrazione Privacy Gate: .zenzic.dev.toml → .zenzic.local.toml
Componente: cli/_standalone.py, models/config.py, core/shield.py, core/codes.py
Descrizione: Il D002 Environmental Privacy Gate originale (_scaffold_dev_toml) creava
.zenzic.dev.toml con una tabella [development_gate] contenente forbidden_patterns
per la redazione delle esportazioni. Questo file non era integrato nella pipeline di
scansione dello Shield — serviva solo come suggerimento locale di redazione per gli
strumenti di export. I pattern non venivano mai verificati contro il contenuto della
documentazione, quindi uno sviluppatore poteva pubblicare inavvertitamente un documento
contenente un nome in codice proibito senza alcun avviso da Zenzic.
Questo gap creava un falso senso di sicurezza: gli utenti configuravano forbidden_patterns
aspettandosi che Zenzic bloccasse quei termini dalla documentazione, ma la scansione
non avveniva mai.
Risoluzione (Sprint D100 — v0.7.0):
-
Nuovo file canonico:
.zenzic.local.tomlsostituisce.zenzic.dev.tomlcome configurazione di privacy locale alla macchina e ignorata da git. È un file TOML piatto con una chiave di primo livelloforbidden_patterns = [...]. -
Gestione automatica di
.gitignore:zenzic initscaffola ora sempre.zenzic.local.tomle aggiunge il nome file a.gitignorese il file esiste e la voce è assente. Nessun passaggio manuale richiesto. -
Deep-merge della config:
ZenzicConfig.load()esegue un merge additivo deiforbidden_patternsda.zenzic.local.tomldopo il caricamento della config primaria (zenzic.tomlo[tool.zenzic]). I duplicati vengono rimossi; l'ordine di inserimento è preservato. -
Z204 FORBIDDEN_TERM — Exit 2:
scan_line_for_forbidden_terms()incore/shield.pyesegue una ricerca verbatim case-insensitive contro la listaforbidden_patternsunita. Qualsiasi corrispondenza su qualsiasi riga di qualsiasi file di documentazione viene emessa comeSecurityFindingconsecret_type="FORBIDDEN_TERM". Lo scanner mappa questo a Z204 (non Z201), preservando una chiara separazione tra leak di credenziali e violazioni di termini proibiti. -
Compatibilità retroattiva:
_scaffold_dev_toml()è mantenuto come shim che delega a_scaffold_local_toml(). Nessun chiamante esterno necessita di aggiornamenti.
Brand Integrity Shield — Design a Due Livelli: Il Privacy Gate Z204 e il Brand Obsolescence Guard Z905 formano un'architettura a due livelli:
- Z204 (
forbidden_patternsin.zenzic.local.toml): exit 2, non sopprimibile. Progettato per termini privati che non devono mai apparire in nessun documento pubblicato. - Z905 (
obsolete_namesinzenzic.toml): exit 1, sopprimibile conzenzic:ignore Z905. Progettato per termini di brand obsoleti dove i riferimenti storici nei file CHANGELOG sono accettabili.
Chiuso in: v0.7.0 sprint D100.