---
phase: 14-osint_ci_cd_logs_web_archives_frontend_leaks
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - pkg/recon/sources/ghactions.go
  - pkg/recon/sources/ghactions_test.go
  - pkg/recon/sources/travisci.go
  - pkg/recon/sources/travisci_test.go
  - pkg/recon/sources/circleci.go
  - pkg/recon/sources/circleci_test.go
  - pkg/recon/sources/jenkins.go
  - pkg/recon/sources/jenkins_test.go
  - pkg/recon/sources/gitlabci.go
  - pkg/recon/sources/gitlabci_test.go
autonomous: true
requirements:
  - RECON-CI-01
  - RECON-CI-02
  - RECON-CI-03
  - RECON-CI-04

must_haves:
  truths:
    - "GitHub Actions workflow log scanning finds keys in public run logs"
    - "Travis CI and CircleCI build log scanning finds keys in public logs"
    - "Jenkins exposed instance scanning finds keys in console output"
    - "GitLab CI pipeline trace scanning finds keys in job traces"
  artifacts:
    - path: "pkg/recon/sources/ghactions.go"
      provides: "GitHubActionsSource implementing ReconSource"
      contains: "func (s *GitHubActionsSource) Sweep"
    - path: "pkg/recon/sources/travisci.go"
      provides: "TravisCISource implementing ReconSource"
      contains: "func (s *TravisCISource) Sweep"
    - path: "pkg/recon/sources/circleci.go"
      provides: "CircleCISource implementing ReconSource"
      contains: "func (s *CircleCISource) Sweep"
    - path: "pkg/recon/sources/jenkins.go"
      provides: "JenkinsSource implementing ReconSource"
      contains: "func (s *JenkinsSource) Sweep"
    - path: "pkg/recon/sources/gitlabci.go"
      provides: "GitLabCISource implementing ReconSource"
      contains: "func (s *GitLabCISource) Sweep"
  key_links:
    - from: "pkg/recon/sources/ghactions.go"
      to: "pkg/recon/source.go"
      via: "implements ReconSource interface"
      pattern: "var _ recon\\.ReconSource"
    - from: "pkg/recon/sources/travisci.go"
      to: "pkg/recon/source.go"
      via: "implements ReconSource interface"
      pattern: "var _ recon\\.ReconSource"
---

<objective>
Implement five CI/CD build log scanning sources: GitHubActionsSource, TravisCISource, CircleCISource, JenkinsSource, and GitLabCISource. Each searches public build logs/pipeline traces for leaked API keys.

Purpose: CI/CD logs are a top vector for key leaks -- build systems often print environment variables, secret injection failures, or debug output containing API keys. Covering the five major CI platforms gives broad detection coverage.

Output: 5 source files + 5 test files 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
@.planning/STATE.md
@pkg/recon/source.go
@pkg/recon/sources/register.go
@pkg/recon/sources/httpclient.go
@pkg/recon/sources/queries.go
@pkg/recon/sources/npm.go
@pkg/recon/sources/npm_test.go

<interfaces>
From pkg/recon/source.go:
```go
type Finding = engine.Finding
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
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)
```

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

From pkg/recon/sources/register.go:
```go
type SourcesConfig struct {
    GitHubToken string
    GitLabToken string
    // ... other fields
    Registry *providers.Registry
    Limiters *recon.LimiterRegistry
}
```
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Implement GitHubActionsSource and TravisCISource with tests</name>
  <files>pkg/recon/sources/ghactions.go, pkg/recon/sources/ghactions_test.go, pkg/recon/sources/travisci.go, pkg/recon/sources/travisci_test.go</files>
  <action>
Create GitHubActionsSource (RECON-CI-01):
- Struct fields: Token string, BaseURL string, Registry *providers.Registry, Limiters *recon.LimiterRegistry, Client *Client
- Name() returns "github-actions"
- RateLimit: rate.Every(2*time.Second), Burst: 3
- RespectsRobots: false (API-based)
- Enabled: returns true only when Token is non-empty
- Sweep: For each query from BuildQueries(registry, "github-actions"), search GitHub API for workflow runs via GET /search/code?q={query}+path:.github/workflows, then for each result fetch the run logs. Use the GitHub Actions API: GET /repos/{owner}/{repo}/actions/runs?per_page=5, then GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs (returns zip). For simplicity, use the search code endpoint to find repos with workflows referencing provider keywords, then emit findings with SourceType "recon:github-actions". Auth via "Authorization: Bearer {token}" header.
- Compile-time interface check: var _ recon.ReconSource = (*GitHubActionsSource)(nil)

Create TravisCISource (RECON-CI-02):
- Struct fields: BaseURL string, Registry *providers.Registry, Limiters *recon.LimiterRegistry, Client *Client
- Name() returns "travis"
- RateLimit: rate.Every(3*time.Second), Burst: 2
- RespectsRobots: true (web scraping)
- Enabled: always true (credentialless, public logs)
- Sweep: For each query from BuildQueries, use Travis CI API v3: GET https://api.travis-ci.com/repos?search={query}&sort_by=recent_activity&limit=5, then for each repo fetch recent builds GET /repo/{slug}/builds?limit=3, then fetch job logs GET /job/{id}/log.txt. Parse log text for provider keywords. Emit findings with SourceType "recon:travis". Use "Travis-API-Version: 3" header.

Tests: Use httptest.NewServer with fixture JSON responses. Test Sweep extracts findings from mock API responses. Test Enabled returns correct boolean based on token presence (for GHActions). Test context cancellation stops early.
  </action>
  <verify>
    <automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestGitHubActions|TestTravis" -count=1 -v</automated>
  </verify>
  <done>GitHubActionsSource and TravisCISource implement ReconSource, emit findings from mock CI logs, all tests pass</done>
</task>

<task type="auto">
  <name>Task 2: Implement CircleCISource, JenkinsSource, and GitLabCISource with tests</name>
  <files>pkg/recon/sources/circleci.go, pkg/recon/sources/circleci_test.go, pkg/recon/sources/jenkins.go, pkg/recon/sources/jenkins_test.go, pkg/recon/sources/gitlabci.go, pkg/recon/sources/gitlabci_test.go</files>
  <action>
Create CircleCISource (RECON-CI-02):
- Struct fields: BaseURL string, Registry *providers.Registry, Limiters *recon.LimiterRegistry, Client *Client
- Name() returns "circleci"
- RateLimit: rate.Every(3*time.Second), Burst: 2
- RespectsRobots: false (API-based)
- Enabled: always true (public project builds are accessible without auth)
- Sweep: Use CircleCI API v2: GET https://circleci.com/api/v2/insights/{project-slug}/workflows?branch=main for public projects. For each query, search via GET /api/v1.1/project/{vcs}/{org}/{repo}?limit=5&filter=completed, then fetch build output. Emit findings with SourceType "recon:circleci". Since CircleCI v2 API requires auth for most endpoints, use the v1.1 public endpoint pattern: GET https://circleci.com/api/v1.1/project/github/{org}/{repo}?limit=5 for public repos discovered via keyword search.

Create JenkinsSource (RECON-CI-03):
- Struct fields: BaseURL string, Registry *providers.Registry, Limiters *recon.LimiterRegistry, Client *Client
- Name() returns "jenkins"
- RateLimit: rate.Every(5*time.Second), Burst: 1
- RespectsRobots: true (web scraping exposed instances)
- Enabled: always true (credentialless, scans exposed instances)
- Sweep: For each query, construct URLs for common exposed Jenkins patterns: {domain}/job/{query}/lastBuild/consoleText. Use provider keywords to search for known Jenkins instances via the query parameter. Emit findings with SourceType "recon:jenkins". Slower rate limit (5s) because scanning exposed instances should be cautious.

Create GitLabCISource (RECON-CI-04):
- Struct fields: Token string, BaseURL string, Registry *providers.Registry, Limiters *recon.LimiterRegistry, Client *Client
- Name() returns "gitlab-ci"
- RateLimit: rate.Every(2*time.Second), Burst: 3
- RespectsRobots: false (API-based)
- Enabled: returns true only when Token is non-empty
- Sweep: Use GitLab API: GET https://gitlab.com/api/v4/projects?search={query}&visibility=public&per_page=5, then for each project GET /api/v4/projects/{id}/pipelines?per_page=3, then GET /api/v4/projects/{id}/jobs/{job_id}/trace. Auth via "PRIVATE-TOKEN: {token}" header. Emit findings with SourceType "recon:gitlab-ci".

Tests for all three: httptest.NewServer with fixture responses. Test Sweep emits findings. Test Enabled logic. Test context cancellation.
  </action>
  <verify>
    <automated>cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestCircleCI|TestJenkins|TestGitLabCI" -count=1 -v</automated>
  </verify>
  <done>CircleCISource, JenkinsSource, and GitLabCISource implement ReconSource, emit findings from mock responses, all tests pass</done>
</task>

</tasks>

<verification>
cd /home/salva/Documents/apikey && go test ./pkg/recon/sources/ -run "TestGitHubActions|TestTravis|TestCircleCI|TestJenkins|TestGitLabCI" -count=1 -v
cd /home/salva/Documents/apikey && go vet ./pkg/recon/sources/
</verification>

<success_criteria>
- 5 new source files compile and implement ReconSource (var _ check)
- 5 test files pass with httptest mocks
- All 5 sources use BuildQueries + Client + LimiterRegistry pattern
- GitHubActionsSource and GitLabCISource gate on Token; others always enabled
</success_criteria>

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