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 scritto | Errore | Causa reale |
|---|---|---|
image:nginx (manca lo spazio) alla riga 3 | mapping values are not allowed here, line 4 | la riga 3: image:nginx è uno scalare, quindi il : della riga 4 arriva in un contesto impossibile |
title: Home: dolce casa | mapping values are not allowed here | il secondo : non quotato |
value_template: {{ ... }} | found unhashable key / while constructing a mapping | {{ letto come flow mapping |
| indentazione incoerente | expected <block end>, but found ... | il primo livello che non torna |
| apice non chiuso alla riga 3 | while scanning a quoted scalar ... line 3 + found unexpected end of stream ... line 7 | qui le due posizioni sono la coppia d’oro: la prima è la causa |
| tab nell’indentazione | found character '\t' that cannot start any token | letterale |
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 cheNOè diventatofalsee che01234è diventato668.
Due avvertenze:
python3 -ceyqnon danno la stessa risposta: PyYAML è 1.1,yqusa la grammatica 1.2. Usa PyYAML per riprodurre quello che vede Home Assistant,yqper un tool moderno.- Su file con tag custom (
!secret,!include,!Ref) qualunque validatore generico fallisce: serve--unsafe, oyaml.customTagsin 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 PrettierIn 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 contoo many spaces inside braces. Si risolve una volta sola, con la rigabraces: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.jsonSchemaStore 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']:
| Chiamata | Esito |
|---|---|
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:
| Parser | Difesa di default |
|---|---|
yaml (npm) | maxAliasCount: 100, protetto |
| js-yaml 5 | nessun limite (maxAliases: -1), configurabile |
| PyYAML | nessun 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
| Serve | Usa |
|---|---|
| leggere config in Python | yaml.safe_load (PyYAML) |
| riscrivere YAML scritto a mano, preservando commenti e stile | ruamel.yaml |
| leggere YAML in JS | js-yaml (v5 = 1.2 stretto; v4 se dipendi da date e merge) |
| round-trip o posizioni sorgente in JS | yaml (eemeli) |
| Go, codice nuovo | go.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: 27.9 Dove continuare
- Specifica YAML 1.2.2: capitoli 7 (scalari), 8 (blocchi), 10 (tipi).
- yaml.org/type/: i tipi 1.1, cioè quello che i parser fanno davvero.
- yamllint · SchemaStore · actionlint
- Home Assistant, YAML
- Docker Compose, fragments
- GitHub Actions, workflow syntax
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.