A fix is one patch for one finding. It is generated on request, it is kept as small as closing the path allows, and nothing reaches your repository until you publish it.
What a fix is allowed to touch
The file the finding is in, and the small number of closely related files the repair has to reach to actually close the path. If the dangerous call lives in a helper the flagged line calls, or the value arrives from a caller that has to stop passing it, that file is changed too. What a patch is never allowed to be is a refactor: it closes the specific path the finding describes, because a diff you can hold in your head is a diff you can actually review, and a review is the only thing standing between a generated patch and your default branch.
A dependency finding on a lockfile is the one case where the patch is not written where the finding is. A lockfile records what an install resolved to, and editing one by hand breaks its integrity hashes. So the change goes to the manifest beside it instead (package-lock.json to package.json, site/bun.lock to site/package.json), and the fix names that manifest. For an npm lockfile, Cefense then runs npm against the patched manifest and commits the lockfile it generates, and the proof checks that lockfile installs the new version. For any other lockfile, or a workspace root, run your installer and commit the refreshed lockfile before merging, which the pull request says too.
The patch applies to the commit that was scanned, never to the current branch tip. Generating against a moving branch would mean patching code that was never analysed.
How a patch is produced
Some repairs have exactly one correct shape, and those are written deterministically, with nothing generating anything. The rest are written for the code in front of them.
| Kind of finding | How the patch is produced |
|---|---|
| A vulnerable package | A version bump in the manifest, computed from the advisory's own fixed version. A package you do not declare directly is pinned with an override entry instead (resolutions for Yarn, pnpm.overrides for pnpm). Nothing is generated. |
| A committed credential | The literal is replaced with a read from the environment, in the form that file's language uses. Nothing is generated. |
| A vulnerability with one standard repair | A known-good transformation applied to the flagged region and nowhere else, so running it twice changes nothing the second time. Nothing is generated. |
| Everything else in your own code | Written against the file as it was scanned, then checked before it is offered. A patch that does not hold up is not offered at all. |
The states a fix moves through
| Status | What it means |
|---|---|
generating | Being written. Poll rather than starting another. |
ready | The diff is available to read, and can be published. |
failed | Generation did not produce a usable patch. The reason is on the fix. |
skipped | No patch was attempted, because this finding is not one a patch can close. |
publishing | The branch and pull request are being created. |
opened | The pull request exists. Its URL and number are on the fix. |
merged | It was merged. |
closed | It was closed without merging. |
Generating one
Opening a finding in Fix only reads its existing patch. Choose Generate patch to start one, after reviewing the usage notice. A proof is a separate action and does not start when generation finishes.
To repair in your own local checkout instead, use Fix with your AI. Preview or download the selected findings, work in your coding agent, then rescan the changed revision. See Fix findings with your coding agent.
cf fix generate <finding-id> --wait
cf fix show <finding-id>--wait polls until the fix settles, for up to about three minutes. Without it you get generating back and poll cf fix show yourself. cf fix with no argument lists every finding grouped by the state of its patch, which is how you see what is ready to read.
Reading the diff
- const rows = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`)
+ const rows = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id])The thing to look for specifically: does the patch change what an attacker can do, or does it only narrow the input that reaches the same dangerous call? The second kind looks like a fix and is not one.
A committed credential gets a patch, and still has to be rotated
That obligation is written into the fix's own explanation and into the pull request, so a reviewer who never opens this page still sees it. The rotation itself is the one part of closing a secret that only a person can do.
When no fix is offered
- There is no fixed version of the dependency to move to yet.
- The credential is already gone from the file at the commit that was scanned, or that line already reads from somewhere else.
- The file the finding is in is too large to patch safely.
- The file is no longer there at the commit the finding came from.
- The repair was attempted, checked, and did not hold up. It is not offered rather than offered and wrong.
In each case the finding still carries remediation guidance describing what to change, even though no diff was generated. The fix reaches skipped with the reason on it rather than sitting at generating forever.
Caching
Two findings that are the same vulnerability in the same code at the same commit share a patch: the second one copies the first rather than generating again. This is why generating a fix for a finding that recurs across a repository is often instant.
Where to go next
Have a correction or need help?
Email the docs team