Falsi positivi nell'analisi statica del codice

Come ridurre i falsi positivi nell'analisi statica del codice

Uno strumento di analisi statica che segnala dieci problemi per ogni pull request, di cui due sono problemi reali e otto sono falsi allarmi, non viene corretto, ma disattivato. La stanchezza da allarmi è la causa più comune del fallimento dei programmi di analisi statica nella pratica. Gli sviluppatori che indagano su otto falsi allarmi per trovare due problemi reali iniziano a saltare l'indagine. Ben presto lo strumento viene eseguito, produce avvisi che nessuno legge e dà l'impressione di una corretta pratica di sicurezza senza che questa lo sia realmente.

Il problema non è che gli strumenti di analisi statica producano falsi positivi; l'eccessiva approssimazione è intrinseca alla loro progettazione, poiché qualsiasi analizzatore statico valido deve segnalare del codice che è teoricamente sicuro. Il problema sono i falsi positivi che sono prevedibili, riproducibili e risolvibili tramite configurazione, soppressione o strumenti migliori. Ridurli non richiede di abbandonare il rigore. Richiede di capire perché si è verificato ogni falso positivo, se può essere eliminato tramite la messa a punto delle regole o la soppressione e come misurare se il tasso sta migliorando nel tempo.

Smettetela di indagare sui risultati nel codice morto

SMART TS XL Identifica quali schemi segnalati si trovano in codice irraggiungibile prima che il tuo team perda tempo a lavorarci.

Maggiori Informazioni

Che cos'è un falso positivo nell'analisi statica del codice?

Un falso positivo si verifica quando uno strumento di analisi statica segnala del codice come problematico quando in realtà è corretto: il codice segnalato non produrrà un bug, una vulnerabilità o una violazione della qualità in fase di esecuzione. L'analisi dello strumento è giunta a una conclusione che non corrisponde al comportamento effettivo del programma.

Comprendere la tassonomia completa aiuta a stabilire le priorità per le correzioni:

Tipo di risultatoLo strumento diceRealtàCosa fare
Vero positivoProblema rilevatoEsiste un problema realeCorreggi il codice
Falso positivoProblema rilevatoNessun problema realeSopprimi o regola
Vero negativoNessun problemaNon esiste alcun problemaCome previsto, buono
Falso negativoNessun problemaEsiste un problema realeMigliorare la profondità/le regole dell'analisi

Il compromesso: ridurre i falsi positivi (aumentando la precisione) spesso comporta un aumento dei falsi negativi. Rendere una regola meno sensibile riduce il rumore, ma diminuisce anche la probabilità di individuare problemi reali. L'obiettivo non è zero falsi positivi, bensì un tasso di falsi positivi sufficientemente basso da indurre gli sviluppatori a fidarsi dello strumento e a indagare su ogni rilevamento.

Perché l'analisi statica produce falsi positivi: le ragioni tecniche

Comprendere il meccanismo alla base di ogni tipo di falso positivo permette di individuare la soluzione corretta.

1. Analisi intraprocedurale senza contesto

Molte regole operano all'interno di una singola funzione senza sapere cosa ha già fatto chi la chiama. Una funzione che dereferenzia un puntatore senza un controllo di nullità potrebbe essere segnalata, anche se chi la chiama convalida sempre il puntatore prima di chiamarla. L'analizzatore non può vedere oltre i confini della funzione.

c

// Caller always validates before calling -- analyzer doesn't know this
void process(Data *d) {
    int result = d->value;  // flagged: potential null dereference
    // But every caller looks like:
    // if (d != NULL) process(d);
}

Soluzione: Passare all'analisi interprocedurale oppure utilizzare un'annotazione per informare l'analizzatore della precondizione.

2. Sovrastima degli intervalli di valori

Un analizzatore basato su intervalli che tiene traccia degli intervalli delle variabili in modo conservativo può segnalare una divisione come potenzialmente una divisione per zero anche quando l'intervallo del divisore esclude lo zero in tutti gli stati raggiungibili.

Giava

// Analyzer computes divisor range as [0, 100] and flags division by zero
// Actual runtime: config.getMinBatchSize() always returns >= 1
int batchCount = totalItems / config.getMinBatchSize();  // flagged

Fix: Aggiungi un'asserzione o una precondizione che restringe l'intervallo tracciato dall'analizzatore oppure configura l'analizzatore con un modello per getMinBatchSize().

3. Falsi allarmi delle librerie di terze parti

Gli analizzatori statici in genere non dispongono di modelli per il comportamento delle librerie di terze parti. Una funzione di una libreria crittografica che convalida internamente i suoi input vedrà i suoi output trattati come potenzialmente inaffidabili perché l'analizzatore non può ispezionare il codice sorgente della libreria.

4. Regole di schema senza comprensione semantica

Molte regole di sicurezza sono basate su modelli: "qualsiasi concatenazione di input utente in una stringa SQL costituisce un'iniezione SQL". Questa regola si attiva correttamente sul codice vulnerabile e in modo errato sul codice che sanifica l'input prima della concatenazione, perché la regola basata sul modello non può verificare che la sanificazione sia corretta o completa.

5. Condizioni valutate staticamente

Questo è il problema specifico alla base della query SC "il codice non viene analizzato perché la condizione viene valutata staticamente come falsa". Un avviso comune dell'analizzatore Coverity/Clang che merita una sezione a parte.

"Il codice non viene analizzato perché la condizione viene valutata staticamente come falsa."

Questo avviso compare in Coverity, Clang Static Analyzer e strumenti simili quando l'analizzatore determina che la condizione di un ramo è sempre falsa, ovvero il codice all'interno di quel ramo non può mai essere raggiunto in nessuna esecuzione, e quindi interrompe l'analisi al suo interno.

Perché si verifica:

c

#define DEBUG 0  // compile-time constant

void process_record(Record *r) {
    if (DEBUG) {
        validate_record(r);  // never analyzed -- condition always false
    }
    use_record(r);  // potential issue here not caught if validate_record was needed
}

L'analizzatore valuta if (DEBUG) as if (0), sempre falso, e non analizza il corpo. Questo è il comportamento corretto: il codice è effettivamente irraggiungibile. L'avviso è informativo, non un falso positivo relativo a un bug.

Quando diventa un problema:

Se il ramo irraggiungibile contiene controlli di sicurezza che avrebbero dovuto essere sempre eseguiti, l'avviso segnala un errore di logica, non un errore di analisi. Il codice era condizionato in modo errato da una costante che lo rende inutilizzabile.

Cause comuni:

c

// Pattern 1: debug-only guard on production-required code
if (ENABLE_VALIDATION) { validate_input(data); }  // if ENABLE_VALIDATION=0, no validation

// Pattern 2: error return always overwritten before checked
int result = do_operation();
result = 0;  // overwrites result -- subsequent if (result != 0) is always false
if (result != 0) { handle_error(); }  // never reached

// Pattern 3: overly conservative NULL check after guaranteed assignment
ptr = malloc(sizeof(Data));
if (ptr == NULL) { ... }  // valid -- malloc can return NULL
ptr->value = 0;
if (ptr == NULL) { ... }  // always false -- analyzer warns here correctly

Risoluzione: Se il ramo dovrebbe essere raggiungibile, correggere la condizione. Se si tratta di codice intenzionalmente morto che può essere rimosso, rimuoverlo. Se si tratta di codice di solo debug con una condizione corretta, l'avviso è previsto e può essere soppresso.

Meccanismi di soppressione tra gli strumenti

La soppressione indica allo strumento di ignorare un risultato specifico in una posizione specifica. Tutti i principali strumenti di analisi statica offrono una sintassi per la soppressione. Utilizzare la soppressione per i falsi positivi confermati, laddove la messa a punto delle regole non sia praticabile.

Attenzione: i registri di soppressione devono essere rivisti periodicamente. Una soppressione aggiunta per un falso positivo nel 2023 potrebbe sopprimere una vulnerabilità reale introdotta nella stessa posizione nel 2025.

ESLint (JavaScript / TypeScript)

javascript

// Suppress next line
// eslint-disable-next-line no-unused-vars
const legacyAdapter = require('./legacy');

// Suppress a block
/* eslint-disable @typescript-eslint/no-explicit-any */
function processLegacyData(data: any): void { ... }
/* eslint-enable @typescript-eslint/no-explicit-any */

SonarQube / SonarLint

Giava

@SuppressWarnings("java:S2077")  // Suppress SQL injection rule for this method
public List<User> searchUsers(String query) {
    // This method uses a parameterized query builder, not raw string concat
    return queryBuilder.executeParameterized(query);
}

Oppure utilizzando i commenti in linea per SonarQube:

Giava

String hash = md5(password);  // NOSONAR - md5 used for non-security cache key only

Pylint (Python)

python

import os  # pylint: disable=unused-import  -- required for side-effect registration

def legacy_function():
    pass  # pylint: disable=W0107  -- intentionally empty for interface compliance

Segrep

YAML

# .semgrepignore -- exclude paths
tests/fixtures/
vendor/

# Inline: suppress specific rule at a line
result = eval(expression)  # nosemgrep: python.lang.security.audit.eval-injection

copertura

c

/* coverity[null_returns] */
Data *ptr = get_config();  // Coverity: ptr may be NULL
// Function contract guarantees non-NULL return when config is initialized

Regolazioni per ridurre i falsi positivi sistematici

La soppressione si occupa dei singoli casi. La messa a punto delle regole si occupa dei modelli sistematici in cui una regola produce costantemente falsi positivi su codice legittimo.

YAML

# SonarQube quality profile configuration
# Reduce sensitivity for cognitive complexity rule
sonar.java.cognitive.complexity.threshold=20  # default 15; raises bar for flagging

# Exclude generated code from analysis
sonar.exclusions=**/generated/**,**/proto/**,**/target/**
sonar.coverage.exclusions=**/*Test.java,**/*Spec.java

# Configure security hotspot categories by risk
# In sonar-project.properties:
sonar.security.hotspot.threshold=HIGH  # only show HIGH severity hotspots

YAML

# ESLint: rule-level tuning
# .eslintrc or eslint.config.js
rules:
  "@typescript-eslint/no-explicit-any": "warn"   # was "error" -- downgrade for gradual migration
  "complexity": ["warn", { "max": 20 }]           # was 10 -- adjust for legacy codebase baseline
  "max-lines-per-function": ["warn", { "max": 60, "skipBlankLines": true }]

L'esclusione dei percorsi è una delle azioni di ottimizzazione più efficaci. File generati, test fixture, codice di terze parti e script di migrazione producono pattern legittimi ma segnalati come anomali. Escluderli dall'ambito di analisi riduce immediatamente il volume dei falsi positivi senza diminuire la copertura del codice di produzione.

Falsi positivi nelle pipeline CI/CD

In una pipeline CI/CD che blocca le fusioni in base ai risultati dell'analisi, i falsi positivi influiscono direttamente sulla velocità di sviluppo. Una pull request bloccata da tre falsi positivi a ogni fusione abitua gli sviluppatori a trovare soluzioni alternative al blocco, anziché fidarsi ciecamente.

Strategie per la gestione dei falsi positivi specifici per le condotte:

Solo nuovi controlli di qualità del codice. Configura SonarQube, CodeClimate o equivalenti per applicare i controlli di qualità solo al codice introdotto nella pull request, non all'intera codebase. I falsi positivi esistenti nella codebase non bloccano il nuovo lavoro; solo i nuovi risultati nel nuovo codice lo fanno.

YAML

# .github/workflows/analysis.yml
- name: SonarCloud Scan
  uses: SonarSource/sonarcloud-github-action@master
  with:
    args: >
      -Dsonar.pullrequest.base=${{ github.base_ref }}
      -Dsonar.pullrequest.branch=${{ github.head_ref }}
      # New-code analysis only: existing findings don't block

Soglie di gravità. Interrompere la pipeline solo in caso di riscontri di gravità CRITICA e ALTA. I riscontri di gravità MEDIA e BASSA devono essere visualizzati come avvisi senza blocco.

File di riferimento. Strumenti come Semgrep e Grype supportano un file di riferimento che registra i risultati presenti in corrispondenza di una specifica commit. Le nuove esecuzioni riportano solo i risultati introdotti dopo il file di riferimento; i falsi positivi esistenti vengono soppressi per impostazione predefinita, senza necessità di soppressione per ogni singola istanza.

bash

# Semgrep: establish baseline, then compare
semgrep scan --baseline-commit=main --output=results.sarif src/
# Only findings introduced since main are reported

Misurazione e monitoraggio del tasso di falsi positivi

Ridurre i falsi positivi senza misurare i risultati è un'operazione a tentoni. Monitora questi parametri nel tempo:

MetricoCome calcolareObiettivo
Tasso di falsi positiviFalsi positivi confermati / Risultati totali × 100%Meno del 20% per gli strumenti di sicurezza; meno del 10% per gli strumenti di qualità.
Densità di soppressioneSoppressioni ogni 1,000 righe di codiceTendenza in aumento = problema sistematico di falsi positivi; necessita di una messa a punto delle regole
Rapporto tra individuazione e risoluzioneRisultati fissi / Risultati totaliRapporto in aumento = maggiore fiducia negli strumenti
Tempo per indagareTempo medio che gli sviluppatori dedicano a ogni ricercaDiminuzione nel tempo = tasso FP in miglioramento

Monitorare la densità di soppressione è particolarmente utile. Se il numero di soppressioni inline cresce più velocemente del codice sorgente, significa che la messa a punto delle regole sarebbe più efficiente rispetto alla soppressione per singola istanza.

Principio chiave: Una soppressione che viene inviata in produzione senza documentazione è debito tecnico. Ogni soppressione dovrebbe includere un commento che spieghi perché il risultato è un falso positivo, non solo il NOSONAR annotazione.

Come SMART TS XL Riduce i falsi positivi attraverso l'analisi strutturale

La maggior parte dei falsi positivi nell'analisi statica deriva da strumenti che analizzano file o funzioni senza comprenderne il contesto più ampio: cosa ha già validato il chiamante, com'è strutturato il grafo delle dipendenze, quali percorsi sono effettivamente raggiungibili dai punti di ingresso in produzione.

SMART TS XL'S analisi statica del codice Questo approccio crea un modello strutturale completo del codice sorgente prima di segnalare eventuali problemi, analizzando il grafo delle dipendenze, il flusso di controllo tra le procedure e il flusso di dati tra i moduli, anziché esaminare ogni file singolarmente. È proprio questo contesto strutturale a distinguere i falsi positivi generati dalla corrispondenza di pattern all'interno dei file dai risultati basati sull'effettiva raggiungibilità e sul flusso di dati del programma.

La funzionalità di mappatura delle dipendenze dell'applicazione riduce la classe di falsi positivi derivanti dalla mancanza di contesto su come interagiscono i componenti. Quando il modello di sicurezza di un programma COBOL può essere compreso solo conoscendo quale job JCL controlla il suo ambiente di esecuzione, o cosa il programma chiamante ha già convalidato prima di invocarlo, tale contesto tra i componenti è disponibile nell'analisi anziché mancare.

La funzionalità di analisi dell'impatto supporta la valutazione dei falsi positivi in ​​grandi codebase legacy: prima di investire tempo nell'analisi di un pattern segnalato, i team possono determinare se tale pattern è raggiungibile da qualsiasi percorso di esecuzione in produzione. I risultati relativi al codice inutilizzato, ovvero i pattern che potrebbero essere teoricamente pericolosi ma che in pratica risultano irraggiungibili, vengono declassati in base a prove di raggiungibilità strutturale, anziché basarsi esclusivamente sul giudizio degli sviluppatori.

La fiducia è il parametro che conta

Il parametro di valutazione di un programma di riduzione dei falsi positivi non è il tasso di falsi positivi, bensì la fiducia degli sviluppatori nei risultati dell'analisi. Un team che analizza ogni risultato, perché sa che lo strumento segnala problemi reali, è un team che trae valore dall'analisi statica. Un team che ignora i risultati a priori, perché storicamente la maggior parte di essi si è rivelata falsi allarmi, è un team il cui programma di analisi ha già fallito.

Per raggiungere questo obiettivo è necessaria la combinazione descritta in questa guida: comprendere perché si verifica ciascuna classe di falsi positivi, sopprimere i falsi positivi confermati con giustificazione documentata, calibrare le regole laddove emergono schemi sistematici, configurare le pipeline in modo che si blocchino sui risultati reali senza bloccarsi sul rumore e misurare il tasso nel tempo per sapere se sta migliorando. L'analisi statica vale l'investimento. La disciplina nella gestione dei falsi positivi è ciò che fa sì che tale investimento ripaghi.