Security
forkctl holds a GitHub token and operates on repos on your behalf. We take that seriously.
Threat model
Section titled “Threat model”| Asset | Risk | Mitigation |
|---|---|---|
GITHUB_TOKEN | Leaked via logs, error messages, telemetry | Never logged or echoed. Scrubbed from error output. No telemetry. |
| Tool inputs (from LLM) | Prompt-injected paths, owner/repo strings, branch names | All inputs validated through Zod schemas. No shell interpolation of inputs. |
| Destination repo | Wrong owner / wrong visibility on create | Operations require explicit destination owner; visibility is opt-in. |
| Source repo | Accidental writes during make_forkable | Default mode is plan. pr mode requires explicit flag. |
| Sync conflicts | Force-overwrite of fork branches | sync never force-pushes. Conflicts surface as propose_sync_pr. |
| Local state DB | Operation history with org/repo metadata | Stored in OS user-state dir, not in repo. Never transmitted. |
Hard guarantees (enforced by code)
Section titled “Hard guarantees (enforced by code)”forkctl will never:
- Force-push to any branch
- Delete a repository
- Delete a branch
- Skip git hooks
- Send telemetry, analytics, or any outbound network call other than to the configured GitHub API
- Print or persist the
GITHUB_TOKENvalue
These aren’t honor-system promises — they’re absent code paths. There is no force: true flag anywhere in the source. There is no telemetry library imported.
Token handling
Section titled “Token handling”Your GITHUB_TOKEN is read once when an Octokit client is built (src/lib/github.ts). From there:
- It’s only sent to the configured
GITHUB_API_URL(defaults tohttps://api.github.com). - Error messages from the GitHub API are run through
scrubToken()before being shown to the user — anyghp_…,gho_…,ghu_…,ghs_…,ghr_…, orgithub_pat_…pattern in error text is replaced with[redacted-token]. - The token never appears in the audit log. The
redactInput()function insrc/lib/audit.tsstrips known sensitive keys and inline PAT patterns before persisting.
Audit redaction details
Section titled “Audit redaction details”Sensitive keys redacted at write time:
token, GITHUB_TOKEN, password, secret, apiKey, api_keyInline patterns redacted in any string value:
| Pattern | Detects |
|---|---|
(ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{16,} | GitHub PATs |
The drift scanner (forkctl_scan_drift) extends this list to AWS access keys (AKIA…), OpenAI keys (sk-…), and Google API keys (AIza…). When secrets are detected in scanned files, the evidence field is set to literal "<redacted>" — the secret value itself is never returned.
Input validation
Section titled “Input validation”Every tool input passes through a Zod schema before reaching its handler. Bad input never reaches GitHub — it’s rejected with INVALID_INPUT and a hint listing the offending paths.
Repo references are pinned to ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ — no schemes, no whitespace, no traversal characters.
make_forkable is conservative by default
Section titled “make_forkable is conservative by default”make_forkable writes to the source repo (which you may not own). To prevent accidents:
- Default
modeisplan— produces a patch plan and writes nothing. mode=pris explicit opt-in. Even then, forkctl opens a PR — it never commits to the default branch.- The PR description always discloses that the change was generated by forkctl and lists the blockers being fixed.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Open a private security advisory. Do not file public issues for security problems.
We aim to acknowledge within 72 hours and ship a fix or mitigation within 14 days for high-severity reports.
Dependency surface
Section titled “Dependency surface”forkctl depends on a small, intentional set of packages:
@modelcontextprotocol/sdk— MCP server transport@octokit/rest+@octokit/request-error— GitHub API clientbetter-sqlite3— local state DBcommander— CLI parserenv-paths— OS user-state directory resolutionzod+zod-to-json-schema— input validation + MCP tool descriptors
npm audit is part of npm ci in CI. The current snapshot reports zero vulnerabilities.