1-SEC contributors · Reviewed · Module: data_poisoning
Agent Web Content Hash Drift: Domain Baselines and Rapid Mutation
Data Poisoning Guard uses reported domain and content hash to establish a first-seen baseline.
What 1-SEC checks
Data Poisoning Guard uses reported domain and content hash to establish a first-seen baseline. A later unequal nonempty hash for that domain emits a change alert. If the reported size differs by more than fifty percent from the previous size, that change can receive critical severity. More than three hash changes within the hourly change window produces a rapid-mutation signal. State is grouped by domain: two different URLs on one domain share the same last hash and size.
Required telemetry and context
Relevant input events: llms_txt_fetch, markdown_endpoint_fetch. Use a trusted producer at the real application boundary.
domainurlcontent_hashcontent_length (number)agent_id
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
Compute digests from exact response bytes in the trusted fetch integration and report stable domain/URL labels and numeric lengths. Keep approved manifests or provenance checks outside this local monitor. Investigate whether a change came from a legitimate publication, an alternate endpoint or an attacker before acting. Supply body text separately through agent_markdown_ingest when you need the containment content scanner; llms_txt_fetch metadata alone does not scan instructions. Enforce any refusal at the fetcher or consumer boundary.
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 '^(TestGuard_WebContentDomainBaselineAndMutation|TestWebContentTracker_WindowResetAndDomainIsolation)$' -count=1New fixtures exercise the actual event handler for first-seen, unchanged, changed and rapidly changing supplied hashes, plus domain isolation and manual change-window expiry. Two URLs on one domain deliberately demonstrate the shared baseline. No live site, hash origin or trusted publisher is validated.
TestGuard_WebContentDomainBaselineAndMutation
Full fixture and assertions — run in its original package with its helpers.
func TestGuard_WebContentDomainBaselineAndMutation(t *testing.T) {
cp := makeCapturingPipeline()
g := startedModuleWithPipeline(t, cp)
defer g.Stop()
report := func(path, hash string) {
t.Helper()
e := core.NewSecurityEvent("test", "llms_txt_fetch", core.SeverityInfo, "inert metadata fixture")
e.Details = map[string]interface{}{"domain": "docs.example", "url": "https://docs.example/" + path, "content_hash": hash, "content_length": 100, "agent_id": "reader"}
if err := g.HandleEvent(e); err != nil {
t.Fatal(err)
}
}
report("llms.txt", "first")
if !cp.hasAlertType("web_content_new_domain") {
t.Fatal("first domain should establish a baseline")
}
before := cp.count()
report("llms.txt", "first")
if cp.count() != before {
t.Fatal("unchanged content should not produce a new finding")
}
report("other.md", "second")
if !cp.hasAlertType("web_content_changed") {
t.Fatal("a different URL shares the same domain baseline")
}
report("llms.txt", "third")
report("llms.txt", "fourth")
report("llms.txt", "fifth")
if !cp.hasAlertType("web_content_rapid_changes") {
t.Fatal("four reported hash changes should produce a rapid-change finding")
}
}TestWebContentTracker_WindowResetAndDomainIsolation
Full fixture and assertions — run in its original package with its helpers.
func TestWebContentTracker_WindowResetAndDomainIsolation(t *testing.T) {
wt := NewWebContentIntegrityTracker()
wt.RecordContent("docs.example", "/a", "a", 100)
if result := wt.RecordContent("other.example", "/a", "b", 200); !result.NewDomain || result.ContentChanged {
t.Fatal("different domains must not share a baseline")
}
for _, hash := range []string{"b", "c", "d", "e"} {
wt.RecordContent("docs.example", "/a", hash, 100)
}
wt.mu.Lock()
wt.domains["docs.example"].ChangeWindow = time.Now().Add(-2 * time.Hour)
wt.mu.Unlock()
result := wt.RecordContent("docs.example", "/a", "e", 100)
if result.RapidChanges || result.ChangeCount != 0 || result.ContentChanged {
t.Fatalf("window reset should preserve the hash and reset change count: %+v", result)
}
}Tuning, evaluation, and coverage limits
The handler does not fetch content, calculate the digest, verify a publisher or compare against an independently approved hash. A first-seen event is not new-domain trust. Ordinary documentation edits and alternating URLs on the same domain can produce change signals; stable malicious content can produce none after becoming the baseline. Reported byte-size change is not a semantic difference measurement. Local state resets with the module, and rapid-change counters are not a durable version history.
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.