1-SEC contributors · Reviewed · Module: data_poisoning

Training Dataset Integrity: Hash Changes and Bulk-Update Signals

Data Poisoning Guard compares each reported dataset hash with the previous nonempty hash for the same dataset ID.

What 1-SEC checks

Data Poisoning Guard compares each reported dataset hash with the previous nonempty hash for the same dataset ID. The first event establishes local state; a later unequal nonempty hash produces an integrity alert, including for an authorized change. After more than three earlier updates, a reported change percentage above both three times the historical average and 10 percent produces a bulk-change signal. The rolling average and last hash update after each event. Dataset events can also flag supplied template-execution or remote-loader configuration strings.

Read the implementation: handleDataUpdate

Required telemetry and context

Relevant input events: training_update, training_data_update, dataset_update, dataset_change, data_ingestion. Use a trusted producer at the real application boundary.

  • dataset_id
  • source
  • hash
  • record_count (number)
  • change_percent (number)
  • dataset_config / loader_config / content (optional configuration text)

Field names above describe relevant context, not a complete API request. Consult the current event contract for envelope and detail requirements. Preserve booleans, numbers, and strings as the receiving handler expects them. Agent-provided text cannot independently verify identity or authorization.

Deployment, prevention, and response

Have a trusted ingestion pipeline compute and report dataset identifiers, digests, record counts and change percentages consistently. Keep approved version manifests and a durable change history outside this tracker. Review the producing job, authorization and intended data revision before quarantining a training run. Do not automatically bless a new source or hash because it appeared in an event. Test first-update and legitimate-change behavior alongside an unexpected change, and keep remote dataset code disabled in the actual loader.

Record the source event, affected agent/host, actual target, policy decision, and response result. Review enforcement configuration separately from detection. A recommendation, selected action, dry-run record, and completed action are different outcomes.

Reproduce the source fixtures

From a checkout of the linked 1-SEC source revision, run the selected fixtures with the repository's Go toolchain. These commands run tests; they do not execute a real attack against your systems.

go test ./internal/modules/datapoisoning -run '^(TestDataIntegrityTracker_FirstUpdate|TestDataIntegrityTracker_IntegrityViolation|TestDataIntegrityTracker_AnomalousChange|TestGuard_HandleEvent_DatasetUpdate_CleanConfig)$' -count=1

The fixtures establish first-event behavior, unequal-hash reporting, the bulk-update threshold and an inert dataset configuration. They use synthetic reported hashes rather than hashing or authenticating a real training corpus. No production provenance or immutable-history integration was tested.

TestDataIntegrityTracker_FirstUpdate

Full fixture and assertions — run in its original package with its helpers.

func TestDataIntegrityTracker_FirstUpdate(t *testing.T) {
	dt := NewDataIntegrityTracker()

	result := dt.RecordUpdate("dataset-1", "trusted-source", "abc123", 1000, 5.0)

	if result.IntegrityViolation {
		t.Error("first update should not trigger IntegrityViolation")
	}
	if result.AnomalousChange {
		t.Error("first update should not trigger AnomalousChange")
	}
	if result.UntrustedSource {
		t.Error("first update should not trigger UntrustedSource (no trusted sources configured)")
	}
}
TestDataIntegrityTracker_IntegrityViolation

Full fixture and assertions — run in its original package with its helpers.

func TestDataIntegrityTracker_IntegrityViolation(t *testing.T) {
	dt := NewDataIntegrityTracker()

	dt.RecordUpdate("dataset-1", "source-a", "hash-v1", 1000, 5.0)
	result := dt.RecordUpdate("dataset-1", "source-a", "hash-v2", 1000, 5.0)

	if !result.IntegrityViolation {
		t.Error("expected IntegrityViolation when hash changes")
	}
	if result.PreviousHash != "hash-v1" {
		t.Errorf("PreviousHash = %q, want %q", result.PreviousHash, "hash-v1")
	}
}
TestDataIntegrityTracker_AnomalousChange

Full fixture and assertions — run in its original package with its helpers.

func TestDataIntegrityTracker_AnomalousChange(t *testing.T) {
	dt := NewDataIntegrityTracker()

	// Build up a baseline with small changes (need >3 updates)
	dt.RecordUpdate("dataset-2", "src", "h1", 1000, 2.0)
	dt.RecordUpdate("dataset-2", "src", "h2", 1000, 3.0)
	dt.RecordUpdate("dataset-2", "src", "h3", 1000, 2.5)
	dt.RecordUpdate("dataset-2", "src", "h4", 1000, 3.0)

	// Now a large change: >3x average and >10%
	result := dt.RecordUpdate("dataset-2", "src", "h5", 1000, 50.0)

	if !result.AnomalousChange {
		t.Error("expected AnomalousChange when change rate exceeds 3x average")
	}
}
TestGuard_HandleEvent_DatasetUpdate_CleanConfig

Full fixture and assertions — run in its original package with its helpers.

func TestGuard_HandleEvent_DatasetUpdate_CleanConfig(t *testing.T) {
	cp := makeCapturingPipeline()
	g := startedModuleWithPipeline(t, cp)
	defer g.Stop()

	ev := core.NewSecurityEvent("test", "dataset_update", core.SeverityInfo, "dataset update")
	ev.Details["dataset_id"] = "clean-dataset"
	ev.Details["source"] = "internal-curated"
	ev.Details["hash"] = "sha256:trusted"
	ev.Details["dataset_config"] = `{"format":"parquet","split":"train","streaming":false}`

	if err := g.HandleEvent(ev); err != nil {
		t.Fatalf("HandleEvent() error: %v", err)
	}
	if cp.hasAlertType("dataset_config_execution") {
		t.Fatal("did not expect executable dataset configuration alert")
	}
}

Tuning, evaluation, and coverage limits

This is last-hash comparison, not a persistent dataset hash history, provenance ledger or proof of tampering. The module does not read the dataset and authenticate its digest for you. Missing IDs skip the handler; missing or empty hashes cannot establish a mismatch. Legitimate retraining and ingestion can trigger alerts. The internal trusted-source map is empty by default and has no demonstrated public provisioning path, so these events do not establish verified training-data provenance. State resets with the module; fixed heuristics are not exposed as tuning knobs.

Evaluate this behavior with representative authorized activity and a controlled known-positive case. Investigate the exact rule, inputs, and state before suppressing an alert. Do not assume a hard-coded heuristic is a configurable setting. Measure your own deployment; the source fixtures do not establish a benchmark, detection rate, compliance certification, or incident-prevention claim.

Protocol and format references

These references describe the underlying format or protocol. The implementation and fixtures above define the subset 1-SEC actually checks.

Related implemented controls

Implementation reviewed at source revision a2ab27289e0fbbce555ad80f31eb19bfffa86f84. Recheck the actual release and integration before deployment. Sources and corrections policy.