Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Trust No Input: Taint Analysis at Compile Time

Trust No Input: Taint Analysis at Compile Time

This talk introduces language-based security: leveraging programming languages and their type systems to enforce security policies at compile time. We’ll focus on taint analysis, a technique for tracking the flow of potentially harmful (or "tainted") data. In particular, we'll see how we can apply it statically to detect and prevent security vulnerabilities before they reach Production. Through practical examples in Java and Scala, we'll see how to model data sensitivity, propagate taint status and catch violations at compile time.

As more code is written or suggested by GenAI, the risk of subtle security flaws increases, making compiler-enforced security guarantees more valuable than ever.

By the end of the talk, you will see how language-based techniques can reduce reliance on dynamic checks and support building secure systems by construction.

Avatar for Matteo Di Pirro

Matteo Di Pirro

March 23, 2026
Tweet

More Decks by Matteo Di Pirro

Other Decks in Programming

Transcript

  1. • Principal Software Engineer @ Kynetics Inc • DevOps +

    application software ◦ Embedded ◦ Cloud • Been writing Scala code for ~ 5 years • Academic major on programming languages and type systems
  2. IBM’s 2025 Cost of a Data Breach Report estimated the

    global average cost of a data breach is $4.44 million
  3. Data vulnerabilities are expensive to fix after release Data vulnerabilities

    are difficult to find Aliases Multiple inputs Proximity
  4. The purpose of taint analysis is to track data flows

    of interest throughout the program to ensure they’re properly handled.
  5. 1. apiKey = readApiKeyFromSecretFile(); 2. apiClient = new Client(apiKey); 3.

    4. logger.debug(“Created new client with API key: “, apiKey);
  6. X Static Taint Analysis is undecidable! ◦ Cannot exclude false

    positives and false negatives at the same time ◦ Approximation based on assumptions • Usually based on call graphs for efficiency ✓ Comprehensive coverage
  7. • Dynamic taint analysis instruments the code ✓ Can flag

    vulnerabilities in real-time • Useful when the code is not available ◦ Malwares ◦ Binaries X Might take a long time to complete X Only inspects paths triggered by the input
  8. • Three Taint levels ◦ Tainted, Sanitised, Pure • Use

    Tainted values only after sanitisation • Taint propagation ◦ Tainted always wins ◦ Sanitised dominates over Pure ◦ Pure remains so only if combined with itself
  9. 1. enum TaintLevel: 2. case Pure, Sanitised, Tainted 3. 4.

    type TaintPropagation[P0 <: TaintLevel, P1 <: TaintLevel] <: TaintLevel = (P0, P1) match 5. case (TaintLevel.Tainted.type, _) => TaintLevel.Tainted.type 6. case (_, TaintLevel.Tainted.type) => TaintLevel.Tainted.type 7. case (TaintLevel.Sanitised.type, _) => TaintLevel.Sanitised.type 8. case (_, TaintLevel.Sanitised.type) => TaintLevel.Sanitised.type 9. case (TaintLevel.Pure.type, TaintLevel.Pure.type) => TaintLevel.Pure.type 10. 11. // TaintPropagation[TaintLevel.Tainted.type, TaintLevel.Pure.type] => TaintLevel.Tainted.type 12. // TaintPropagation[TaintLevel.Pure.type, TaintLevel.Sanitised.type] => TaintLevel.Sanitised.type
  10. 1. enum TaintLevel: 2. case Pure, Sanitised, Tainted 3. 4.

    type TaintPropagation[P0 <: TaintLevel, P1 <: TaintLevel] <: TaintLevel = (P0, P1) match 5. case (TaintLevel.Tainted.type, _) => TaintLevel.Tainted.type 6. case (_, TaintLevel.Tainted.type) => TaintLevel.Tainted.type 7. case (TaintLevel.Sanitised.type, _) => TaintLevel.Sanitised.type 8. case (_, TaintLevel.Sanitised.type) => TaintLevel.Sanitised.type 9. case (TaintLevel.Pure.type, TaintLevel.Pure.type) => TaintLevel.Pure.type 10. 11. // TaintPropagation[TaintLevel.Tainted.type, TaintLevel.Pure.type] => TaintLevel.Tainted.type 12. // TaintPropagation[TaintLevel.Pure.type, TaintLevel.Sanitised.type] => TaintLevel.Sanitised.type
  11. 1. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A):

    2. 3. private lazy val value = computeValue() 4. 5. def flatMap[P1 <: TaintLevel, B](f: A => TaintTracked[P1, B]): 6. TaintTracked [TaintPropagation [P, P1], B] { 7. val result = f(value) 8. new TaintTracked(() => result.value) 9. } 10. 11. object TaintTracked: 12. 13. def apply[A](a: => A): TaintTracked [TaintLevel.Tainted.type, A] = new TaintTracked(() => a) 14. 15. def unsafe[A](a: => A): TaintTracked [TaintLevel.Pure.type, A] = new TaintTracked(() => a)
  12. 1. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A):

    2. 3. private lazy val value = computeValue() 4. 5. def flatMap[P1 <: TaintLevel, B](f: A => TaintTracked[P1, B]): 6. TaintTracked [TaintPropagation [P, P1], B] { 7. val result = f(value) 8. new TaintTracked(() => result.value) 9. } 10. 11. object TaintTracked: 12. 13. def apply[A](a: => A): TaintTracked [TaintLevel.Tainted.type, A] = new TaintTracked(() => a) 14. 15. def unsafe[A](a: => A): TaintTracked [TaintLevel.Pure.type, A] = new TaintTracked(() => a)
  13. 1. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A):

    2. 3. private lazy val value = computeValue() 4. 5. def flatMap[P1 <: TaintLevel, B](f: A => TaintTracked[P1, B]): 6. TaintTracked [TaintPropagation [P, P1], B] { 7. val result = f(value) 8. new TaintTracked(() => result.value) 9. } 10. 11. object TaintTracked: 12. 13. def apply[A](a: => A): TaintTracked [TaintLevel.Tainted.type, A] = new TaintTracked(() => a) 14. 15. def unsafe[A](a: => A): TaintTracked [TaintLevel.Pure.type, A] = new TaintTracked(() => a)
  14. 1. object TaintTracked: 2. type Sanitised[E, A] = Either[E, TaintTracked

    [TaintLevel.Sanitised.type, A]] 3. 4. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A): 5. import TaintTracked.Sanitised 6. 7. private lazy val value = computeValue() 8. 9. def sanitise[E, B](s: A => Either[E, B]): Sanitised[E, B] = 10. val result = s(value) 11. result.map(r => new TaintTracked(() => r)) sanitise() introduces an error type (E) and forces taint label to Sanitised
  15. 1. object TaintTracked: 2. type Sanitised[E, A] = Either[E, TaintTracked

    [TaintLevel.Sanitised.type, A]] 3. 4. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A): 5. import TaintTracked.Sanitised 6. 7. private lazy val value = computeValue() 8. 9. def sanitise[E, B](s: A => Either[E, B]): Sanitised[E, B] = 10. val result = s(value) 11. result.map(r => new TaintTracked(() => r)) sanitise() introduces an error type (E) and forces taint label to Sanitised
  16. 1. trait CanOpen[P <: TaintLevel] 2. 3. object CanOpen: 4.

    given CanOpen[TaintLevel.Pure.type] with {} 5. 6. given CanOpen[TaintLevel.Sanitised.type] with {} 7. 8. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A): 9. 10. private lazy val value = computeValue() 11. 12. def open(using CanOpen[P]): A = value Compilation error if open() gets called with a Tainted value
  17. 1. trait CanOpen[P <: TaintLevel] 2. 3. object CanOpen: 4.

    given CanOpen[TaintLevel.Pure.type] with {} 5. 6. given CanOpen[TaintLevel.Sanitised.type] with {} 7. 8. class TaintTracked[P <: TaintLevel, +A] private(computeValue: () => A): 9. 10. private lazy val value = computeValue() 11. 12. def open(using CanOpen[P]): A = value Compilation error if open() gets called with a Tainted value
  18. • Web application to add widgets to a database •

    Users can input name and price for the widget • A vulnerability in the code allows HTML Injection • TaintTracked to verify Tainted values are not stored in the DB
  19. 1. public interface TaintLevel {} 2. public interface CanOpen {}

    3. public record Tainted() implements TaintLevel {} 4. public record Sanitised() implements TaintLevel, CanOpen {} 5. public record Pure() implements TaintLevel, CanOpen {} 6. 7. public final class TaintTracked<T extends TaintLevel, A> { 8. private final T taintLevel; 9. private final Supplier<A> valueSupplier; 10. 11. public A open() { 12. if (!(taintLevel instanceof CanOpen)) { 13. throw new SecurityException( "Cannot open tainted value" ); 14. } 15. return valueSupplier. get(); 16. } 17. }
  20. Security is a team effort Pick tools or languages your

    team is familiar with whenever possible!