API Automated Security Testing
Contract testing, schema fuzzing, OpenAPI/Swagger-based scanning, GraphQL introspection + automated query generation, and CI pipeline integration. How to find API vulnerabilities before they ship.
Contract testing, schema fuzzing, OpenAPI/Swagger-based scanning, GraphQL introspection + automated query generation, and CI pipeline integration. How to find API vulnerabilities before they ship.
Manual security testing does not scale. As APIs grow from a handful of endpoints to hundreds, automated security testing becomes the only practical way to catch regressions, misconfigurations, and logic flaws before they reach production. This lesson covers contract testing, fuzzing, GraphQL-specific analysis, and CI pipeline integration.
Contract testing is the first line of defence. Given an OpenAPI or Swagger specification, automated tools validate that every endpoint returns the correct status codes, response shapes, and content types. A mismatch between spec and implementation — for example, a field documented as string that returns number, or a documented 404 handler that never actually fires — signals a bug that might also be a security vulnerability.
Fuzzing goes a step further by sending inputs the spec never intended. A fuzzer might send a JSON array where a string is expected, omit a required field, set a numeric field to a negative number, or send a 10-megabyte payload to a name field. The goal is to find deserialisation errors, buffer overflows, SQL injection points, and denial-of-service vectors that would never appear in normal usage.
GraphQL APIs require specialised analysis. The introspection system (via the __schema query) exposes every type, field, argument, and resolver in the API. Automated tools can extract the full schema, analyse it for common weaknesses (missing authentication on mutation types, excessive nesting depth, unused fields that leak internal data), and generate a risk report.
Run an automated security scan against a simulated API. Watch each stage execute and review the final vulnerability report.
Run a full security scan against the API surface.
Run a scan to see security findings.
In 2016, Uber's security team ran automated contract and fuzz tests against their internal API suite. The tests discovered that several API endpoints returned authentication tokens in response to unauthenticated requests — a bug that had been present for months without detection. Because the endpoints were designed for authenticated use, the developers had never tested them without a valid session.
The root cause was an implicit trust relationship: the API controller assumed that if a request reached it, the request had already passed authentication middleware. An endpoint reorganisation had accidentally placed some routes before the authentication middleware in the route table, making them publicly accessible. Automated fuzzing caught the issue because it sent requests without session cookies to every endpoint — including the ones the developers assumed were “internal only”.
Uber subsequently integrated automated API security scanning into their CI pipeline, running contract tests, fuzz tests, and GraphQL introspection analysis on every pull request. The bug was fixed before any external attacker discovered it. This case demonstrates why automated testing is not just about finding bugs — it is about finding the bugs that manual testing would never think to look for.
An effective automated security testing pipeline combines three layers: contract validation, fuzzing, and GraphQL-specific analysis. Contract tests run first and fail fast if the implementation diverges from the spec. Fuzzing runs against every endpoint with a combinatorially generated set of malformed inputs. GraphQL analysis extracts the schema and checks for dangerous configurations.
// Contract test example (Vitest + supertest)
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import spec from './openapi.json' assert { type: 'json' };
describe('API contract tests', () => {
it('GET /api/users returns 200 with user array', async () => {
const res = await request(app).get('/api/users');
expect(res.status).toBe(200);
expect(Array.isArray(res.body)).toBe(true);
expect(res.body[0]).toHaveProperty('id');
expect(typeof res.body[0].id).toBe('number');
});
it('GET /api/users/:id returns 404 for unknown user', async () => {
const res = await request(app).get('/api/users/999999');
expect(res.status).toBe(404);
expect(res.body).toHaveProperty('error');
});
});
// Fuzz test — send unexpected types
describe('API fuzz tests', () => {
const fuzzPayloads = [
{ name: null },
{ name: 12345 },
{ name: { '$ref': '#/definitions/User' } },
{ name: 'A'.repeat(100_000) },
{},
];
it.each(fuzzPayloads)('POST /api/users with %j', async (body) => {
const res = await request(app)
.post('/api/users')
.send(body)
.set('Content-Type', 'application/json');
// Should never return 500
expect(res.status).not.toBe(500);
});
});
// GraphQL introspection check
describe('GraphQL security', () => {
it('introspection is disabled in production', async () => {
const res = await request(app)
.post('/graphql')
.send({ query: '{ __schema { types { name } } }' });
// In production, introspection should return 400 or empty
expect(res.body.data?.__schema).toBeUndefined();
});
it('query depth is limited to 5', async () => {
const deepQuery = '{ a { b { c { d { e { id } } } } } }';
const res = await request(app)
.post('/graphql')
.send({ query: deepQuery });
expect(res.status).toBe(400);
});
});1.What is the primary goal of contract testing for APIs?
2.What did Uber's automated security testing discover in 2016?
3.Why is GraphQL introspection considered a security risk in production?