Skip to content

feat(core): add asyncapi security-defined lint rule#2759

Open
harshit078 wants to merge 92 commits into
Redocly:mainfrom
harshit078:add-asyncapi-rule
Open

feat(core): add asyncapi security-defined lint rule#2759
harshit078 wants to merge 92 commits into
Redocly:mainfrom
harshit078:add-asyncapi-rule

Conversation

@harshit078

@harshit078 harshit078 commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

What/Why/How?

  • Added asyncapi-operation-security-defined rule for AsyncAPI 2.x and 3.x. both which reports when a security scheme referenced from an operation or server security array is not defined in ``components.securitySchemes.

Reference

#2667

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
New default error in recommended rulesets can break previously clean AsyncAPI lint runs; security and server-binding logic is nuanced and could produce false positives or misses on edge cases.

Overview
Adds the security-defined built-in rule for AsyncAPI 2.x and 3.x, aligned with the existing OpenAPI rule name and behavior.

For 2.x, the rule validates scheme names on operation/server security against components.securitySchemes, and flags operations with no own security when applicable servers (including channel bindings and traits) do not fully cover them. For 3.x, it validates $ref targets under components.securitySchemes and applies similar operation/server coverage checks on root operations.

The rule is wired into async2/async3 rule registries, built-in rule IDs, and preset configs (recommended at error, minimal at warn). Docs, sidebar, changeset, and broad unit tests are included.

Supporting changes add isAsyncOperationSecured, richer AsyncAPI typings/visitors for security-related nodes, and minor AsyncAPI split typing refactors (AnyAsyncApiComponents).

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

@changeset-bot

changeset-bot Bot commented Apr 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ffc7ef8

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

This PR includes changesets to release 3 packages
Name Type
@redocly/openapi-core Minor
@redocly/cli 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

@harshit078 harshit078 changed the title feat: Add asyncapi-operation-security-defined lint rule feat(core): Add asyncapi-operation-security-defined lint rule Apr 24, 2026
@harshit078 harshit078 marked this pull request as ready for review April 24, 2026 07:51
@harshit078 harshit078 requested review from a team as code owners April 24, 2026 07:51
@harshit078 harshit078 changed the title feat(core): Add asyncapi-operation-security-defined lint rule feat(core): add asyncapi-operation-security-defined lint rule Apr 24, 2026

@vadyvas vadyvas left a comment

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.

I would suggest a slightly different approach:

  1. keep the AsyncAPI logic separate and do not reuse shared logic from the OAS rule
  2. use the same rule name, security-defined, for AsyncAPI as well, and register the AsyncAPI implementation in the AsyncAPI ruleset
  3. do not update the v1 docs in this PR

I think this would make the change smaller, clearer, and safer.

Thank you for the contribution, overall the PR looks good

Comment thread packages/core/src/rules/async2/asyncapi-operation-security-defined.ts Outdated
Comment thread packages/core/src/rules/common/security-scheme-references.ts Outdated
Comment thread packages/core/src/rules/async2/index.ts Outdated
Comment thread docs/@v1/guides/lint-asyncapi.md Outdated
Comment thread packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap Outdated
@harshit078 harshit078 requested a review from vadyvas April 30, 2026 12:45

@vadyvas vadyvas left a comment

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.

left a few comments, could you take a look?

'info-contact': InfoContact as Async2Rule,
'info-license-strict': InfoLicenseStrict as Async2Rule,
'operation-operationId': OperationOperationId as Async2Rule,
'security-defined': SecurityDefined,

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.

Please add support for AsyncAPI 3 as well. Right now the rule only applies to AsyncAPI2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

okay sure !

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.

The code uses the rule name security-defined, but the docs still say asyncapi-operation-security-defined
Can you update related changes?

@@ -0,0 +1,83 @@
# asyncapi-operation-security-defined

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.

Please don’t add this rule to the v1 docs

Comment thread docs/@v1/v1.sidebars.yaml Outdated
Comment thread packages/core/src/rules/async2/index.ts
@harshit078 harshit078 requested a review from DmitryAnansky June 15, 2026 08:49
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

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

Comment thread packages/core/src/rules/async2/security-defined.ts Outdated
@harshit078

Copy link
Copy Markdown
Contributor Author

Hey @DmitryAnansky , I have fixed all comments and failing e2e tests and PR is good to review again. Thanks !

);
});

it('should call createTestContext for arazzo type using the spec form without `workflows` segment', async () => {

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 do we need those changes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I missed the existing check which was already present hence I agree with you and I should remove this.

Comment thread packages/core/src/rules/async2/security-defined.ts
Comment thread packages/core/src/rules/async2/security-defined.ts
Comment thread packages/core/src/utils/is-operation-secured.ts Outdated
Comment thread packages/core/src/rules/async3/security-defined.ts
@harshit078

Copy link
Copy Markdown
Contributor Author

Hey @Daryna-del , I have addressed all the comments left by you and cursor and also resolved the failing e2e tests. Can you review the recent changes again ? Thanks !

@harshit078 harshit078 requested a review from Daryna-del July 3, 2026 12:30
location: itemLocation,
name,
refPointer,
resolved: resolved.node !== undefined,

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.

This check treats a null-valued scheme entry as defined, am I right?
Seems that a scheme key with no value (half-finished YAML) silently passes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I guess you are correct and I didnt catch it. $ref to a key with a null value resolves to node === null and since null !== undefined was marked as defined and silently passed.

operation: SecuredOperation | undefined,
resolve: UserContext['resolve']
): boolean {
if (operation?.security) return true;

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.

This condition might be confusing.
Seems the function consider empty security (e.g. security: []) as Secured operation.
But it is actually public.

And have similar check that will result in opposite verdict:

      serverHasSecurity.set(
        key.toString(),
        Array.isArray(server?.security) && server.security.length > 0
      );

if (rootOperations && rootLocation) {
const operationsLocation = rootLocation.child(['operations']);
for (const [opName, opRef] of Object.entries(rootOperations)) {
const operation = isRef(opRef) ? resolve<Async3Operation>(opRef).node : opRef;

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.

Looks like only refs against the root document will be resolved here.
Please double check, but it seems that refs recorded relative to another file never resolve, producing false 'Every operation should have security defined' errors on multi-file docs.
Please check context.resolve functionality, maybe you need to handle this case as well.

return {
Root: {
enter(root, { location }: UserContext) {
rootServers = root?.servers;

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.

Could you please check if you handle the scenario with two files correctly:

# main.yaml
asyncapi: 3.0.0
info:
  title: Test
  version: 1.0.0
servers:
  $ref: './servers.yaml'      # ← map-level ref
channels:
  myChannel:
    address: user/signedup
operations:
  sendUserSignedUp:
    action: send
    channel:
      $ref: '#/channels/myChannel'

# servers.yaml — the server IS secured
prod:
  host: example.com
  protocol: kafka
  security:
    - type: apiKey
      in: user


function pointsToSecurityScheme(pointer: string): boolean {
return (
pointer.startsWith(SECURITY_SCHEMES_POINTER) &&

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.

Seems external file references are not handled here.

The AsyncAPI 3.0 specification defines security requirements as an array of Security Scheme Objects, and states the general rule: any time a Schema Object can be used, a Reference Object can be used in its place, allowing referencing definitions instead of defining them inline. The Reference Object itself uses JSON Reference resolution — "For this specification, reference resolution is done as defined by the JSON Reference specification and not by the JSON Schema specification." JSON Reference allows the $ref value to be any URI, which includes relative file paths and remote URLs.

Please check the example. We able to bundle it with ref resolution but this new rule will produce false failure during file lint.

asyncapi: 3.0.0
info:
  title: Async3 External Security Ref
  version: 1.0.0
servers:
  production:
    host: broker.example.com
    protocol: mqtt
    security:
      - $ref: './apiKey.yaml'
channels:
  userSignups:
    address: user/signedup
    messages:
      userSignedUp:
        payload:
          type: object
          properties:
            name:
              type: string
operations:
  sendUserSignups:
    action: send
    channel:
      $ref: '#/channels/userSignups'
    security:
      - $ref: './apiKey.yaml'

apiKey.yaml =>

type: httpApiKey
name: api_key
in: header
description: API key security scheme defined in an external file

@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 2 potential issues.

Fix All in Cursor

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

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ffc7ef8. Configure here.

location: itemLocation,
name,
refPointer,
resolved: resolved.node !== undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Null scheme counts as defined

Medium Severity

AsyncAPI 3 $ref resolution treats a target node of null as defined because resolved uses resolved.node !== undefined. AsyncAPI 2 marks any visited SecurityScheme key as defined without checking the scheme body, so a null entry under components.securitySchemes also passes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ffc7ef8. Configure here.

enter(root, { location }: UserContext) {
rootServers = root?.servers;
rootOperations = root?.operations;
rootLocation = location;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Map-level servers ref ignored

High Severity

When top-level servers is a single $ref to another file (instead of a named map), AsyncAPI 3 keeps the raw ref in rootServers and later treats the resolved value as one server object. AsyncAPI 2 never records those servers in serverHasSecurity, so applicability checks see no secured servers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ffc7ef8. 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.

6 participants