When a project changes development teams, the largest unknown is neither the quote nor the pitch deck. It is the state of the code. What the previous team left behind, which parts can be trusted, and which parts break when touched are rarely things anyone can explain on handover day. How to choose the new team is covered in how to choose a software development vendor. This article deals with the most concrete step after that choice: turning the state of the code into information a decision can rest on.
Software does not stand still on launch day. Parnas, 1994 proposed the idea of software aging: aging cannot be avoided, but its causes can be understood, its effects limited, and part of it reversed, so long-term product health deserves more attention than the first release. Lehman, 1980 points in the same direction with the law of continuing change: a program that is used and has to reflect reality becomes less useful unless it keeps being adjusted, and changes made without maintenance work degrade structure and add complexity. A system that has been in production for some time sits in exactly that state. People use it, it has been modified many times, and the reasons for the modifications mostly are not written in the code.
Changing teams is also a supplier risk. NIST SP 800-161 gives practices for identifying, assessing, and reducing the risks that suppliers and procurement bring to systems, products, and services, such as malicious functionality, counterfeits, and poor development quality (Boyens et al., 2024). Whether the previous team's system is reliable belongs in the category that has to be identified and assessed. A full code audit is one way to assess it: no impressions, just reading the code.
The Audit Process: Scan, Candidates, Rebuttal, Report
A full code audit covers the entire codebase, not a sample of pages. The process runs in four stages. In the first, the system is divided into seven areas that are scanned in parallel, each producing candidate findings. In the second, every candidate is verified against the code to confirm that the problem exists. In the third, an adversarial review deliberately looks for reasons to overturn the audit's own findings, and the reviewer has to settle two things: why that piece of code is there, and what happens if it is taken out. In the fourth, what remains is written into the report.
There is no single correct way to divide the areas. OWASP ASVS, 2025 is a list of verification requirements that developers can check against while building, coding, and testing web applications and services, and it helps confirm that common verification topics are not missed. The OWASP Top 10, 2025 is a consensus list of the most serious security risks to web applications, with the 2025 edition marked as the current one; during severity rating it serves as a cross-check that the most serious class of risk has not been overlooked.

This asks a different question from vulnerability scanning or penetration testing. The differences between the two, and when each fits, are laid out in vulnerability scanning versus penetration testing. A code audit reads the code itself, so it can see things that rarely surface while the system runs: a fallback that has never been triggered, an error that is swallowed, and what the program does when a setting is left blank.
- Candidate findingsabout seventyMerged from seven parallel scans, each finding verified against the code
- Overturned in rebuttalnearly 10%Mostly patches that earlier developers left on purpose
- Kept for the reportabout sixtySorted into red, yellow, and green
One full audit; figures are approximate
In one full audit, the merged list of candidate findings came to about seventy. After the rebuttal step, nearly 10% were rejected and the rest went into the report. The rejected ones were not wasted effort. Each leaves behind the reasoning for why it looked like a defect and was not, so a later maintainer does not remove the same patch as if it were an error.
Rebuttal Is Where a Report Gets Its Credibility
Teams taking over a system tend to worry about two things in an audit report: padding and false alarms. Padding means listing everything that looks suspicious until the few findings that matter are buried. The rebuttal step exists to filter that out. Most rejected candidates looked like defects but were patches the previous developers had left in on purpose. Read in context, that code was added to deal with a specific problem, and taking the patch out brings the problem back. A well-meant fix then causes an incident.

False alarms do more than waste time; the larger cost is lost trust. Sadowski et al., 2018 summarizes experience building static analysis tools at Google and states that false positives make users distrust analysis results; for checks that run during code review the criterion is an effective false-positive rate below 10%, and compile-time checks must produce no effective false positives. An interview study of developers also found that the way false positives and warnings are presented is one of the main reasons developers do not use static analysis tools, even though interviewees generally thought the tools helped (Johnson et al., 2013). Both studies concern automated tools rather than manual audits, so the figures cannot be compared directly. The principle carries over, though: every finding in a report that does not hold up lowers the reader's trust in the findings that do.
A Report Should Say What Customers Will Run Into
How a report is written decides whether it gets used. A list of technical terms is legible only to people who write code; the person responsible for the product needs to know what users or customers will run into. A usable report sorts findings into red, yellow, and green. Red is for problems that can hurt people, lose data, or invite attack, and each red finding is written in four fields: what the user or customer will experience, when it happens, who should fix it, and the estimated time. Red entries read like this:
- Admin login has no attempt limit, and failed attempts are not logged: an account can be tried over and over, with nothing to trace afterward.
- A person whose access was revoked can still change data for a period afterward: disabling the account did not block the session at once.
- A deployment command permanently deletes files that are not under version control: the next deploy removes the files, with no way to recover.
Yellow lists problems that do not yet rise to red. Green is a separate list of what is done well and must not be taken apart by later maintainers, because one class of incident after a takeover is a new team removing a deliberate earlier design that looked like a defect. A final section, "What Not to Do Now," names the items not worth reworking at present, so that the audit does not turn into a round of sweeping changes. This approach runs in the same direction as monitoring practice: the chapter on monitoring distributed systems in the SRE literature argues that alerts should in principle target only problems that need human intervention and affect users, and should be symptom-based first, with causes second (Ewaschuk, 2017). A report likewise states the symptom first and then where the cause sits.
- Verify against the codeConfirm the problem exists
- Try to overturn itCheck whether it is a patch left on purpose
- It can hurt people, lose data, or invite attackFix nowRated red, with who should fix it and the estimated time
- It carries risk but is not yet redSchedule the fixRated yellow, placed in the maintenance plan after handover
- It looks like a defect but proves to be a deliberate patchKeep and recordWrite down why it must not be removed, so no one fixes it by mistake
The Costliest Defects Raise No Error
The costliest defects often raise no error. The feature appears to work, automated tests are green, no error message appears, and the protection has never actually been active. Such defects are invisible at acceptance and surface only when something goes wrong. Defects of this kind share a common shape, shown here by a few de-identified examples:
- The key for bot verification was never set, and the program, on finding it unset, switched the check off entirely and let everything through without a single log line; during the audit a fake verification code still reached the next step.
- The condition guarding a fallback is always true, so the fallback has never been triggered.
- An empty error handler combined with a full overwrite in the back end: a failed write is swallowed, data is silently corrupted, and the screen reports success.
- Some framework versions do not catch errors thrown from asynchronous functions, so adding a single layer of error handling does nothing.
- An environment variable was not set, so the system was treated as a test environment, every outgoing email was blocked, and no alert was raised.
- Log rotation failed repeatedly and no one knew.
Three countermeasures apply. First, when a setting is missing, the system refuses to start or at least raises a clear alert: failure should make noise and leave a line in the log. Second, every fix leaves behind a check that fails when something breaks, not just a comment. The method is a negative test, which feeds deliberately wrong input to prove the check really catches the problem. Third, separate "checked" from "checked correctly." Tests run against mock data, but after a move or a configuration change the switch that points to the mock can quietly stop working, and the tests stay green while actually hitting the real system. So where a test connects is itself something to check, and the latency of the test environment is measured first so that waiting times scale with it, rather than a fixed wait producing false alarms.
Whether to Keep the Admin Panel Is Answered by Usage Records
When an admin panel is recommended as essential, or an existing one is said to be indispensable, the operation records after launch are the most direct evidence. The method is to read three months of operation records, set aside logins, logouts, and account management, and count only genuine content edits. If content edits trend toward zero, move the content into version control and keep the admin panel for viewing only. Every change then carries a version record and a reason, and there is no need to maintain a full editing interface for users who never appeared. Changes made to a database by script are invisible in the audit trail, whereas content in version control shows every change. Retiring the panel cannot be undone, so the safer path is to make it read-only and watch for a while before deciding whether to shut it down.
The First Month: Old Problems Stay, New Code Adds No Risk
In the first month after handover, the scope has to be drawn clearly. Existing problems in the old system are not repaired opportunistically, and newly written code must not introduce new risk. Information is thinnest at the start of a takeover, which makes sweeping rework the riskiest move, while new code is the one part the development team fully controls. NIST's SSDF describes secure development practices as a high-level set that can be added to any software development life cycle model, aiming to reduce vulnerabilities in released software, limit the impact of undetected defects, and address root causes (Souppaya et al., 2022). Old problems are recorded and ranked first, and new code follows a short set of concrete rules:
- Database queries are always parameterized.
- Reads and writes check ownership, with the query restricted to both the record and the user.
- Callbacks from third-party services, such as payment providers, are always verified by signature.
- Secrets stay out of version control, and logs never print secrets in plain text; only a hash is compared or a length is printed.
When AI assists with development, permissions are cut one layer further: AI does feature work only; changes to security settings, version control tracking, and deployment scripts always need the responsible lead to confirm first, and a security problem found by AI is reported, not fixed on its own. The reason is practical: when deployment relies on pulling the latest version, one wrong tracking change can cause files to be deleted, and the cost is downtime.
Phases show order, not a fixed number of days
After the first month, the system moves into long-term maintenance. The POLISH Engineering Team covers this stage with Polish+ takeover and long-term maintenance; how long-term maintenance works in practice can be seen in the long-term maintenance case alongside a Ministry of Education project.
Keep the Real World Out of the Test Environment
After a takeover, every test has to be certain it will not touch real users. In a test environment, email goes only to the operator, or is not sent at all; when an internal interface has no key configured, or the key is too short, it answers with not found to everything outside and looks as if it does not exist. Responses carry no personal data, and an email address is looked up only on the server, at the moment of sending, never through a public interface.
Tables are sorted by nature into three levels: modifiable, read-only, and insert-only. For a modifiable table, a restorable copy is saved before any change; the table holding member personal data is read-only; every other existing table only gets new tables alongside, never replacements. The production database is touched only with read-only tools, and database change scripts must be safe to run repeatedly.
A configuration change that could take down the whole site follows a fixed procedure: back up the entire configuration, test in isolation, apply with automatic rollback on failure, and verify item by item over the public network. File sync scripts include safeguards: a script stops if the source directory is empty, so an incomplete build cannot wipe the remote; it stops if more than half of the remote files would be deleted, unless a flag says otherwise; and after every upload it compares hashes to confirm the files match.
When a Full Code Audit Does Not Fit
A full code audit is not recommended in the following situations.
- The system is about to be replaced: the value of an audit is realized through the repairs that follow, and when the system is nearly retired, the investment does not pay off.
- The source code is out of reach: a full code audit reads the code, and without it the work cannot proceed.
- The goal is only a cheap maintenance contact: an audit exists to see the current state before deciding how to repair it.
What to Prepare Before a Takeover
Three things need to be ready before work starts: the source code or access to it, the current deployment method, and the problems that have occurred in the last six months. With all three in hand, the scope and order of the audit can be judged. Once the materials are ready, describe the current system and arrange a conversation with the development team.




