Skip to main content

AutoFix pull requests

AutoFix turns a dependency finding into a pull request. Click Fix on a finding or on a Recommendations card, or call the API, and hosted Renovate opens one pull request in that repository. It bumps the package to the lowest released version that fixes every open advisory on it.

AutoFix covers npm and Go dependencies. Other ecosystems answer not yet supported. No AI model is involved: the bump is computed from the OSV advisory records.

For a curated set of SAST findings, AutoFix can also propose an AI-generated fix, using only your organisation’s own model provider. That engine is off by default; see AI fixes for SAST findings.

What happens when you click Fix

  1. fremforge collects every open default-branch finding on the same package in the same repository. One pull request closes all of them.
  2. It computes the target version. For each installed version, that is the lowest released version outside every affected range of every open advisory. The pull request takes the highest of those, so it fixes the whole group.
  3. A targeted hosted-Renovate run starts, as your organisation’s Renovate bot. It is limited to that one package and that one version. Every other dependency is left alone.
  4. Renovate opens the pull request on a branch under autofix/. AutoFix runs go ahead of ordinary Renovate runs, so this usually takes under 15 minutes.
  5. fremforge checks the pull request before keeping it open. It may change only the manifest and the lockfile (package.json, package-lock.json, pnpm-lock.yaml, yarn.lock, go.mod, go.sum), and only for that package. A change to any other file, a new direct dependency, or a bump of another package closes the pull request and deletes its branch.

The finding then shows Fix proposed: #N with the state of the pull request: open, merged or closed.

fremforge never merges it

An AutoFix pull request is never armed for auto-merge, whatever your Renovate settings say about security updates. A person reviews it and merges it. Branch protection, required approvals and required checks apply as they do to any pull request. Your repository’s CI and the fremforge security checks run on it like on any other.

The pull request body lists the findings it closes, with severity and summary. It explains why that version was chosen and what to check before merging. If the repository has no CI workflow, the body says so: no tests will run on it.

After the merge

When the pull request is merged and the next default-branch scan no longer reports a finding, the finding closes. Its history in the compliance report records it as fixed by AutoFix (fixed_source: autofix).

When no pull request is opened

What you seeWhy
No released version fixes every open advisoryThere is no fix to propose yet. The finding stays open.
npm and Go onlyThe finding is in another ecosystem.
No PR yet: too newThe fixed version is newer than your minimum release age (3 days). The cooldown is never skipped for AutoFix. Ask again once the version is old enough.
No PR: nothing to changeRenovate found nothing to change. The usual cause is a package that is only a transitive dependency, or a repository Renovate configuration that ignores it.
PR closed: out of scopeRenovate’s change touched more than the manifest and the lockfile, so fremforge closed it.

AutoFix never proposes a fix for malware findings, deny-list findings, the Go standard library, or findings raised on a pull request.

AI fixes for SAST findings

For a small set of SAST rules whose fix is narrow and checkable, AutoFix can ask an AI model for a fix and open a pull request with it. Propose fix sits on each SAST finding of such a rule.

Your own provider only, off by default

  • Bring your own key. The model is your organisation’s own provider under AI integrations, billed to you and under your contract with that vendor, exactly like AI pull request review. fremforge holds no AI key of its own and has no fallback. Without a provider, Propose fix is disabled and says why.
  • Off by default. An owner turns it on under Security → Findings → SAST → AutoFix with AI, or with PUT .../code-security/autofix/settings and "ai_enabled": true. Per-repository overrides and the autofix feature default under AI integrations apply, as do your AI spend caps.
  • A person clicks. Nothing is proposed automatically. Each pull request starts with someone clicking Propose fix on one finding, or calling the API for one finding.

What is sent to the model

  • the rule’s public description and a fix instruction for that rule;
  • at most 60 lines of the affected file, from the commit the finding was raised on: the lines around the finding, plus the file’s import block when it fits in the 60;
  • the file path and the finding’s line number.

Never the rest of the repository, other files, issues, pull request text or commit messages. The code is wrapped as untrusted data, with an instruction that it carries no instructions.

The prompt, the code and the diff are not logged and not stored by fremforge. They exist in memory while the fix is prepared, and afterwards only in the pull request in your repository. The request record keeps ids, counts and the outcome: which rule, which provider and model, how many tokens, how many lines changed.

What is checked before a pull request exists

The model’s answer must be exactly a unified diff and one explanation paragraph; anything else is rejected. The diff must then pass every one of these, checked by fremforge and not by the model:

  • one file: the finding’s own;
  • at most 40 changed lines, and only around the finding or in the file’s imports;
  • no change to CI configuration (.forgejo/, .github/, …), CODEOWNERS, suppression files (.trivyignore*, osv-scanner.toml, .gitleaks.toml, .semgrepignore), tests or dependency manifests;
  • no added suppression comment (nosemgrep, nolint, nosec, noqa, eslint-disable, type: ignore, …);
  • no new dependency: an added import must be the standard library or a module the file already imports.

Then the patched file is re-scanned with the same OpenGrep binary and rule set as the pull request scan. The finding must show up on the original file (so a clean result means something), be gone from the patched file, and no new finding may appear in it. Only then is the pull request opened.

A prompt-injection attempt in the code can at worst produce a bad diff in that one file, inside the window. It still has to pass the checks above, the re-scan, your CI and a person’s review.

The pull request

  • Opened by your organisation’s AutoFix bot, <org>-fremforge-bot-autofix, on a new branch under autofix-ai/. The bot only writes to branches it creates and can never approve.
  • Title fix(security): <rule> in <path>, labels autofix and ai-generated.
  • The body names the finding, gives the model’s explanation, the provider and model used and the tokens spent, says how it was checked, and lists what to check before merging. It carries the EU AI Act Art. 50 disclosure: a readable statement that the change is AI-generated, and a machine-readable fremforge:ai-disclosure marker naming the feature, vendor and model.
  • Never armed for auto-merge, and the AutoFix bot’s pull requests are not exempt from required approvals: a person reviews and merges it. An auto-merge armed on it without an approving review is cancelled.

Rules covered

RuleLanguageThe fix
SQL string concatenation (go_sql_rule-concat-sqli)GoA parameterised query
Overly permissive file permissions (go_file-permissions_rule-fileperm, -mkdir)GoA narrower mode
HTTP server without timeouts (go_http_rule-http-serve)GoAn http.Server with ReadHeaderTimeout
SQL string concatenation (python_sql_rule-hardcoded-sql-expression)PythonA parameterised query
Unsafe XML parsing (python_xml_rule-element, python_xml_rule-etree)Pythondefusedxml, only when the file already imports it
Overly permissive file permissions (python_file-permissions_rule-general-bad-permission)PythonA narrower mode
Non-literal RegExp (javascript_dos_rule-non-literal-regexp)JavaScript, TypeScriptA string operation or literal patterns

Other rules show Not on the AutoFix rule list: their findings need a person’s judgement more than a patch. A rule leaves the list if fewer than 30 % of its pull requests are merged.

When no pull request is opened

The finding then shows No PR: the proposed change did not pass the checks or No PR: the fix could not be completed, and GET .../autofix/ai-requests gives the reason, for example scope_ci_config, new_dependency, suppression_comment, rescan_finding_still_present, rescan_new_finding or ai_over_cap. You can ask again after 10 minutes.

The same limits as for dependency fixes apply, shared between both: at most 3 open AutoFix pull requests per repository and 20 requests per organisation in 24 hours.

Limits

  • One live request per (repository, package). A second click on the same package answers already requested.
  • At most 3 open AutoFix pull requests per repository.
  • At most 20 requests per organisation in any 24 hours.

Both limits can be lowered, down to 0, which turns AutoFix off for the organisation:

curl -X PUT -H "Authorization: Bearer ${FREMFORGE_PAT}" -H "Content-Type: application/json" \
  -d '{"max_open_per_repo": 1, "max_per_org_per_day": 5}' \
  "https://frem.sh/_app/api/v1/orgs/acme/code-security/autofix/settings"

AutoFix needs hosted Renovate enabled for the organisation, and the repository must not be opted out of it.

Via REST API

  • Request a fix POST /_app/api/v1/orgs/:slug/code-security/deps/findings/:id/autofix, scope findings:write. Answers 202 with the request id, the target version, the finding keys it covers and "automerge": false. A refusal is problem+json with a reason, for example no_fixed_version or ecosystem_not_supported.
  • List requests GET /_app/api/v1/orgs/:slug/code-security/autofix/requests, scope findings:read, with each request’s status and pull request.
  • Counters GET /_app/api/v1/orgs/:slug/code-security/autofix/metrics: requests, pull requests opened, merged and closed, acceptance rate, pull requests merged without an approving review, and findings fixed by AutoFix.
  • Limits and the AI switch GET and PUT /_app/api/v1/orgs/:slug/code-security/autofix/settings (PUT needs policy:write). ai_enabled turns AI fixes on or off; ai_provider_configured says whether the organisation has its own provider.
  • Request an AI fix POST /_app/api/v1/orgs/:slug/code-security/sast/findings/:id/autofix, scope findings:write. Answers 202 with the request id and "automerge": false; the pull request follows within a few minutes. Refusals carry a reason, for example not_curated, ai_disabled or no_provider.
  • List AI requests GET /_app/api/v1/orgs/:slug/code-security/autofix/ai-requests, scope findings:read: status, reason, pull request, provider, model and token counts.

Every dependency finding returned by GET /_app/api/v1/orgs/:slug/code-security/deps/findings carries autofix_eligible and autofix, the latest request covering it. Every SAST finding from GET .../code-security/sast/findings carries autofix_eligible, autofix_unavailable_reason and autofix.

curl -X POST -H "Authorization: Bearer ${FREMFORGE_PAT}" \
  "https://frem.sh/_app/api/v1/orgs/acme/code-security/deps/findings/${FINDING_ID}/autofix"

Audit

Every step is in the audit log: autofix.proposed, autofix.pr_opened, autofix.rejected (with the reason), autofix.merged and autofix.closed, each with the finding keys, and engine: ai or deterministic. For AI fixes they carry the provider id and token counts, never the prompt or the diff. Turning the AI switch on or off is autofix.settings.updated.

See also