docs(10-osint-code-hosting): create phase 10 plans (9 plans across 3 waves)
This commit is contained in:
163
.planning/phases/10-osint-code-hosting/10-04-PLAN.md
Normal file
163
.planning/phases/10-osint-code-hosting/10-04-PLAN.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
phase: 10-osint-code-hosting
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: [10-01]
|
||||
files_modified:
|
||||
- pkg/recon/sources/bitbucket.go
|
||||
- pkg/recon/sources/bitbucket_test.go
|
||||
- pkg/recon/sources/gist.go
|
||||
- pkg/recon/sources/gist_test.go
|
||||
autonomous: true
|
||||
requirements: [RECON-CODE-03, RECON-CODE-04]
|
||||
must_haves:
|
||||
truths:
|
||||
- "BitbucketSource queries Bitbucket 2.0 code search API and emits Findings"
|
||||
- "GistSource queries GitHub Gist search (re-uses GitHub token) and emits Findings"
|
||||
- "Both disabled when respective credentials are empty"
|
||||
artifacts:
|
||||
- path: "pkg/recon/sources/bitbucket.go"
|
||||
provides: "BitbucketSource implementing recon.ReconSource"
|
||||
- path: "pkg/recon/sources/gist.go"
|
||||
provides: "GistSource implementing recon.ReconSource"
|
||||
key_links:
|
||||
- from: "pkg/recon/sources/gist.go"
|
||||
to: "pkg/recon/sources/httpclient.go"
|
||||
via: "Client.Do with Bearer <github-token>"
|
||||
pattern: "client\\.Do"
|
||||
- from: "pkg/recon/sources/bitbucket.go"
|
||||
to: "pkg/recon/sources/httpclient.go"
|
||||
via: "Client.Do"
|
||||
pattern: "client\\.Do"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement BitbucketSource (RECON-CODE-03) and GistSource (RECON-CODE-04). Grouped
|
||||
because both are small API integrations with similar shapes (JSON array/values,
|
||||
per-item URL, token gating).
|
||||
|
||||
Purpose: RECON-CODE-03, RECON-CODE-04.
|
||||
Output: Two new ReconSource implementations + 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/10-osint-code-hosting/10-CONTEXT.md
|
||||
@.planning/phases/10-osint-code-hosting/10-01-SUMMARY.md
|
||||
@pkg/recon/source.go
|
||||
@pkg/recon/sources/httpclient.go
|
||||
@pkg/recon/sources/queries.go
|
||||
|
||||
<interfaces>
|
||||
Bitbucket 2.0 search (docs: https://developer.atlassian.com/cloud/bitbucket/rest/api-group-search/):
|
||||
GET /2.0/workspaces/{workspace}/search/code?search_query=<query>
|
||||
Auth: Bearer <token> (app password or OAuth)
|
||||
Response: { "values": [{ "content_match_count": N, "file": {"path":"","commit":{...}}, "page_url": "..." }] }
|
||||
Note: Requires a workspace param — make it configurable via SourcesConfig.BitbucketWorkspace;
|
||||
if unset, source is disabled. Rate: 1000/hour → rate.Every(3.6 * time.Second), burst 1.
|
||||
|
||||
GitHub Gist search: GitHub does not expose a dedicated /search/gists endpoint that
|
||||
searches gist contents. Use the /gists/public endpoint + client-side filtering as
|
||||
fallback: GET /gists/public?per_page=100 returns public gists; for each gist, fetch
|
||||
/gists/{id} and scan file contents for keyword matches. Keep implementation minimal:
|
||||
just enumerate the first page, match against keyword list, emit Findings with
|
||||
Source = gist.html_url. Auth: Bearer <github-token>. Rate: 30/min → rate.Every(2s).
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: BitbucketSource + tests</name>
|
||||
<files>pkg/recon/sources/bitbucket.go, pkg/recon/sources/bitbucket_test.go</files>
|
||||
<behavior>
|
||||
- Test A: Enabled false when token OR workspace empty
|
||||
- Test B: Enabled true when both set
|
||||
- Test C: Sweep queries /2.0/workspaces/{ws}/search/code with Bearer header
|
||||
- Test D: Decodes `{values:[{file:{path,commit:{...}},page_url:"..."}]}` and emits Finding with Source=page_url, SourceType="recon:bitbucket"
|
||||
- Test E: 401 → ErrUnauthorized
|
||||
- Test F: Ctx cancellation
|
||||
</behavior>
|
||||
<action>
|
||||
Create `pkg/recon/sources/bitbucket.go`:
|
||||
- Struct `BitbucketSource { Token, Workspace, BaseURL string; Registry *providers.Registry; Limiters *recon.LimiterRegistry; client *Client }`
|
||||
- Default BaseURL: `https://api.bitbucket.org`
|
||||
- Name "bitbucket", RateLimit rate.Every(3600*time.Millisecond), Burst 1, RespectsRobots false
|
||||
- Enabled = s.Token != "" && s.Workspace != ""
|
||||
- Sweep: for each query in BuildQueries(reg, "bitbucket"), limiters.Wait, issue
|
||||
GET request, decode into struct with `Values []struct{ PageURL string "json:page_url"; File struct{ Path string } "json:file" }`, emit Findings
|
||||
- Compile-time assert `var _ recon.ReconSource = (*BitbucketSource)(nil)`
|
||||
|
||||
Create `pkg/recon/sources/bitbucket_test.go` with httptest server, synthetic
|
||||
registry, assertions on URL path `/2.0/workspaces/testws/search/code`, Bearer
|
||||
header, and emitted Findings.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run TestBitbucket -v -timeout 30s</automated>
|
||||
</verify>
|
||||
<done>
|
||||
BitbucketSource passes all tests, implements ReconSource.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: GistSource + tests</name>
|
||||
<files>pkg/recon/sources/gist.go, pkg/recon/sources/gist_test.go</files>
|
||||
<behavior>
|
||||
- Test A: Enabled false when GitHub token empty
|
||||
- Test B: Sweep fetches /gists/public?per_page=100 with Bearer auth
|
||||
- Test C: For each gist, iterates files map; if any file.content contains a provider keyword, emits one Finding with Source=gist.html_url
|
||||
- Test D: Ctx cancellation
|
||||
- Test E: 401 → ErrUnauthorized
|
||||
- Test F: Gist without matching keyword → no Finding emitted
|
||||
</behavior>
|
||||
<action>
|
||||
Create `pkg/recon/sources/gist.go`:
|
||||
- Struct `GistSource { Token, BaseURL string; Registry *providers.Registry; Limiters *recon.LimiterRegistry; client *Client }`
|
||||
- BaseURL default `https://api.github.com`
|
||||
- Name "gist", RateLimit rate.Every(2*time.Second), Burst 1, RespectsRobots false
|
||||
- Enabled = s.Token != ""
|
||||
- Sweep flow:
|
||||
1. Build keyword list from registry (flat set)
|
||||
2. GET /gists/public?per_page=100 with Bearer header
|
||||
3. Decode `[]struct{ HTMLURL string "json:html_url"; Files map[string]struct{ Filename, RawURL string "json:raw_url" } "json:files" }`
|
||||
4. For each gist, for each file, if we can match without fetching raw content,
|
||||
skip raw fetch (keep Phase 10 minimal). Fallback: fetch file.RawURL and
|
||||
scan content for any keyword from the set; on hit, emit one Finding
|
||||
per gist (not per file) with ProviderName from matched keyword.
|
||||
5. Respect limiters.Wait before each outbound request (gist list + each raw fetch)
|
||||
- Compile-time assert `var _ recon.ReconSource = (*GistSource)(nil)`
|
||||
|
||||
Create `pkg/recon/sources/gist_test.go`:
|
||||
- httptest server with two routes: `/gists/public` returns 2 gists each with 1 file, raw_url pointing to same server `/raw/<id>`; `/raw/<id>` returns content containing "sk-proj-" for one and an unrelated string for the other
|
||||
- Assert exactly 1 Finding emitted, Source matches the gist's html_url
|
||||
- 401 test, ctx cancellation test, empty-token test
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run TestGist -v -timeout 30s</automated>
|
||||
</verify>
|
||||
<done>
|
||||
GistSource emits Findings only when a known provider keyword is present in a gist
|
||||
file body; all tests green.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `go build ./...`
|
||||
- `go test ./pkg/recon/sources/ -run "TestBitbucket|TestGist" -v`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
RECON-CODE-03 and RECON-CODE-04 satisfied.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/10-osint-code-hosting/10-04-SUMMARY.md`.
|
||||
</output>
|
||||
Reference in New Issue
Block a user