Guide 001 worked guide

How to triage a security finding before opening a developer ticket

Use six evidence gates to check a scanner alert, confirm the affected path, and send developers only findings they can act on.

A scanner says “broken access control.” The developer says “we already check the account.” Both can be right about the code they looked at. The useful triage question is whether the request in the alert must pass the check the developer identified.

Turn the alert into one claim, follow its input through the controls to a harmful effect, and obtain the evidence that could defeat it. A checked control can justify rejection. A missing decisive fact requires an investigation. A confirmed finding needs corroborated evidence and review proportionate to its consequence and uncertainty. Precautionary repair or containment can proceed while the technical conclusion remains open, with its purpose recorded separately.

The walkthrough below takes an invoice alert through those choices. It shows what to request, what the evidence changes and the handoff each outcome deserves. The method requires no attempt against production.

Start with a claim small enough to settle

Everything in the Bluefin case—code, identifiers, deployment records and outcomes—is illustrative. No customer system was assessed, no requests were executed and no independent human review of this example is claimed. Treat the supplied artifacts as an exercise in deciding what a real reviewer would need to verify.

Bluefin Billing lets signed-in customers download invoice PDFs. An alert points to a lookup by invoice ID at revision ABC123. The handler has login middleware, but no visible account comparison. The alert title says “Any customer can download any invoice.”

That title combines several assertions: an ordinary customer can reach the route; the customer controls the ID; the lookup can select another account's invoice; nothing later denies delivery; and the customer can obtain a valid foreign ID. The highlighted line establishes only part of that chain.

Rewrite the claim before discussing severity:

Given an ordinary account-A session and a valid invoice ID owned by B, the direct download route may return B's PDF because no mandatory check binds the selected invoice to the caller's account.

This is narrower than the scanner title. It makes the foreign ID a prerequisite, rather than inventing a way to discover every customer's invoices. It also identifies the observation that would defeat the claim: a compulsory ownership check before delivery.

Keep a small intake packet:

Input Bluefin record Why retain it?
Alert identity BF-42, rule object-access, run scan-18 Preserve the original allegation when the conclusion changes.
Source Repository billing, revision ABC123, direct-download handler Both reviewers must assess the same code.
Expected rule Ordinary users may download invoices belonging to their current authorized account Decide what would be unauthorized before judging implementation.
Missing evidence Document lookup, delivery wrapper and deployed route binding These can change the verdict; another severity estimate cannot.

If the revision cannot be recovered, repair that evidence first. A current branch with similar code is not necessarily the source of yesterday's alert.

The developer's check is real, but on another path

The developer supplies the invoice-list function. It includes an account predicate, and the UI only displays invoices from that list. This is useful evidence about the list. Now compare its callers with the direct download:

Invoice list
  trusted current account
    → list_invoices_for_account(account_id)
    → only that account's invoice records

Direct download
  trusted current account + requested invoice ID
    → download_invoice(invoice_id)
    → invoice record → PDF delivery

The direct-download function receives the ID without the account. That difference identifies the next question; it does not yet prove disclosure. The delivery component could make an independent access decision.

Login and object permission are different checks. OWASP's API1:2023 guidance explains that a user may legitimately access an endpoint while lacking permission for the object selected through it. The Authorization Cheat Sheet recommends validating permissions for every request. In Bluefin, a list filter is relevant only if the download necessarily uses its authorized result.

The analyst's next message can therefore be specific:

The account filter protects the list call. Please provide the direct-download function, the delivery wrapper it calls and the route/artifact mapping for the assessed deployment. We need to establish whether that download checks the selected invoice's owner before returning its PDF.

This asks the developer for three bounded artifacts, not proof that the entire application is safe. The issue remains an investigation while they are missing.

Follow the missing evidence to a completed decision

Suppose the response contains the following simplified implementation. These are deliberately incomplete application fragments illustrating the relevant flow, not production-ready code:

function download_invoice(invoice_id):
    invoice = invoices.find_by_id(invoice_id)
    if invoice is absent:
        return not_found
    return delivery.read(invoice.pdf_key)

function delivery.read(pdf_key):
    return object_store.read_as_billing_service(pdf_key)

The remaining records establish how those calls are used:

Artifact Supplied fact Consequence for the claim
Route map for artifact billing-18 GET /invoices/{id}/pdf invokes this handler; ordinary account members can enter after authentication The alleged actor and entry match the selected deployment.
Build record billing-18 contains revision ABC123 and the displayed delivery implementation The review is not mixing current source with another deployed wrapper.
Invoice-store contract find_by_id selects globally by invoice ID; no tenant predicate or row policy applies on this call A foreign ID can select a foreign invoice in the supplied design.
Storage policy and wrapper The billing service can read the invoice-object area; the wrapper receives a key and makes no end-user decision Service permission to read storage does not supply the missing customer-to-invoice check.
Response path The returned bytes become the PDF response without another authorization step The lookup connects to delivery rather than ending at an unused value.

The decisive fact is not simply “no if statement appears in the handler.” It is that the supplied route, lookup, delivery and response evidence form one continuous path, with the plausible later control now accounted for.

On these exercise facts, the claim survives. A real independent reviewer should check the original artifacts rather than approve this summary: is the account supplied by trusted session context, is the lookup truly global, does this deployed handler use this wrapper, and is the response the one described? Approval follows that challenge, not the apparent completeness of the table.

The resulting disposition would be:

Supported for independent verification: An ordinary account member who possesses another account's valid invoice ID can select and receive that invoice through the assessed direct-download path. The list's account filter does not govern this request. No mandatory ownership comparison appears in the supplied lookup, storage or response chain.

Limit: This is a source-and-configuration argument, not observed exploitation. How a customer could obtain a foreign invoice ID remains unestablished. Exports and older deployments were not examined.

Identifier availability affects the practical difficulty of using the path. It does not insert an ownership check into the code. Keep that uncertainty visible without claiming either effortless bulk disclosure or safety from an opaque identifier.

Change one control and the correct answer changes

Now consider an alternative implementation, not an additional fact about the previous branch. The deployed download passes trusted account context to a mandatory delivery method:

function download_invoice(invoice_id, trusted_current_account):
    return delivery.read_invoice(invoice_id, trusted_current_account.id)

function delivery.read_invoice(invoice_id, account_id):
    invoice = invoices.find_by_id_and_account(invoice_id, account_id)
    if invoice is absent:
        return not_found
    return object_store.read_as_billing_service(invoice.pdf_key)

In this branch, the supplied route record binds every assessed direct-download request to this method. The account comes from current server-side authorization context, the lookup constrains both invoice and account, and the no-match branch returns before storage access. Those facts defeat the proposed foreign-invoice path.

The service's broad storage credential still exists, but the end-user decision happens before it is used. A source reviewer should examine that enforcement boundary rather than conclude that broad service access alone proves this particular disclosure.

A completed rejection record would say:

Reject BF-42 for the assessed direct-download route and artifact. The mandatory delivery call uses the caller's trusted account in the invoice lookup; a foreign-account ID produces no invoice and reaches no PDF read. Retain the source, query and route references. Reopen if account provenance, query predicates, delivery callers or route wiring change.

Do not write “authorization is safe everywhere.” This rejection covers one path. If an export uses the first branch's unscoped lookup, it needs a separate assessment. Likewise, a control shown only in a developer's local branch does not defeat an allegation about a different deployed artifact.

Give engineering the right kind of work

The three outcomes require different handoffs:

Evidence at the end of triage Work to assign What stays out of the verified fix count
Delivery implementation unavailable Evidence task: service owner supplies the wrapper and effective route binding by the agreed checkpoint The unresolved vulnerability claim
Mandatory account-bound delivery defeats the path Rejection with exact scope and a change trigger The rejected alert
Complete path survives independent verification and approval Fix task with the failed rule, affected path and positive closure conditions Speculation about ID discovery or unreviewed exports

For the approved first branch, the developer packet can be short:

Problem and impact: The direct invoice-download path returns the invoice selected by ID without binding it to the caller's account. A customer with a foreign invoice ID could receive another customer's billing document.

Evidence: Approved finding BF-42 at ABC123 and artifact billing-18; restricted references connect route, global lookup, delivery and response. The safe list query is a different call.

Required behavior: Authorize the current caller for the selected invoice before returning content or a usable download link. The implementation can use an account-scoped query or another compulsory resource check.

Closure plan: At a new revision, review the complete changed path. In an authorized environment with synthetic accounts, check a known own-account invoice and a known foreign-account invoice; legitimate access should work and foreign access should be denied. Account for relevant alternate delivery paths and confirm deployment separately.

A nonexistent invoice is a poor substitute for the foreign-account check: both a safe and unsafe lookup might return “not found” when there is no object. The closure evidence must distinguish the unwanted behavior from ordinary failure.

Keep urgency separate from the verdict

If the possible disclosure warrants a precaution, the service owner may restrict downloads while the investigation continues. Record the operational effect and restoration condition. That action does not prove the alert was true, and a later rejection does not make a reasonable precaution dishonest.

Similarly, a score does not supply missing evidence. CVSS v4.0 separates Base, Threat and Environmental information; the work order still needs the actual application path and business context. “High potential impact; delivery control not yet checked” is a more useful interim statement than an unsupported critical finding.

The triage record should travel with the decision. OWASP's reporting guidance emphasizes scope, limitations and enough detail for technical teams to act. Retain both the original allegation and the evidence that changed it, with sensitive source available only to authorized readers.

Let the evidence task and repair proceed together

The missing delivery wrapper need not stop every useful change. If the billing owner can make the direct lookup account-bound with a small, reviewable change, they can propose that repair while platform confirms the deployed wrapper. Record two separate outcomes: what the original evidence established and what the new implementation enforces. A precautionary repair does not retroactively confirm the initial allegation.

Choose review depth according to consequence and ambiguity. A routine account predicate may fit normal qualified code review; a disputed cross-service policy or consequential disclosure claim benefits from a reviewer who can challenge the full boundary. The exercise's independent approval step is one review arrangement, not a universal prerequisite for protecting data.

For the next alert, write the alleged harmful action and the one fact that would defeat it before opening a fix ticket. Request that fact, then finish the decision. Use the finding checklist to package an approved result, or the reachability walkthrough when the missing edge is the route into the code.