Passa al contenuto principale

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: --apply esce comunque con 1

    se 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

Cos'era l'Operazione?

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.


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:

  1. Ordinamento del ciclo di vitaadapter.set_slug_map(md_contents) viene ora chiamato (tramite guardia hasattr per sicurezza cross-engine) immediatamente prima di build_vsm(). La VSM è costruita sull'identità virtuale corretta, non sul filename fisico.

  2. 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 lookup dict.get() e riporta FILE_NOT_FOUND quando 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 solleva FILE_NOT_FOUND
  • test_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):

  1. Nuovo file canonico: .zenzic.local.toml sostituisce .zenzic.dev.toml come configurazione di privacy locale alla macchina e ignorata da git. È un file TOML piatto con una chiave di primo livello forbidden_patterns = [...].

  2. Gestione automatica di .gitignore: zenzic init scaffola ora sempre .zenzic.local.toml e aggiunge il nome file a .gitignore se il file esiste e la voce è assente. Nessun passaggio manuale richiesto.

  3. Deep-merge della config: ZenzicConfig.load() esegue un merge additivo dei forbidden_patterns da .zenzic.local.toml dopo il caricamento della config primaria (zenzic.toml o [tool.zenzic]). I duplicati vengono rimossi; l'ordine di inserimento è preservato.

  4. Z204 FORBIDDEN_TERM — Exit 2: scan_line_for_forbidden_terms() in core/shield.py esegue una ricerca verbatim case-insensitive contro la lista forbidden_patterns unita. Qualsiasi corrispondenza su qualsiasi riga di qualsiasi file di documentazione viene emessa come SecurityFinding con secret_type="FORBIDDEN_TERM". Lo scanner mappa questo a Z204 (non Z201), preservando una chiara separazione tra leak di credenziali e violazioni di termini proibiti.

  5. 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_patterns in .zenzic.local.toml): exit 2, non sopprimibile. Progettato per termini privati che non devono mai apparire in nessun documento pubblicato.
  • Z905 (obsolete_names in zenzic.toml): exit 1, sopprimibile con zenzic:ignore Z905. Progettato per termini di brand obsoleti dove i riferimenti storici nei file CHANGELOG sono accettabili.

Chiuso in: v0.7.0 sprint D100.