---
phase: 16-osint-threat-intel-mobile-dns-api-marketplaces
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - pkg/recon/sources/virustotal.go
  - pkg/recon/sources/virustotal_test.go
  - pkg/recon/sources/intelligencex.go
  - pkg/recon/sources/intelligencex_test.go
  - pkg/recon/sources/urlhaus.go
  - pkg/recon/sources/urlhaus_test.go
autonomous: true
requirements: [RECON-INTEL-01, RECON-INTEL-02, RECON-INTEL-03]

must_haves:
  truths:
    - "VirusTotal source searches VT API for files/URLs containing provider keywords"
    - "IntelligenceX source searches IX archive for leaked credentials"
    - "URLhaus source searches abuse.ch URLhaus API for malicious URLs containing keys"
  artifacts:
    - path: "pkg/recon/sources/virustotal.go"
      provides: "VirusTotalSource implementing recon.ReconSource"
      contains: "func (s *VirusTotalSource) Sweep"
    - path: "pkg/recon/sources/intelligencex.go"
      provides: "IntelligenceXSource implementing recon.ReconSource"
      contains: "func (s *IntelligenceXSource) Sweep"
    - path: "pkg/recon/sources/urlhaus.go"
      provides: "URLhausSource implementing recon.ReconSource"
      contains: "func (s *URLhausSource) Sweep"
  key_links:
    - from: "pkg/recon/sources/virustotal.go"
      to: "pkg/recon/sources/queries.go"
      via: "BuildQueries call"
      pattern: "BuildQueries\\(s\\.Registry"
    - from: "pkg/recon/sources/intelligencex.go"
      to: "pkg/recon/sources/queries.go"
      via: "BuildQueries call"
      pattern: "BuildQueries\\(s\\.Registry"
    - from: "pkg/recon/sources/urlhaus.go"
      to: "pkg/recon/sources/queries.go"
      via: "BuildQueries call"
      pattern: "BuildQueries\\(s\\.Registry"
---

<objective>
Implement three threat intelligence ReconSource modules: VirusTotal, IntelligenceX, and URLhaus.

Purpose: Detect API keys appearing in threat intelligence feeds — malware samples (VT), breach archives (IX), and malicious URL databases (URLhaus).
Output: Three source files + tests in pkg/recon/sources/
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@pkg/recon/source.go
@pkg/recon/sources/httpclient.go
@pkg/recon/sources/queries.go
@pkg/recon/sources/sentry.go
@pkg/recon/sources/sentry_test.go
</context>

<interfaces>
<!-- Established patterns from the codebase that executors must follow -->

From pkg/recon/source.go:
```go
type ReconSource interface {
    Name() string
    RateLimit() rate.Limit
    Burst() int
    RespectsRobots() bool
    Enabled(cfg Config) bool
    Sweep(ctx context.Context, query string, out chan<- Finding) error
}
```

From pkg/recon/sources/httpclient.go:
```go
func NewClient() *Client  // 30s timeout, 2 retries
func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)
```

From pkg/recon/sources/queries.go:
```go
func BuildQueries(reg *providers.Registry, source string) []string
```

From pkg/recon/sources/travisci.go:
```go
var ciLogKeyPattern = regexp.MustCompile(`(?i)(api[_-]?key|secret[_-]?key|token|password|credential|auth[_-]?token)['":\s]*[=:]\s*['"]?([a-zA-Z0-9_\-]{16,})['"]?`)
```
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: VirusTotal and IntelligenceX sources</name>
  <files>pkg/recon/sources/virustotal.go, pkg/recon/sources/virustotal_test.go, pkg/recon/sources/intelligencex.go, pkg/recon/sources/intelligencex_test.go</files>
  <action>
Create VirusTotalSource in virustotal.go following the exact SentrySource pattern:

- Struct: VirusTotalSource with APIKey, BaseURL, Registry (*providers.Registry), Limiters (*recon.LimiterRegistry), Client (*Client) fields.
- Name() returns "virustotal". RateLimit() returns rate.Every(15*time.Second) (VT free tier: 4 req/min). Burst() returns 2. RespectsRobots() returns false. Enabled() returns s.APIKey != "".
- Compile-time interface check: `var _ recon.ReconSource = (*VirusTotalSource)(nil)`
- Sweep(): Default BaseURL to "https://www.virustotal.com/api/v3". Use BuildQueries(s.Registry, "virustotal") to get keyword list. For each query, call GET `{base}/intelligence/search?query={url-encoded query}&limit=10` with header `x-apikey: {APIKey}`. Parse JSON response `{"data":[{"id":"...","attributes":{"meaningful_name":"...","tags":[...],...}}]}`. For each result, stringify the attributes JSON and check with ciLogKeyPattern.MatchString(). Emit Finding with SourceType "recon:virustotal", Source as VT permalink `https://www.virustotal.com/gui/file/{id}`.
- Rate-limit via s.Limiters.Wait(ctx, s.Name(), ...) before each HTTP call, same as SentrySource pattern.

Create IntelligenceXSource in intelligencex.go:

- Struct: IntelligenceXSource with APIKey, BaseURL, Registry, Limiters, Client fields.
- Name() returns "intelligencex". RateLimit() returns rate.Every(5*time.Second). Burst() returns 3. RespectsRobots() false. Enabled() returns s.APIKey != "".
- Sweep(): Default BaseURL to "https://2.intelx.io". Use BuildQueries. For each query: POST `{base}/intelligent/search` with JSON body `{"term":"{query}","maxresults":10,"media":0,"timeout":5}` and header `x-key: {APIKey}`. Parse response `{"id":"search-id","status":0}`. Then GET `{base}/intelligent/search/result?id={search-id}&limit=10` with same x-key header. Parse `{"records":[{"systemid":"...","name":"...","storageid":"...","bucket":"..."}]}`. For each record, fetch content via GET `{base}/file/read?type=0&storageid={storageid}&bucket={bucket}` — read up to 512KB, check with ciLogKeyPattern. Emit Finding with SourceType "recon:intelligencex".

Tests: Follow sentry_test.go pattern exactly. Use httptest.NewServer with mux routing. Test Name(), Enabled() (true with key, false without), Sweep with mock responses returning key-like content, and Sweep with empty results.
  </action>
  <verify>
    <automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestVirusTotal|TestIntelligenceX" -count=1 -v</automated>
  </verify>
  <done>VirusTotalSource and IntelligenceXSource implement ReconSource, tests pass with httptest mocks proving Sweep emits findings for key-containing responses and zero findings for clean responses</done>
</task>

<task type="auto">
  <name>Task 2: URLhaus source</name>
  <files>pkg/recon/sources/urlhaus.go, pkg/recon/sources/urlhaus_test.go</files>
  <action>
Create URLhausSource in urlhaus.go:

- Struct: URLhausSource with BaseURL, Registry (*providers.Registry), Limiters (*recon.LimiterRegistry), Client (*Client) fields. No API key needed — URLhaus API is free/unauthenticated.
- Name() returns "urlhaus". RateLimit() returns rate.Every(3*time.Second). Burst() returns 2. RespectsRobots() false. Enabled() always returns true (credentialless).
- Sweep(): Default BaseURL to "https://urlhaus-api.abuse.ch/v1". Use BuildQueries(s.Registry, "urlhaus"). For each query: POST `{base}/tag/{url-encoded query}/` (URLhaus tag lookup). If that returns empty or error, fallback to POST `{base}/payload/` with form body `md5_hash=&sha256_hash=&tag={query}`. Parse JSON response `{"query_status":"ok","urls":[{"url":"...","url_status":"...","tags":[...],"reporter":"..."}]}`. For each URL entry, stringify the URL record and check with ciLogKeyPattern. Emit Finding with SourceType "recon:urlhaus", Source as the url field.
- Note: URLhaus uses POST with form-encoded body for most endpoints. Set Content-Type to "application/x-www-form-urlencoded".

Tests: httptest mock. Test Name(), Enabled() (always true), Sweep happy path, Sweep empty results.
  </action>
  <verify>
    <automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestURLhaus" -count=1 -v</automated>
  </verify>
  <done>URLhausSource implements ReconSource, tests pass confirming credentialless Sweep emits findings for key-containing URL records</done>
</task>

</tasks>

<verification>
All three threat intel sources compile and pass unit tests:
```bash
cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestVirusTotal|TestIntelligenceX|TestURLhaus" -count=1 -v
go vet ./pkg/recon/sources/
```
</verification>

<success_criteria>
- virustotal.go, intelligencex.go, urlhaus.go each implement recon.ReconSource
- VirusTotal and IntelligenceX are credential-gated (Enabled returns false without API key)
- URLhaus is credentialless (Enabled always true)
- All tests pass with httptest mocks
- ciLogKeyPattern used for content matching (no custom regex)
</success_criteria>

<output>
After completion, create `.planning/phases/16-osint_threat_intel_mobile_dns_api_marketplaces/16-01-SUMMARY.md`
</output>
