Files
keyhunter/.planning/phases/09-osint-infrastructure/09-03-PLAN.md
2026-04-06 00:39:27 +03:00

187 lines
7.3 KiB
Markdown

---
phase: 09-osint-infrastructure
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
- pkg/recon/stealth.go
- pkg/recon/stealth_test.go
- pkg/recon/dedup.go
- pkg/recon/dedup_test.go
autonomous: true
requirements: [RECON-INFRA-06]
must_haves:
truths:
- "Stealth mode exposes a UA pool of 10 realistic browser user-agents (Chrome/Firefox/Safari across Linux/macOS/Windows)"
- "RandomUserAgent returns a UA from the pool, distributed across calls"
- "Dedup drops duplicate Findings keyed by SHA256(provider + masked_key + source)"
- "Dedup preserves first-seen order and metadata"
artifacts:
- path: "pkg/recon/stealth.go"
provides: "UA pool, RandomUserAgent, StealthHeaders helper"
contains: "var userAgents"
- path: "pkg/recon/dedup.go"
provides: "Dedup([]Finding) []Finding keyed by sha256(provider|masked|source)"
contains: "func Dedup"
key_links:
- from: "pkg/recon/dedup.go"
to: "crypto/sha256"
via: "finding hash key"
pattern: "sha256\\.Sum256"
---
<objective>
Implement stealth mode helpers (UA rotation) and cross-source deduplication. Both are small, self-contained, and unblock the parallel sweep orchestrator from producing noisy duplicate findings.
Purpose: Satisfies RECON-INFRA-06 (stealth UA rotation) and provides the dedup primitive that SweepAll callers use to satisfy RECON-INFRA-08's "deduplicates findings before persisting" criterion.
Output: pkg/recon/stealth.go, pkg/recon/dedup.go, and their tests
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/phases/09-osint-infrastructure/09-CONTEXT.md
@pkg/engine/finding.go
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Stealth UA pool + RandomUserAgent</name>
<files>pkg/recon/stealth.go, pkg/recon/stealth_test.go</files>
<behavior>
- userAgents is an unexported slice of exactly 10 realistic UA strings covering Chrome/Firefox/Safari on Linux/macOS/Windows
- RandomUserAgent() returns a random entry from the pool
- StealthHeaders() returns map[string]string{"User-Agent": RandomUserAgent(), "Accept-Language": "en-US,en;q=0.9"}
- Tests: TestUAPoolSize (== 10), TestRandomUserAgentInPool (returned value is in pool), TestStealthHeadersHasUA
</behavior>
<action>
Create pkg/recon/stealth.go with a package-level `userAgents` slice of 10 realistic UAs. Include at least:
- Chrome 120 Windows
- Chrome 120 macOS
- Chrome 120 Linux
- Firefox 121 Windows
- Firefox 121 macOS
- Firefox 121 Linux
- Safari 17 macOS
- Safari 17 iOS
- Edge 120 Windows
- Chrome Android
```go
package recon
import "math/rand"
var userAgents = []string{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.2; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.2210.61",
"Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
}
// RandomUserAgent returns a random browser user-agent from the pool.
// Used when Config.Stealth is true.
func RandomUserAgent() string {
return userAgents[rand.Intn(len(userAgents))]
}
// StealthHeaders returns a minimal headers map with rotated UA and Accept-Language.
func StealthHeaders() map[string]string {
return map[string]string{
"User-Agent": RandomUserAgent(),
"Accept-Language": "en-US,en;q=0.9",
}
}
```
Create pkg/recon/stealth_test.go with the three tests. TestRandomUserAgentInPool should loop 100 times and assert each result is present in the `userAgents` slice.
</action>
<verify>
<automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/ -run 'TestUAPool|TestRandomUserAgent|TestStealthHeaders' -count=1</automated>
</verify>
<done>Tests pass. Pool has exactly 10 UAs. Random selection always within pool.</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Cross-source finding dedup</name>
<files>pkg/recon/dedup.go, pkg/recon/dedup_test.go</files>
<behavior>
- Dedup(in []Finding) []Finding drops duplicates keyed by sha256(ProviderName + "|" + KeyMasked + "|" + Source)
- First-seen wins: returned slice preserves the first occurrence's metadata (SourceType, DetectedAt, etc.)
- Order is preserved from the input (stable dedup)
- Nil/empty input returns nil
- Tests:
- TestDedupEmpty: Dedup(nil) == nil
- TestDedupNoDuplicates: 3 distinct findings -> 3 returned
- TestDedupAllDuplicates: 3 identical findings -> 1 returned
- TestDedupPreservesFirstSeen: two findings with same key, different DetectedAt — the first-seen timestamp wins
- TestDedupDifferentSource: same provider/masked, different Source URLs -> both kept
</behavior>
<action>
Create pkg/recon/dedup.go:
```go
package recon
import (
"crypto/sha256"
"encoding/hex"
)
// Dedup removes duplicate findings using SHA256(provider|masked|source) as key.
// Stable: preserves input order and first-seen metadata.
func Dedup(in []Finding) []Finding {
if len(in) == 0 {
return nil
}
seen := make(map[string]struct{}, len(in))
out := make([]Finding, 0, len(in))
for _, f := range in {
h := sha256.Sum256([]byte(f.ProviderName + "|" + f.KeyMasked + "|" + f.Source))
k := hex.EncodeToString(h[:])
if _, dup := seen[k]; dup {
continue
}
seen[k] = struct{}{}
out = append(out, f)
}
return out
}
```
Create pkg/recon/dedup_test.go with the five tests. Use testify require.
</action>
<verify>
<automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/ -run TestDedup -count=1</automated>
</verify>
<done>All dedup tests pass. First-seen wins. Different Source URLs are kept separate.</done>
</task>
</tasks>
<verification>
- `go test ./pkg/recon/ -run 'TestUAPool|TestRandom|TestStealth|TestDedup' -count=1` passes
- `go vet ./pkg/recon/...` clean
</verification>
<success_criteria>
- Stealth UA pool (10 entries) exported via RandomUserAgent/StealthHeaders
- Dedup primitive removes duplicates stably by sha256(provider|masked|source)
</success_criteria>
<output>
After completion, create `.planning/phases/09-osint-infrastructure/09-03-SUMMARY.md`
</output>
</content>