To check a security claim that crosses repositories, trace one actor and one request from entry to effect, and pin the version of every service, shared package, and configuration that could change the answer. Write down each handoff: what value crosses it, which identity is carried, which object is selected, and which control actually runs. A search result in three repositories is a lead, not a trace. Your conclusion changes if the deployed service uses a different package version, a gateway supplies a missing check, or a worker handles the action asynchronously.
Start with the claimed harmful action, such as “a customer could download another customer's invoice,” then work backward and forward until you can support or defeat it. OWASP's object-level authorization guidance makes the central question concrete: the service acting on an object ID must check whether this user may perform that action on that object. A logged-in route alone does not answer it.
Worked case: an invoice link crosses four repositories
Illustrative case, not a customer assessment or an executed exploit: Northstar sells a B2B billing application. An internal review raises this claim: “An ordinary account user can request a download for an invoice belonging to another account.” The request starts in portal-web, reaches billing-api, asks document-service for a file, and uses authorization code from account-policy. The file may instead be delivered through a background export worker. No one has yet shown which versions were deployed together.
The staff engineer's first job is to turn the broad sentence into a bounded path:
Actor: ordinary signed-in user of account A. Action: request invoice B's PDF by ID. Proposed effect: receive B's document. Decision: does the deployed path authorize this actor for that invoice before any document bytes or reusable download capability are returned?
That statement separates a proposed effect from proof. A route accepting an invoice ID and a document service returning PDFs do not by themselves establish a cross-account read. Equally, an authentication decorator does not establish invoice ownership. The outcome depends on the checks along the same request path.
1. Pin the deployed version set
Record immutable revisions, not branch names or a screenshot of “main.” Git's revision documentation describes how a revision name resolves to an object; a moving name must be resolved before using it as evidence. In a multi-repository system, one commit per repository is still insufficient if the service consumes a published package or container image built from a different commit.
For Northstar, the engineer asks the release owner for the deployment manifest, image digests, the account-policy package version used by billing-api, and the gateway configuration active in the relevant environment. The release owner may have only a time-stamped deployment record. Mark the mapping unknown until the image or package can be tied back to source. If the shared policy is a Git submodule, the parent stores a pinned gitlink commit rather than the submodule's contents; Git's submodule documentation explains this distinction.
Reconcile two individually convincing, incompatible traces
The first reviewer opens the current billing-api branch, which calls account-policy version 3 and checks invoice ownership. A second reviewer opens the production release source and finds a version 2 caller that checks only membership. Both line citations are accurate. They answer different questions.
The fictional release evidence resolves the difference as follows. Labels such as API-17 are shortened artifact identities for readability; a real restricted record needs full immutable identifiers.
| Component | Current source reviewed first | Production set at 09:00 | Consequence |
|---|---|---|---|
| Billing API | Commit A18, policy V3; object check before downstream request | Image API-17 → commit A17 | A18 cannot prove what the deployed route enforces. |
| Policy artifact | Workspace V3 | API-17 package manifest and matched package content → V2 | Inspect V2's actual membership-only decision. |
| Document service | Commit D9 | Image DOC-9 → D9, matching build record | D9 requires a verified account identity and scopes invoice lookup to that account. |
| Gateway | Current proposed rules G6 | Active G5 routes the synchronous endpoint | Proposed G6 cannot establish current exposure. |
| Browser assets | Current portal branch | Asset digest missing | User-interface behavior unknown; direct API request is the bounded server question. |
| Export worker | Queue code in repository | Running image not supplied | Asynchronous export remains a separate unresolved path. |
The version join reveals a weaker API check than current source suggests, but it does not establish a successful foreign-invoice read. The deployed document service still has an independent control. The reviewer must trace the identity delivered to D9, not stop at discovering V2.
At 11:00, the release owner supplies a proposed rollout to API-18. That creates a second version set, not an edit to the 09:00 history. If the rollout has multiple running versions, record them separately until the old build is gone. A planned package upgrade is not retroactive evidence for the old request path.
2. Follow the value and the authority
At the browser edge, find the link or API call that supplies invoiceId. Then inspect the server route that receives it. The actor is the authenticated account user; the object is the invoice selected by the ID. Record which claims or session attributes are trusted and where the account identity is derived. Never treat a client-supplied account ID as proof of authority.
At the billing-api handoff, read the exact call to account-policy. Does it ask “can this user read this invoice?” or only “is this user signed in?” Does it use the invoice's stored owning account, or compare two caller-controlled fields? Follow the return value into the branch that asks document-service for the file. A check can exist in a shared package yet be absent from this route, ignored after an error, or called after a download token is issued.
Next inspect document-service. It may recheck object ownership, trust an internal token, or accept any request from billing-api. Record the service identity and the exact scope of its authority. If the API returns a signed URL, the effect may occur when that URL is redeemed, so include token generation, lifetime, and storage policy in the trace. If the route starts an export job instead, follow the message fields and the worker's authorization before calling the path complete. Each alternative is a separate path; do not mix a protective check from one branch with an unprotected effect in another.
3. Record every edge with its contrary evidence
A compact trace is more useful than a pile of screenshots. For the illustrative route, the engineer builds this handoff record at the pinned revisions:
| Edge | Source evidence to inspect | What it appears to establish | What could defeat or change it |
|---|---|---|---|
| User → API | Route definition and request schema | User can supply an invoice ID | Gateway might replace or restrict the ID |
| API → policy | Actual caller and package artifact | Route checks login but ownership call is not visible | A wrapper or middleware might check the invoice |
| API → document | Request fields and service authentication | API forwards the chosen ID | Document service might enforce stored owner |
| Document → file | Lookup and response/token issuance | PDF or download capability is returned | Storage policy might independently bind account |
| API → worker | Queue publisher and worker consumer | An alternate export path may exist | Worker may use a separate validated object selection |
Every row should point to a repository, pinned revision, path and exact range in the internal review note. Add a confidence label such as observed, inferred, or missing. “The function is named authorizeInvoice” is not evidence of what it checks. A diagram may show the suspected relationship, but only the source and deployment record can support that relationship for this version set.
Resolve the synchronous claim at the matched versions
In the illustrative production set, A17 derives account A from a verified session. V2 establishes membership in A. The API then sends account A and selected invoice B to D9 over the authenticated internal path. The source for D9 validates that account context and loads the invoice with both the account and invoice key. There is no fallback to a global invoice lookup after a miss.
The completed edge record therefore reads:
| Edge | Resolved evidence | Effect on claim |
|---|---|---|
| Caller selects invoice B | A17 route accepts the ID | Actor controls the selector; this alone is not unauthorized access. |
| A17 establishes account A | Session derivation and V2 membership check | Account context is not taken from the caller's invoice or account field. |
| A17 sends A plus B to D9 | Matched client/server contract and service authentication | The document service receives the trusted account context used by the caller. |
D9 reads (A, B) |
Tenant-scoped accessor with no global fallback | Invoice owned by B is not returned under account A. |
| D9 misses | Error branch returns no bytes and issues no download token | The proposed synchronous read is defeated in this source/configuration trace. |
The narrow finding disposition is “reject the proposed cross-account synchronous download for the matched 09:00 version set.” This rejection concerns cross-account reads only. If Northstar also restricts invoices among members of the same account, tenant-scoped lookup does not establish that finer entitlement; give that question its own obligation. The API's membership-only helper can still be a defense-in-depth concern if the architecture requires each service to enforce object permissions. State that as a separate requirement and change request; do not preserve a contradicted exploit claim to justify the improvement.
This conclusion depends on the account context being authentic and the scoped accessor actually governing the route. A forged account header accepted by D9, an unscoped fallback, or another document route would reopen the path. Those are concrete counterchecks for the reviewer, not implied assurances that all internal traffic is safe.
Preserve the time boundary during a rolling release
The 09:00 version set and the proposed 11:00 rollout can coexist. A request might still reach API-17 while another reaches API-18. The reviewer should identify which combinations the release process actually permits, rather than analyzing an imaginary system made from the newest version of every repository.
If D9 serves both API versions, its scoped lookup may remain reusable evidence for the same contract. The changed API caller still needs its own review, and any worker or delivery change needs its own binding. Record when older artifacts stop serving or launching work before describing the new version set as the whole deployment.
This timeline also protects historical statements. A later stronger API check can improve the current design without proving that an earlier production concern was exploitable or that no earlier exposure occurred. The trace should keep those questions separate and give each the evidence appropriate to its time.
Give the worker its own unresolved record
The asynchronous export does not inherit the synchronous result. Its publisher stores an invoice selector in a job, and its consumer may call a different document method. The worker image and policy artifact remain unmapped. Record “export delivery under investigation; platform owner to supply running image and source mapping, then reviewer traces job identity to document read and recipient.” Neither the new V3 helper nor D9's synchronous endpoint settles that branch.
The handoff to the release owner has three lines: current production synchronous claim rejected on matched evidence; proposed A18 upgrade assessed separately for its changed behavior; asynchronous export unresolved with exact missing artifacts. No runtime exploit or production request is claimed to have been executed. An independent review can reopen the pinned records and challenge the joins, rather than reconstructing which branch each screenshot came from.
For the next cross-repository claim, start a version-set table before opening search results. When a handoff points into an external Git submodule or missing package, use the submodule and dependency decision guide. To keep many such traces reviewable, use the large-codebase evidence workflow.