Skip to content

feat: experimental traffic analysis#2848

Open
sobanieca-redocly wants to merge 28 commits into
mainfrom
feat/traffic-analysis
Open

feat: experimental traffic analysis#2848
sobanieca-redocly wants to merge 28 commits into
mainfrom
feat/traffic-analysis

Conversation

@sobanieca-redocly

@sobanieca-redocly sobanieca-redocly commented Jun 3, 2026

Copy link
Copy Markdown

What/Why/How?

Added two new (experimental) commands - drift and proxy for collecting traffic data and validating it against provided OpenAPI definition.

Reference

Testing

Screenshots (optional)

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

Medium Risk
Large new experimental surface (proxy forwarding, auth/security heuristics on captured traffic) but isolated commands with no changes to existing CLI auth flows; flags and behavior may still evolve.

Overview
Adds two experimental CLI commands for API contract validation against real HTTP traffic, shipped as a minor @redocly/cli release with docs and changesets.

drift ingests recorded traffic (HAR, Kong, Nginx/Apache JSON, NDJSON, plus pluggable parsers), indexes OpenAPI 3.x via @redocly/openapi-core, matches exchanges to operations (--match-mode or --server), and runs builtin/custom rules. Reports exit 1 on error-level findings and support pretty, json, csv, and sarif output.

proxy runs a local reverse proxy (undici upstream) that streams HAR entries to disk and optionally reuses the same ValidationSession as drift for live findings plus a shutdown report.

Shared engine includes AJV schema validation (readOnly/writeOnly handling), rules for undocumented routes, schema consistency, security baselines, and opt-in OWASP API Top 10 heuristics. E2E snapshots cover formats and report outputs.

Reviewed by Cursor Bugbot for commit c424e5a. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Jun 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c424e5a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@sobanieca-redocly sobanieca-redocly changed the title feat: traffic analysis poc feat: experimental traffic analysis Jun 8, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 81.44% (🎯 81%) 7459 / 9158
🔵 Statements 80.8% (🎯 80%) 7753 / 9595
🔵 Functions 84.4% (🎯 84%) 1483 / 1757
🔵 Branches 73.16% (🎯 73%) 5043 / 6893
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/types.ts 100% 100% 100% 100%
Generated in workflow #10514 for commit c424e5a by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x (Fastest) ▓ 1.01x ± 0.01 ▓ 1.00x (Fastest)
cli-next ▓ 1.00x ± 0.01 ▓ 1.00x (Fastest) ▓ 1.00x ± 0.01

@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

Comment thread packages/cli/src/commands/proxy/server.ts Outdated
Comment thread packages/cli/src/commands/drift/utils/http.ts
Comment thread packages/cli/src/commands/proxy/server.ts
Comment thread packages/cli/src/commands/drift/engine/runner.ts
Comment thread packages/cli/src/commands/drift/rules/builtins/security.ts
@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

Comment thread packages/cli/src/commands/proxy/server.ts
Comment thread packages/cli/src/commands/proxy/server.ts Outdated
Comment thread packages/cli/src/commands/proxy/index.ts Outdated
Comment thread packages/cli/src/commands/drift/openapi/matcher.ts Outdated
@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

Comment thread packages/cli/src/commands/proxy/server.ts Outdated
Comment thread packages/cli/src/commands/drift/rules/builtins/schema.ts
Comment thread packages/cli/src/commands/drift/rules/builtins/schema.ts
Comment thread packages/cli/src/commands/drift/openapi/loader.ts
@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

Comment thread packages/cli/src/commands/drift/index.ts
Comment thread packages/cli/src/commands/drift/log-formats/ndjson.ts Outdated
Comment thread packages/cli/src/commands/drift/engine/schema-validator.ts
Comment thread packages/cli/src/commands/drift/openapi/generator.ts Outdated
@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

Comment thread packages/cli/src/commands/drift/log-formats/helpers.ts Outdated
@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

Comment thread packages/cli/src/commands/proxy/index.ts
Comment thread packages/cli/src/commands/drift/log-formats/har.ts
Comment thread packages/cli/src/commands/drift/rules/builtins/schema.ts
Comment thread vitest.config.ts
'packages/**/__tests__/**/*',
'packages/cli/src/index.ts',
'packages/cli/src/utils/assert-node-version.ts',
'packages/cli/src/commands/drift/**',

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it fine if we ignore coverage for experimental commands? I would expect that source code there may change significantly over time after getting feedback on usage....

Comment thread packages/cli/src/commands/drift/README.md Outdated
maxFindings?: number;
}

const ANSI = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have colorette as a dep in this repo, let's use it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks.

Comment thread packages/cli/src/index.ts Outdated
Comment on lines +15 to +16
import { handleDrift, type DriftArgv } from './commands/drift/index.js';
import type { FindingSeverity, MatchMode, TrafficFormat } from './commands/drift/types/index.js';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use dynamic import

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks.

Comment thread packages/cli/src/index.ts
yargs
.env('REDOCLY_CLI_DRIFT')
.positional('traffic', {
describe: 'Path to a traffic log file or folder (HAR, Kong, Nginx/Apache JSON, NDJSON).',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it support those other formats?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it does 👍

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it should support other formats.

@sobanieca-redocly

Copy link
Copy Markdown
Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 0e8c763. Configure here.

@sobanieca-redocly sobanieca-redocly marked this pull request as ready for review June 17, 2026 12:58
@sobanieca-redocly sobanieca-redocly requested review from a team as code owners June 17, 2026 12:58
Comment thread packages/cli/src/commands/drift/README.md Outdated
Comment thread packages/cli/src/commands/drift/README.md Outdated
Comment thread packages/cli/src/commands/drift/README.md Outdated
Comment thread packages/cli/src/commands/drift/README.md Outdated
Comment thread packages/cli/src/commands/drift/README.md Outdated
Comment thread packages/cli/src/commands/proxy/README.md Outdated
Comment thread packages/cli/src/commands/proxy/README.md Outdated
Comment thread packages/cli/src/commands/proxy/README.md Outdated
Comment thread packages/cli/src/commands/proxy/README.md
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
Comment thread packages/cli/src/commands/drift/README.md Outdated
Comment thread packages/cli/src/commands/drift/utils/server.ts
type Options,
type ValidateFunction,
type Ajv2020 as Ajv2020Instance,
} from 'ajv/dist/2020.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be better to use our fork @redocly/ajv, this could unify usage in the repo

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks.

method: params.method,
url: params.forwardUrl.toString(),
httpVersion: `HTTP/${params.req.httpVersion}`,
cookies: parseCookieHeader(singleHeader(params.req.headers.cookie)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's worth cleaning up sensitive data ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now this command should be used for short-lived local capture (like during e2e tests etc). Not sure about use cases yet, I would wait until we get more feedback from users before designing proper sensitive data removal...

findings.push(...ruleFindings);
}
} catch (error) {
findings.push({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it actually finding? maybe simply notify the user via the logger?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used logger instead. Thanks!

* JSON object) and serialized through a promise chain so concurrent captures
* never interleave file writes.
*/
export class HarWriter {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keeps all entries in memory and re-serializes + rewrites the entire file on every request

Maybe it would be possible to add debouncing or stream the writes using something like stream-json:
https://www.npmjs.com/package/stream-json

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the idea here. We will store data in temporary file which get's converted into HAR. This should cut memory usage.

return '***';
}

return `${value.slice(0, 4)}…`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why 4 ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed magic number and named it MASK_REVEALED_PREFIX_LENGTH. This is just arbitrary number on how many characters we want to show from masked value.

reason: string;
}

interface SkippedSecurityCheck {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as SecurityIssue

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate removed. Thanks.

return { specFiles: [specPath], fromDirectory: false };
}

const YAML_OPENAPI_ROOT_KEY_RE = /^(['"]?)openapi\1\s*:/m;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detectSpec form core?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this changes necessary ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - this is to avoid typescript issues between drift --format and lint --format

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'm checking for some alternatives. Maybe indeed we can avoid changes in lint 👀

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed param to report-format with alias. This allows to get rid of overlap with lint command types.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the plugin system compatible with the one that's already in the core? I think having two different plugin systems for the cli is a bad idea

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we are dealing with different purpose of rules (in this case traffic data). This means different data contracts etc. I would defer this unification when this command goes out of experimental phase.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add documentation for v2 for both commands?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docs, but @JLekawa I will need your re-review on this one. Thanks!

Comment thread packages/cli/src/commands/proxy/har-writer.ts
Comment thread packages/cli/src/commands/drift/openapi/loader.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c424e5a. Configure here.

)}), or use --server to declare the server the traffic was captured against.`;
logger.warn(
`None of the ${validatedExchanges} validated exchange(s) matched a documented operation. ${hint}\n`
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Misleading server mismatch warning

Medium Severity

After a run where every validated exchange fails operation matching, warnWhenNothingMatched always logs a hint to fix host, base path, or --server, even when traffic URLs already use the same host as the description servers. That message appears alongside correct undocumented-endpoint findings and points users at the wrong cause.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c424e5a. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants