Anonymized findings from live testing and bug bounty research in 2026. Each report shows what was broken, what we proved, where we stopped, and why an automated scanner misses it.
No company names, hostnames, account ids or requests. Sandbox and test environments are marked as such in each report.
A caller-controlled path in an API console reached the gateway admin API on the internal network. We read it and stopped before any write.
Read the case study CriticalBanking & FintechAn organization with no subscriptions still received a payment token, and consent accepted an account it did not hold. Sandbox only.
Read the case study CriticalAutomotiveTwo calls on two hosts set a password on an address before its real owner signed up. Proven on three mailboxes we controlled.
Read the case study CriticalAI SystemsA public media server still used its image defaults. With no account we joined a room we did not create and received audio frames.
Read the case study HighConsumer PlatformsFour unauthenticated calls added up to bulk export, hidden fields and a public notify endpoint. The risk is the chain, not one call.
Read the case study HighBanking & FintechFinancing drafts had no ownership check, so a guest login could read and change another draft. Proven between two identities we created.
Read the case study HighAutomotiveA path parameter split the edge filter from the service behind it and exposed another account's consent records. Both accounts were ours.
Read the case study CriticalAutomotiveA checkout call placed an order with no login, only an offer id. One state change, confirmed by a read before and after, then we stopped.
Read the case study CriticalBanking & FintechA sandbox login accepted any six-digit code and issued a valid id token with unmasked contact fields. Sandbox authentication API only.
Read the case study HighHosting & DevOpsA fallback string in the public bundle was the production signing key. We forged a session for a chosen identity and wrote under it.
Read the case study HighAI SystemsHidden text in a supplier PDF changed the account number an AI assistant extracted. Vendor AI test environment, no transfer was sent.
Read the case study HighConsumer PlatformsA debug header returned identity fields of other users, and the edge cache then served them to plain requests with no header.
Read the case study CriticalHosting & DevOpsA public build server gave its script console to anonymous callers. One non-destructive identity print, then we stopped.
Read the case study CriticalHosting & DevOpsA confirm URL with no token completed QR login for whichever session was waiting. Proven end to end on a test account.
Read the case study HighConsumer PlatformsThe bet API stored the odds the client sent, not the market price. Bets were priced at about 58x the market. Nothing settled.
Read the case studyMany of these reports are two or three normal-looking behaviors in a row. A scanner checks each step alone, so the risk never shows up.
A missing ownership check appears only when one identity touches the object of another. That takes a second session, a write and a read back.
We prove the issue with the least possible impact and write down what we did not do. Every report states its limit.
Tell us about your website, API or AI-built app. We start with a free initial security review.
The API console built the upstream URL by concatenating a caller-controlled path onto a fixed API base. There was no host allow-list. A leading @ made the parser treat the official host as userinfo, so the real host came from the caller.
The proxy reflected status, headers, and body. On GET it followed redirects. An HTTPS redirect we controlled answered with a Location on plain HTTP to the Docker bridge, port of the gateway admin API. Egress left TLS and landed there. That admin API had no authentication.
This is not a production payment. The money-movement issue on the same portal is a separate report. This one is the console proxy seeing the mesh.
No signature for @ userinfo in a JSON path field. No signature for "follow the redirect, drop TLS, hit the bridge address." The bug is three behaviors in a row.
Buyer takeaway: A developer console in front of a bank is part of the bank. Test the proxy as if it can see the mesh.
Need this kind of testing for your own application?
Request a Free Security ReviewAn organization whose subscription list was empty could still register an app with the payment-initiation product id. The portal issued a token carrying the payment scope. The payment API accepted that token.
The consent call then accepted any valid sandbox account number in the account-selection field. There was no check that the caller owned the account. The debtor on the payment rebound to that account. Sandbox settlement completed for a credit transfer and for an instant transfer. The amounts were cents. The debtor was our own sandbox identity. A shared demo account was the stand-in for the other party.
Product entitlement and consent ownership both failed, in that order, on the sandbox payment API. The payment object showed the rebound debtor and a settled sandbox status.
This is not production customer money. Sandbox rails, our own organization, synthetic payments. The control-plane failure is the report: the product id was not checked against subscriptions, and the account number was not checked against the consent holder.
"Must be logged in" and "this is a sandbox" both look closed. The missing check is a field the spec treats as a product catalogue id, and a second field the spec treats as the payer's own account.
Buyer takeaway: Entitlement is not a checkbox in the developer UI. The token and the consent call have to enforce it, including in sandbox, because sandbox is where the integration is copied from.
Need this kind of testing for your own application?
Request a Free Security ReviewTwo GraphQL mutations, on two hosts, neither of them shown as a pair in the UI.
The first created an identity for any address that was not already a credentialed consumer, and returned the internal user id. The second, on the business portal, accepted that id, a password we chose, and a default invite code. Login with that password returned the MFA-next step. A wrong password returned unauthenticated. The inbox was not required.
Addresses that were already credentialed were rejected. The hole is the window before the real owner signs up, including mailboxes that existed only as stubs.
A sibling on the fleet portal distinguished provisioned internal mailboxes from absent ones, and exposed a 6-digit one-time code on the password-set mutation with no useful rate limit. We did not run that code against a real employee mailbox. MFA on the consumer path still fires. For a mailbox we own, we finish MFA and receive tokens. For a mailbox the victim owns, the password is ours and the victim receives the code.
Three disposable mailboxes we controlled: create, set the password with the default invite code, login accepts that password and rejects a wrong one.
Not a login to a named employee's car account. A broken registration and invite-code control, proven on identities we created.
The UI never offers that invite code. The two mutations live on two hosts. The field names were in the bundle, not in the docs.
Buyer takeaway: Provisional and invite flows are authentication. A default code is a password.
Need this kind of testing for your own application?
Request a Free Security ReviewA LiveKit server on a public address still used the image's default API key and secret. The matching dashboard accepted a default basic-auth pair. With that keypair a caller who had no account could:
Negative controls: no token was 401, a wrong secret was 401, a token with subscribe turned off received no audio frames. Positive: our own room and a foreign room both returned on the order of a thousand audio frames.
We listed trunks. We did not modify them. A recording call answered success. We did not start a new recording job. Some control calls (mute, kick, delete) answered 404 or 503 that day: reachable and authorized, not a torn-down call.
An established RTC session on a room we did not create, with audio frames, from the default keypair. Reproduced from a clean state by a second pass.
We do not claim a bounty was paid, and we do not name the parent organization. The fact is the default key on a public media server.
A default-credential check that stops at "dashboard returned 200" never joins a room and never counts audio frames. The first pass stopped at a WebSocket handshake that closed. The second pass finished the join.
Buyer takeaway: The RTC port is production. Image defaults on that port are a wiretap.
Need this kind of testing for your own application?
Request a Free Security ReviewNo authentication on a pipeline of four calls.
An unauthenticated caller can pick a street or a postal code, enrich to a current phone and a point, and send that person a message. About two requests per person. The directory covers a national population, on the order of ten million residents.
People-search products publish some of this by design. The report is not "the directory exists." It is bulk export with no auth, a selector the UI never shows, fields in the HTML that the page pretends to hide, and a notify call that turns the dataset into contact.
Each piece looks like a public API. The risk is the chain. The masked-versus-rendered check is a read of the HTML, not a CVE.
Buyer takeaway: If the frontend hides a field behind a click, assume the HTML already has it. A reminder endpoint is a messaging channel. Put a session and a rate limit on it.
Need this kind of testing for your own application?
Request a Free Security ReviewThe public API spec listed the financing draft as the object that holds date of birth, birthplace, nationality, address, IBAN, income, employer, payroll files, and identity files.
The draft endpoints performed no ownership check. A guest login, with no email and no fee, was enough. Against another identity's draft:
| Operation | Result |
|---|---|
| Read | Full draft JSON |
| Update | The stored state changed |
| Attach a file | The file landed on the other draft |
| Delete a file | The file was removed |
| Submit | Reached business validation of the stored form, and stopped on missing test fields, not on authorization |
The draft id is a random UUID. The ad id is not a secret either. UUID is a locator.
Read, update, file attach, and file delete across two identities we created. Submit reached the business rules of the other draft.
We did not download a real customer's passport. The schema says what the object holds in production. The missing check is the finding. An empty test draft is enough to prove it.
Authenticated object authorization needs two sessions and a write followed by a read. "Must be logged in" is not "may only touch their own draft."
Buyer takeaway: Every id in a finance or KYC flow needs an ownership check on read and on write. If the spec says the object holds an identity scan, a missing check is High on an empty test draft.
Need this kind of testing for your own application?
Request a Free Security ReviewConsent records are bound to the account id in the URL. A normal call with another account's id was rejected. The same call with a matrix parameter on that path segment was accepted, as long as the parameter value was the caller's own account id.
The edge filter matched the decorated segment. The service behind it stripped the parameter and loaded the bare id. The caller, logged in as themselves, received the other account's consent records. A write on the same route appended a consent record to that account. Both accounts were ours. We restored the written state in the same session.
A recheck in November 2026, read-only, still returned the other account's records when the parameter was the caller's id, and still rejected the call when the parameter was someone else's id. A missing token was rejected. An encoded semicolon was rejected as a bad parameter, which is a different failure from the bypass.
Byte-level agreement between the bypass read and the account owner's own read, on consent purpose, status, country, language, and timestamp. No name, email, or phone in that payload. The write was the original test, between two accounts we control.
Not a dump of a stranger's inbox. Consent state for an account whose id is known: marketing and legal accept/reject records. The id is a UUID, so this is not an open listing of every customer. It is a broken binding once any id is known.
The rejected call looks like authorization working. The bypass is a path-parameter trick that most clients encode away. A tool that percent-encodes the semicolon never hits the filter split.
Buyer takeaway: Subject binding has to use the same identifier the backend uses. If a filter and a service disagree on what the path segment is, the filter is decorative.
Need this kind of testing for your own application?
Request a Free Security ReviewThe used-vehicle checkout exposed an order call. It required no cookie, no customer login, and no anti-forgery token that the caller could not read from the public page. The body was an offer id.
Offer ids were available from an unauthenticated listing of live offers on the same site. One call with one live offer id returned accepted, type order-placed.
That offer belonged to a real customer, not a test fixture. Before the call its status was in progress. After the call the same offer read back as order-placed. We stopped. No second write. No payment. No delivery step.
One unauthenticated state change on one live offer, confirmed by a read before and a read after.
Not a paid vehicle, not a captured card, not a campaign against the catalogue. One call, then a stop. The severity is the missing authentication on a purchase-state change, demonstrated once.
The happy path is a browser session on a public marketing site. A scanner that only flags 401-versus-200 on obvious /admin paths does not treat "place order" as an anonymous function, and it should not be spraying live offer ids to find out.
Buyer takeaway: A checkout that keys only on an offer id is a purchase API. Authentication is the offer's customer, not the presence of a header copied from the page.
Need this kind of testing for your own application?
Request a Free Security ReviewThe sandbox user-authentication API identified a payer from an email address or from a payment id, plus the public client id. The response included a user id and masked email and phone.
The next call was the one-time-code check for the "remember me" step. Any six-digit code was accepted. The response was a valid id token for that payer, plus the unmasked email and phone.
Identify, then authenticate with a code that was not the one sent to the payer. The token was issued. Wrong-shape input was rejected, so this was not "the endpoint ignores the body."
Not a production payer. The host was the sandbox authentication API. We do not claim production money movement. A sandbox login that accepts every code is still the flow integrators copy, and it discloses the full contact fields of sandbox users.
A login form that "sends a code" looks like MFA. The bug is that the verify step does not check the code. Tools that stop at "OTP field exists" mark it passed.
Buyer takeaway: Treat the sandbox auth host as production auth. A remember-me code that is not checked is a password made of six digits, and then not even those.
Need this kind of testing for your own application?
Request a Free Security ReviewThe analytics host set a session cookie that was an HS256 JWT. The signing key in the server was an environment variable with a hardcoded fallback. That fallback string was in the public client bundle.
A real session cookie from the live host verified against the fallback. The fallback was the production key, not a dead default. With it, an unauthenticated caller minted a cookie for any user id, any email, and the staff flag.
The server rendered that identity into the page payload. A theme API read and wrote state under the forged user id. No password, no victim click.
An unsigned-by-us, signed-with-the-public-fallback cookie was accepted. The page greeted the forged identity. A write to the theme endpoint persisted against the forged user id. A cookie signed with a different secret was rejected.
Not a cloud account takeover and not a reading of other customers' stores beyond the identity and the state that endpoint stores. The impact we showed is authentication bypass and a cross-user write on that panel.
Secret scanners flag high-entropy strings. A short fallback next to JWT_SECRET looks like a dev leftover. The proof is verifying a live cookie against it and showing the server acts on the forged id.
Buyer takeaway: A fallback secret in client code is the production secret until a live cookie fails to verify against it. Check that before calling it dead code.
Need this kind of testing for your own application?
Request a Free Security ReviewThe assistant extracts payment details from a supplier invoice the user uploads. The file reader treats text inside the PDF as the document. A note embedded in the file, telling the model to ignore the printed account number and use a different one, was followed.
The assistant returned the embedded account number as the invoice's payment detail. A clean invoice with the same printed account number produced the printed number. The model presented the swapped number as extracted fact, with no warning that an instruction inside the file had overridden the page.
Paired invoices, same payee on the page, one with an embedded payment instruction and one without. Only the embedded instruction changed the account number in the answer. No transfer was sent. The test ran on the vendor's AI program host, with our own company context.
Not a live customer payment and not a jailbreak of a general chatbot. The victim still has to pay what the assistant shows. The assistant is the system a finance user trusts to read the invoice, which is the point.
There is no crash, no 500, and no script in the PDF. The control is a second document that differs by one invisible instruction.
Buyer takeaway: An assistant that "reads invoices" is a payment instruction channel. Instructions inside the file are untrusted input. The printed payment block has to win, or a human has to see the conflict.
Need this kind of testing for your own application?
Request a Free Security ReviewThe profile API trusted a client-supplied debug role header. With that header set, an unauthenticated request was treated as a privileged reader and the response included another user's legal name, email, date of birth, and home city and country.
The edge cache keyed on the URL and did not include that header. A personalized response was stored and then served to a later request that did not send the header. Repeating the plain request was enough to hit the cached copy. A request with no header and a cache miss did not return the profile. That is the negative control.
Real profile fields for other users, first with the header, then without it once the response had been cached. The same user id without a warm cache entry did not leak.
Not a write, not an account takeover, and not a full database dump in one call. It is unauthenticated read of identity fields the product does not show on the public profile, amplified by the cache.
The header looks like an internal test switch. A single unauthenticated request often misses, because the leak depends on a cached variant. Tools that do not vary cache state call the endpoint safe.
Buyer takeaway: A debug role header is a credential. If a cache sits in front of it, the header has to be part of the cache key, or the response must not be cacheable.
Need this kind of testing for your own application?
Request a Free Security ReviewA Jenkins host on the public internet served the script console to an anonymous caller. The authentication plugin was installed. The console was not behind it.
We sent one non-destructive script that printed the process user, the host name, and the Jenkins version. The response was the service account on that VM and a current 2.x version. Anonymous reads of the job list, the agent list, and the credentials root also answered.
We did not read credential values, change a job, drop a shell, or pivot.
Remote code execution as the CI service account, shown by an identity print. The same console without the anonymous path is a login wall on a hardened Jenkins. Here the login wall was absent.
Not "we own the domain." One identity print and a stop. The impact of an open script console is whatever that service account can reach. We did not map that further.
Version checks and default-password checks both pass if the anonymous user is simply allowed to compile Groovy. The proof is a print of user.name, not a CVE number.
Buyer takeaway: A CI script console on the internet is remote code execution. Authentication on the login form does not count if /script is anonymous.
Need this kind of testing for your own application?
Request a Free Security ReviewThe product offers "log in with a QR code." A desktop browser opens a pending session. A phone that has paired before holds long-lived pairing cookies and confirms that session.
The confirm URL was a GET. It did not carry an anti-forgery token. It did not check that the pending session belonged to the person confirming it. The pairing cookies were not HttpOnly and defaulted to Lax, so a cross-site top-level navigation sent them.
Sequence, on accounts we controlled:
A confirm with no pairing cookies did not complete the login.
End-to-end session on a test account: pending id we created, confirm from the paired browser, authenticated session on the waiting side.
Not a takeover of an unrelated customer in production. The victim condition is "this browser completed QR login once," and the proof used a paired test browser we held. The missing check is the report.
CSRF scanners look for cookie-authenticated POSTs. This is a GET that completes a login, and the privileged cookie is the pairing cookie, not the session under test.
Buyer takeaway: A device-pairing confirm is a login. It needs a token bound to the pending session, and it should not be a GET.
Need this kind of testing for your own application?
Request a Free Security ReviewThe bet API takes the selection, the stake, and the odds. In the window we tested, the server stored the odds value from the client. It did not replace it with the live market price.
Bets at about 10x, 36x, and 58x the quoted market price were accepted into the bet engine. Bet history showed the inflated odds and a potential payout computed from that number. A one-unit stake at the highest test price was stored with a three-digit potential win. The fair payout on that selection was under two units.
The bet status was rejected. The reason was the sandbox wallet: the balance could not subtract the stake, and that check ran after the bet had been created and priced. We did not fund a wallet. We did not settle a bet.
Server-side bet records, on an account we own, containing the client-supplied odds and a potential win derived from them. The market price on the same selection was the control.
Not a withdrawal and not a cashed winning ticket. Empty sandbox balance stopped settlement. The engine had already trusted the client for the price.
The HTTP status of a rejected bet looks like a working control. The evidence is the stored odds and the stored potential win, read back from bet history, compared with the market.
Buyer takeaway: Odds are a server price. If bet history shows the client's number, the balance check is the only thing between a test account and a priced winning bet.
Need this kind of testing for your own application?
Request a Free Security Review