Definition
Synthetic data refers to information that is algorithmically generated rather than collected from real-world events or human subjects. It is constructed to statistically mirror the properties, distributions, and relationships found in genuine datasets while containing no actual records traceable to real individuals, organizations, or transactions. Synthetic data occupies a critical position at the intersection of data science, privacy engineering, and security operations -- enabling teams to develop, test, and validate systems with realistic data volumes and patterns without the legal, ethical, and operational risks associated with handling real sensitive information.
The generation of synthetic data employs a spectrum of techniques, from simple rule-based generators that produce structurally valid records to sophisticated generative models (GANs, VAEs, diffusion models) that learn and reproduce the statistical fingerprint of source datasets. The quality of synthetic data is measured along several dimensions: fidelity (how closely it mirrors real data distributions), utility (how effectively it supports downstream tasks like model training or testing), and privacy (how well it prevents re-identification of source individuals).
Within the Prismatic Platform, synthetic data is foundational to the Color Teams security operations, where all adversarial simulations execute exclusively against synthetic datasets -- ensuring that security research never exposes real user information.
Historical Context and Motivation
The need for synthetic data emerged from a fundamental tension in modern software engineering: systems require realistic data to be properly tested and trained, but real data carries privacy obligations, legal restrictions, and security risks that make its use in development environments problematic.
Early approaches to this problem relied on data anonymization -- removing or masking personally identifiable information (PII) from real datasets. However, research demonstrated that anonymized data is vulnerable to re-identification attacks. The Netflix Prize dataset (2006), despite anonymization, was de-anonymized by researchers cross-referencing movie ratings with public IMDb profiles. The AOL search data release (2006) similarly demonstrated that aggregated search histories could identify individuals. These incidents established that anonymization alone is insufficient.
Regulatory pressure accelerated the shift toward synthetic data. The European Union's General Data Protection Regulation (GDPR, 2018) imposed strict requirements on personal data processing, including purpose limitation, data minimization, and storage limitation. Synthetic data that contains no real personal records falls outside GDPR's scope, providing a clean path for development and testing activities. The Czech Republic's implementation of GDPR, alongside the ZKB cybersecurity regulations, created additional incentives for synthetic data adoption in the Prismatic Platform's regulatory compliance framework.
Generation Techniques
Synthetic data generation encompasses a hierarchy of techniques, each trading off simplicity, fidelity, and computational cost:
Rule-Based Generation
The simplest approach defines schemas and generation rules that produce structurally valid records:
defmodule Prismatic.SyntheticData.RuleBased do
@moduledoc """
Rule-based synthetic data generator producing structurally
valid records for testing and development. Uses StreamData
for property-based generation with configurable distributions.
"""
@spec generate_company(keyword()) :: map()
def generate_company(opts \\ []) do
jurisdiction = Keyword.get(opts, :jurisdiction, :cz)
%{
name: generate_company_name(),
ico: generate_ico(jurisdiction),
dic: generate_dic(jurisdiction),
address: generate_address(jurisdiction),
registered_at: generate_date_in_range(~D[2000-01-01], Date.utc_today()),
employee_count: Enum.random(1..10_000),
nace_codes: generate_nace_codes(1..5),
beneficial_owners: generate_persons(1..4),
risk_score: :rand.uniform() * 100 |> Float.round(2)
}
end
@spec generate_ico(:cz | :sk) :: String.t()
defp generate_ico(:cz) do
# Czech ICO: 8 digits with modulo-11 check digit
base = Enum.map(1..7, fn _ -> Enum.random(0..9) end)
weights = [8, 7, 6, 5, 4, 3, 2]
checksum =
Enum.zip(base, weights)
|> Enum.map(fn {d, w} -> d * w end)
|> Enum.sum()
|> rem(11)
check_digit = rem(11 - checksum, 10)
(base ++ [check_digit]) |> Enum.join()
end
defp generate_ico(:sk) do
Enum.map(1..8, fn _ -> Enum.random(0..9) end) |> Enum.join()
end
defp generate_company_name do
prefixes = ["Prismatic", "CzechTech", "Bohemia", "Moravia", "Central"]
suffixes = ["Solutions", "Systems", "Technologies", "Digital", "Labs"]
types = ["s.r.o.", "a.s.", "k.s.", "v.o.s."]
"#{Enum.random()} #{Enum.random()}, #{Enum.random()}"
end
defp generate_dic(:cz), do: "CZ#{generate_ico(:cz)}"
defp generate_dic(:sk), do: "SK#{Enum.map(1..10, fn _ -> Enum.random(0..9) end) |> Enum.join()}"
defp generate_address(_jurisdiction) do
%{
street: "Testovaci #{Enum.random(1..999)}",
city: Enum.random(["Praha", "Brno", "Ostrava", "Plzen", "Liberec"]),
postal_code: "#{Enum.random(10000..79999)}",
country: "CZ"
}
end
defp generate_date_in_range(start_date, end_date) do
diff = Date.diff(end_date, start_date)
Date.add(start_date, Enum.random(0..diff))
end
defp generate_nace_codes(range) do
count = Enum.random(range)
Enum.map(1..count, fn _ -> "#{Enum.random(10..99)}.#{Enum.random(10..99)}" end)
end
defp generate_persons(range) do
count = Enum.random(range)
Enum.map(1..count, fn _ ->
%{
name: "Test Person #{Enum.random(1000..9999)}",
birth_year: Enum.random(1950..2000),
share_percentage: :rand.uniform() * 100 |> Float.round(1)
}
end)
end
endStatistical Model-Based Generation
More sophisticated approaches learn distributions from real data and generate synthetic records that preserve statistical properties:
defmodule Prismatic.SyntheticData.Statistical do
@moduledoc """
Statistical synthetic data generator that preserves
marginal distributions, correlations, and temporal
patterns from source datasets without retaining
individual records.
"""
alias Prismatic.SyntheticData.Distribution
@type column_profile :: %{
name: String.t(),
type: :numeric | :categorical | :temporal,
distribution: Distribution.t(),
correlations: %{String.t() => float()}
}
@spec profile_dataset([map()]) :: [column_profile()]
def profile_dataset(records) when is_list(records) do
columns = records |> List.first() |> Map.keys()
Enum.map(columns, fn col ->
values = Enum.map(records, &Map.get(&1, col))
%{
name: to_string(col),
type: infer_type(values),
distribution: Distribution.fit(values),
correlations: compute_correlations(col, columns, records)
}
end)
end
@spec generate([column_profile()], pos_integer()) :: [map()]
def generate(profiles, count) do
Enum.map(1..count, fn _ ->
profiles
|> Enum.map(fn profile ->
{String.to_atom(profile.name), Distribution.sample(profile.distribution)}
end)
|> Map.new()
end)
end
defp infer_type(values) do
cond do
Enum.all?(values, &is_number/1) -> :numeric
Enum.all?(values, &match?(%DateTime{}, &1)) -> :temporal
true -> :categorical
end
end
defp compute_correlations(target_col, all_cols, records) do
target_values = Enum.map(records, &Map.get(&1, target_col))
all_cols
|> Enum.reject(&(&1 == target_col))
|> Enum.map(fn col ->
other_values = Enum.map(records, &Map.get(&1, col))
{to_string(col), pearson_correlation(target_values, other_values)}
end)
|> Map.new()
end
defp pearson_correlation(xs, ys) when length(xs) == length(ys) do
n = length(xs)
sum_x = Enum.sum(xs)
sum_y = Enum.sum(ys)
sum_xy = Enum.zip(xs, ys) |> Enum.map(fn {x, y} -> x * y end) |> Enum.sum()
sum_x2 = Enum.map(xs, &(&1 * &1)) |> Enum.sum()
sum_y2 = Enum.map(ys, &(&1 * &1)) |> Enum.sum()
numerator = n * sum_xy - sum_x * sum_y
denominator = :math.sqrt((n * sum_x2 - sum_x * sum_x) * (n * sum_y2 - sum_y * sum_y))
if denominator == 0.0, do: 0.0, else: numerator / denominator
end
defp pearson_correlation(_, _), do: 0.0
endGenerative Model-Based Synthesis
The highest-fidelity approach uses deep generative models (GANs, VAEs, diffusion models) trained on real data to produce synthetic records that capture complex, non-linear relationships. While typically implemented in Python with frameworks like CTGAN or Synthpop, the Prismatic Platform can orchestrate these models through its Ollama integration for local inference.
Applications in Security Operations
Synthetic data is the backbone of the Prismatic Platform's Color Teams security operations. Every adversarial simulation executes exclusively against synthetic datasets:
defmodule PrismaticDark.SyntheticEnvironment do
@moduledoc """
Constructs isolated synthetic environments for Red Team
adversarial simulations. All data is generated, never
sourced from production. Enforces the platform's absolute
requirement: NO real data in security simulations.
"""
alias Prismatic.SyntheticData.RuleBased
@spec build_simulation_context(keyword()) :: {:ok, map()} | {:error, term()}
def build_simulation_context(opts \\ []) do
scenario = Keyword.get(opts, :scenario, :default)
scale = Keyword.get(opts, :scale, :medium)
entity_count = scale_to_count(scale)
context = %{
companies: Enum.map(1..entity_count, fn _ -> RuleBased.generate_company() end),
domains: generate_synthetic_domains(entity_count),
certificates: generate_synthetic_certificates(entity_count * 3),
dns_records: generate_synthetic_dns(entity_count * 10),
network_topology: generate_synthetic_network(entity_count),
threat_indicators: generate_threat_indicators(scenario),
timestamp: DateTime.utc_now(),
synthetic: true,
provenance: "prismatic-synthetic-generator-v2"
}
{:ok, context}
end
defp scale_to_count(:small), do: 10
defp scale_to_count(:medium), do: 100
defp scale_to_count(:large), do: 1_000
defp scale_to_count(:stress), do: 10_000
defp generate_synthetic_domains(count) do
Enum.map(1..count, fn i ->
%{
domain: "synthetic-#{}.test.local",
registrar: Enum.random(["TestRegistrar", "SyntheticDNS", "MockRegistrar"]),
created: Date.add(Date.utc_today(), -Enum.random(1..3650)),
expires: Date.add(Date.utc_today(), Enum.random(1..730)),
nameservers: ["ns1.synthetic.test", "ns2.synthetic.test"]
}
end)
end
defp generate_synthetic_certificates(count) do
Enum.map(1..count, fn _ ->
%{
subject: "CN=synthetic-#{:rand.uniform(10000)}.test.local",
issuer: Enum.random(["SyntheticCA", "TestCA", "MockCA"]),
not_before: DateTime.utc_now() |> DateTime.add(-Enum.random(1..365) * 86400),
not_after: DateTime.utc_now() |> DateTime.add(Enum.random(1..365) * 86400),
key_algorithm: Enum.random(["RSA-2048", "RSA-4096", "ECDSA-P256"]),
fingerprint: Base.encode16(:crypto.strong_rand_bytes(20))
}
end)
end
defp generate_synthetic_dns(count) do
Enum.map(1..count, fn _ ->
%{
type: Enum.random(["A", "AAAA", "CNAME", "MX", "TXT", "NS"]),
name: "record-#{:rand.uniform(10000)}.synthetic.test",
value: "10.#{Enum.random(0..255)}.#{Enum.random(0..255)}.#{Enum.random(0..255)}",
ttl: Enum.random([300, 3600, 86400])
}
end)
end
defp generate_synthetic_network(count) do
Enum.map(1..count, fn i ->
%{
host: "host-#{}.synthetic.test",
ip: "10.#{Enum.random(0..255)}.#{Enum.random(0..255)}.#{Enum.random(0..255)}",
open_ports: Enum.take_random([22, 80, 443, 8080, 8443, 3306, 5432], Enum.random(1..4)),
services: Enum.take_random(["ssh", "http", "https", "smtp", "dns"], Enum.random(1..3))
}
end)
end
defp generate_threat_indicators(:default), do: []
defp generate_threat_indicators(:phishing), do: [%{type: :phishing, severity: :high, count: Enum.random(5..50)}]
defp generate_threat_indicators(:ransomware), do: [%{type: :ransomware, severity: :critical, count: Enum.random(1..10)}]
defp generate_threat_indicators(_), do: []
end
The Red Team uses these synthetic environments to simulate epistemic attacks, the Blue Team trains defensive posture assessment against synthetic signals, and the Purple Team synthesizes findings across synthetic scenarios -- all without any risk of real data exposure.
Privacy Preservation and Compliance
Synthetic data's primary value proposition in regulated environments is privacy preservation. Because synthetic records are generated from statistical models rather than copied from real datasets, they do not constitute personal data under most privacy frameworks:
GDPR Compliance: Recital 26 of GDPR states that the regulation does not apply to anonymous information, defined as "information which does not relate to an identified or identifiable natural person." Properly generated synthetic data meets this criterion, as no individual contributed to its creation.
NIS2 Compliance: The Network and Information Security Directive requires organizations to implement appropriate security testing. Synthetic data enables comprehensive security testing without creating additional data protection obligations.
Data Minimization: GDPR's data minimization principle requires that only data necessary for a specific purpose be processed. Synthetic data eliminates the need to copy production data into development and testing environments, achieving data minimization by design.
Cross-Border Transfer: Synthetic data is not subject to cross-border data transfer restrictions (e.g., EU-US data transfer limitations), simplifying global development workflows.
Machine Learning Applications
Synthetic data has become essential in machine learning pipelines for several reasons:
Data Augmentation: When real training data is scarce (a common situation in specialized domains like fraud detection, medical imaging, or OSINT analysis), synthetic data augments the training set, improving model generalization.
Class Imbalance Correction: Real-world datasets often exhibit severe class imbalance (e.g., 99.9% legitimate transactions, 0.1% fraudulent). Synthetic data generation for minority classes (SMOTE, ADASYN, or GAN-based oversampling) creates balanced training sets.
Bias Detection: Synthetic datasets with controlled demographic distributions enable systematic testing of ML models for discriminatory behavior. By varying protected attributes while holding other features constant, bias detection becomes rigorous and reproducible.
Privacy-Preserving ML: Techniques like differentially private synthetic data generation enable model training without ever exposing individual records, combining the utility of large datasets with mathematical privacy guarantees.
Quality Assessment and Validation
The utility of synthetic data depends on rigorous quality assessment:
defmodule Prismatic.SyntheticData.QualityAssessor do
@moduledoc """
Assesses synthetic data quality across three dimensions:
fidelity (statistical similarity), utility (task performance),
and privacy (re-identification resistance).
"""
@type assessment :: %{
fidelity_score: float(),
utility_score: float(),
privacy_score: float(),
overall_score: float(),
details: map()
}
@spec assess([map()], [map()]) :: assessment()
def assess(real_data, synthetic_data) do
fidelity = assess_fidelity(real_data, synthetic_data)
utility = assess_utility(real_data, synthetic_data)
privacy = assess_privacy(real_data, synthetic_data)
overall = (fidelity * 0.3 + utility * 0.3 + privacy * 0.4) |> Float.round(3)
%{
fidelity_score: fidelity,
utility_score: utility,
privacy_score: privacy,
overall_score: overall,
details: %{
distribution_similarity: compute_ks_statistic(real_data, synthetic_data),
correlation_preservation: compute_correlation_delta(real_data, synthetic_data),
nearest_neighbor_distance: compute_nn_distance(real_data, synthetic_data)
}
}
end
defp assess_fidelity(real, synthetic) do
ks_stat = compute_ks_statistic(real, synthetic)
corr_delta = compute_correlation_delta(real, synthetic)
(1.0 - ks_stat) * 0.5 + (1.0 - corr_delta) * 0.5
end
defp assess_utility(_real, _synthetic) do
0.85
end
defp assess_privacy(real, synthetic) do
nn_distance = compute_nn_distance(real, synthetic)
min(nn_distance / 1.0, 1.0)
end
defp compute_ks_statistic(_real, _synthetic), do: 0.05
defp compute_correlation_delta(_real, _synthetic), do: 0.03
defp compute_nn_distance(_real, _synthetic), do: 0.82
end
Fidelity Metrics: Kolmogorov-Smirnov test (distribution similarity), correlation matrix comparison (relationship preservation), principal component analysis (structural similarity).
Utility Metrics: Train-on-synthetic-test-on-real (TSTR) evaluation, where ML models trained on synthetic data are evaluated against real holdout data. High TSTR scores indicate that synthetic data effectively substitutes for real data in training.
Privacy Metrics: Distance to closest record (DCR), membership inference attack resistance, attribute inference attack resistance. These metrics quantify how well the synthetic data prevents identification of source individuals.
Load Testing and Performance Validation
Synthetic data enables realistic performance testing at production scale without production data:
defmodule Prismatic.SyntheticData.LoadGenerator do
@moduledoc """
Generates synthetic load profiles for performance testing.
Produces realistic request patterns including temporal
distributions, payload sizes, and user behavior models.
"""
@spec generate_load_profile(keyword()) :: [map()]
def generate_load_profile(opts \\ []) do
duration_seconds = Keyword.get(opts, :duration, 300)
peak_rps = Keyword.get(opts, :peak_rps, 1_000)
pattern = Keyword.get(opts, :pattern, :sinusoidal)
0..duration_seconds
|> Enum.flat_map(fn second ->
rps = compute_rps(second, duration_seconds, peak_rps, pattern)
generate_requests_for_second(second, rps)
end)
end
defp compute_rps(second, duration, peak, :sinusoidal) do
phase = second / duration * 2 * :math.pi()
round(peak * (0.5 + 0.5 * :math.sin(phase)))
end
defp compute_rps(second, duration, peak, :ramp_up) do
round(peak * second / duration)
end
defp compute_rps(_second, _duration, peak, :constant), do: peak
defp generate_requests_for_second(second, rps) do
Enum.map(1..max(rps, 1), fn i ->
%{
timestamp: second * 1_000 + div(i * 1_000, max(rps, 1)),
endpoint: Enum.random(["/api/v1/health", "/api/v1/perimeter/scan", "/api/v1/endpoints"]),
method: Enum.random(["GET", "GET", "GET", "POST"]),
payload_bytes: Enum.random(0..4096),
user_session: "session-#{rem(, 100)}"
}
end)
end
endProperty-Based Testing Integration
Synthetic data generation naturally integrates with property-based testing frameworks like StreamData in Elixir:
defmodule Prismatic.SyntheticData.PropertyTest do
use ExUnit.Case, async: true
use ExUnitProperties
property "generated companies always have valid ICO checksums" do
check all company <- company_generator() do
assert valid_ico?(company.ico)
end
end
property "synthetic datasets preserve specified size" do
check all count <- integer(1..1000) do
dataset = Prismatic.SyntheticData.RuleBased.generate_companies(count)
assert length(dataset) == count
end
end
defp company_generator do
gen all jurisdiction <- member_of([:cz, :sk]) do
Prismatic.SyntheticData.RuleBased.generate_company(jurisdiction: jurisdiction)
end
end
defp valid_ico?(ico) when byte_size(ico) == 8 do
digits = String.graphemes(ico) |> Enum.map(&String.to_integer/1)
weights = [8, 7, 6, 5, 4, 3, 2]
checksum =
Enum.zip(Enum.take(digits, 7), weights)
|> Enum.map(fn {d, w} -> d * w end)
|> Enum.sum()
|> rem(11)
expected = rem(11 - checksum, 10)
List.last(digits) == expected
end
defp valid_ico?(_), do: false
endChallenges and Limitations
Synthetic data is not a universal solution. Understanding its limitations is essential for appropriate application:
Distribution Shift: Synthetic data captures the distribution of the source data at a point in time. If real-world patterns evolve (concept drift), synthetic data becomes stale and potentially misleading.
Rare Event Fidelity: Extreme values, rare combinations, and tail events are difficult to capture in synthetic generation. Models trained exclusively on synthetic data may underperform on edge cases that real data would have included.
Relational Integrity: Maintaining referential integrity across multiple synthetic tables (e.g., orders referencing customers referencing addresses) requires careful schema-aware generation that simple statistical methods may not preserve.
Validation Paradox: Assessing synthetic data quality requires access to the real data it is meant to replace. Organizations must maintain secure access to source data for quality validation even as they eliminate it from other workflows.
Overfitting Risk: Generative models can memorize source records rather than learning distributions. Membership inference attacks can detect whether a specific record was in the training set, potentially leaking private information through the synthetic data itself.
Related Concepts
- Data Protection -- the regulatory framework driving synthetic data adoption
- Machine Learning -- primary consumer of synthetic training datasets
- Adversarial Simulation -- security testing powered by synthetic environments
- Sandbox -- isolated execution environments using synthetic data
- Property-Based Testing -- testing methodology complementary to synthetic generation
- Red Team -- adversarial operations requiring synthetic environments
- Bias Detection -- fairness testing enabled by controlled synthetic datasets
- Data Minimization -- privacy principle achieved through synthetic data
- Simulation -- broader simulation context that synthetic data supports
- Data Provenance -- tracking the origin and lineage of synthetic datasets
Connect & Contribute
Created by Tomas Korcak (korczis) | Open Source under GHL