7.3 KiB
7.3 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-osint-infrastructure | 03 | execute | 1 |
|
true |
|
|
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
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/phases/09-osint-infrastructure/09-CONTEXT.md @pkg/engine/finding.go Task 1: Stealth UA pool + RandomUserAgent pkg/recon/stealth.go, pkg/recon/stealth_test.go - 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 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.
cd /home/salva/Documents/apikey && go test ./pkg/recon/ -run 'TestUAPool|TestRandomUserAgent|TestStealthHeaders' -count=1
Tests pass. Pool has exactly 10 UAs. Random selection always within pool.
Task 2: Cross-source finding dedup
pkg/recon/dedup.go, pkg/recon/dedup_test.go
- 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
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.
cd /home/salva/Documents/apikey && go test ./pkg/recon/ -run TestDedup -count=1
All dedup tests pass. First-seen wins. Different Source URLs are kept separate.
- `go test ./pkg/recon/ -run 'TestUAPool|TestRandom|TestStealth|TestDedup' -count=1` passes
- `go vet ./pkg/recon/...` clean
<success_criteria>
- Stealth UA pool (10 entries) exported via RandomUserAgent/StealthHeaders
- Dedup primitive removes duplicates stably by sha256(provider|masked|source) </success_criteria>