07: Strumenti, sicurezza, debug e reference

← 06 - YAML nella pratica · Indice

7.1 Come si legge un errore YAML

Il parser segnala dove si è accorto del problema, non dove hai sbagliato. Quasi sempre la causa è una o più righe prima.

✅ casi verificati con PyYAML:

Cosa hai scrittoErroreCausa reale
image:nginx (manca lo spazio) alla riga 3mapping values are not allowed here, line 4la riga 3: image:nginx è uno scalare, quindi il : della riga 4 arriva in un contesto impossibile
title: Home: dolce casamapping values are not allowed hereil secondo : non quotato
value_template: {{ ... }}found unhashable key / while constructing a mapping{{ letto come flow mapping
indentazione incoerenteexpected <block end>, but found ...il primo livello che non torna
apice non chiuso alla riga 3while scanning a quoted scalar ... line 3 + found unexpected end of stream ... line 7qui le due posizioni sono la coppia d’oro: la prima è la causa
tab nell’indentazionefound character '\t' that cannot start any tokenletterale

Euristica: leggi la prima posizione riportata (quella dopo “while scanning/parsing”), non l’ultima. Poi guarda le 1-5 righe precedenti. Sospetta, in ordine: un : non virgolettato, un apice non chiuso, un tab, indentazione mista, una riga che inizia con {, *, & o !.

7.2 Validare dal terminale

# è YAML valido? e cosa diventano davvero i valori?
python3 -c "import yaml,json;print(json.dumps(yaml.safe_load(open('f.yaml')),indent=2,default=str))"
 
# multi-documento (Kubernetes)
python3 -c "import yaml;print(list(yaml.safe_load_all(open('k8s.yaml'))))"
 
# stile e regole
yamllint -f colored f.yaml
yamllint -f github .            # annotazioni inline dentro GitHub Actions
 
# query
yq . f.yaml
yq -r '.jobs | keys[]' .github/workflows/ci.yml
 
# validatori specifici, sempre migliori di quelli generici
hass --script check_config       # Home Assistant
docker compose config            # Compose: mostra il file espanso
kubectl apply --dry-run=server -f m.yaml
actionlint                       # GitHub Actions: controlla anche le espressioni ${{ }}

Il trucco più utile del modulo

Quando un valore “non funziona”, non guardare il file: stampa cosa diventa. Il comando python3 -c "import yaml,json;print(json.dumps(yaml.safe_load(...)))" ti mostra in un secondo che NO è diventato false e che 01234 è diventato 668.

Due avvertenze:

  • python3 -c e yq non danno la stessa risposta: PyYAML è 1.1, yq usa la grammatica 1.2. Usa PyYAML per riprodurre quello che vede Home Assistant, yq per un tool moderno.
  • Su file con tag custom (!secret, !include, !Ref) qualunque validatore generico fallisce: serve --unsafe, o yaml.customTags in VS Code, o il validatore nativo.

7.3 yamllint

Il linter di riferimento (1.38.x). Default utili già attivi: key-duplicates (errore), indentation, trailing-spaces, truthy (warning), line-length a 80.

Configurazione ragionevole per un repo misto, in .yamllint:

extends: default
rules:
  line-length: {max: 120, level: warning}
  document-start: disable
  truthy: {check-keys: false}        # per la chiave `on:` dei workflow GitHub
  comments: {min-spaces-from-content: 1}
  braces: {min-spaces-inside: 1, max-spaces-inside: 1}   # compatibile con Prettier

In pre-commit:

- repo: https://github.com/adrienverge/yamllint.git
  rev: v1.38.0
  hooks:
    - id: yamllint
      args: [--strict]

Prettier e yamllint litigano se non li accordi

Prettier formatta il flow style come { a: 1 } (con spazi interni), mentre yamllint di default protesta con too many spaces inside braces. Si risolve una volta sola, con la riga braces: qui sopra. Altrimenti format-on-save e CI si contraddicono per sempre.

7.4 Schemi: la cosa che ti cambia la vita davvero

Un JSON Schema associato al file ti dà autocompletamento e validazione mentre scrivi, nell’editor. Vale più di qualsiasi disciplina personale sul quoting.

In VS Code con l’estensione YAML di Red Hat, basta una riga in cima al file:

# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json

SchemaStore ospita gli schemi dei formati più diffusi (GitHub Actions, Docker Compose, Kubernetes, e centinaia di altri) e l’estensione ne riconosce molti automaticamente dal nome del file.

Per i file con tag custom, va detto all’estensione di non spaventarsi:

// settings.json
"yaml.customTags": [
  "!secret scalar", "!include scalar",
  "!include_dir_list scalar", "!include_dir_merge_list scalar",
  "!include_dir_named scalar", "!include_dir_merge_named scalar",
  "!env_var scalar", "!input scalar"
]

7.5 Sicurezza

1. yaml.load() senza loader sicuro = esecuzione di codice.

La documentazione di PyYAML è esplicita: “non è sicuro chiamare yaml.load con dati che arrivano da una fonte non fidata: yaml.load è potente quanto pickle.load e può chiamare qualsiasi funzione Python.” È il CVE-2017-18342, gravità 9.8 su 10.

✅ verificato su PyYAML 6.0.3 con il payload !!python/object/apply:os.system ['echo PWNED']:

ChiamataEsito
yaml.load(s)TypeError: dalla 6.0 il parametro Loader è obbligatorio
yaml.load(s, Loader=yaml.UnsafeLoader)esegue il comando
yaml.load(s, Loader=yaml.FullLoader)bloccato
yaml.safe_load(s)bloccato

La buona notizia: dal 2021 non ci caschi per distrazione. La cattiva: il rischio residuo è chi copia Loader=yaml.UnsafeLoader da Stack Overflow per far sparire un errore. Regola: yaml.safe_load(), sempre.

2. Le “bombe” con gli anchor.

Gli alias non copiano: condividono. Un documento di poche centinaia di byte con anchor annidati può espandersi in miliardi di nodi. Il parsing resta economico, l’esplosione avviene quando materializzi (serializzazione JSON, deep clone, validazione ricorsiva). È il CVE-2019-11253 di Kubernetes, gravità 7.5.

Stato dei parser, verificato:

ParserDifesa di default
yaml (npm)maxAliasCount: 100, protetto
js-yaml 5nessun limite (maxAliases: -1), configurabile
PyYAMLnessun limite

Se il tuo programma legge YAML che non hai scritto tu: limita la dimensione dell’input, usa un parser con limite sugli alias, conta i nodi prima di serializzare, e tratta ogni eccezione come “documento rifiutato”.

3. I tag custom sono codice. Un file con !!python/object o !ruby/object è un vettore. Non è un difetto di implementazione: è la specifica che prevede i tag locali mappati su strutture native del linguaggio.

7.6 Quale libreria usare

ServeUsa
leggere config in Pythonyaml.safe_load (PyYAML)
riscrivere YAML scritto a mano, preservando commenti e stileruamel.yaml
leggere YAML in JSjs-yaml (v5 = 1.2 stretto; v4 se dipendi da date e merge)
round-trip o posizioni sorgente in JSyaml (eemeli)
Go, codice nuovogo.yaml.in/yaml/v4

Il punto che sorprende: PyYAML non preserva i commenti. Se scrivi uno script che modifica un configuration.yaml con PyYAML, al salvataggio hai perso tutti i commenti e tutta la formattazione. Per quel lavoro esiste ruamel.yaml, ed è l’unica risposta giusta.

7.7 Quando YAML è la scelta sbagliata

Onestamente:

  • File generati da un programma → usa JSON. Nessuna ambiguità, nessun costo di leggibilità che tanto non serve.
  • Configurazioni piatte di progetto → TOML è migliore (pyproject.toml, Cargo.toml): semantica ovvia, niente tipizzazione a sorpresa.
  • Molti valori che sono identificatori (versioni, hash, codici, orari) → ogni riga è una potenziale trappola. Valuta JSON o TOML.
  • Confine di sicurezza (input non fidato) → YAML ha una superficie d’attacco che JSON non ha.
  • Serve logica (condizioni, variabili, funzioni) → non piegare gli anchor a fare ereditarietà: genera il file con uno strumento vero.

YAML resta la scelta giusta quando: il file è scritto e letto da persone, la struttura è gerarchica con molte liste di mapping, e servono commenti. Cioè: esattamente le automazioni, i workflow e i compose file.

Il problema strutturale di YAML non è la sintassi: è l’assenza di uno schema obbligatorio. Ed è per questo che il punto 7.4 vale più di tutto il resto del corso.

7.8 Cheatsheet

# --- struttura ---
chiave: valore
annidato:
  a: 1
  b: 2
lista:
  - primo
  - secondo
lista_di_mappe:
  - nome: uno
    val: 1
  - nome: due
    val: 2
flow: {a: 1, b: [1, 2]}
 
# --- null ---
a: null      # anche: Null, NULL, ~, oppure niente
b: ""        # stringa vuota, NON null
 
# --- stringhe ---
plain: testo semplice
singoli: 'niente escape, l''apice si raddoppia'
doppi: "escape veri:\n\ttab"
 
# --- valori da virgolettare SEMPRE ---
paese: 'NO'
versione: '1.10'
cap: '01234'
orario: '12:30'
colore: '#FF0000'
stato: 'on'
 
# --- blocchi ---
script: |          # a capo conservati (USA QUESTO per gli script)
  echo uno
  echo due
testo: >-          # a capo → spazi, niente newline finale
  frase lunga
  su più righe
 
# |  a capo conservati + 1 finale     >  piegati + 1 finale
# |- a capo conservati, niente finale >- piegati, niente finale
# |+ tutto conservato                 >+ piegati, tutto conservato
 
# --- riuso ---
base: &base
  restart: always
derivato:
  <<: *base        # attenzione: non è nella spec 1.2, e in JS è spento di default
  extra: true
 
# --- documenti multipli ---
---
doc: 1
---
doc: 2

7.9 Dove continuare

7.10 Manutenzione di queste note

Scritte il 21 agosto 2026 sulla specifica YAML 1.2.2. Gli esempi ✅ sono stati eseguiti con PyYAML 6.0.3 e con lo schema core di YAML 1.2.

YAML come formato è fermo dal 2021 e non si muoverà. Quello che cambia sono i parser e le applicazioni: js-yaml ha cambiato schema di default passando alla v5, GitHub ha aggiunto gli anchor nel 2025, Kubernetes ha introdotto KYAML nel 2026. Se qualcosa qui non torna, il primo sospetto è che sia cambiato uno strumento.

Punto su cui queste note sono deliberatamente caute: il supporto (o meno) della merge key << in GitHub Actions, dove le fonti si contraddicono e non ho potuto eseguire un workflow reale per verificare.