-
Notifications
You must be signed in to change notification settings - Fork 7
feat: drift and proxy commands article #332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
c3ec489
feat: drift and proxy commands article
sobanieca-redocly ea4794d
Apply suggestions from code review
JLekawa f099d6c
chore: remove 404 image link
sobanieca-redocly c29422a
Revert "chore: remove 404 image link"
sobanieca-redocly 0c7ef22
chore: adjust according to pr comments
sobanieca-redocly 6bf2ea7
chore: replace em dash
sobanieca-redocly 0bb8cdf
chore: pr comments
sobanieca-redocly 9a42482
chore: reorder diagram
sobanieca-redocly 7a8263e
chore: center diagram
sobanieca-redocly 8229312
chore: adjust diagram
sobanieca-redocly 6f28555
fix: emoji text width issue
sobanieca-redocly File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| --- | ||
| template: ../@theme/templates/BlogPost | ||
| title: Catch API drift with the new proxy and drift commands | ||
| description: Record real HTTP traffic with the proxy command, then let the drift command tell you where your API and your OpenAPI description disagree. | ||
| seo: | ||
| title: Catch API drift with the new proxy and drift commands | ||
| description: Record real HTTP traffic with the proxy command, then let the drift command tell you where your API and your OpenAPI description disagree. | ||
| author: adam-sobaniec | ||
| publishedDate: "2026-07-08" | ||
| categories: | ||
| - redocly:redocly-cli | ||
| - redocly:product-updates | ||
| - api-testing:integration-testing | ||
| --- | ||
|
|
||
| Your OpenAPI description says one thing. | ||
| Your API does another. | ||
| Nobody notices, because nothing fails - until an SDK generated from that description breaks in a customer's build, or a partner integration chokes on a field that was "documented" but never returned. | ||
|
|
||
| The gap between what you describe and what you ship has a name: drift. | ||
| It accumulates quietly, one hotfix and one forgotten field at a time. | ||
|
|
||
| Redocly CLI ships two commands that work together to close this gap: `proxy` records what your API actually does, and `drift` compares it against what your OpenAPI description claims. | ||
|
|
||
| ## Record first, judge later | ||
|
|
||
| The `proxy` command starts a local reverse proxy in front of your API. | ||
| Point any client at it - a browser, a test suite, a curl script - and every request and response that passes through is captured into a standard HAR file. | ||
|
|
||
| The `drift` command takes that traffic and replays it against your OpenAPI description. | ||
| It matches each exchange to a documented operation and reports the discrepancies: undocumented endpoints, undocumented parameters, missing required fields, response bodies that don't match their schemas. | ||
|
|
||
| Because the two commands meet at a plain HAR file, you don't have to use them together. | ||
| Traffic exported from browser DevTools, Kong, or Nginx/Apache JSON logs works with `drift` too. | ||
| And if you prefer immediate feedback, `proxy` accepts an `--api` flag and validates traffic live, as it flows. | ||
|
|
||
| {% admonition type="warning" name="HAR files record everything" %} | ||
| A HAR file is a full capture: request and response headers, cookies, tokens, and bodies all land in it verbatim. | ||
| Run `proxy` and handle the recorded HAR files in closed sandboxes - a local machine or an isolated e2e environment exercised with synthetic accounts and test credentials - so no real user data or production secrets end up in the recording. | ||
| If you do record traffic that touches real credentials, treat the HAR file like a secret: keep it out of version control, redact it before sharing, and delete it when you're done. | ||
| {% /admonition %} | ||
|
|
||
| ## Put it in your test pipeline | ||
|
|
||
| Here is the setup we like most: you probably already have e2e or integration tests that exercise your API. | ||
| That traffic is a free, realistic sample of how your API actually behaves - you're just letting it evaporate after every run. | ||
|
|
||
| Instead, start `proxy` before the test run, route the tests through it, and stop it when they finish. | ||
| The proxy sits between your tests and the API: requests and responses pass through it unchanged, and every exchange is captured on the way. | ||
|
|
||
| ```mermaid {% align="center" %} | ||
| flowchart TD | ||
| tests["E2E / integration tests"] -->|"connects to http://localhost:4040"| proxy["redocly proxy"] | ||
| proxy -->|"proxies to http://localhost:9000"| target["Your API"] | ||
| proxy -.->|"records"| har["test-traffic.har"] | ||
| har --> drift["redocly drift"] | ||
| openapi["openapi.yaml"] --> drift | ||
| drift -->|"produces"| report["✨ Drift report "] | ||
| ``` | ||
|
|
||
| In a script, the whole setup fits in a few lines: | ||
|
|
||
| ```bash | ||
| redocly proxy --target http://localhost:9000 --har ./test-traffic.har & | ||
| PROXY_PID=$! | ||
| # run your e2e or integration tests against http://localhost:4040 | ||
| kill "$PROXY_PID" && wait "$PROXY_PID" | ||
|
sobanieca-redocly marked this conversation as resolved.
|
||
| redocly drift ./test-traffic.har --api ./openapi.yaml | ||
|
redocly[bot] marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| The `kill` step matters: `proxy` streams captured exchanges to a temporary file and assembles the final HAR only on a clean shutdown, so `drift` finds nothing to read until the proxy has stopped. | ||
|
|
||
| The `drift` command exits with a non-zero code when it finds error-level problems, so it slots into CI as a gate. | ||
| Every test run now doubles as a contract check, with zero extra test code to maintain. | ||
|
sobanieca-redocly marked this conversation as resolved.
|
||
|
|
||
| ## Try it on the Cafe API | ||
|
|
||
| You don't need your own API to see it work. | ||
| Redocly Cafe is our public demo API, and its OpenAPI description is published along with the docs. | ||
|
|
||
| Start a proxy in front of the live Cafe API: | ||
|
|
||
| ```bash | ||
| redocly proxy --target https://api.cafe.redocly.com --har ./cafe.har | ||
| ``` | ||
|
|
||
| ``` | ||
| Proxy listening on http://localhost:4040 → forwarding to https://api.cafe.redocly.com/ | ||
| Recording traffic to ./cafe.har | ||
| Press Ctrl+C to stop. | ||
| ``` | ||
|
|
||
| Send some traffic through it: | ||
|
|
||
| ```bash | ||
| curl http://localhost:4040/menu | ||
| curl "http://localhost:4040/menu?category=dessert" | ||
| ``` | ||
|
|
||
| Press <kbd>`Ctrl`</kbd> + <kbd>`C`</kbd> and the proxy writes the HAR file: | ||
|
|
||
| ``` | ||
| Captured 2 exchange(s) to ./cafe.har | ||
| ``` | ||
|
|
||
| Now grab the Cafe OpenAPI description: | ||
|
|
||
| ```bash | ||
| curl -s https://cafe.redocly.com/page-data/shared/oas-openapi/cafe.yaml.json \ | ||
| | jq .definition > cafe-openapi.json | ||
| ``` | ||
|
|
||
| To simulate drift, open `cafe-openapi.json` and make the description lie: | ||
| in the `Page` schema, change the type of the `total` property from `integer` to `string`. | ||
| Then replay the recorded traffic against it: | ||
|
|
||
| ```bash | ||
| redocly drift ./cafe.har --api ./cafe-openapi.json | ||
| ``` | ||
|
|
||
| ``` | ||
| ┃ Exchanges: total=2 documented=2 undocumented=0 | ||
| ┃ Findings: total=3 error=2 warning=1 info=0 | ||
|
|
||
| ✖ ERROR ×2 → Response field "page.total" must be string. | ||
| ↳ sample exchange=0 GET /menu (200) listMenuItems | ||
| expected: type "string" | ||
| actual: 5 | ||
|
|
||
| ▲ WARN → Undocumented query parameter in traffic: "category" | ||
| ↳ sample exchange=1 GET /menu (200) listMenuItems | ||
| ``` | ||
|
|
||
| The error is the discrepancy we planted: the API returns a number where the description now promises a string. | ||
| The warning is a bonus we didn't plant at all. | ||
| The `category` query parameter we sent isn't documented anywhere in the Cafe description. | ||
| Two requests of traffic, and drift already surfaced something real. | ||
|
|
||
| ## More than schemas | ||
|
|
||
| Findings come from built-in rules that you can select with the `--rules` flag: | ||
|
|
||
| - `undocumented-endpoint` flags traffic that doesn't match any documented operation. | ||
| - `schema-consistency` validates parameters, headers, and request/response bodies against your schemas. | ||
| - `security-baseline` checks that requests actually satisfy the security requirements your description declares, and flags things like credentials sent over plain HTTP. | ||
|
redocly[bot] marked this conversation as resolved.
|
||
| Loopback hosts such as `localhost` and `127.0.0.1` are exempt from the transport check, so sandboxed recordings against a local target stay warning-free. | ||
| - `owasp-api-top10` scans recorded traffic with heuristics based on the OWASP API Security Top 10 - enable it with `--rules owasp-api-top10`. It looks for credential-like query parameters, insecure CORS (wildcard origin with credentials enabled), weak cookie attributes, sensitive-looking fields in response payloads, and large unpaginated responses. | ||
|
|
||
| One caveat before you wire `owasp-api-top10` into a CI gate: it inspects each exchange in isolation. | ||
| It has no notion of user identity or ownership across requests, so it cannot detect authorization logic flaws like Broken Object Level Authorization. | ||
|
|
||
| Reports come out as human-readable text, JSON, CSV, or SARIF, so the same run can feed a terminal, a dashboard, or a code-scanning integration. | ||
|
|
||
| {% admonition type="warning" name="Experimental" %} | ||
| Both commands are experimental. | ||
| Flags, output formats, and behavior may change - including breaking changes - in upcoming releases while we shape them with your feedback. | ||
| {% /admonition %} | ||
|
|
||
| ## Get started | ||
|
|
||
| The `proxy` and `drift` commands are available now in the latest [Redocly CLI](https://redocly.com/docs/cli). | ||
| Check out the [drift](https://redocly.com/docs/cli/commands/drift) and [proxy](https://redocly.com/docs/cli/commands/proxy) command references, run them against your own traffic, and tell us what you find - feedback and ideas are welcome on the [Redocly CLI GitHub repository](https://github.com/Redocly/redocly-cli/issues). | ||
|
|
||
| Your API and its description should tell the same story. | ||
| Now you can check. | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.