Documentation
¶
Index ¶
- Variables
- type ApplyOutcome
- type Detected
- type EngineOptions
- type EngineResult
- type MatchSite
- type Patcher
- type PatcherInput
- type PatcherResult
- func ClientSideLlmKeyPatcher(in PatcherInput) PatcherResult
- func DependencyCvePatcher(in PatcherInput) PatcherResult
- func InsecureCorsPatcher(in PatcherInput) PatcherResult
- func InsecureRandomPatcher(in PatcherInput) PatcherResult
- func OpenRedirectPatcher(in PatcherInput) PatcherResult
- func UnboundedStreamPatcher(in PatcherInput) PatcherResult
- func WeakCryptoPatcher(in PatcherInput) PatcherResult
- func XssPatcher(in PatcherInput) PatcherResult
Constants ¶
This section is empty.
Variables ¶
var Registry = map[string]Patcher{ "client-side-llm-key": ClientSideLlmKeyPatcher, "dependency-cve": DependencyCvePatcher, "insecure-cors": InsecureCorsPatcher, "insecure-random": InsecureRandomPatcher, "open-redirect": OpenRedirectPatcher, "unbounded-stream": UnboundedStreamPatcher, "weak-crypto": WeakCryptoPatcher, "xss": XssPatcher, }
Registry maps a security category to its deterministic patcher. A category absent from this map has no auto-fix locally — surface as explanation-only, never invent a patch. New patchers register here.
Mirrors workers/src/fix/patchers/index.ts (`patchers`).
Functions ¶
This section is empty.
Types ¶
type ApplyOutcome ¶
type ApplyOutcome struct {
Detected Detected
OK bool
Description string
Reason string
ManualCommand string
}
ApplyOutcome is the per-Detected result of an engine run. One entry per detected pattern, whether we ended up rewriting the file or not.
type Detected ¶
type Detected struct {
Category string
FilePath string
LineStart int
LineEnd int
MatchedSpan string
}
Detected is one fixable-pattern hit. Mirrors the relevant subset of scan.Finding so the engine can drive patchers without depending on the SAST package.
type EngineOptions ¶
type EngineOptions struct {
// Workdir is the directory the detector walks and the apply path
// writes into. Must already be a directory; the engine doesn't
// create it.
Workdir string
// DryRun = true: detect + run patchers but do NOT write to disk and
// do NOT create a backup directory. Used for `--apply` previewing.
DryRun bool
// Logf is the optional progress logger (printed to stderr by the CLI).
Logf func(format string, args ...any)
// Now is injected for testability; nil = time.Now().
Now func() time.Time
}
EngineOptions configures one engine run.
type EngineResult ¶
type EngineResult struct {
BackupDir string
Outcomes []ApplyOutcome
}
EngineResult is what `fix --local-only` reports back to the CLI for printing. Holds the backup path + the per-pattern outcomes.
func Run ¶
func Run(opts EngineOptions) (EngineResult, error)
Run drives the full pipeline: detect → group by file → for each file, in stable order, run every patcher whose detection landed there, accumulating the new source — then write once at the end.
Backup writes ALWAYS precede source writes, so an abort partway through still leaves a usable rollback artifact.
func (EngineResult) Stats ¶
func (r EngineResult) Stats() (applied, declined, manual int, filesTouched int)
Stats summarises the result for the CLI's footer line.
type MatchSite ¶
MatchSite is the location a detector emitted — same 1-based line numbers and matched-text shape the regex passes already use.
type Patcher ¶
type Patcher func(PatcherInput) PatcherResult
Patcher is the contract the registry maps category → impl against.
type PatcherInput ¶
type PatcherInput struct {
// Source is the full content of the file under patch.
Source string
// FilePath is the workdir-relative path. Used only for messages —
// the patcher does not perform any filesystem operation with it.
FilePath string
// Match is the detector-emitted location to operate on.
Match MatchSite
// Context carries detector-specific fields a small subset of
// patchers need (e.g. dependency-cve reads packageName +
// fixedVersion). Most patchers ignore it. Mirrors the hosted
// PatcherInput.context contract.
Context map[string]string
}
PatcherInput is everything a patcher needs to do its work. No embedded *os.File, no path resolution — the engine reads the file once and hands it in.
type PatcherResult ¶
type PatcherResult struct {
OK bool
// Patched is the full file content after the patch — only set when OK.
// The engine computes the diff for display; patchers don't emit unified
// diffs themselves.
Patched string
// Description is the dev-readable summary: what was changed, what
// follow-up the dev needs to do. Surfaced in the CLI summary after
// `getdebug fix --local-only --apply`.
Description string
// Reason is the explain-why-not when OK is false. Always present in
// the decline case so the CLI can render an honest skip note.
Reason string
// ManualCommand is an optional copy-pastable shell command for the
// "structurally unfixable but the dev can fix it" case (e.g.
// dependency-cve hitting a JS lockfile whose integrity hash the
// patcher can't recompute). When set on a decline (OK=false), the
// CLI prints it as a suggested follow-up instead of just dropping
// the finding silently. Mirrors workers PatcherResult.manualCommand.
ManualCommand string
}
PatcherResult is the sum type a patcher returns. Either it produced a new file content + a dev-readable explanation (OK = true), or it declined to patch with a reason the engine surfaces honestly (OK = false). A patcher never panics on bad input — drift, oversized files, and structurally unfixable shapes are all decline-with-reason cases.
func ClientSideLlmKeyPatcher ¶
func ClientSideLlmKeyPatcher(in PatcherInput) PatcherResult
ClientSideLlmKeyPatcher rewrites a single public-prefixed env var reference and prepends a three-line explainer comment. The matched span must be present at the matched line — drift declines cleanly.
func DependencyCvePatcher ¶
func DependencyCvePatcher(in PatcherInput) PatcherResult
DependencyCvePatcher rewrites a single `==`-pin in requirements.txt to the advisory's fixed version. Lockfile and go.mod hits return a manual-command result for the CLI to surface.
func InsecureCorsPatcher ¶
func InsecureCorsPatcher(in PatcherInput) PatcherResult
InsecureCorsPatcher rewrites a single wildcard Access-Control-Allow- Origin pair on the matched line to an env lookup that fails closed.
func InsecureRandomPatcher ¶
func InsecureRandomPatcher(in PatcherInput) PatcherResult
InsecureRandomPatcher rewrites a single Math.random() on the matched line. Returns ok=true when exactly one rewrite was made; declines on zero (drift / alias / non-literal) or multiple (ambiguous).
func OpenRedirectPatcher ¶
func OpenRedirectPatcher(in PatcherInput) PatcherResult
OpenRedirectPatcher wraps the redirect target in a same-origin path guard. Reads `arg` four times — safe because the regex rules out function calls (no side effects in identifier-chain expressions).
func UnboundedStreamPatcher ¶
func UnboundedStreamPatcher(in PatcherInput) PatcherResult
UnboundedStreamPatcher adds an AbortController to a streaming LLM call. Refuses to patch a `decoder.decode({ stream: true })` line — that's the Web Streams API and a known false-positive shape on the detector side.
func WeakCryptoPatcher ¶
func WeakCryptoPatcher(in PatcherInput) PatcherResult
WeakCryptoPatcher rewrites a single MD5/SHA-1 hash call on the matched line. Returns ok=true when exactly one rewrite was made; declines on zero (file drift) or multiple (ambiguous).
func XssPatcher ¶
func XssPatcher(in PatcherInput) PatcherResult
XssPatcher rewrites a single `.innerHTML =` assignment on the matched line to `.textContent =`. Declines on zero matches (drift / wrong shape) or multiple matches (ambiguous).