# Stale approval state on object change

Approval or freshness flags cached once, then trusted after the reviewed object is retargeted, edited, or the app is removed

_Area: Web business logic and race conditions. Seen in 3 disclosed reports; one confirmed bounty: $150; latest 2026-09-17. Updated 2026-10-25._

## How it works
An approval decision is computed once, against the object's state at that moment, and stored as a boolean or token. Later the object changes — its base branch, its file contents, its owning app — but the stored decision is never recomputed or invalidated. Merge or execution then trusts the stale flag. The broken assumption: that the inputs to an authorization decision stay fixed for the life of the record.

## Where it shows up
- A boolean like `official`/`approved`/`verified` written at submission time and read at merge time.
- Mutation paths (retarget, transfer, rename, edit, uninstall) that update the object but not its reviews or flags.
- Security checks gated on an optional client field (etag, version, hash) that fail open when the field is absent.
- Tokens with no expiry, scoped to app install rather than to the token itself.

## How to review
Enumerate every mutation of the inputs an approval depended on, and confirm each one either re-evaluates or dismisses the stored decision. The invariant: a stored approval must be valid only for the exact object state it was granted against; any change to that state invalidates it. For freshness checks, verify the check runs unconditionally and rejects a missing parameter rather than skipping. For tokens, confirm expiry and revocation on uninstall.

## Variants seen
- Stale approval flag survives PR retarget to a protected branch [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-w5pg-649r-p6gg.md).
- Freshness check skipped when etag parameter omitted [E2](https://bugbountylibrary.com/r/h1%3A3610332.md).
- Preview token never expires and survives app uninstall [E3](https://bugbountylibrary.com/r/h1%3A915940.md).

## Why fixes fail
E2 shows a check that only runs when the client supplies the field; absence is treated as "no change" rather than "unverified." Fixes that validate the value but not its presence leave the bypass open.

## Impact and severity
Bypassed branch protection and unauthorized merges [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-w5pg-649r-p6gg.md); approval of unreviewed file changes [E2](https://bugbountylibrary.com/r/h1%3A3610332.md); hidden scripts persisting after uninstall [E3](https://bugbountylibrary.com/r/h1%3A915940.md). Severity rises when the stale flag guards a protected or privileged action and the mutation is available to a lower-privileged user.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-w5pg-649r-p6gg.md
- E2: https://bugbountylibrary.com/r/h1%3A3610332.md
- E3: https://bugbountylibrary.com/r/h1%3A915940.md
