Technical due diligence checklist for software acquisitions
This checklist finds what a software company's code and engineering practice will cost a buyer after the deal. It is for the buyer's engineer, the corporate development team that briefs them, and the founder preparing to sell, who should run it first. Each item says what to check, why a buyer cares, and how to check it.
How to use it
- Work on a full clone of each repository, not a shallow one. Most checks read the history, and a shallow clone hides it.
- Run the commands from the repository's root. They are starting points: read what they print, then read the code it points at.
- Treat each finding as a question, not a verdict. Record the evidence for it: the commit, the path and the line.
- Most items need only the repository and a terminal. The last area lists what the code cannot tell you. You ask those in the room.
1. Code and architecture
- Where the size sits. Find the largest files and the
files that change most. A very large file that changes every week is
where merge conflicts, slow reviews and single-owner knowledge
concentrate. It is the first thing that slows an inheriting team down.
git ls-files -z | xargs -0 wc -l 2>/dev/null | sort -n | tail -20 git log --since="12 months ago" --format= --name-only | sort | uniq -c | sort -rn | head -20
- Runtimes and frameworks, and their age. An
end-of-life language version or framework is a migration the buyer pays
for. Read the version files and the base images.
git ls-files | grep -E '(^|/)(\.nvmrc|\.python-version|\.tool-versions|go\.mod|Dockerfile)$' git grep -n '^FROM' -- '*Dockerfile*'
- Vendored code and submodules. Copied-in third-party
code carries its own licence and receives no upgrades. A submodule may
point at a repository that is not in the data room.
git ls-files | grep -E '(^|/)(vendor|third_party|external)/' | cut -d/ -f1-2 | sort -u cat .gitmodules 2>/dev/null
- Tests. Check that a suite exists, that CI runs it,
and that bug fixes add tests. A fix without the test that would have
caught the bug can come back.
git ls-files | grep -Eci '(^|/)(tests?|spec|__tests__)/|[._-](test|spec)\.'
- Imported history. A huge first commit or a bulk
import means the history before it is not visible. Ask where that code
came from and who wrote it.
git log --reverse --shortstat --date=short --format='%h %ad' | head -20
2. Engineering process
- Code review. Check that changes reach the main
branch through a reviewed pull request. Unreviewed changes put the whole
weight of quality on each author.
gh pr list --state merged --limit 100 --json reviewDecision --jq '.[].reviewDecision' | sort | uniq -c gh api repos/{owner}/{repo}/branches/main/protection # needs admin access - Continuous integration. Read the pipeline config.
Note what runs on each change: build, tests, lint, security scans. A
pipeline that only builds is not a test gate. Then check whether recent
runs pass.
ls .github/workflows .gitlab-ci.yml .circleci Jenkinsfile 2>/dev/null gh run list --limit 20
- Releases. Check how often code reaches users and
whether a customer can tell which version they run. Look for tags, a
changelog and release notes.
git for-each-ref --sort=-creatordate --format='%(creatordate:short) %(refname:short)' refs/tags | head -20
- Cadence. Commits by month show whether the work is
steady, and whether it slowed before the sale.
git log --since="24 months ago" --format=%ad --date=format:%Y-%m | sort | uniq -c
3. Key-person risk and the team
This area is about how knowledge is spread, not about any individual. Read the shape of the numbers. Leave names out of anything written that circulates, and take specific questions to a private conversation with the seller. A small company with one dominant contributor is normal. The buyer plans for it; it is not a defect.
- How many people ship. Count the people who
committed in the last year.
git shortlog -sn --no-merges --since="12 months ago" HEAD | wc -l
- How concentrated the work is. This prints the
largest single share of last year's commits as a percentage, with no
name.
git shortlog -sn --no-merges --since="12 months ago" HEAD \ | awk '{t+=$1; if (NR==1) m=$1} END {printf "%.0f%%\n", 100*m/t}' - Who could fix it tomorrow. For each critical area,
such as billing, authentication or the data pipeline, count the people
who changed it in the last year. An area only one person has touched is
a question for the room.
git log --since="12 months ago" --format=%ae -- path/to/billing | sort -u | wc -l
Then ask:
- If one engineer left next month, which systems would stop moving?
- Who holds production access, the cloud root account, the domain registrar and the code-signing certificates? Are those credentials in a company vault?
- Which people does the product depend on, and what keeps them through the transition?
- How long does a new engineer take to ship a first change?
4. Security
- Known vulnerabilities in dependencies. Check every
lockfile against a vulnerability database. A match is a question, not a
breach: ask whether the flawed code ships and is reachable. Most are
fixed by an upgrade.
osv-scanner scan source -r . npm audit; pip-audit; cargo audit # per ecosystem - Advisories against the product itself. Count the
published advisories, their severity and how fast each was fixed. Check
for a
SECURITY.mdand a way to report a vulnerability.gh api repos/{owner}/{repo}/security-advisories --jq '.[] | [.published_at[:10], .severity, .summary] | @tsv' - Secrets in history. A credential deleted from the
working tree is still in the history, and deleting the file does not
revoke it. Scan the history, then ask whether each one was rotated.
gitleaks git .
- Risky patterns. Look for shell commands built from
strings, SQL built by concatenation, and outbound calls with no timeout.
Ask where each one's input comes from.
git grep -nE 'shell=True|os\.system\(|child_process\.exec\(' - Dependency upkeep. Check for automated update
tooling, and whether its pull requests get merged or pile up.
ls .github/dependabot.yml renovate.json .renovaterc* 2>/dev/null
5. Licences and IP
- The licence of the code. Read the licence that
covers the repository and check it against the buyer's plans. A copyleft
or source-available licence (AGPL, BSL, SSPL) limits how the code can be
hosted, sold or closed.
ls LICEN[CS]E* COPYING* 2>/dev/null git grep -h 'SPDX-License-Identifier' | sort | uniq -c | sort -rn
- Licence changes in history. Code taken under an
earlier licence stays under it, so both licences matter. A relicence also
raises the question of who owned the contributions it covered.
git log --diff-filter=ADMR --date=short --format='%h %ad %s' -- 'LICEN[CS]E*' 'COPYING*'
For example, RustDesk relicensed from GPL-3.0 to AGPL-3.0 on 2022-06-27. Its public reading shows the change with the licence file behind each side. Every licence change in Borehole's library is counted on one page. - Copyleft dependencies. A GPL library in a
distributed product, or an AGPL library in a hosted one, can oblige the
owner to publish source. Build a licence inventory and read every
copyleft entry.
npx license-checker --summary; pip-licenses; cargo deny check licenses syft dir:. -o spdx-json > sbom.json # or a full SBOM - Contributor provenance. Find out who wrote the code
and whether each of them assigned it: employees, contractors, agencies
and outside contributors. Commit email domains show the mix as counts,
with no names. Look for a contributor licence agreement or sign-offs.
git log --since="24 months ago" --format=%ae | sed 's/.*@//' | sort | uniq -c | sort -rn git log --format=%b | grep -c 'Signed-off-by'
- Copied code. Ask about code from a previous employer or from answers sites. Search for licence headers that do not match the project's own.
- AI-generated code disclosure. Ask which AI coding
tools the team uses, under which terms, and what review their output
gets. The answer feeds the IP warranties. Configuration files and commit
trailers show some use. Their absence proves nothing.
git ls-files | grep -iE '(^|/)(\.cursorrules|\.cursor/|CLAUDE\.md|AGENTS\.md|copilot-instructions\.md)' git log -i -E --grep='co-authored-by:.*(claude|copilot|cursor|devin|aider)' --oneline | wc -l
6. Operations
- Deploy. Find out how code reaches production. A
manual deploy from one laptop is both an outage risk and a key-person
risk. Look for pipeline deploy steps and infrastructure as code.
git ls-files | grep -iE '\.tf$|Dockerfile|docker-compose|helm|k8s|deploy'
- Observability. Check for an error reporter,
structured logs, metrics and tracing. Without an error reporter, a
customer usually reports a failure before anyone sees it.
git grep -liE 'sentry|rollbar|bugsnag|opentelemetry|datadog|newrelic|prometheus' -- ':!*.lock' ':!*lock.json'
- Request tracing. Check that a request or correlation id follows a request through the logs. Without one, a customer's failure cannot be picked out of everyone else's log lines.
- Runbooks. Look for written procedures for deploys,
rollbacks and incidents. A runbook is knowledge that survives a
departure.
git ls-files | grep -iE 'runbook|playbook|on-?call|incident|postmortem'
- Data and recovery. Check that schema migrations live in the repository. Then ask when a restore from backup was last tested.
7. Product maturity
- Versioning and change. Check whether the public API is versioned, whether a changelog exists, and how features are deprecated.
- Backlog. Count open issues and look at the age of
the oldest bugs.
gh issue list --state open --limit 1000 --json number --jq length
- Fixes against features. A rising share of fix
commits can mean the product is fighting its own debt.
git log --since="12 months ago" --oneline | grep -ciE '\bfix' git log --since="12 months ago" --oneline | wc -l
- Customer-specific code. Look for long-lived
branches, forks and flags kept for single customers. Each one is a
product the buyer also maintains.
git branch -r | wc -l
- Documentation. Check that a new engineer can set up and run the system from the README in a day.
8. What the code can't tell you
A repository holds none of the following. Ask for each in the room, and ask for the documents behind the answer.
- Contracts. Which customer contracts carry SLAs, source-code escrow or exclusivity? Which third-party licences need consent to assign on a change of control?
- On-call. Who is paged, how often, and for what?
- Incidents. The last two years of outages and security incidents, with their postmortems and any breach notifications.
- Roadmap. What has been promised to customers and not built?
- Cost. What hosting and third-party services cost, and how that grows with customers.
- Compliance. SOC 2 or ISO 27001 reports, penetration tests, and what was fixed after each.
- Access. Who outside the company can reach production, the code or customer data.
Doing the repository part in minutes
Borehole runs the repository part of this checklist. It reads a repository's git history and working tree, runs 106 diligence checks, and rates it on seven dimensions:
- Architecture & Codebase — including AI provenance
- Engineering Process & SDLC Maturity
- Organization & Key-Person Risk
- Product & Engineering Maturity
- Security & Reliability Posture
- Observability & Operations
- Fit with the Acquirer
The last one is not in any repository. Every finding cites its evidence: the commit, path and lines it rests on, or, when something is missing, what was looked for. What the code cannot answer becomes a question for the room.
- A public repository. Paste its address. The survey is free.
- A private repository. The collector reads it on
your machine. Your repository stays there. What it sends: never whole files; facts and short excerpts of up to 120 characters where a check matched.
The dry run prints everything a survey would send, and sends nothing. It
needs no purchase, no account and no collector token.
pip install borehole borehole collect . --dry-run # prints everything that would be sent, sends nothing borehole collect . # sends it, returns a report
A survey of a private repository spends a Report key. A Borehole Report costs €249 launch price · €499 after launch. What happens to your code lists everything the collector sends. A buyer whose target is private reads Surveying a target.
A worked example: RustDesk
RustDesk is an open-source remote desktop application. Its
public reading shows what Borehole
found at commit b5d8b97, across 11,491 commits. Two
findings map to this checklist:
- Licences. The root licence changed from GPL-3.0 to AGPL-3.0 on 2022-06-27. Code taken before that date stays under GPL-3.0.
- Tests. 11 of 565 bug fixes in 24 months added or changed a test.
The full report covers every dimension, with the questions for the room.
The checklist on one page
Code and architecture
- Largest and most-changed files
- Runtime and framework versions against end of life
- Vendored code and submodules
- A test suite, run by CI; fixes that add tests
- Bulk imports and where that code came from
Engineering process
- Reviewed pull requests; branch protection
- What CI runs, and whether it passes
- Release tags, changelog and cadence
Key-person risk and the team
- People committing in the last year
- Concentration of the work, without names
- People who can change each critical area
- Who holds access, accounts and certificates
Security
- Dependency vulnerabilities, and whether they ship
- Advisories against the product;
SECURITY.md - Secrets in history, and their rotation
- Shell, SQL and timeout patterns
- Automated dependency updates
Licences and IP
- The licence, against the buyer's plans
- Licence changes in history
- Copyleft dependencies
- Contributor provenance and assignment
- Copied code
- AI-generated code disclosure
Operations
- How deploys run
- Error reporting, logs, metrics, tracing, request ids
- Runbooks
- Migrations and a tested restore
Product maturity
- API versioning and deprecation
- Issue backlog and its age
- Fixes against features
- Customer-specific branches, forks and flags
- Setup documentation
For the room
- Contracts, escrow and change-of-control consents
- On-call and incidents
- Roadmap promises
- Hosting cost
- Compliance reports and penetration tests
- Outside access to production