--- phase: 10-osint-code-hosting plan: 05 type: execute wave: 2 depends_on: [10-01] files_modified: - pkg/recon/sources/codeberg.go - pkg/recon/sources/codeberg_test.go autonomous: true requirements: [RECON-CODE-05] must_haves: truths: - "CodebergSource queries Gitea REST API /api/v1/repos/search and /api/v1/repos/.../contents for keyword matches" - "No token required for public repos (but optional token honored if provided)" - "Findings tagged SourceType=\"recon:codeberg\"" artifacts: - path: "pkg/recon/sources/codeberg.go" provides: "CodebergSource implementing recon.ReconSource (Gitea-compatible)" key_links: - from: "pkg/recon/sources/codeberg.go" to: "pkg/recon/sources/httpclient.go" via: "Client.Do" pattern: "client\\.Do" --- Implement CodebergSource targeting Gitea's REST API. Codeberg.org runs Gitea, so the same code works for any Gitea instance by configuring BaseURL. Public repos do not require auth, but a token can be passed to raise rate limits. Purpose: RECON-CODE-05. Output: pkg/recon/sources/codeberg.go + tests. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.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 Gitea API (v1, docs: https://docs.gitea.com/api): GET /api/v1/repos/search?q=&limit=50 Response: { "data": [{ "full_name": "...", "html_url": "..." }], "ok": true } Header (optional): Authorization: token For this phase we only use /repos/search — matching on repo metadata (name/description). Full-content code search is not uniformly available across Gitea instances (Codeberg has gitea "code search" enabled via Bleve index; we rely on it when present via GET /api/v1/repos/search?q=... which returns repos only. For content matching we fall back to searching each provider keyword as a query string and emitting Findings keyed to the repo html_url). Rate: public unauth 60 req/hour → rate.Every(60 * time.Second). Burst 1. With token: 1000/hour → rate.Every(3600 * time.Millisecond). Detect via token presence. Task 1: CodebergSource + tests pkg/recon/sources/codeberg.go, pkg/recon/sources/codeberg_test.go - Test A: Enabled always true (public API, token optional) - Test B: Sweep queries /api/v1/repos/search?q=&limit=50 for each BuildQueries entry - Test C: Decodes `{data:[{full_name,html_url}]}` and emits Finding with Source=html_url, SourceType="recon:codeberg", ProviderName from keywordIndex - Test D: With token set, Authorization header is "token "; without token, header absent - Test E: Ctx cancellation - Test F: Unauth rate limit applied when Token empty (verified via RateLimit() return) Create `pkg/recon/sources/codeberg.go`: - Struct `CodebergSource { Token, BaseURL string; Registry *providers.Registry; Limiters *recon.LimiterRegistry; client *Client }` - Default BaseURL: `https://codeberg.org` - Name "codeberg", RespectsRobots false - RateLimit(): if Token == "" return rate.Every(60*time.Second), else rate.Every(3600*time.Millisecond) - Burst 1 - Enabled always returns true - Sweep: for each query, build `base + /api/v1/repos/search?q=&limit=50`, set Authorization only when Token set, client.Do, decode, emit Findings - Compile-time assert Create `pkg/recon/sources/codeberg_test.go` with httptest server returning a `{data:[...],ok:true}` body. Two test cases: with token (header present) and without (header absent — use a flag inside the handler to capture). cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run TestCodeberg -v -timeout 30s CodebergSource implements ReconSource, tests green for both auth modes. - `go test ./pkg/recon/sources/ -run TestCodeberg -v` RECON-CODE-05 satisfied. After completion, create `.planning/phases/10-osint-code-hosting/10-05-SUMMARY.md`.