DocsReports and fixes
Fixing what we found
Fix kits by stack, asking us to fix it, and what each status means.
On this page
- In short
- Where in the app
- Step by step
- What a fix kit looks like
- Verification boundaries
- Connect your code
- Whether Vallit can open pull requests
- What happens behind the scenes
- Reviewing a configuration finding
- We check again before we work on it
- How a fix reaches your code
- What the We fix it lines say
- A fix as a pull request
- Fixes we write without AI
- If something goes wrong
- Related
In short
- When we have a fix kit for a finding, it sits inside the finding under the Fix it yourself tab, free on every plan. It gives the steps, code for your stack and a way to see it worked. Copy AI prompt puts the same fix in one message for the AI builder you made the app with.
- On a Care app, Request fix hands the work to a person at Vallit. Care includes every fix for a problem our checks find, with no monthly number. A single job over two hours we agree with you first. On a Watch app, a Free app and the free check, Fix it for €99 pays for one fix on its own. You tick a box that accepts the Terms first. A paid fix arrives within two business days of your order, and the time stands still while we wait for something from you. If we cannot deliver a paid fix, you get the €99 back. You follow the request in the app's Inbox. Both sit under the We fix it tab. When the problem is gone, the request closes by itself and you get an email.
- On Watch or Care, connecting the app's repository lets the code checks read it. The free check and apps on Free never have their code read. The connect page shows whether GitHub allows Vallit to open pull requests, and where to allow them.
- With pull requests allowed, a fix for a finding in your code arrives as a pull request from the Vallit GitHub App. Nothing changes until you merge it, and your activity says when you merged or closed it.
Where in the app
Each open finding ends in a card with two tabs. Fix it yourself holds the fix kit. We fix it holds Request fix, Fix it for €99 or See plans.
Step by step
Open the finding
On the report, click the finding under Findings. The worst one is already open. Its fix card opens on the Fix it yourself tab. Once a fix is requested, it opens on We fix it, where the request is followed.
Pick your stack
The steps are folded away. Click the button under the panel's summary to show them, then click the tab for your stack, such as Next.js or Vercel. A kit written for one stack shows that stack's name in place of tabs. On a phone or tablet, that button and the Read the reference link under the steps are full-size touch targets, so a thumb does not miss them.
Make the change
Follow the numbered steps. Each snippet has a copy button in its corner. Pressing it draws a check and shows a small Copied bubble above the button. Replace
example.comand<your-project>with your own values before you use the snippet.Or hand it to your AI builder
Under the button that shows the steps, click Copy AI prompt. The button reads Copied. Paste the message into Lovable, Bolt, v0, Cursor or Replit, whichever you built the app with. It asks the builder to fix the finding and change nothing else.
Check it worked
Do what the kit says under Then check it worked. When the fix works, the next check no longer lists the finding. In a watched app's report, it appears under Fixed since.
Or ask us to fix it
Click the We fix it tab. Above the button, short lines say what would happen: Price, Who, When where a time is promised, Arrives and You. What the We fix it lines say lists them.
On a Care app, click Request fix. The button reads Requesting, then turns into Fix requested, and a person at Vallit is told straight away. The same lines now stand under it and say what happens from here.
Anywhere else, tick the box above the button. It says you order for a business and accept the Terms and the Data Processing Agreement. Then click Fix it for €99. Without the tick, the box is marked and says Tick this to continue. The button reads Opening, then Stripe's payment page opens. Once you have paid, Stripe brings you back to the report at that finding. The report says Payment received. The fix is filed., names the day the change is due and offers Open Inbox. If Stripe's confirmation has not arrived yet, it says Your payment is being confirmed. instead. The finding keeps its place in the list and shows Fix requested within a minute.
Where payments are not switched on, See plans stands in its place, beside On Care, a person at Vallit does it.
Follow the request
In the sidebar, click Inbox. In the In progress card, each request shows the finding, a line that says how it is paid for and who does it, and its current status. A paid fix reads
Paid, €99. A person at Vallit prepares the change by Friday 9 October., with its own day. While we wait for something from you, the line leaves the day out. A request that is done leaves the card, and What we did on Home says how it closed.
What a fix kit looks like
This is the kit a report shows for Browsers may guess file types.
Stop browsers second-guessing what a file is, so a response you serve as data cannot be run as code.
- Add X-Content-Type-Options: nosniff to every response. It has one value and no options.
- Check that your server sends a correct Content-Type for the things it serves. With sniffing off, a wrong type means a file no longer loads instead of loading wrongly.
- Pay particular attention to anything users upload: it should be served with a type you chose, not one taken from the uploaded filename.
The header
X-Content-Type-Options: nosniffHow to see it worked: curl -sI https://example.com | grep -i x-content-type-options returns the header, and your app still loads every stylesheet, script and image it did before.
A code check's kit names the places in your code and gives steps for each sign-in or AI provider it covers. This is the kit for Your app believes a sign-in it never checks.
Take the user from a check that asks your sign-in provider, not from the cookie or a decoded token.
- Open each place listed above and find where the user id comes from.
- On the server, replace `supabase.auth.getSession()` with `supabase.auth.getUser()` (or `getClaims()`), and use that user’s id.
- Where a token is decoded by hand (`jwt.decode`, `decodeJwt`, `atob`), verify it instead: `jwt.verify` with your secret, `jwtVerify` with your provider’s keys, or `supabase.auth.getUser(token)` in an edge function.
- Keep `getSession()` only in browser code, where it is fine for showing who is signed in.
app/api/orders/route.ts
const supabase = await createClient()
// getUser() asks Supabase to confirm the token; getSession() only reads the cookie.
const { data: { user } } = await supabase.auth.getUser()
if (!user) return new Response('Unauthorized', { status: 401 })
const orders = await db.order.findMany({ where: { userId: user.id } })How to see it worked: Edit your session cookie (or token) so it names a different user id, send the request again, and confirm it answers 401 instead of that user’s data.
Verification boundaries
These code findings each include a fix kit with a rejection test and a safe control. Keep verification failures away from database writes and message delivery. Choose token keys from server configuration and include the full owner scope in shared cache keys.
Make every data change depend on successful verification.
- Open the file and call path named in the finding. Find the first operation that changes stored data.
- Check the verifier’s return type. Await a Promise and reject false boolean results; synchronous throwing verifiers stop by throwing.
- Return or throw when verification fails. Logging the failure does not stop the request. Move data changes out of finally.
- Send a false signature, a missing signature and a verifier rejection. Every case must leave the database unchanged.
Enforce the trust boundary
const valid = await verifySignature(rawBody, signature)
if (!valid) return new Response('Invalid signature', { status: 401 })
// All database writes come after this gate.
await applyEvent(JSON.parse(rawBody))How to see it worked: A missing or false signature returns an error and causes zero writes, emails or paid calls.
Choose authentication keys from trusted server configuration.
- Trace the key argument back to its source. Remove keys and JWKS addresses read from the request or decoded token.
- Configure the trusted issuer and its exact JWKS address on the server. Do not build the address from jku, x5u or jwk.
- Require the intended issuer, audience and algorithms. A kid value may select among trusted keys without choosing new key material.
- Sign a test token with a fresh unrelated key and send its key in the header or request. Verification must reject it.
Enforce the trust boundary
import { createRemoteJWKSet, jwtVerify } from 'jose'
const trustedKeys = createRemoteJWKSet(new URL(process.env.TRUSTED_JWKS_URL!))
const { payload } = await jwtVerify(token, trustedKeys, {
issuer: process.env.TRUSTED_ISSUER!,
audience: 'your-app',
algorithms: ['RS256'],
})How to see it worked: A token signed by an attacker’s key fails even when it claims the right issuer and audience.
Permit explicit fields and reject prototype paths at every level.
- Replace whole-object merging with a list of fields the caller may change wherever possible.
- If nested paths are required, reject __proto__, constructor and prototype before reading or writing each segment.
- Traverse only own properties. Use null-prototype containers or Maps where arbitrary names are needed.
- Try both __proto__ and constructor.prototype payloads in an isolated test. New objects must keep their original properties.
Enforce the trust boundary
function pickSettings(input: Record<string, unknown>) {
return {
displayName: typeof input.displayName === 'string' ? input.displayName : '',
locale: input.locale === 'de' ? 'de' : 'en',
}
}
// No recursive merge of request-controlled keys.
const settings = pickSettings(await request.json())How to see it worked: Prototype-path payloads cannot change properties inherited by other objects.
Keep every extracted entry within a fresh directory owned by the server.
- Use a fresh extraction directory. Reject symbolic links and hard links and do not extract into an existing tree that contains links.
- Resolve the root and each destination. Require the destination to start with the canonical root followed by the path separator.
- Reject absolute names and paths escaping the root. Joining or normalizing a pathname alone does not contain it.
- Bound the number of entries and total expanded bytes. Test parent paths, sibling-prefix paths and link entries before deploying.
Enforce the trust boundary
import path from 'node:path'
const root = path.resolve(freshExtractionDirectory)
const destination = path.resolve(root, entry.entryName)
if (path.isAbsolute(entry.entryName) || !destination.startsWith(root + path.sep)) {
throw new Error('Archive path leaves its destination')
}
// Reject link entries and use a fresh tree without symlinks before writing.
await writeFile(destination, entry.getData(), { flag: 'wx' })How to see it worked: Entries such as ../outside.txt and ../uploads-old/server.js are rejected and create no files outside the fresh root.
Authorize each request and include its private data scope in persistent cache keys.
- Find the captured user or workspace value named by the cache callback.
- Authorize the caller for that scope before invoking a shared cache. Keep authorization outside the cached callback.
- Pass the scope as a function argument or include the exact scope value in keyParts. Tags invalidate data but do not separate callers.
- Warm the cache as one user, then read as a second user from another workspace. Neither may receive the other’s private records.
Enforce the trust boundary
import { unstable_cache } from 'next/cache'
const readInvoices = unstable_cache(
async (workspaceId: string) => db.invoice.findMany({ where: { workspaceId } }),
['invoices'],
)
// Authorization runs on every request, before the shared cache.
const tenant = await requireTenant()
return readInvoices(tenant.companyId)How to see it worked: Two users in different workspaces receive separate records, including after a cache hit.
Connect your code
Connecting the repository behind an app lets the code checks read it at every check. Reading code is part of a paid plan, per app. For an app on Free, or without a plan, the connect page says Reading your code is part of a plan. and offers See plans instead of GitHub. A repository connected earlier stays listed and is read again once the app is on Watch or Care. You need the app in your company and a GitHub account that can see its repository.
Open the connect page
On your app's page, click Connect code in the Settings card. Without a plan, the card shows On a plan and See plans instead. On a report of your own, the same link sits under What we checked. The page Connect your code opens. On Watch or Care, What Vallit sees at the foot of the page says what connecting hands over before you click anything, and links to What we read and keep.
Connect GitHub
Click Connect GitHub and sign in to GitHub. If the Vallit GitHub App is not installed yet, GitHub asks you to install it and choose the repositories it may read. Share only the repository behind this app. You come back to a page that says GitHub is connected.
Choose the repository
If you shared one repository, Vallit picks it for you, and the line under GitHub is connected names it, such as
Connected to acme/shop. Vallit can only read it.If you shared several, the line reads Pick the repository behind this app below. Select it and click Use this repository. A repository another app of your company already reads shows used by and that app's name. The page says Repository saved.Decide about pull requests
Tick the box that starts When Vallit has a tested fix if you want fixes as pull requests, then click Save. The page says Your choice about fix pull requests is recorded. A line under the box says whether GitHub allows pull requests, as Whether Vallit can open pull requests shows. Saved with the box ticked before you allow them, the page says Saved. One step on GitHub and offers Allow on GitHub .
Wait for the next check
The page now says Your code is connected, and the repository's row reads reading. From the next check on, What we checked adds the groups for your code, such as Who can reach which data and Where money can leak.
What Vallit sees says, line by line:
- Vallit reads the code of the repositories you share, with their names and settings. Read-only: it never runs your code or keeps a copy.
- To confirm a finding, the code on its path goes to an AI model through the Vercel AI Gateway, with keys masked. Never the whole repository.
- A person at Vallit may read the lines around a finding to help fix it.
- Share only the repository behind this app.
- Fixes as pull requests need one more permission on GitHub. You can grant it later.
To stop, click Disconnect on the same page. The page says Vallit no longer reads this code. To remove its access on GitHub as well, uninstall the Vallit GitHub App in your GitHub settings.
An earlier fix pull request stays linked to the repository where it was opened. Disconnecting stops its follow-up. After you reconnect and choose a repository, Vallit can follow the earlier pull request if the new installation still has access to its repository. Choosing another repository does not move the earlier pull request.
Whether Vallit can open pull requests
A pull request needs more than reading: GitHub has to let Vallit write to a branch and open the request. The Vallit GitHub App asks for that access. If you installed it before it asked, GitHub leaves your installation as it was until you accept the new permission. With a repository chosen, a line under the pull request box says where things stand. After you accept, the mark next to GitHub reads reads, proposes fixes in place of read-only.
| What you see | What it means | What to do |
|---|---|---|
| Vallit can open pull requests on this repository. | GitHub allows it, and the box is ticked. | Nothing. A fix for a finding in this repository can arrive as a pull request. |
| GitHub allows pull requests from Vallit here. They stay off until you tick the box. | GitHub allows it, and you have not asked for pull requests. | If you want them, tick the box that starts When Vallit has a tested fix and click Save. |
| GitHub has not allowed Vallit to open pull requests here yet. | Vallit asks for write access, and your installation has not accepted it. | Click Allow on GitHub. The installation's settings open on GitHub, where you accept the new permission. Then reload the page. |
| GitHub did not answer, so whether pull requests are allowed is not known right now. | Vallit could not ask GitHub while it drew the page. | Nothing changed. Reload the page in a moment. |
What happens behind the scenes
Every check has a fix kit, the checks that read your domain, your libraries and your repository included. So do the rule checks that read your code, and the checks for links that change data, database search commands, sign-in with other services, webhooks and search patterns. A check that can report two findings has a kit for each. For passwords, Your app keeps users’ passwords readable and Your app stores passwords with a hash that is quick to crack get different steps. The fix kits are the same for every app. Their snippets use example.com and <your-project>, so a snippet pasted unchanged never carries someone else's address or key. When a finding has no kit, How we fix it is the guidance.
Copy AI prompt builds the message in your browser from what the finding already shows: its title, what could happen, where it was seen, and the kit's summary, steps and check. Where it was seen is a file and line in your code when there is one, else the address. It never copies the values in Evidence, and keys in the finding's words are masked again on the way in. A long kit keeps its first steps and asks the builder to follow the rest from the same fix. The message always ends with "Do not change anything else."
When you click Request fix:
- The request is recorded against the finding. Asking twice counts as one request.
- The finding is marked as in progress. It still counts in the score until the fix is done.
- A person at Vallit is told at once. Requests from Care companies are picked up first.
- On a Care app, every fix is included. Nothing is counted and nothing runs out.
- A single job we expect to take over two hours is agreed with you before we start. The request then shows We need something from you before we can finish with our note: what the job involves and how long we expect it to take. Reply to the note, and the work goes on once we have agreed.
- Clicking Fix it for €99 again before the request shows opens the same payment page. A paid fix is filed once Stripe reports the money arrived, and the line on Home starts "You paid for a fix:". A payment that settles later is filed when it settles, and one that fails files nothing.
- One finding is never paid for twice. A payment for a finding that was already asked about, for free on Care or with another payment, is refunded to the same card straight away.
- On a Care app, the €99 Fix cannot be charged. Request fix is the way in.
- The What we did list on Home gains a line that starts "You asked us to fix:".
Reviewing a configuration finding
The deployment and build checks describe explicit repository settings. Their fix kits guide you through the setting and a separate verification of the deployed result. Review the workload's requirements before changing a setting used intentionally, such as host access for a monitoring service. Record the exception and its external controls when you choose Accepted risk.
For containers, render the complete configuration and inspect the created workload after testing the reduced permissions. For cloud resources, inspect the running service and plan any replacement or data migration before applying the change. A repository recheck confirms only that the committed configuration no longer matches the rule. It does not establish that the replacement was deployed or that the running service is protected.
An engine socket needs review even when its filesystem mount is read-only:
Remove direct engine-socket access or place a narrowly authorized service between the workload and the engine.
- Locate the bind mount or Kubernetes hostPath and the workload that uses it.
- Turn off use_api_socket in Compose and remove engine sockets from workload mounts, including mounts marked read-only.
- If engine access is essential, use a separate service that authorizes only the required operations.
- Test the workload without direct socket access and inspect its resulting mounts before deploying.
Review the deployment setting
Locate the bind mount or Kubernetes hostPath and the workload that uses it.
Turn off use_api_socket in Compose and remove engine sockets from workload mounts, including mounts marked read-only.
If engine access is essential, use a separate service that authorizes only the required operations.
Test the workload without direct socket access and inspect its resulting mounts before deploying.How to see it worked: Render the complete configuration with docker compose config or your Kubernetes deployment tool. Inspect the created container or Pod to confirm the setting, and test the workload before removing its old deployment. The next repository scan checks the committed configuration only.
Review effective cloud permissions alongside the policy declaration:
Replace unrestricted grants with the actions and resources required by the identity.
- Identify the users, groups or roles intended to use the reported policy.
- Replace wildcard actions and resources or AdministratorAccess with the specific permissions needed.
- Use IAM Access Analyzer and a reviewed permissions boundary where appropriate.
- Deploy the narrowed policy and test both allowed tasks and actions that should be denied.
Review the deployment setting
Identify the users, groups or roles intended to use the reported policy.
Replace wildcard actions and resources or AdministratorAccess with the specific permissions needed.
Use IAM Access Analyzer and a reviewed permissions boundary where appropriate.
Deploy the narrowed policy and test both allowed tasks and actions that should be denied.How to see it worked: Inspect the deployed attachments and effective constraints; test expected allowed and denied operations with IAM policy simulation or an isolated identity.
For a mutable workflow reference, review the upstream version before pinning it. The Low finding records a reproducibility concern, rather than evidence that the action is malicious:
Pin the step to a reviewed full commit SHA or immutable container image digest.
- Review the intended action version and its upstream repository.
- Replace the reference with its full commit SHA or container digest.
- Keep a readable version comment beside the reference.
- Use a reviewed dependency-update process to advance the pin.
Review the deployment setting
Review the intended action version and its upstream repository.
Replace the reference with its full commit SHA or container digest.
Keep a readable version comment beside the reference.
Use a reviewed dependency-update process to advance the pin.How to see it worked: Confirm the action SHA belongs to its upstream repository, or verify the container digest, and run the workflow.
We check again before we work on it
When a person at Vallit opens one of your findings, the check that found it runs again at that moment. It reads what is there now: your code on its default branch, or your live address. Nothing is taken from the stored report. If the check no longer finds the problem, nobody at Vallit has to confirm it:
- The finding closes as fixed, and so does the same finding in your latest report. The score of that report is counted again.
- An open fix request for it closes as done and leaves the In progress list.
- What we did on Home gains one line, and the fixed email goes to your alert address, as Alerts shows.
If the check still finds the problem, or cannot run again, nothing changes on your side. The finding stays open and the work goes on. Opening the finding again later closes nothing twice and sends no second email. A finding marked Not an issue, by you or by us, keeps that mark: a recheck never turns it into fixed.
The daily check closes requests too. When it no longer finds a finding you asked us to fix, your fix request closes as done, and you get the fixed email whatever the finding's severity.
Each status shows up in one of three places:
| What you see | Where | What it means |
|---|---|---|
| Fix requested | Inbox, under In progress | We have your request. |
| We have picked this up | Inbox, under In progress | A person at Vallit has taken the work. |
| Being fixed now | Inbox, under In progress | The work has started. |
| Confirm the domain first. A fix needs to show where the problem is, and that opens once the domain is yours. | Beside Request fix | The finding comes from a targeted check on an app whose domain is not confirmed, so we cannot yet show where it sits. Nothing was requested or charged. Confirm the domain with Confirm your domain, then ask again. |
| We need something from you before we can finish: … | What we did, on Home | We are waiting for you. Our note, if we wrote one, follows the finding's name after a full stop. A job over two hours is agreed here first. The request leaves the In progress list meanwhile. |
| Fixed: … | What we did | The work is done. The finding moves to Handled as Fixed and leaves the score. |
| We checked again: “…” is fixed. Your fix request is closed. | What we did | The check ran again when we opened the finding and no longer finds it. The finding is fixed and your request is done. |
| Fixed since the last check on …: …. Your fix request is closed. | The Guardian card, on the app's page | The daily check no longer finds a finding you asked us to fix. The request is done. |
| We are not going ahead with: … | What we did | We decided not to make the change. The finding moves to Handled as Accepted risk and leaves the score. |
Without a fix request, the recheck's line reads "We checked again: “…” is fixed." without the second sentence.
How a fix reaches your code
On a report of your own, What happens from here ends the report while something is left to add. It shows what your company has now, what a plan adds and what connecting your code adds, each with its button: See plans or Connect GitHub. When we fix a finding for you, it takes four steps:
- We read your code, read-only and only the repository you choose. A person at Vallit reads the lines around the finding at the commit the check read, and What we did on Home says so.
- A person at Vallit prepares the change, the one the fix steps describe, fitted to your code.
- You follow every step in your Inbox: picked up, being fixed, fixed.
- The next check confirms it, and the finding moves to Fixed since.
Once Vallit can open pull requests, the second and third steps read We write the fix on a branch of its own and You review the pull request. Nothing reaches your main branch until you merge it.
What the We fix it lines say
Under We fix it, before you ask and again after, the card says what happens in up to five short lines.
| Line | What it says |
|---|---|
| Price | Included in Care on a Care app. For the €99 Fix, €99, once. Care includes every fix before you pay, and Paid, €99 after. |
| Who | A person at Vallit, or Vallit when Vallit writes the change itself and opens the pull request. |
| When | Answered by 18:00 the next business day for a critical finding on a Care app, Within two business days, paused while we wait for you for a paid fix before you pay. Once it is filed, the line names its day, for example By Friday 9 October, unless we wait for you, and reads Paused while we wait for you while we wait for something from you. The day is the second business day after your order, in Berlin time, and moves on by the business time we waited. If the day passes before the change arrives, the line reads Was due Friday 9 October. If we cannot deliver it, you get your €99 back, and the Inbox line says we are late. The line reads In minutes, or a person at Vallit takes it when Vallit writes the change itself. Otherwise there is no line, because no time is promised. |
| Arrives | How the change reaches you, from the table below. |
| You | What is left for you to do, with a link where there is a step to take. |
A finding on the running app, such as a missing header, is fixed with a setting, not in your code. Its card always reads As the change, for you to apply and Apply the change we send. For a finding in your code, the last two lines follow the connect page:
| When | Arrives | You |
|---|---|---|
| No repository is connected to the app | As the change, for you to apply | Apply the change we send, and Connect code for a pull request |
| The pull request box on the connect page is unticked | As the change, for you to apply | Apply the change we send, and Turn on pull requests |
| Your GitHub installation has not accepted write access | As a pull request on the repository, once GitHub allows it | Allow pull requests, which opens the connect page |
| GitHub allows pull requests and the box is ticked | As a pull request on the repository | Review and merge it. Nothing changes until you do |
| GitHub did not answer while the report opened | As a pull request where GitHub allows it, otherwise as the change to apply | Merge the pull request, or apply the change we send |
While fixes as pull requests are not switched on, every card reads As the change, for you to apply.
With a connected repository, every check reads the code on its default branch, at the commit the branch points to when the check starts. When a person at Vallit opens a finding, the lines around each place it names are fetched from that commit and not stored. A file with a committed key or a committed environment file is never opened. Vallit can only choose from repositories you shared with the Vallit GitHub App. The pull request setting is off until you tick it. A pull request needs write access to the repository's contents and its pull requests. With that access and the setting on, Vallit pushes only to a branch of its own and opens a pull request. Vallit never merges it, so your default branch stays untouched until you merge. With the box unticked, Vallit opens no pull request.
A fix as a pull request
This is how a fix for a finding in your code reaches you when GitHub allows pull requests and the box on the connect page is ticked.
- A person at Vallit works on copies of only the files the finding names. The copies come through the Vallit GitHub App, at the commit the check read. A file with a committed key, and a committed environment file, are never opened. The copies are removed when the pull request is opened.
- Before the pull request opens, Vallit's server runs the check that found the problem again, on your repository with the change applied. For a check that can run again this way, the pull request opens only when the check no longer finds the problem and finds nothing new. None of your code is run: the check reads the files, as it does at every check.
- The proposed fix stays tied to the repository revision that was checked. If Vallit sees that the default branch moved or the connected repository changed while the fix was prepared, it stops before opening the pull request. The fix needs to be checked against the newer revision.
- The pull request comes from the Vallit GitHub App, on a branch whose name starts with
vallit/fix-. Its description says what was found, what the change does, which files it touches and how it was checked. It says "Nothing changes until you merge this pull request." If the relevant check can run after the merge, it evaluates whether the finding remains. - Vallit never merges the pull request and never pushes to your default branch. You review it on GitHub and merge it, or close it.
Retrying the same proposal reuses its existing pull request. Vallit does not replace a different change or work you added to its review branch. Your default branch can still move while a pull request is being opened or reviewed; review the current diff and your repository's checks before merging.
If a check that can verify the fix cannot complete, Vallit stops before opening the pull request. Unread configuration stays unread when the proposed patch is checked; it cannot silently become a completed check. A finding without a verifier is stated as unverified in the proposal.
If nobody asked for the fix yet, the finding gets a fix request of its own. Your Inbox lists it under In progress as Being fixed now. Once an hour, Vallit asks GitHub whether the pull request was merged or closed, and your activity on Home says what happened.
After a merge, the request stays in progress until a check confirms the finding is fixed. The merge makes the app's full check due at once. It runs within a few minutes, even when a report finished earlier that day, and it is stored as a new report. If that check no longer finds the problem, the request closes, leaves the In progress list, and you get the usual email. If it finds the problem again, in the same file or another one, the request stays open and your activity says so once. If the check cannot finish, for example because a file is too large to read, the request stays open and the next check tries again. A check that did not finish never counts as a fix. The hourly look at GitHub takes turns over all open pull requests, so each one is asked about within a few hours, however many are open.
| What you see | What it means | What to do |
|---|---|---|
| We opened a pull request with the fix for “…”. Nothing changes until you merge it. | The pull request is open on GitHub, with the finding's name in the quotes. | Review the pull request on GitHub, then merge it or close it. |
| You merged the fix for “…”. Merging does not verify this change. If the relevant check can run, it evaluates whether the finding remains. | GitHub reports the pull request as merged. The fix request remains in progress until a check confirms the finding is fixed. | Once a successful check no longer finds the problem, the finding moves to Fixed since. |
| We checked again after you merged the fix for “…”: it is fixed. Your fix request is closed. | The first full check after the merge no longer finds the problem. | Nothing. The finding moves to Fixed since. |
| We checked again after you merged the fix for “…”: the check still finds it. Your fix request stays open and we are looking at it. | The first full check after the merge finds the same problem, possibly in another place. The new report shows where. | Nothing. A person at Vallit looks at it. |
| We checked again after you merged the fix for “…”, but that check could not finish. The next check tries again. | The check that would confirm the fix did not finish, so the merge is neither confirmed nor rejected. | Nothing. The next check tries again. |
| The pull request with the fix for “…” was closed without merging. | The pull request was closed on GitHub and not merged. The request goes back to you and leaves the In progress list. If the request was closed as fixed before, for example by a recheck, it stays closed. | Tell us if you want the fix done differently. |
A proposal for a finding without an automatic verifier says "This change has not been verified automatically. Review and test it before merging." It does not promise that a later check will confirm the fix.
Fixes we write without AI
For a few kinds of findings the fix follows a fixed pattern. Vallit writes those changes itself, without an AI model:
- A workflow in
.github/workflowsthat pastes text anyone can write into a script. The text moves into the step'senv:, and the script reads it as a quoted variable. - A database view that shows rows your rules hide. A new migration sets
security_invoker = trueon the view. - A database function that skips your rules and that nothing in your code calls. A new migration takes the right to call it away from the browser's keys; the service role keeps it.
- A code or token made with
Math.random(). It is drawn fromnode:cryptoinstead, with the same length and the same characters.
Before such a pull request opens, three things must hold on your repository with the change applied. Every changed code file still parses. The check that found the problem no longer finds it. No check finds anything new. If one of them fails, or your code has a shape the fixer does not know, nothing is opened and a person at Vallit fixes the finding as usual. The pull request says that it was written without an AI model and how it was checked. Everything in A fix as a pull request applies to it too, and Vallit never merges it.
Today a person at Vallit starts each of these fixes. Pull requests that open by themselves when you click Request fix are not switched on yet. Once they are, the lines under Fix requested read Vallit under Who and In minutes, or a person at Vallit takes it under When.
If something goes wrong
| What you see | What it means | What to do |
|---|---|---|
| Having us fix things is part of Care, or €99 for this one fix. Choose either and we will get started. | The app is on Free or on no plan, for example because the plan ended after the report opened. | Reload the report and click Fix it for €99, or choose Care. Plans and billing compares them. |
| This fix is not part of your plan. Fix it for €99, or put the app on Care, which includes every fix. | The app is on Watch. | Click Fix it for €99, or move the app to Care on Billing. |
| Payments are not switched on yet. | This copy of Vallit cannot take payments. | Use Get help in the sidebar to reach us. |
| Accept the Terms to continue. Nothing was charged. | The payment was asked for without the box ticked. | Tick the box above Fix it for €99 and click it again. |
| Our Terms changed since this page opened. Reload it to read the new version. Nothing was charged. | The Terms have a new version since the report loaded. | Reload the report, read the new version and tick the box again. |
| We could not open the payment page. Try again in a moment. | Stripe did not create the payment page. Nothing was charged. | Wait a moment, then click Fix it for €99 again. |
| Your plan includes this fix. Use Request fix; nothing was charged. | The app is on Care, which includes every fix. | Reload the report and click Request fix. |
| That finding is not on your account. | The report belongs to a different company. | Sign in with the account of the company that owns the app, then open the report again. |
| That finding no longer exists. | The request did not reach us with a finding attached. | Reload the report and click Request fix again. |
| GitHub did not answer | GitHub did not respond while Vallit saved your choice or listed your repositories. | Nothing changed. Click Try again if the message offers it, or choose the same repository in a moment. |
| That repository is not shared with us | GitHub no longer lists the repository for the Vallit GitHub App. | Click Share on GitHub, add the repository there, then choose it again. |
| No repository shared yet | The Vallit GitHub App has access to no repository. | Click Share on GitHub, add the repository behind this app, then reload the connect page. |
| That did not work | GitHub did not confirm that your account can see the installation. | Click Start again, signed in to the GitHub account that installed the app. |
| Waiting for an organization owner | Your GitHub organization needs an owner to approve the install. | Come back to the connect page once they approve it. |
| GitHub did not finish connecting | The way back from GitHub expired or could not be checked. Nothing was connected. | Click Start again. If it shows on Home, open your app and click Connect GitHub again. |
If a finding shows neither Request fix nor See plans, you are not signed in, or the report is not in your company. A report started without an account needs taking over first, as Reading a report shows.