Guide 032 template

What a useful security fix ticket looks like

Write a security fix ticket with the failed rule, evidence, accepted owner and clear completion conditions, so a developer can start without reconstructing triage.

A useful security fix ticket tells the developer what harmful behavior to stop, where the relevant path is, why the existing control fails, what change would satisfy the policy, and how someone will verify it. It also distinguishes established facts from assumptions. A severity label and a file path are insufficient. If the claim is still under investigation, use a fact-finding task with a named missing check instead of filing it as a confirmed vulnerability. If immediate containment is needed, alert the service owner while that check continues.

The template below is deliberately short enough to fit a normal work item, with deeper evidence linked for reviewers who need it. Its purpose is to reduce back-and-forth without pretending that a suggested patch is the only safe implementation. OWASP's testing report guidance asks for clear scope, impact, remediation and limitations. The ticket is the operational version of that handoff.

A filled ticket, not just a blank form

Illustrative case, not a customer finding or an executed exploit: Finch Analytics lets a signed-in analyst filter reports by customer name. At revision OLD_SHA, an independent reviewer traces POST /reports/filter passing the request's customer_name into SQL text before execution. The intended behavior is to search only records the caller is allowed to see, with the name treated as data. A query builder elsewhere in the service uses bound parameters, but this route does not call it. The reviewer confirms that the route is in the current production artifact and that the effective database policy does not defeat query alteration on this path, then approves the precise source-supported finding for fix work. No attack strings were sent to a live system, and the exact rows an altered query might return remain untested.

Here is the work item a developer should receive:

Title: Report filter places a user-supplied name into SQL text.
Status: Verified and approved for fix work at OLD_SHA after independent source, route, and control review; no executed exploit is claimed.
Owner: Reports service team; AppSec reviewer for reassessment.
Affected scope: Finch Analytics, POST /reports/filter, revision OLD_SHA, selected reports service.
Problem: The route accepts customer_name from the request and builds SQL text before calling the database driver. A signed-in analyst can choose that value. The cited path does not pass the value as a bound parameter.
Potential impact: The input may alter the intended query, which could change which report rows are read. The exact effect depends on database behavior, the query and the account scoping in the executed statement; no live exploit is claimed.
Evidence: route → filter service → query construction → driver call, with exact source links at OLD_SHA; separate note identifies the neighboring safe builder but shows it is not called here.
Requested outcome: Keep the SQL structure fixed and bind the name as a value using the service's approved database API. Preserve or add the account scope. Any user-selectable sort column must come from an explicit allow-list because column names are not ordinary value parameters.
Acceptance: At a new immutable revision, AppSec can trace the route to a parameterized query with trusted account scoping; normal searches still work; an authorized staging check shows input is treated as data and cannot change query structure. Record the test artifact and scope.
Unknowns: The exact rows an altered query could return and behavior of other query-helper callers have not been tested; do not describe a customer-data disclosure as observed.
Next review: Reports developer links the patch; AppSec reassesses this path and any other callers of the changed query helper.

OWASP's SQL Injection Prevention Cheat Sheet explains why prepared statements with bound values keep SQL code separate from user data. It also distinguishes value binding from cases such as dynamic table or column names, for which an allow-list or redesign is needed. The ticket asks for the security property, not a copy-pasted patch whose framework behavior has not been reviewed.

Copyable ticket template

Use this for a supported finding or a clearly labeled precautionary repair. If a decisive route or control fact is missing, include the investigation owner and deadline; do not describe the claim as confirmed. A safe correction can proceed while applicability is checked. Replace bracketed text with facts.

Title: [actor or input] can [harmful action] on [asset or operation]
Finding decision: [finding ID, evidence status, reviewer and review date]
Evidence basis: [source-supported / runtime-observed if an authorized check occurred]
Owner: [code owner]       Security reviewer: [name or team]
Affected scope: [repository, service, route/job, immutable revision, deployment if known]

Problem: [one paragraph: actor, controlled input, entry, failed or missing control, effect]
Expected rule: [one sentence describing the behavior that must hold]
Evidence: [links to exact old-revision source and relevant counterevidence]
Potential impact: [asset and business consequence; separate what is observed from conditional]
Practical prerequisites: [login, role, network, feature flag, known object, etc.]

Requested outcome: [security property; optional safe implementation choices]
Acceptance checks: [allowed behavior, denied behavior, alternate paths, new revision]
Out of scope / unknown: [unread code, runtime, deployment, database policy, other callers]
Next step and owner: [who supplies missing evidence and who reassesses the patch]

Put sensitive source, credentials, payloads and customer examples only in a suitably restricted evidence system. The broadly visible ticket should be understandable without copying a private repository or raw report into another tool. A link is useful only if the developer and reviewer actually have permission to read it.

Let the developer choose an implementation without losing the invariant

The developer proposes reusing search_reports(name), the neighboring parameterized helper. Its SQL text is fixed, but its optional account argument defaults to an administrative all-accounts view. That helper was safe for its original restricted caller; importing it into this analyst route would fix value handling while discarding account scope.

The developer's useful return is precise:

“I can route the name through the approved bound-value API. The helper defaults to all accounts when account is omitted. Is the required behavior the analyst's current authorized account, and may this route ever use the administrative default?”

The ticket amendment answers the implementation question:

“For this route, derive the current account from server-verified authorization context and pass it explicitly. Omission must fail closed for the analyst path. The administrative helper behavior is outside this task unless the shared API must change; if it changes, review its administrative callers separately. The effective database policy at OLD_SHA was checked and does not defeat the original query-alteration path. Exact exposed rows remain unobserved.”

The database policy is known in this example; the exact runtime disclosure is not. Keep those limits separate so the developer can act on the established defect without mistaking potential disclosure for an observed incident.

The acceptance matrix now checks the properties that could be lost during repair. Outcomes below are requirements for a controlled check, not claimed executions:

Input and authority Expected property
Analyst A searches a normal name with matching A rows Appropriate A rows remain available.
Same name exists in A and B B rows are not returned to analyst A.
Name contains punctuation meaningful to SQL Driver receives fixed SQL text and the name separately as data; no query-structure change.
Account argument is omitted by the route No administrative/all-account fallback; request cannot gain broader authority.
Administrative caller uses its existing approved operation Its intended scope is explicitly preserved or intentionally changed and reviewed.

Avoid an overprescriptive request such as “replace line 42 with this exact helper call.” The completed task is fixed query structure plus trusted account scope plus preserved legitimate search. The developer can choose a different approved API if its actual semantics establish those properties. A green scanner or a helper's reassuring name establishes none of them alone.

The SQL prevention guidance supports the value-binding distinction; the account rule comes from Finch's stated application policy. The helper contract is illustrative and must be checked against the actual implementation in a real ticket.

Make the first developer response part of the ticket

A handoff becomes useful when the developer can accept it, ask one specific question or return it with evidence. Ask for that response before treating assignment as a delivery commitment. In Finch, the developer's discovery of an administrative default changes the implementation plan, not the need to keep user input separate from query structure.

The first response can be short:

I own the filter route and can replace its query construction. The shared helper has a broader default, so I will pass the server-authorized account explicitly and inspect its omission branch. Database platform must confirm the helper contract before I use it; otherwise I will keep a dedicated scoped query. I will return the proposed revision and the allowed/denied check plan.

That response gives the coordinator a real dependency and preserves developer judgment. It also prevents a patch copied from the ticket from accidentally widening access. The acceptance condition stays stable while the chosen implementation changes.

If the developer finds counterevidence to the original allegation, preserve it in the same claim history and request review. Do not force a repair merely because the issue is already assigned. If the evidence instead reveals another affected caller, decide whether it falls within the original scope or needs a linked task. Avoid letting the fix ticket expand silently into a review of every database operation.

Before work begins, check that the evidence links resolve for the assigned engineer and that the required reviewer has capacity. An inaccessible report or missing reassessment slot is a delivery dependency, just like a shared helper change. The ticket should expose it early so the team can finish the work, rather than discovering after merge that nobody can verify the security property.

When closing the task, retain the final implementation choice and its evidence. A future maintainer should see why the administrative default was inappropriate for this route without having to reconstruct a long comment thread.

Route it without losing the security context

The code owner may be the reports service team, while a database platform team owns the shared query helper and AppSec owns closure. Record all three roles when needed. A CODEOWNERS file can help route review requests on GitHub, but GitHub's documentation describes file ownership, not a guarantee that the named team owns the runtime service or security decision. If ownership is disputed, keep the ticket assigned to a dispatcher who can resolve it rather than making the finding disappear between teams.

Ask the developer to link the proposed commit and note whether it changes a shared helper. Ask the security reviewer to verify against the new revision and record fixed, still exists or inconclusive. A merged pull request by itself is not the closure record. For the exact reassessment steps, continue with how to verify a fix in a new commit. If the disagreement is about whether the scanner's original claim is real, use the finding triage guide when the original claim itself needs clarification.