<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Conor Griffin</title>
    <description>The latest articles on DEV Community by Conor Griffin (@conorgriffindev).</description>
    <link>https://dev.to/conorgriffindev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3961673%2F2f0b4271-94d2-4a31-aec0-1dc14658fc4d.png</url>
      <title>DEV Community: Conor Griffin</title>
      <link>https://dev.to/conorgriffindev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/conorgriffindev"/>
    <language>en</language>
    <item>
      <title>What an OpenAPI spec doesn't tell you</title>
      <dc:creator>Conor Griffin</dc:creator>
      <pubDate>Sun, 31 May 2026 21:32:13 +0000</pubDate>
      <link>https://dev.to/conorgriffindev/what-an-openapi-spec-doesnt-tell-you-56d1</link>
      <guid>https://dev.to/conorgriffindev/what-an-openapi-spec-doesnt-tell-you-56d1</guid>
      <description>&lt;p&gt;An OpenAPI spec is a promise. It says: this field is an integer, this one has a maximum &lt;br&gt;
of 100, this one is required, this one is a string no longer than 50 characters. It reads &lt;br&gt;
like a contract.&lt;/p&gt;

&lt;p&gt;The problem is that nobody verifies the contract. The spec describes what the API claims &lt;br&gt;
to accept. It says nothing about what happens when you send it everything it claims to &lt;br&gt;
reject, and that gap is where the interesting bugs live.&lt;/p&gt;

&lt;p&gt;I spent a while building a tool that lives entirely inside that gap. This is what I learned &lt;br&gt;
from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A spec is a list of things to violate
&lt;/h2&gt;

&lt;p&gt;The moment you stop reading a spec as documentation and start reading it as a list of rules &lt;br&gt;
to break, it becomes generative. Every constraint implies its own violation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;minimum: 1 → send 0, send -1&lt;/li&gt;
&lt;li&gt;maxLength: 50 → send 51 characters, send 10,000&lt;/li&gt;
&lt;li&gt;required → omit it, send null&lt;/li&gt;
&lt;li&gt;type: integer → send "not_an_int", send Integer.MAX_VALUE, send a float&lt;/li&gt;
&lt;li&gt;type: boolean → send "true" as a string, send 1, send "yes"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to imagine test cases. The spec hands them to you. A field with three &lt;br&gt;
constraints generates a dozen payloads mechanically. An API with 60 fields generates &lt;br&gt;
thousands. This is the part manual testing can't keep up with,  not because the cases &lt;br&gt;
are hard to think of, but because there are too many to remember and too tedious to write &lt;br&gt;
by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The response tells you more than the status code
&lt;/h2&gt;

&lt;p&gt;A 500 is the obvious finding. But the response body is where the API leaks what it actually &lt;br&gt;
does internally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Java stack trace in the body means an unhandled exception reached the client and 
exposed your package structure while it was at it.&lt;/li&gt;
&lt;li&gt;A SQLException or an ORA- error string means a database error propagated all the way 
out and you're now looking at a potential injection surface.&lt;/li&gt;
&lt;li&gt;A 200 on input you know is invalid is the quietest and most dangerous finding of all. 
The API didn't crash. It accepted bad data and moved on. Nobody gets paged for that. 
It just sits in your database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category, the silent acceptance is the one I find most interesting, because &lt;br&gt;
it produces no error anywhere. The only way to catch it is to send input you know is wrong &lt;br&gt;
and be suspicious of success.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I pointed it at
&lt;/h2&gt;

&lt;p&gt;The official Swagger Petstore demo. The reference API. The one the tutorials use.&lt;/p&gt;

&lt;p&gt;GET /user/login returns a token for null credentials. Oversized login strings get a 200. &lt;br&gt;
Write endpoints 500 on malformed bodies. Again, it's a demo, none of it matters in &lt;br&gt;
practice. But it's a clean illustration of the central point: being the canonical example &lt;br&gt;
of an API and correctly handling adversarial input are two completely unrelated properties.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the thing
&lt;/h2&gt;

&lt;p&gt;If you want to build something similar, the pipeline is straightforward:&lt;/p&gt;

&lt;p&gt;parse the spec → generate payloads per field → fire them → analyse responses → report&lt;/p&gt;

&lt;p&gt;The only non-obvious parts are payload generation (drive it off the declared type and &lt;br&gt;
constraints, fall back to a static list for undeclared fields) and response analysis &lt;br&gt;
(don't just check the status, pattern-match the body for leaks and treat unexpected &lt;br&gt;
successes as findings).&lt;/p&gt;

&lt;p&gt;I wrote mine in Java 21 with REST Assured and put the output in an Allure report, mostly &lt;br&gt;
because that's the stack the QA teams I want to work with already use. The code is here &lt;br&gt;
if it's useful as a reference:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ConorGriffin-Dev/chaos-monkey" rel="noopener noreferrer"&gt;https://github.com/ConorGriffin-Dev/chaos-monkey&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The takeaway I'd leave you with: you already have a test suite sitting in your repo. It's &lt;br&gt;
called openapi.json. You're just not running it adversarially yet.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>testdev</category>
      <category>restapi</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
