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
- fremforge collects every open default-branch finding on the same package in the same repository. One pull request closes all of them.
- 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.
- 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.
- 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. - 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 see | Why |
|---|---|
| No released version fixes every open advisory | There is no fix to propose yet. The finding stays open. |
| npm and Go only | The finding is in another ecosystem. |
| No PR yet: too new | The 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 change | Renovate 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 scope | Renovate’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/settingsand"ai_enabled": true. Per-repository overrides and theautofixfeature 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 underautofix-ai/. The bot only writes to branches it creates and can never approve. - Title
fix(security): <rule> in <path>, labelsautofixandai-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-disclosuremarker 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
| Rule | Language | The fix |
|---|---|---|
SQL string concatenation (go_sql_rule-concat-sqli) | Go | A parameterised query |
Overly permissive file permissions (go_file-permissions_rule-fileperm, -mkdir) | Go | A narrower mode |
HTTP server without timeouts (go_http_rule-http-serve) | Go | An http.Server with ReadHeaderTimeout |
SQL string concatenation (python_sql_rule-hardcoded-sql-expression) | Python | A parameterised query |
Unsafe XML parsing (python_xml_rule-element, python_xml_rule-etree) | Python | defusedxml, only when the file already imports it |
Overly permissive file permissions (python_file-permissions_rule-general-bad-permission) | Python | A narrower mode |
Non-literal RegExp (javascript_dos_rule-non-literal-regexp) | JavaScript, TypeScript | A 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, scopefindings:write. Answers202with the request id, the target version, the finding keys it covers and"automerge": false. A refusal isproblem+jsonwith areason, for exampleno_fixed_versionorecosystem_not_supported. - List requests
GET /_app/api/v1/orgs/:slug/code-security/autofix/requests, scopefindings: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
GETandPUT /_app/api/v1/orgs/:slug/code-security/autofix/settings(PUTneedspolicy:write).ai_enabledturns AI fixes on or off;ai_provider_configuredsays whether the organisation has its own provider. - Request an AI fix
POST /_app/api/v1/orgs/:slug/code-security/sast/findings/:id/autofix, scopefindings:write. Answers202with the request id and"automerge": false; the pull request follows within a few minutes. Refusals carry areason, for examplenot_curated,ai_disabledorno_provider. - List AI requests
GET /_app/api/v1/orgs/:slug/code-security/autofix/ai-requests, scopefindings: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
- OSV recommendations: the cards the Fix button sits on.
- Hosted Renovate: the bot that opens the dependency pull request.
- AI integrations: where your organisation’s own model provider is configured.