8.6 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 12-osint_iot_cloud_storage | 01 | execute | 1 |
|
true |
|
|
Purpose: Enable discovery of exposed LLM endpoints (vLLM, Ollama, LiteLLM proxies) via internet-wide device scanners. Output: Three source files + tests following the established Phase 10 pattern.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @pkg/recon/source.go @pkg/recon/sources/httpclient.go @pkg/recon/sources/github.go @pkg/recon/sources/bing.go @pkg/recon/sources/queries.go @pkg/recon/sources/register.go 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:
type Client struct { HTTP *http.Client; MaxRetries int; UserAgent string }
func NewClient() *Client
func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error)
var ErrUnauthorized = errors.New("sources: unauthorized (check credentials)")
From pkg/recon/sources/queries.go:
func BuildQueries(reg *providers.Registry, source string) []string
ShodanSource (shodan.go):
- Struct:
ShodanSourcewith fieldsAPIKey string,BaseURL string,Registry *providers.Registry,Limiters *recon.LimiterRegistry,client *Client - Compile-time assertion:
var _ recon.ReconSource = (*ShodanSource)(nil) - Name(): "shodan"
- RateLimit(): rate.Every(1 * time.Second) — Shodan allows ~1 req/s on most plans
- Burst(): 1
- RespectsRobots(): false (authenticated REST API)
- Enabled(): returns
s.APIKey != "" - BaseURL default: "https://api.shodan.io"
- Sweep(): For each query from BuildQueries(s.Registry, "shodan"), call GET
{base}/shodan/host/search?key={apikey}&query={url.QueryEscape(q)}. Parse JSON response{"matches":[{"ip_str":"...","port":N,"data":"..."},...]}. Emit a Finding per match with Source=fmt.Sprintf("shodan://%s:%d", match.IPStr, match.Port), SourceType="recon:shodan", Confidence="low", ProviderName from keyword index. - Add
shodanKeywordIndexhelper (same pattern as bingKeywordIndex). - Error handling: ErrUnauthorized aborts, context cancellation aborts, transient errors continue.
CensysSource (censys.go):
- Struct:
CensysSourcewith fieldsAPIId string,APISecret string,BaseURL string,Registry *providers.Registry,Limiters *recon.LimiterRegistry,client *Client - Name(): "censys"
- RateLimit(): rate.Every(2500 * time.Millisecond) — Censys free tier is 0.4 req/s
- Burst(): 1
- RespectsRobots(): false
- Enabled(): returns
s.APIId != "" && s.APISecret != "" - BaseURL default: "https://search.censys.io/api"
- Sweep(): For each query, POST
{base}/v2/hosts/searchwith JSON body{"q":q,"per_page":25}. Set Basic Auth header using APIId:APISecret. Parse JSON response{"result":{"hits":[{"ip":"...","services":[{"port":N,"service_name":"..."}]}]}}. Emit Finding per hit with Source=fmt.Sprintf("censys://%s", hit.IP). - Add
censysKeywordIndexhelper.
ZoomEyeSource (zoomeye.go):
- Struct:
ZoomEyeSourcewith fieldsAPIKey string,BaseURL string,Registry *providers.Registry,Limiters *recon.LimiterRegistry,client *Client - Name(): "zoomeye"
- RateLimit(): rate.Every(2 * time.Second)
- Burst(): 1
- RespectsRobots(): false
- Enabled(): returns
s.APIKey != "" - BaseURL default: "https://api.zoomeye.org" (ZoomEye uses v1-style API key in header)
- Sweep(): For each query, GET
{base}/host/search?query={url.QueryEscape(q)}&page=1. Set headerAPI-KEY: {apikey}. Parse JSON response{"matches":[{"ip":"...","portinfo":{"port":N},"banner":"..."}]}. Emit Finding per match with Source=fmt.Sprintf("zoomeye://%s:%d", match.IP, match.PortInfo.Port). - Add
zoomeyeKeywordIndexhelper.
Update formatQuery in queries.go to add cases for "shodan", "censys", "zoomeye" — all use bare keyword (same as default).
All sources must use sources.NewClient() for HTTP, s.Limiters.Wait(ctx, s.Name(), ...) before each request, and follow the same error handling pattern as BingDorkSource.Sweep.
cd /home/salva/Documents/apikey/.claude/worktrees/agent-a6700ee2 && go build ./pkg/recon/sources/
Three source files compile, each implements recon.ReconSource interface
<success_criteria> Three IoT scanner sources (Shodan, Censys, ZoomEye) implement recon.ReconSource, use shared Client for HTTP, respect rate limiting via LimiterRegistry, and pass unit tests with mock API responses. </success_criteria>
After completion, create `.planning/phases/12-osint_iot_cloud_storage/12-01-SUMMARY.md`