A contract test records what one service expects from another and fails if the provider changes. Because a contract can include "this endpoint requires a token" or "this returns a forbidden status for another tenant," it catches a dropped security check early, without running a slow end to end suite.
What a contract test is
Martin Fowler's practical test pyramid article describes consumer driven contracts. The consuming team defines its expectations of a service, and the providing team verifies them. This lets teams change independently while knowing when they would break each other. Pact is the example tool the article names.
Most contract examples are about data shapes: fields, types and status codes. The same mechanism can describe behavior that matters for security.
What to put in a security contract
Pick expectations that, if broken, would be serious:
- Authentication required. A request without a valid token returns an unauthorized status.
- Authorization enforced. A valid token for user A requesting user B's resource returns forbidden or not found, whichever you decided on.
- Error shape. Errors have a stable form that does not include stack traces or internal identifiers. OWASP's guidance says errors should be logged securely without revealing sensitive details to attackers.
- Required headers. A response carries the headers you rely on.
- Rate limit signal. A burst of requests yields the throttling status.
Each one is a statement a consumer depends on, and each can be verified automatically on the provider side before release.
How the workflow runs
- The consumer team writes interactions that describe a request and the expected response.
- The contract is shared with the provider, in a repository or broker.
- The provider's pipeline replays the contract against its real code, with test data.
- If the provider has dropped a check, the verification fails and the release stops.
The provider learns about the break in its own build, minutes after the change, instead of in production.
Where it fits
The pyramid article says to push tests as far down as possible, and to keep end to end tests few. A contract test sits between unit and end to end. It exercises a real boundary but without a browser and without starting the whole system.
Use contracts for service to service boundaries and for public API surfaces where several clients exist. Use unit tests for the logic behind the check, and keep a small end to end test for the most critical journey.
Limits
- A contract only checks what someone wrote down. An authorization flaw in a case nobody described is not caught.
- It does not test the quality of the implementation. A check that returns the right status for the test token may still fail for an edge case.
- Contracts need maintenance. Stale ones create false failures or false comfort.
- It does not replace review, threat modeling or testing by someone trying to break the system.
Getting started
Pick your most sensitive internal API. Write three interactions: no token, wrong tenant, valid token. Run them against the provider in CI. Expand only when those three have proven useful.
Safeguard complements this with API security scanning and dependency checks. Check the product documentation for how your stack is covered, and try the free scan at gold.safeguard.sh/scan.
Quick check
Do contract tests replace penetration testing?
No. They guard against regressions in known expectations. Testing by people trying to break the system finds cases nobody wrote down.
Who owns the contract?
The consumer defines expectations and the provider verifies them. Both should treat failures as release blockers.
Sources
Last reviewed: 19 October 2026.