Guide 161 pillar

How to scope security review for a large monorepo

Scope a large monorepo review around deployment units and security boundaries, with a bounded starting map and honest limits on repository-wide inventory claims.

Scope a large monorepo by deployed application and security boundary, then record exactly which code and controls the review examined. Start with an immutable revision and an inventory that identifies the selected path, its dependencies and any enumeration limits. Complete the inventory before making repository-wide totals; a bounded release review can begin while unrelated subtrees are still being enumerated. Map deployable services, shared libraries, entry points, protected data, authorization decisions and owners. Select a small number of high-consequence paths for deep review, including the shared controls they depend on. Publish a coverage ledger that separates discovered files from files actually reviewed.

The answer changes when the request is a release gate, an incident response, a first baseline or a narrow pull request: each needs a different scope and deadline. A targeted review can be useful without claiming that the whole repository is safe. If inventory is truncated, a submodule is unavailable, or a control requires deployment evidence, label the result partial or unresolved. OWASP's secure code review guidance distinguishes baseline reviews from change-focused reviews and calls for architecture, entry point, data-flow and business-logic analysis.

Choose the decision before choosing the files

“Scan our monorepo” is too vague to be a review instruction. Ask what decision the result must support:

Decision Useful initial scope What the result can say
Can this pull request merge? Changed code, affected callers and shared controls, relevant deployment/config changes Whether the declared change introduced or retained a supported issue; unrelated code remains outside scope.
What should we fix this sprint? Deployed customer-facing paths and open findings with evidence, impact and ownership A defensible work order, with known gaps; not a count of all vulnerabilities.
Is a new service ready to release? Its entry points, identity, tenant/object access, data outputs, dependencies and deployment policy Whether the named controls were reviewed and what remains to verify.
Where should a first baseline begin? Asset and service map followed by highest-impact boundaries An ordered review program and its first completed slices.
Did a known issue get fixed? Original actor-to-effect path at a new pinned revision, including alternate routes Fixed, still present or inconclusive for that claim.

Record the requester, deadline and authorized assessment method. A release decision may require runtime or configuration evidence that static source inspection cannot supply. A short budget may support triage, but it must not silently become a whole-repository verdict. OWASP's attack surface analysis guidance points reviewers toward exposed functions and the authentication, authorization, logging and validation code protecting them.

Build an inventory before a component map

Pin a commit or equivalent immutable revision. Enumerate the selected repository tree with continuation when the provider response is paginated or truncated. Classify each path by role: deployable application, shared package, infrastructure, migration, CI configuration, test, documentation, generated output, vendored component, submodule or unavailable content. Record why a path is excluded. Do not equate a directory listing with a content read, or a content read with a security review.

The inventory should answer three plain questions: What exists? What could not be acquired? What was deliberately left for later? A submodule reference does not include the child repository's contents; Git's submodule documentation describes the separately recorded commit and checkout behavior. Do not claim the child code was covered merely because a parent repository listed the link. A generated file may be omitted from line-by-line review, but its generator, template, dependency or output behavior may still be security relevant.

Next, turn paths into components. A component is a security-relevant unit such as an API, job runner, shared identity package or infrastructure boundary. Verify a proposed unit against entry points, build manifests, dependency edges, deployment configuration and data stores. Folder names are clues, not proof that two folders run in separate trust zones. One service can deploy from several folders; one folder can support multiple services.

Use CODEOWNERS as an ownership hint, then verify the current maintainer and deployment owner. GitHub's CODEOWNERS documentation explains how matching patterns request review; it does not establish who can explain a service's runtime security controls. A stale or absent entry should lead to an ownership question, not an automatic exclusion.

Illustrative case: one release, several coupled components

Illustrative case. Northstar, a fictional B2B SaaS company, keeps apps/portal, services/accounts, services/billing, workers/export, packages/identity, packages/tenant-db, infra/, and a generated client under one repository. The AppSec lead has two days to advise on a billing release. The exact counts and service names here are invented. No code was scanned or exploited for this example.

The first review target is the customer path: an account member requests an invoice export; the portal calls the billing API; the API creates a job; the worker reads invoices and produces a download. The meaningful question is whether a member of account A can cause account B's invoice to be read or delivered. This path requires looking beyond services/billing: the API's identity adapter, packages/tenant-db, the worker's job authorization, storage lookup and download route are all relevant. OWASP API1:2023 describes the difference between access to an endpoint and permission to act on a particular object.

Negotiate the scope when the release owner says “billing only”

The release owner proposes reviewing only services/billing/**. The identity maintainer points out that the release also changes packages/identity: a missing account selection now falls back to the user's first membership. Both billing and the admin service consume that helper. The worker does not; it uses a job record. A path filter matching the release team's folder would omit the change with the widest effect.

The AppSec lead does not respond by promising to review the entire monorepo in two days. The revised charter separates the release decision from the larger backlog:

Charter field Initial request Agreed revision and reason
Decision “Review billing security” Decide whether export and the changed account-selection behavior have enough evidence for this release.
Source boundary services/billing/** at illustrative commit b41c9e2 Billing route, identity helper, tenant database accessor, export worker, download route and their release bindings.
Additional caller None Admin bulk action: one materially different consumer of the changed helper. Review account selection and object use, not the whole admin product.
Alternate producer Ordinary export only Include support retry because it can select the same invoice and publish to the same worker.
Deferred baseline Unspecified Other admin functions, analytics and unrelated account-management controls. Named continuation after this release; no claim of review now.
Generated client Exclude directory Inventory it; read the schema and emitted account/invoice selectors used by this flow. Defer mechanical transport methods.
Critical external evidence “Platform to confirm” Active API/worker image mapping, retry route policy and admin route policy, each with a platform owner and decision deadline.

The missing optional fraud submodule gets a sharper disposition too. In the fictional source, invoice export does not import or call it and its deployment is a separate fraud job. It remains inventoried and unavailable, but is non-blocking for the declared export question. If the call graph or build mapping contradicts that separation, it returns to scope. “Missing” is not always “release blocker”; the connection to the decision matters.

Close the charter with actual findings, including a rejected lead

Suppose the bounded source review finds that the retry producer accepts an invoice ID without checking ownership. That initially looks like the main concern. Reading the worker changes the answer: it reloads the authorized job, takes the invoice ID from the stored job rather than the message, and uses a tenant-scoped accessor. The proposed replacement-ID path is defeated at that source revision. Production applicability still requires the worker build mapping.

The admin caller is more consequential. It accepts an account selector but invokes the changed helper without passing it; the fallback silently chooses the user's first membership. The admin mutation then uses that chosen account. This supports an account-selection correctness concern with possible security consequences, which need the caller's intended delegation policy and deployment evidence. It does not support a cross-tenant exploit merely because the selected account differs from the request.

The final charter receipt therefore says: “Export replacement-ID claim rejected in the examined worker source; running worker mapping pending. Admin account-selection change needs an explicit product rule and caller correction before its release conclusion. Other admin controls remain deferred.” The scope expansion paid for a different answer, rather than a longer file list. The release owner can now separate the worker mapping request from the admin behavior decision.

Make the scope portable to the next reviewer

The charter identifies the question and its required edges; the receipt records what was actually examined. For Northstar, the changed helper's two selected callers and the worker's stored-job binding need separate outcomes. A source range in the helper cannot stand for every importer, and a missing production mapping cannot become a pass because the deadline arrived.

Use the following ledger to preserve those distinctions. Put the broader requirement/component measurement in its own record rather than adding a repository-wide “percent secure” badge to this release decision.

Keep a coverage ledger that survives handoffs

Use one row per component/control obligation, plus a summary of source states. The ledger should distinguish at least discovered, acquired, indexed or searchable, retrieved for the reviewer, and actually examined. The denominators differ. A repository may have a complete inventory while only two customer paths receive close review. A search index may contain a file nobody read for a specific claim.

Component and question Source state Review state Evidence or reason Next owner
Billing API: bind invoice to account Acquired and examined Reviewed for selected export path Exact revision and source locations in technical record Billing engineer
Export worker: validate job and invoice owner Acquired and examined Replacement-ID lead defeated in source Stored job selects invoice; running build mapping pending Platform owner
Shared identity package: session subject Acquired, searched and examined Reviewed for selected callers Two callers checked; other consumers deferred Identity maintainer
Optional fraud submodule Pointer discovered; content unavailable Outside bounded export question Separate fraud deployment; revisit if a dependency edge appears Platform owner
Analytics batch Inventoried Excluded by declared release scope Does not serve billing release; revisit on analytics review Analytics lead

Capture the reviewer, time, methodology/version, immutable source revision, scope policy, queries or ranges examined, and the outcome. Where tools allow path filters, check that they affect analysis rather than only when a workflow starts. GitHub's CodeQL workflow configuration reference distinguishes scan paths/paths-ignore from event filters with the same names. A CI job that did not run on a path change is not evidence that the path was analyzed elsewhere.

Do not compress this ledger into a single “percent secure” number. If a dashboard needs a progress figure, choose an honest denominator such as “six of nine planned obligations completed,” and also show that those nine were selected from a larger inventory. Newly found components can expand the plan. Approved findings can exist in a partially reviewed component; a no-finding slice can still be incomplete.

Make the stop rule and next review explicit

Before work starts, define what ends this pass: deadline, available analyst hours, monetary scan budget, required paths or a release decision. Reserve time to validate candidate findings, read counterevidence and write the scope receipt. More shallow file visits are a poor trade if no one can explain the reported issue to its owner. A source line can support a claim but cannot alone establish deployment reachability or a successful exploit.

At the stop point, use one of three statements: completed declared scope, partial declared scope with listed unfinished work, or blocked by missing authority/evidence. Keep “no verified issue found in the examined path” distinct from “safe.” If a critical route is blocked, the owner of the release decides whether more evidence is required; the reviewer must not turn missing context into a pass. Plan a linked continuation for the highest-value unfinished obligation, bound to a new budget and the same or a newly pinned revision.

For the Northstar case, the AppSec lead can tell the release owner: “The worker source defeats the retry replacement-ID lead; its running version still needs confirmation. The changed identity helper also affects an admin caller, whose account-selection rule must be resolved. The unrelated fraud submodule remains unavailable and outside this bounded question.” That is more actionable than a giant alert list or a green monorepo badge.

Next step: Choose one actual release or open risk decision and fill the scope charter before assigning review time. Use the boundary-mapping guide to decide what must travel with the selected path, then the budget guide to order the work.