---
phase: 16-osint-threat-intel-mobile-dns-api-marketplaces
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - pkg/recon/sources/apkmirror.go
  - pkg/recon/sources/apkmirror_test.go
  - pkg/recon/sources/crtsh.go
  - pkg/recon/sources/crtsh_test.go
  - pkg/recon/sources/securitytrails.go
  - pkg/recon/sources/securitytrails_test.go
autonomous: true
requirements: [RECON-MOBILE-01, RECON-DNS-01, RECON-DNS-02]

must_haves:
  truths:
    - "APKMirror source searches for APK metadata containing provider keywords"
    - "crt.sh source discovers subdomains via CT logs and probes config endpoints for keys"
    - "SecurityTrails source searches DNS/subdomain data for key exposure indicators"
  artifacts:
    - path: "pkg/recon/sources/apkmirror.go"
      provides: "APKMirrorSource implementing recon.ReconSource"
      contains: "func (s *APKMirrorSource) Sweep"
    - path: "pkg/recon/sources/crtsh.go"
      provides: "CrtShSource implementing recon.ReconSource"
      contains: "func (s *CrtShSource) Sweep"
    - path: "pkg/recon/sources/securitytrails.go"
      provides: "SecurityTrailsSource implementing recon.ReconSource"
      contains: "func (s *SecurityTrailsSource) Sweep"
  key_links:
    - from: "pkg/recon/sources/crtsh.go"
      to: "pkg/recon/sources/httpclient.go"
      via: "Client.Do for endpoint probing"
      pattern: "client\\.Do\\(ctx"
    - from: "pkg/recon/sources/securitytrails.go"
      to: "pkg/recon/sources/queries.go"
      via: "BuildQueries call"
      pattern: "BuildQueries\\(s\\.Registry"
---

<objective>
Implement APKMirror (mobile), crt.sh (CT log DNS), and SecurityTrails (DNS intel) ReconSource modules.

Purpose: Detect API keys in mobile app metadata, discover subdomains via certificate transparency and probe their config endpoints, and search DNS intelligence for key exposure.
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>
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
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
```
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: APKMirror and crt.sh sources</name>
  <files>pkg/recon/sources/apkmirror.go, pkg/recon/sources/apkmirror_test.go, pkg/recon/sources/crtsh.go, pkg/recon/sources/crtsh_test.go</files>
  <action>
Create APKMirrorSource in apkmirror.go:

- Struct: APKMirrorSource with BaseURL, Registry (*providers.Registry), Limiters (*recon.LimiterRegistry), Client (*Client) fields. Credentialless.
- Name() returns "apkmirror". RateLimit() returns rate.Every(5*time.Second). Burst() returns 2. RespectsRobots() returns true (scraping). Enabled() always true.
- Sweep(): Default BaseURL to "https://www.apkmirror.com". Use BuildQueries(s.Registry, "apkmirror"). For each query: GET `{base}/?post_type=app_release&searchtype=apk&s={url-encoded query}`. Parse HTML response — search for APK listing entries. Since we cannot decompile APKs in a network sweep, focus on metadata: search page HTML content for ciLogKeyPattern matches in APK descriptions, changelogs, and file listings. Emit Finding with SourceType "recon:apkmirror", Source as the page URL.
- Note: This is a metadata/description scanner, not a full APK decompiler. The decompile capability (apktool/jadx) is noted in RECON-MOBILE-01 but that requires local binary dependencies — the ReconSource focuses on web-searchable APK metadata for keys in descriptions and changelogs.

Create CrtShSource in crtsh.go:

- Struct: CrtShSource with BaseURL, Registry (*providers.Registry), Limiters (*recon.LimiterRegistry), Client (*Client) fields. Credentialless.
- Name() returns "crtsh". RateLimit() returns rate.Every(3*time.Second). Burst() returns 3. RespectsRobots() false (API). Enabled() always true.
- Sweep(): Default BaseURL to "https://crt.sh". The query parameter is used as the target domain. If query is empty, use BuildQueries but for crt.sh the query should be a domain — if query looks like a keyword rather than a domain, skip (return nil). GET `{base}/?q=%25.{domain}&output=json` to find subdomains. Parse JSON array `[{"name_value":"sub.example.com","common_name":"..."}]`. Deduplicate name_value entries. For each unique subdomain (limit 20), probe three config endpoints: `https://{subdomain}/.env`, `https://{subdomain}/api/config`, `https://{subdomain}/actuator/env`. Use a short 5s timeout per probe. For each successful response (200 OK), check body with ciLogKeyPattern. Emit Finding with SourceType "recon:crtsh", Source as the probed URL.
- Important: The probe HTTP client should be separate from the crt.sh API client — create a short-timeout *http.Client{Timeout: 5*time.Second} for probing. Do NOT use the retry Client for probes (probes should fail fast, not retry).

Tests: httptest for both. APKMirror: mock returns HTML with key-like content in description. CrtSh: mock returns JSON subdomain list, mock probe endpoints return .env-like content with key patterns.
  </action>
  <verify>
    <automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestAPKMirror|TestCrtSh" -count=1 -v</automated>
  </verify>
  <done>APKMirrorSource scans APK metadata pages, CrtShSource discovers subdomains and probes config endpoints, both emit findings on ciLogKeyPattern match</done>
</task>

<task type="auto">
  <name>Task 2: SecurityTrails source</name>
  <files>pkg/recon/sources/securitytrails.go, pkg/recon/sources/securitytrails_test.go</files>
  <action>
Create SecurityTrailsSource in securitytrails.go:

- Struct: SecurityTrailsSource with APIKey, BaseURL, Registry (*providers.Registry), Limiters (*recon.LimiterRegistry), Client (*Client) fields.
- Name() returns "securitytrails". RateLimit() returns rate.Every(2*time.Second). Burst() returns 5. RespectsRobots() false. Enabled() returns s.APIKey != "".
- Sweep(): Default BaseURL to "https://api.securitytrails.com/v1". The query parameter is used as the target domain. If empty, return nil. Two-phase approach:
  1. Subdomain enumeration: GET `{base}/domain/{domain}/subdomains?children_only=false` with header `APIKEY: {APIKey}`. Parse `{"subdomains":["www","api","staging",...]}`. Build full FQDNs by appending `.{domain}`.
  2. For each subdomain (limit 20), probe same three config endpoints as CrtShSource: `/.env`, `/api/config`, `/actuator/env`. Use short-timeout probe client (5s, no retries). Check responses with ciLogKeyPattern. Emit Finding with SourceType "recon:securitytrails".
- Also: GET `{base}/domain/{domain}` for the domain's DNS history. Parse response and check the full JSON body with ciLogKeyPattern (DNS TXT records sometimes contain API keys).

Tests: httptest mock. Test Enabled() with/without API key. Sweep with mock subdomain list and probe endpoints.
  </action>
  <verify>
    <automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestSecurityTrails" -count=1 -v</automated>
  </verify>
  <done>SecurityTrailsSource discovers subdomains via API, probes config endpoints, and scans DNS records for key patterns; credential-gated via API key</done>
</task>

</tasks>

<verification>
All three sources compile and pass tests:
```bash
cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestAPKMirror|TestCrtSh|TestSecurityTrails" -count=1 -v
go vet ./pkg/recon/sources/
```
</verification>

<success_criteria>
- apkmirror.go, crtsh.go, securitytrails.go each implement recon.ReconSource
- APKMirror and crt.sh are credentialless (Enabled always true)
- SecurityTrails is credential-gated
- crt.sh and SecurityTrails both probe /.env, /api/config, /actuator/env on discovered subdomains
- All tests pass with httptest mocks
</success_criteria>

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