From e5d2d218cb2d40454589bc68e5301ace16f13269 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 18 Aug 2026 13:54:18 -0500 Subject: [PATCH 01/22] feat: support OpenAPI 3.0.4/3.1.1/3.1.2 and handle 3.1 array-form type --- CHANGELOG.md | 20 ++++++++++++++++ pom.xml | 4 ++-- .../checks/format/AbstractFormatCheck.java | 3 ++- .../format/OAR115VerifyRequiredFields.java | 3 ++- .../owasp/OAR070BrokenAccessControlCheck.java | 16 ++++++------- .../OAR029StandardResponseSchemaCheck.java | 2 +- .../schemas/OAR108SchemaValidatorCheck.java | 5 +++- .../OAR074NumericParameterIntegrityCheck.java | 6 ++--- .../OAR075StringParameterIntegrityCheck.java | 4 +++- .../OAR082BinaryOrByteFormatCheck.java | 10 +++++--- .../security/OAR085OpenAPIVersionCheck.java | 2 +- .../sonar/openapi/utils/JsonNodeUtils.java | 20 +++++++++++++++- .../OAR082BinaryOrByteFormatCheckTest.java | 5 ++++ .../OAR085OpenAPIVersionCheckTest.java | 17 +++++++++++++- .../OAR085/valid-openapi-version-304.yaml | 6 +++++ .../security/OAR082/array-type-format.yaml | 23 +++++++++++++++++++ .../OAR085/valid-openapi-version-311.yaml | 6 +++++ .../OAR085/valid-openapi-version-312.yaml | 6 +++++ 18 files changed, 134 insertions(+), 24 deletions(-) create mode 100644 src/test/resources/checks/v3/security/OAR085/valid-openapi-version-304.yaml create mode 100644 src/test/resources/checks/v31/security/OAR082/array-type-format.yaml create mode 100644 src/test/resources/checks/v31/security/OAR085/valid-openapi-version-311.yaml create mode 100644 src/test/resources/checks/v31/security/OAR085/valid-openapi-version-312.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ef5475..608bb082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.1-beta-3] + +### Added + +- OAR085 - Accept `3.0.4`, `3.1.1`, `3.1.2` in the default valid-versions. + +### Changed + +- Bump `sonar-openapi` core to `1.2.2-beta-1` (parses the new versions). +- `JsonNodeUtils` - `isType`/`getPrimaryType` accept array-form `type` (OpenAPI 3.1). +- OAR082 - Accept array-form `type`; accept `contentEncoding`/`contentMediaType` as byte/binary. +- OAR029 - Accept array-form `type`. +- OAR070 - Accept array-form `type`. +- OAR074 - Accept array-form `type`. +- OAR075 - Accept array-form `type`. +- OAR108 - Accept array-form `type`. +- OAR115 - Accept array-form `type`. +- OAR016 / OAR037 / OAR052 / OAR076 - Accept array-form `type` via `AbstractFormatCheck`. + + ## [1.5.1-beta-2] ### Fixed diff --git a/pom.xml b/pom.xml index 41d2b466..ab58a265 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.5.1-beta-2 + 1.5.1-beta-3 sonar-plugin SonarQube OpenAPI Community Rules @@ -64,7 +64,7 @@ 8.7.0.41497 6.7 - 1.2.1 + 1.2.2-beta-1 1.22.0.848 20231013 4.13.2 diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/format/AbstractFormatCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/format/AbstractFormatCheck.java index adb15cde..5f54f027 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/format/AbstractFormatCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/format/AbstractFormatCheck.java @@ -7,6 +7,7 @@ import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar; import org.apiaddicts.apitools.dosonarapi.api.v32.OpenApi32Grammar; import apiaddicts.sonar.openapi.checks.BaseCheck; +import apiaddicts.sonar.openapi.utils.JsonNodeUtils; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import java.util.Set; @@ -25,7 +26,7 @@ public void visitNode(JsonNode node) { private void visitV2Node(JsonNode node) { JsonNode typeNode = node.get("type"); - String type = typeNode.getTokenValue(); + String type = JsonNodeUtils.getPrimaryType(typeNode); JsonNode formatNode = node.get("format"); if (formatNode.isMissing()) { validate(type, null, typeNode, node); diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR115VerifyRequiredFields.java b/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR115VerifyRequiredFields.java index 235065ec..01ac76e2 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR115VerifyRequiredFields.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR115VerifyRequiredFields.java @@ -2,6 +2,7 @@ import apiaddicts.sonar.openapi.checks.BaseCheck; import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isExternalRef; +import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isObjectType; import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.resolve; import com.google.common.collect.ImmutableSet; import com.sonar.sslr.api.AstNodeType; @@ -58,7 +59,7 @@ public void resolveExteralRef(JsonNode node) { public void verifyTypeObject(JsonNode node){ JsonNode typeNode = node.get("type"); - if (typeNode != null && "object".equals(typeNode.getTokenValue())) { + if (typeNode != null && isObjectType(typeNode)) { validateRequiredFields(node); } } diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/owasp/OAR070BrokenAccessControlCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/owasp/OAR070BrokenAccessControlCheck.java index 3ed3cd59..3caf3280 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/owasp/OAR070BrokenAccessControlCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/owasp/OAR070BrokenAccessControlCheck.java @@ -4,6 +4,8 @@ import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.sonar.check.Rule; +import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isType; + @Rule(key = OAR070BrokenAccessControlCheck.KEY) public class OAR070BrokenAccessControlCheck extends AbstractParameterCheck { @@ -24,20 +26,18 @@ protected void visitParameterNode(JsonNode node) { JsonNode schemaNode = node.get("schema"); boolean isNumericType = - typeNode != null && - ("integer".equals(typeNode.getTokenValue()) || - "number".equals(typeNode.getTokenValue()) || - "float".equals(typeNode.getTokenValue())); + isType(typeNode, "integer") || + isType(typeNode, "number") || + isType(typeNode, "float"); if (!isNumericType && schemaNode != null) { JsonNode schemaTypeNode = schemaNode.get("type"); isNumericType = - schemaTypeNode != null && - ("integer".equals(schemaTypeNode.getTokenValue()) || - "number".equals(schemaTypeNode.getTokenValue()) || - "float".equals(schemaTypeNode.getTokenValue())); + isType(schemaTypeNode, "integer") || + isType(schemaTypeNode, "number") || + isType(schemaTypeNode, "float"); typeNode = schemaTypeNode; } diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java index e61aee17..93f7e452 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java @@ -173,7 +173,7 @@ private void validateDataProperty(String name, JSONObject schema, Map { - boolean isArray = "array".equals(parent.get("type").getTokenValue()); + boolean isArray = isArrayType(parent.get("type")); if (getAllProperties(node).isEmpty() && !isArray) { addIssue(KEY, translate("OAR029.error-required-one-property", name), handleExternalRef.getTrueNode(node.key())); } diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java index 8153a58c..51871ce2 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java @@ -4,6 +4,7 @@ import com.sonar.sslr.api.AstNodeType; import org.sonar.check.Rule; import apiaddicts.sonar.openapi.checks.BaseCheck; +import apiaddicts.sonar.openapi.utils.JsonNodeUtils; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar; import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar; @@ -75,7 +76,9 @@ private Map extractSchemaTypes(JsonNode schemaNode) { for (Map.Entry entry : propertiesNode.propertyMap().entrySet()) { String propertyName = entry.getKey(); JsonNode propertyTypeNode = entry.getValue().get("type"); - String propertyType = propertyTypeNode != null ? propertyTypeNode.stringValue() : null; + String propertyType = (propertyTypeNode != null && propertyTypeNode.isArray()) + ? JsonNodeUtils.getPrimaryType(propertyTypeNode) + : (propertyTypeNode != null ? propertyTypeNode.stringValue() : null); schemaTypes.put(propertyName, propertyType); } } diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR074NumericParameterIntegrityCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR074NumericParameterIntegrityCheck.java index 0eb84e65..ca1d63b3 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR074NumericParameterIntegrityCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR074NumericParameterIntegrityCheck.java @@ -3,6 +3,8 @@ import org.sonar.check.Rule; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isType; + @Rule(key = OAR074NumericParameterIntegrityCheck.KEY) public class OAR074NumericParameterIntegrityCheck extends AbstractTypedParameterIntegrityCheck { @@ -15,9 +17,7 @@ public OAR074NumericParameterIntegrityCheck() { @Override protected boolean isTargetType(JsonNode typeNode) { - if(typeNode == null || typeNode.isMissing()) return false; - String t = typeNode.getTokenValue(); - return "integer".equals(t) || "number".equals(t) || "float".equals(t); + return isType(typeNode, "integer") || isType(typeNode, "number") || isType(typeNode, "float"); } @Override diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java index 5e2a3b8c..4bcd52a7 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java @@ -7,6 +7,8 @@ import java.util.Set; import java.util.stream.Collectors; +import static apiaddicts.sonar.openapi.utils.JsonNodeUtils.isStringType; + @Rule(key = OAR075StringParameterIntegrityCheck.KEY) public class OAR075StringParameterIntegrityCheck extends AbstractTypedParameterIntegrityCheck { @@ -27,7 +29,7 @@ public OAR075StringParameterIntegrityCheck() { @Override protected boolean isTargetType(JsonNode typeNode) { - return typeNode != null && !typeNode.isMissing() && "string".equals(typeNode.getTokenValue()); + return isStringType(typeNode); } @Override diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheck.java index 2f619e19..4411d026 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheck.java @@ -13,6 +13,8 @@ import org.apiaddicts.apitools.dosonarapi.api.v32.OpenApi32Grammar; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import apiaddicts.sonar.openapi.utils.JsonNodeUtils; + import java.util.Arrays; import java.util.List; import java.util.Set; @@ -57,11 +59,13 @@ private void visitV2Node(JsonNode node) { if (fieldNode == null || fieldNode.isMissing()) continue; JsonNode typeNode = fieldNode.get("type"); - String type = typeNode.isMissing() ? null : typeNode.getTokenValue(); - if ("string".equals(type)) { + if (JsonNodeUtils.isStringType(typeNode)) { String format = fieldNode.get("format").getTokenValue(); - if (!"binary".equals(format) && !"byte".equals(format)) { + boolean hasBinaryFormat = "binary".equals(format) || "byte".equals(format); + boolean hasContentEncoding = !fieldNode.get("contentEncoding").isMissing() + || !fieldNode.get("contentMediaType").isMissing(); + if (!hasBinaryFormat && !hasContentEncoding) { addIssue(KEY, translate(MESSAGE, fieldsApply), typeNode.key()); } } diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheck.java index 4ba0c578..bd0614f7 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheck.java @@ -20,7 +20,7 @@ public class OAR085OpenAPIVersionCheck extends BaseCheck { public static final String KEY = "OAR085"; private static final String MESSAGE = "OAR085.error"; - private static final String DEFAULT_VALID_VERSIONS = "2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.2.0"; + private static final String DEFAULT_VALID_VERSIONS = "2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.0.4,3.1.0,3.1.1,3.1.2,3.2.0"; @RuleProperty( key = "valid-versions", diff --git a/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java b/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java index 1763698f..cd070798 100644 --- a/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java +++ b/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java @@ -184,8 +184,26 @@ public static boolean isBooleanType(JsonNode schemaNode) { return isType(schemaNode, TYPE_BOOLEAN); } + public static String getPrimaryType(JsonNode typeNode) { + if (typeNode == null || typeNode.isMissing()) return null; + if (typeNode.isArray()) { + for (JsonNode element : typeNode.elements()) { + String value = element.getTokenValue(); + if (value != null && !"null".equals(value)) return value; + } + return null; + } + return typeNode.getTokenValue(); + } + public static boolean isType(JsonNode type, String name) { - return TYPE_ANY.equals(name) || name.equals(type.getTokenValue()); + if (TYPE_ANY.equals(name)) return true; + if (type == null || type.isMissing()) return false; + if (name.equals(type.getTokenValue())) return true; + for (JsonNode element : type.elements()) { + if (name.equals(element.getTokenValue())) return true; + } + return false; } public static boolean isOperation(JsonNode node) { diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheckTest.java index 8efe8794..72a09a2e 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR082BinaryOrByteFormatCheckTest.java @@ -35,6 +35,11 @@ public void verifyvalidV32() { verifyV32("valid-format"); } + @Test + public void verifyArrayFormTypeV31() { + verifyV31("array-type-format.yaml"); + } + @Override public void verifyRule() { assertRuleProperties("OAR082 - BinaryOrByte - The string properties of the specified parameters must define a byte or binary format.", RuleType.VULNERABILITY, Severity.MAJOR, tags("safety")); diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheckTest.java index 3d09c539..1968df46 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR085OpenAPIVersionCheckTest.java @@ -50,6 +50,21 @@ public void verifyInV32() { verifyV32("valid-openapi-version"); } + @Test + public void verifyValidOpenApiVersion304InV3() { + verifyV3("valid-openapi-version-304.yaml"); + } + + @Test + public void verifyValidOpenApiVersion311InV31() { + verifyV31("valid-openapi-version-311.yaml"); + } + + @Test + public void verifyValidOpenApiVersion312InV31() { + verifyV31("valid-openapi-version-312.yaml"); + } + @Test public void verifyInvalidOpenApiVersionInV3() { verifyV3("invalid-openapi-version"); @@ -137,6 +152,6 @@ public void verifyRule() { @Override public void verifyParameters() { assertNumberOfParameters(1); - assertParameterProperties("valid-versions", "2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.2.0", RuleParamType.STRING); + assertParameterProperties("valid-versions", "2.0,3.0.0,3.0.1,3.0.2,3.0.3,3.0.4,3.1.0,3.1.1,3.1.2,3.2.0", RuleParamType.STRING); } } \ No newline at end of file diff --git a/src/test/resources/checks/v3/security/OAR085/valid-openapi-version-304.yaml b/src/test/resources/checks/v3/security/OAR085/valid-openapi-version-304.yaml new file mode 100644 index 00000000..4fec745f --- /dev/null +++ b/src/test/resources/checks/v3/security/OAR085/valid-openapi-version-304.yaml @@ -0,0 +1,6 @@ +openapi: 3.0.4 +info: + title: Sample API + description: This is a sample API. + version: 1.0.0 +paths: {} diff --git a/src/test/resources/checks/v31/security/OAR082/array-type-format.yaml b/src/test/resources/checks/v31/security/OAR082/array-type-format.yaml new file mode 100644 index 00000000..10f83e4a --- /dev/null +++ b/src/test/resources/checks/v31/security/OAR082/array-type-format.yaml @@ -0,0 +1,23 @@ +openapi: "3.1.1" +info: + version: "1.0.0" + title: "Swagger Petstore" +paths: + /invoices: + get: + responses: + '200': + description: A invoice. + content: + application/json: + schema: + type: object + properties: + product: + type: ["string", "null"] # Noncompliant {{OAR082: The string properties among product,line,price must define a byte or binary format}} + line: + type: ["string", "null"] + contentEncoding: base64 + price: + type: string + format: binary diff --git a/src/test/resources/checks/v31/security/OAR085/valid-openapi-version-311.yaml b/src/test/resources/checks/v31/security/OAR085/valid-openapi-version-311.yaml new file mode 100644 index 00000000..99a531e9 --- /dev/null +++ b/src/test/resources/checks/v31/security/OAR085/valid-openapi-version-311.yaml @@ -0,0 +1,6 @@ +openapi: "3.1.1" +info: + title: Sample API + description: This is a sample API. + version: 1.0.0 +paths: {} diff --git a/src/test/resources/checks/v31/security/OAR085/valid-openapi-version-312.yaml b/src/test/resources/checks/v31/security/OAR085/valid-openapi-version-312.yaml new file mode 100644 index 00000000..038eebbf --- /dev/null +++ b/src/test/resources/checks/v31/security/OAR085/valid-openapi-version-312.yaml @@ -0,0 +1,6 @@ +openapi: "3.1.2" +info: + title: Sample API + description: This is a sample API. + version: 1.0.0 +paths: {} From c566d5d4efe9255fa74b3efad687da37f91f8c0c Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Wed, 19 Aug 2026 08:16:19 -0500 Subject: [PATCH 02/22] fix: OAR003 resolve $ref and iterate map-form x-wso2-scopes --- CHANGELOG.md | 7 ++ pom.xml | 2 +- .../apim/wso2/AbstractWso2ScopesCheck.java | 10 ++- ...DefinedWso2ScopesDescriptionCheckTest.java | 26 +++++++ .../apim/wso2/OAR003/fail-ref-security.json | 29 ++++++++ .../apim/wso2/OAR003/fail-ref-security.yaml | 67 +++++++++++++++++++ .../apim/wso2/OAR003/fail-scopes-as-map.json | 27 ++++++++ .../apim/wso2/OAR003/fail-scopes-as-map.yaml | 66 ++++++++++++++++++ .../v31/apim/OAR003/fail-ref-security.json | 29 ++++++++ .../v31/apim/OAR003/fail-ref-security.yaml | 20 ++++++ .../v31/apim/OAR003/fail-scopes-as-map.json | 27 ++++++++ .../v31/apim/OAR003/fail-scopes-as-map.yaml | 19 ++++++ .../v32/apim/OAR003/fail-ref-security.json | 29 ++++++++ .../v32/apim/OAR003/fail-ref-security.yaml | 20 ++++++ .../v32/apim/OAR003/fail-scopes-as-map.json | 27 ++++++++ .../v32/apim/OAR003/fail-scopes-as-map.yaml | 19 ++++++ 16 files changed, 421 insertions(+), 3 deletions(-) create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.json create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.yaml create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.json create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.yaml create mode 100644 src/test/resources/checks/v31/apim/OAR003/fail-ref-security.json create mode 100644 src/test/resources/checks/v31/apim/OAR003/fail-ref-security.yaml create mode 100644 src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.json create mode 100644 src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.yaml create mode 100644 src/test/resources/checks/v32/apim/OAR003/fail-ref-security.json create mode 100644 src/test/resources/checks/v32/apim/OAR003/fail-ref-security.yaml create mode 100644 src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.json create mode 100644 src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ef5475..fd1ad0df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.1-beta-3] + +### Fixed + +- OAR003 - Resolve a `$ref` on `x-wso2-security` and iterate map-form `x-wso2-scopes` (shared `AbstractWso2ScopesCheck`), so referenced and mapping-keyed scopes are detected. + + ## [1.5.1-beta-2] ### Fixed diff --git a/pom.xml b/pom.xml index 41d2b466..89572cfa 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.5.1-beta-2 + 1.5.1-beta-3 sonar-plugin SonarQube OpenAPI Community Rules diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java index e87b71ff..9d824a48 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java @@ -7,8 +7,10 @@ import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar; import org.apiaddicts.apitools.dosonarapi.api.v32.OpenApi32Grammar; import apiaddicts.sonar.openapi.checks.BaseCheck; +import apiaddicts.sonar.openapi.utils.JsonNodeUtils; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -25,10 +27,14 @@ public void visitNode(JsonNode node) { } private void visitV2NV3Node(JsonNode node) { - JsonNode scopesNode = node.get("x-wso2-security").get("apim").get("x-wso2-scopes"); + JsonNode securityNode = node.get("x-wso2-security"); + if (!securityNode.isMissing()) securityNode = JsonNodeUtils.resolve(securityNode); + JsonNode scopesNode = securityNode.get("apim").get("x-wso2-scopes"); visitScopesNode(scopesNode); if (scopesNode.isMissing() || scopesNode.isNull()) return; - List scopes = scopesNode.elements(); + List scopes = scopesNode.isObject() + ? new ArrayList<>(scopesNode.propertyMap().values()) + : scopesNode.elements(); visitScopes(scopes); scopes.forEach(this::visitScope); } diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java index 2cbf3ffb..2321dfad 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java @@ -63,6 +63,32 @@ public void verifyInV32WithoutScopeDescription() { public void verifyInV3WithNullScopeDescription() { verifyV3("with-null-description"); } + + @Test + public void verifyInV3WithScopesAsMap() { + verifyV3("fail-scopes-as-map"); + } + @Test + public void verifyInV31WithScopesAsMap() { + verifyV31("fail-scopes-as-map"); + } + @Test + public void verifyInV32WithScopesAsMap() { + verifyV32("fail-scopes-as-map"); + } + + @Test + public void verifyInV3WithRefSecurity() { + verifyV3("fail-ref-security"); + } + @Test + public void verifyInV31WithRefSecurity() { + verifyV31("fail-ref-security"); + } + @Test + public void verifyInV32WithRefSecurity() { + verifyV32("fail-ref-security"); + } @Test public void verifyInV31WithNullScopeDescription() { verifyV31("with-null-description"); diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.json b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.json new file mode 100644 index 00000000..2f8441e3 --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.json @@ -0,0 +1,29 @@ +{ + "openapi" : "3.0.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "x-wso2-definitions" : { + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.yaml b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.yaml new file mode 100644 index 00000000..4cd86003 --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-ref-security.yaml @@ -0,0 +1,67 @@ +# OAR003 — the whole x-wso2-security block behind a $ref. JsonNode.get() never resolves references, +# so before the fix the scope list looked absent to Sonar and it reported nothing, while Spectral +# lints the resolved document. After the fix Sonar resolves the reference and reports the scope. +openapi: "3.0.3" +info: + title: Pet Store WSO2 scopes behind a reference + description: The same catalogue API with its WSO2 security block factored out behind a $ref. + version: 1.0.0 + contact: + name: APIQuality + email: support@apiquality.io + url: https://apiquality.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: https://api.example.com/petstore/v1 + description: Production +tags: + - name: pets + description: Everything about the pets in the catalogue +paths: + /pets: + get: + tags: + - pets + operationId: listPets + summary: List pets + description: Returns every pet in the catalogue. + responses: + '200': + description: The list of pets + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" +components: + schemas: + Pet: + type: object + description: One pet in the catalogue. + required: + - id + - name + properties: + id: + type: integer + format: int64 + description: The pet's identifier. + name: + type: string + description: The pet's name. +x-wso2-security: + $ref: "#/x-wso2-definitions/security" +x-wso2-definitions: + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.json b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.json new file mode 100644 index 00000000..94bd3753 --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.json @@ -0,0 +1,27 @@ +{ + "openapi" : "3.0.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } + } + } + } +} diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.yaml b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.yaml new file mode 100644 index 00000000..0c66f6f7 --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-scopes-as-map.yaml @@ -0,0 +1,66 @@ +# OAR003 — x-wso2-scopes written as a mapping keyed by scope name instead of the array WSO2 expects. +# A mapping, so ObjectNode.elements() yields nothing and OAR003 visited no scope on the Sonar side +# before the fix, while Spectral's [*] iterates the mapping's values. +openapi: "3.0.3" +info: + title: Pet Store WSO2 scopes as a mapping + description: The same catalogue API with its WSO2 scopes written as a mapping instead of an array. + version: 1.0.0 + contact: + name: APIQuality + email: support@apiquality.io + url: https://apiquality.io + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html +servers: + - url: https://api.example.com/petstore/v1 + description: Production +tags: + - name: pets + description: Everything about the pets in the catalogue +paths: + /pets: + get: + tags: + - pets + operationId: listPets + summary: List pets + description: Returns every pet in the catalogue. + responses: + '200': + description: The list of pets + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Pet" +components: + schemas: + Pet: + type: object + description: One pet in the catalogue. + required: + - id + - name + properties: + id: + type: integer + format: int64 + description: The pet's identifier. + name: + type: string + description: The pet's name. +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v31/apim/OAR003/fail-ref-security.json b/src/test/resources/checks/v31/apim/OAR003/fail-ref-security.json new file mode 100644 index 00000000..c86d0759 --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/fail-ref-security.json @@ -0,0 +1,29 @@ +{ + "openapi" : "3.1.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "x-wso2-definitions" : { + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v31/apim/OAR003/fail-ref-security.yaml b/src/test/resources/checks/v31/apim/OAR003/fail-ref-security.yaml new file mode 100644 index 00000000..cf221e8d --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/fail-ref-security.yaml @@ -0,0 +1,20 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/security" +x-wso2-definitions: + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.json b/src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.json new file mode 100644 index 00000000..a7d21d3e --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.json @@ -0,0 +1,27 @@ +{ + "openapi" : "3.1.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } + } + } + } +} diff --git a/src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.yaml b/src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.yaml new file mode 100644 index 00000000..6063131b --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/fail-scopes-as-map.yaml @@ -0,0 +1,19 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v32/apim/OAR003/fail-ref-security.json b/src/test/resources/checks/v32/apim/OAR003/fail-ref-security.json new file mode 100644 index 00000000..e3149baf --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/fail-ref-security.json @@ -0,0 +1,29 @@ +{ + "openapi" : "3.2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "x-wso2-definitions" : { + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v32/apim/OAR003/fail-ref-security.yaml b/src/test/resources/checks/v32/apim/OAR003/fail-ref-security.yaml new file mode 100644 index 00000000..7475799b --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/fail-ref-security.yaml @@ -0,0 +1,20 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/security" +x-wso2-definitions: + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.json b/src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.json new file mode 100644 index 00000000..cf7cf14c --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.json @@ -0,0 +1,27 @@ +{ + "openapi" : "3.2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } + } + } + } +} diff --git a/src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.yaml b/src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.yaml new file mode 100644 index 00000000..5419e731 --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/fail-scopes-as-map.yaml @@ -0,0 +1,19 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE From 6dc3e832ec3589ccd5d4111e2610363f27b484ec Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Mon, 24 Aug 2026 09:18:30 -0500 Subject: [PATCH 03/22] feat: add new param path-exclusions for rule OAR060 --- .../OAR060QueryParametersOptionalCheck.java | 50 ++++++++++++++- ...AR060QueryParametersOptionalCheckTest.java | 63 ++++++++++++++++++- .../v2/parameters/OAR060/excluded-path.json | 35 +++++++++++ .../v2/parameters/OAR060/excluded-path.yaml | 23 +++++++ .../v2/parameters/OAR060/required-true.json | 2 +- .../v2/parameters/OAR060/required-true.yaml | 2 +- .../parameters/OAR060/components-query.yaml | 18 ++++++ .../v3/parameters/OAR060/edge-params.yaml | 24 +++++++ .../v3/parameters/OAR060/excluded-path.json | 39 ++++++++++++ .../v3/parameters/OAR060/excluded-path.yaml | 25 ++++++++ .../v3/parameters/OAR060/no-exclusions.yaml | 16 +++++ .../v3/parameters/OAR060/non-query.yaml | 26 ++++++++ .../v3/parameters/OAR060/path-level.yaml | 16 +++++ .../v3/parameters/OAR060/required-true.json | 2 +- .../v3/parameters/OAR060/required-true.yaml | 2 +- .../checks/v3/parameters/OAR060/subpath.yaml | 16 +++++ .../v31/parameters/OAR060/excluded-path.json | 39 ++++++++++++ .../v31/parameters/OAR060/excluded-path.yaml | 25 ++++++++ .../v31/parameters/OAR060/required-true.json | 2 +- .../v31/parameters/OAR060/required-true.yaml | 2 +- .../v32/parameters/OAR060/excluded-path.json | 39 ++++++++++++ .../v32/parameters/OAR060/excluded-path.yaml | 25 ++++++++ .../v32/parameters/OAR060/required-true.json | 2 +- .../v32/parameters/OAR060/required-true.yaml | 2 +- 24 files changed, 485 insertions(+), 10 deletions(-) create mode 100644 src/test/resources/checks/v2/parameters/OAR060/excluded-path.json create mode 100644 src/test/resources/checks/v2/parameters/OAR060/excluded-path.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/components-query.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/edge-params.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/excluded-path.json create mode 100644 src/test/resources/checks/v3/parameters/OAR060/excluded-path.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/no-exclusions.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/non-query.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/path-level.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/subpath.yaml create mode 100644 src/test/resources/checks/v31/parameters/OAR060/excluded-path.json create mode 100644 src/test/resources/checks/v31/parameters/OAR060/excluded-path.yaml create mode 100644 src/test/resources/checks/v32/parameters/OAR060/excluded-path.json create mode 100644 src/test/resources/checks/v32/parameters/OAR060/excluded-path.yaml diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java index f53c3d0c..0e39099f 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java @@ -1,6 +1,19 @@ package apiaddicts.sonar.openapi.checks.parameters; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + import org.sonar.check.Rule; +import org.sonar.check.RuleProperty; + +import com.sonar.sslr.api.AstNode; + +import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar; +import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar; +import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar; +import org.apiaddicts.apitools.dosonarapi.api.v32.OpenApi32Grammar; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; @Rule(key = OAR060QueryParametersOptionalCheck.KEY) @@ -8,6 +21,27 @@ public class OAR060QueryParametersOptionalCheck extends AbstractParameterCheck { public static final String KEY = "OAR060"; private static final String MESSAGE = "OAR060.error"; + private static final String DEFAULT_EXCLUSION = "/status"; + + @RuleProperty( + key = "path-exclusions", + description = "List of explicit paths to exclude from this rule.", + defaultValue = DEFAULT_EXCLUSION + ) + private String exclusionStr = DEFAULT_EXCLUSION; + + private Set exclusion = Collections.emptySet(); + + @Override + protected void visitFile(JsonNode root) { + exclusion = (exclusionStr == null || exclusionStr.trim().isEmpty()) + ? Collections.emptySet() + : Arrays.stream(exclusionStr.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toSet()); + super.visitFile(root); + } @Override protected void visitParameterNode(JsonNode node) { @@ -16,6 +50,10 @@ protected void visitParameterNode(JsonNode node) { if (inNode != null && "query".equals(inNode.getTokenValue())) { + if (isExcludedPath(node)) { + return; + } + JsonNode requiredNode = node.get("required"); if (requiredNode != null && "true".equals(requiredNode.getTokenValue())) { @@ -23,4 +61,14 @@ protected void visitParameterNode(JsonNode node) { } } } -} \ No newline at end of file + + private boolean isExcludedPath(JsonNode node) { + AstNode pathNode = node.getFirstAncestor( + OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH, OpenApi32Grammar.PATH); + if (pathNode == null) { + return false; + } + String path = ((JsonNode) pathNode).key().getTokenValue(); + return exclusion.contains(path); + } +} diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java index e9058dbb..2a713a78 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java @@ -1,9 +1,12 @@ package apiaddicts.sonar.openapi.checks.parameters; +import java.lang.reflect.Field; + import org.junit.Before; import org.junit.Test; import org.sonar.api.rule.Severity; import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.RuleParamType; import apiaddicts.sonar.openapi.BaseCheckTest; public class OAR060QueryParametersOptionalCheckTest extends BaseCheckTest{ @@ -50,9 +53,67 @@ public void verifyInV31requiredTrue() { public void verifyInV32requiredTrue() { verifyV32("required-true"); } + @Test + public void verifyInV2ExcludedPath() { + verifyV2("excluded-path"); + } + @Test + public void verifyInV3ExcludedPath() { + verifyV3("excluded-path"); + } + @Test + public void verifyInV31ExcludedPath() { + verifyV31("excluded-path"); + } + @Test + public void verifyInV32ExcludedPath() { + verifyV32("excluded-path"); + } + @Test + public void verifyNonQueryParamsIgnored() { + verifyV3("non-query.yaml"); + } + @Test + public void verifyPathLevelQueryParam() { + verifyV3("path-level.yaml"); + } + @Test + public void verifyComponentsQueryParam() { + verifyV3("components-query.yaml"); + } + @Test + public void verifySubpathIsNotExcluded() { + verifyV3("subpath.yaml"); + } + @Test + public void verifyEmptyExclusionsFlagsEveryPath() throws Exception { + setExclusions(""); + verifyV3("no-exclusions.yaml"); + } + @Test + public void verifyNullExclusionsFlagsEveryPath() throws Exception { + setExclusions(null); + verifyV3("no-exclusions.yaml"); + } + @Test + public void verifyRefParamAndQueryWithoutRequiredIgnored() { + verifyV3("edge-params.yaml"); + } + + private void setExclusions(String value) throws Exception { + Field field = OAR060QueryParametersOptionalCheck.class.getDeclaredField("exclusionStr"); + field.setAccessible(true); + field.set(check, value); + } + + @Override + public void verifyParameters() { + assertNumberOfParameters(1); + assertParameterProperties("path-exclusions", "/status", RuleParamType.STRING); + } @Override public void verifyRule() { assertRuleProperties("OAR060 - QueryParametersOptional - All parameters in query must be defined as optional", RuleType.BUG, Severity.CRITICAL, tags("parameters")); } - + } diff --git a/src/test/resources/checks/v2/parameters/OAR060/excluded-path.json b/src/test/resources/checks/v2/parameters/OAR060/excluded-path.json new file mode 100644 index 00000000..ea1bff5e --- /dev/null +++ b/src/test/resources/checks/v2/parameters/OAR060/excluded-path.json @@ -0,0 +1,35 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/status" : { + "get" : { + "parameters" : [ { + "in" : "query", + "name" : "other", + "type" : "array", + "required" : true, + "items" : { + "type" : "string" + } + }, { + "in" : "query", + "name" : "$filter", + "type" : "array", + "required" : true, + "items" : { + "type" : "string" + } + } ], + "responses" : { + "206" : { + "description" : "Ok" + } + } + } + } + } + } diff --git a/src/test/resources/checks/v2/parameters/OAR060/excluded-path.yaml b/src/test/resources/checks/v2/parameters/OAR060/excluded-path.yaml new file mode 100644 index 00000000..0a40c64d --- /dev/null +++ b/src/test/resources/checks/v2/parameters/OAR060/excluded-path.yaml @@ -0,0 +1,23 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /status: + get: + parameters: + - in: query + name: other + type: array + required: true + items: + type: string + - in: query + name: $filter + type: array + required: true + items: + type: string + responses: + 206: + description: Ok diff --git a/src/test/resources/checks/v2/parameters/OAR060/required-true.json b/src/test/resources/checks/v2/parameters/OAR060/required-true.json index 7873ddd1..32746f81 100644 --- a/src/test/resources/checks/v2/parameters/OAR060/required-true.json +++ b/src/test/resources/checks/v2/parameters/OAR060/required-true.json @@ -5,7 +5,7 @@ "title" : "Swagger Petstore" }, "paths" : { - "/status" : { + "/pets" : { "get" : { "parameters" : [ { "in" : "query", diff --git a/src/test/resources/checks/v2/parameters/OAR060/required-true.yaml b/src/test/resources/checks/v2/parameters/OAR060/required-true.yaml index 3072d144..5f7a4462 100644 --- a/src/test/resources/checks/v2/parameters/OAR060/required-true.yaml +++ b/src/test/resources/checks/v2/parameters/OAR060/required-true.yaml @@ -3,7 +3,7 @@ info: version: 1.0.0 title: Swagger Petstore paths: - /status: + /pets: get: parameters: - in: query diff --git a/src/test/resources/checks/v3/parameters/OAR060/components-query.yaml b/src/test/resources/checks/v3/parameters/OAR060/components-query.yaml new file mode 100644 index 00000000..92d52d01 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/components-query.yaml @@ -0,0 +1,18 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OAR060 components query param +paths: + /pets: + get: + responses: + "200": + description: OK +components: + parameters: + SharedQuery: + in: query + name: q + required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}} + schema: + type: string diff --git a/src/test/resources/checks/v3/parameters/OAR060/edge-params.yaml b/src/test/resources/checks/v3/parameters/OAR060/edge-params.yaml new file mode 100644 index 00000000..16b93b65 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/edge-params.yaml @@ -0,0 +1,24 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OAR060 edge params +paths: + /pets: + get: + parameters: + - $ref: '#/components/parameters/SomeRef' + - in: query + name: noRequiredFlag + schema: + type: string + responses: + "200": + description: OK +components: + parameters: + SomeRef: + in: header + name: X-Ref + required: false + schema: + type: string diff --git a/src/test/resources/checks/v3/parameters/OAR060/excluded-path.json b/src/test/resources/checks/v3/parameters/OAR060/excluded-path.json new file mode 100644 index 00000000..0cbe884b --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/excluded-path.json @@ -0,0 +1,39 @@ +{ + "openapi" : "3.0.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/status" : { + "get" : { + "parameters" : [ { + "in" : "query", + "name" : "other", + "required": true, + "schema": { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "in" : "query", + "name" : "$filter", + "required": true, + "schema": { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "206" : { + "description" : "Ok" + } + } + } + } + } + } diff --git a/src/test/resources/checks/v3/parameters/OAR060/excluded-path.yaml b/src/test/resources/checks/v3/parameters/OAR060/excluded-path.yaml new file mode 100644 index 00000000..124f66f9 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/excluded-path.yaml @@ -0,0 +1,25 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /status: + get: + parameters: + - in: query + name: other + required: true + schema: + type: array + items: + type: string + - in: query + name: $filter + required: true + schema: + type: array + items: + type: string + responses: + 206: + description: Ok diff --git a/src/test/resources/checks/v3/parameters/OAR060/no-exclusions.yaml b/src/test/resources/checks/v3/parameters/OAR060/no-exclusions.yaml new file mode 100644 index 00000000..c75c3921 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/no-exclusions.yaml @@ -0,0 +1,16 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OAR060 empty exclusions +paths: + /status: + get: + parameters: + - in: query + name: verbose + required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}} + schema: + type: boolean + responses: + "200": + description: OK diff --git a/src/test/resources/checks/v3/parameters/OAR060/non-query.yaml b/src/test/resources/checks/v3/parameters/OAR060/non-query.yaml new file mode 100644 index 00000000..7d873771 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/non-query.yaml @@ -0,0 +1,26 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OAR060 non-query params +paths: + /pets/{id}: + get: + parameters: + - in: path + name: id + required: true + schema: + type: string + - in: header + name: X-Trace + required: true + schema: + type: string + - in: cookie + name: session + required: true + schema: + type: string + responses: + "200": + description: OK diff --git a/src/test/resources/checks/v3/parameters/OAR060/path-level.yaml b/src/test/resources/checks/v3/parameters/OAR060/path-level.yaml new file mode 100644 index 00000000..20b5be86 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/path-level.yaml @@ -0,0 +1,16 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OAR060 path-level params +paths: + /pets: + parameters: + - in: query + name: shared + required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}} + schema: + type: string + get: + responses: + "200": + description: OK diff --git a/src/test/resources/checks/v3/parameters/OAR060/required-true.json b/src/test/resources/checks/v3/parameters/OAR060/required-true.json index c57cdd06..ffb18531 100644 --- a/src/test/resources/checks/v3/parameters/OAR060/required-true.json +++ b/src/test/resources/checks/v3/parameters/OAR060/required-true.json @@ -5,7 +5,7 @@ "title" : "Swagger Petstore" }, "paths" : { - "/status" : { + "/pets" : { "get" : { "parameters" : [ { "in" : "query", diff --git a/src/test/resources/checks/v3/parameters/OAR060/required-true.yaml b/src/test/resources/checks/v3/parameters/OAR060/required-true.yaml index 00a697fb..f7579b07 100644 --- a/src/test/resources/checks/v3/parameters/OAR060/required-true.yaml +++ b/src/test/resources/checks/v3/parameters/OAR060/required-true.yaml @@ -3,7 +3,7 @@ info: version: 1.0.0 title: Swagger Petstore paths: - /status: + /pets: get: parameters: - in: query diff --git a/src/test/resources/checks/v3/parameters/OAR060/subpath.yaml b/src/test/resources/checks/v3/parameters/OAR060/subpath.yaml new file mode 100644 index 00000000..61f0af84 --- /dev/null +++ b/src/test/resources/checks/v3/parameters/OAR060/subpath.yaml @@ -0,0 +1,16 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: OAR060 subpath not excluded +paths: + /status/health: + get: + parameters: + - in: query + name: verbose + required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}} + schema: + type: boolean + responses: + "200": + description: OK diff --git a/src/test/resources/checks/v31/parameters/OAR060/excluded-path.json b/src/test/resources/checks/v31/parameters/OAR060/excluded-path.json new file mode 100644 index 00000000..736eaa28 --- /dev/null +++ b/src/test/resources/checks/v31/parameters/OAR060/excluded-path.json @@ -0,0 +1,39 @@ +{ + "openapi" : "3.1.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/status" : { + "get" : { + "parameters" : [ { + "in" : "query", + "name" : "other", + "required": true, + "schema": { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "in" : "query", + "name" : "$filter", + "required": true, + "schema": { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "206" : { + "description" : "Ok" + } + } + } + } + } + } diff --git a/src/test/resources/checks/v31/parameters/OAR060/excluded-path.yaml b/src/test/resources/checks/v31/parameters/OAR060/excluded-path.yaml new file mode 100644 index 00000000..b988b0a9 --- /dev/null +++ b/src/test/resources/checks/v31/parameters/OAR060/excluded-path.yaml @@ -0,0 +1,25 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /status: + get: + parameters: + - in: query + name: other + required: true + schema: + type: array + items: + type: string + - in: query + name: $filter + required: true + schema: + type: array + items: + type: string + responses: + 206: + description: Ok diff --git a/src/test/resources/checks/v31/parameters/OAR060/required-true.json b/src/test/resources/checks/v31/parameters/OAR060/required-true.json index d9ffefcf..b01ebf5d 100644 --- a/src/test/resources/checks/v31/parameters/OAR060/required-true.json +++ b/src/test/resources/checks/v31/parameters/OAR060/required-true.json @@ -5,7 +5,7 @@ "title" : "Swagger Petstore" }, "paths" : { - "/status" : { + "/pets" : { "get" : { "parameters" : [ { "in" : "query", diff --git a/src/test/resources/checks/v31/parameters/OAR060/required-true.yaml b/src/test/resources/checks/v31/parameters/OAR060/required-true.yaml index 95969ebd..e973fa15 100644 --- a/src/test/resources/checks/v31/parameters/OAR060/required-true.yaml +++ b/src/test/resources/checks/v31/parameters/OAR060/required-true.yaml @@ -3,7 +3,7 @@ info: version: 1.0.0 title: Swagger Petstore paths: - /status: + /pets: get: parameters: - in: query diff --git a/src/test/resources/checks/v32/parameters/OAR060/excluded-path.json b/src/test/resources/checks/v32/parameters/OAR060/excluded-path.json new file mode 100644 index 00000000..7c3d226c --- /dev/null +++ b/src/test/resources/checks/v32/parameters/OAR060/excluded-path.json @@ -0,0 +1,39 @@ +{ + "openapi" : "3.2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/status" : { + "get" : { + "parameters" : [ { + "in" : "query", + "name" : "other", + "required": true, + "schema": { + "type" : "array", + "items" : { + "type" : "string" + } + } + }, { + "in" : "query", + "name" : "$filter", + "required": true, + "schema": { + "type" : "array", + "items" : { + "type" : "string" + } + } + } ], + "responses" : { + "206" : { + "description" : "Ok" + } + } + } + } + } + } diff --git a/src/test/resources/checks/v32/parameters/OAR060/excluded-path.yaml b/src/test/resources/checks/v32/parameters/OAR060/excluded-path.yaml new file mode 100644 index 00000000..662fc575 --- /dev/null +++ b/src/test/resources/checks/v32/parameters/OAR060/excluded-path.yaml @@ -0,0 +1,25 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /status: + get: + parameters: + - in: query + name: other + required: true + schema: + type: array + items: + type: string + - in: query + name: $filter + required: true + schema: + type: array + items: + type: string + responses: + 206: + description: Ok diff --git a/src/test/resources/checks/v32/parameters/OAR060/required-true.json b/src/test/resources/checks/v32/parameters/OAR060/required-true.json index cf62bf43..36dc495a 100644 --- a/src/test/resources/checks/v32/parameters/OAR060/required-true.json +++ b/src/test/resources/checks/v32/parameters/OAR060/required-true.json @@ -5,7 +5,7 @@ "title" : "Swagger Petstore" }, "paths" : { - "/status" : { + "/pets" : { "get" : { "parameters" : [ { "in" : "query", diff --git a/src/test/resources/checks/v32/parameters/OAR060/required-true.yaml b/src/test/resources/checks/v32/parameters/OAR060/required-true.yaml index b6fbafbf..3588cdd2 100644 --- a/src/test/resources/checks/v32/parameters/OAR060/required-true.yaml +++ b/src/test/resources/checks/v32/parameters/OAR060/required-true.yaml @@ -3,7 +3,7 @@ info: version: 1.0.0 title: Swagger Petstore paths: - /status: + /pets: get: parameters: - in: query From 4eb855f287cc76454c347537fc2a5e2609adc3f4 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Mon, 24 Aug 2026 12:20:55 -0500 Subject: [PATCH 04/22] feat: added new rule OAR116 for paths pattern --- .../sonar/openapi/checks/RulesLists.java | 3 +- .../checks/format/OAR116PathPatternCheck.java | 53 ++++++++++++ src/main/resources/messages/errors.properties | 1 + .../resources/messages/errors_es.properties | 1 + .../openapi/rules/openapi/format/OAR116.html | 53 ++++++++++++ .../openapi/rules/openapi/format/OAR116.json | 13 +++ .../openapi/rules/openapi/format/OAR116.html | 53 ++++++++++++ .../openapi/rules/openapi/format/OAR116.json | 13 +++ .../format/OAR116PathPatternCheckTest.java | 83 +++++++++++++++++++ .../checks/v2/format/OAR116/invalid.yaml | 15 ++++ .../checks/v2/format/OAR116/valid.json | 33 ++++++++ .../checks/v2/format/OAR116/valid.yaml | 20 +++++ .../checks/v3/format/OAR116/invalid.yaml | 15 ++++ .../checks/v3/format/OAR116/valid.json | 35 ++++++++ .../checks/v3/format/OAR116/valid.yaml | 21 +++++ .../checks/v31/format/OAR116/invalid.yaml | 15 ++++ .../checks/v31/format/OAR116/valid.json | 35 ++++++++ .../checks/v31/format/OAR116/valid.yaml | 21 +++++ .../checks/v32/format/OAR116/invalid.yaml | 15 ++++ .../checks/v32/format/OAR116/valid.json | 35 ++++++++ .../checks/v32/format/OAR116/valid.yaml | 21 +++++ 21 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 src/main/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheck.java create mode 100644 src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.html create mode 100644 src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.json create mode 100644 src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.html create mode 100644 src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.json create mode 100644 src/test/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheckTest.java create mode 100644 src/test/resources/checks/v2/format/OAR116/invalid.yaml create mode 100644 src/test/resources/checks/v2/format/OAR116/valid.json create mode 100644 src/test/resources/checks/v2/format/OAR116/valid.yaml create mode 100644 src/test/resources/checks/v3/format/OAR116/invalid.yaml create mode 100644 src/test/resources/checks/v3/format/OAR116/valid.json create mode 100644 src/test/resources/checks/v3/format/OAR116/valid.yaml create mode 100644 src/test/resources/checks/v31/format/OAR116/invalid.yaml create mode 100644 src/test/resources/checks/v31/format/OAR116/valid.json create mode 100644 src/test/resources/checks/v31/format/OAR116/valid.yaml create mode 100644 src/test/resources/checks/v32/format/OAR116/invalid.yaml create mode 100644 src/test/resources/checks/v32/format/OAR116/valid.json create mode 100644 src/test/resources/checks/v32/format/OAR116/valid.yaml diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/RulesLists.java b/src/main/java/apiaddicts/sonar/openapi/checks/RulesLists.java index 8f855576..df069337 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/RulesLists.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/RulesLists.java @@ -52,7 +52,8 @@ public static List> getFormatChecks() { OAR110LicenseInformationCheck.class, OAR111ContactInformationCheck.class, OAR113CustomFieldCheck.class, - OAR115VerifyRequiredFields.class + OAR115VerifyRequiredFields.class, + OAR116PathPatternCheck.class ); } diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheck.java new file mode 100644 index 00000000..c4eb3c98 --- /dev/null +++ b/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheck.java @@ -0,0 +1,53 @@ +package apiaddicts.sonar.openapi.checks.format; + +import java.util.Set; +import java.util.regex.Pattern; + +import com.google.common.collect.ImmutableSet; +import com.sonar.sslr.api.AstNodeType; + +import org.sonar.check.Rule; +import org.sonar.check.RuleProperty; + +import apiaddicts.sonar.openapi.checks.BaseCheck; +import org.apiaddicts.apitools.dosonarapi.api.v2.OpenApi2Grammar; +import org.apiaddicts.apitools.dosonarapi.api.v3.OpenApi3Grammar; +import org.apiaddicts.apitools.dosonarapi.api.v31.OpenApi31Grammar; +import org.apiaddicts.apitools.dosonarapi.api.v32.OpenApi32Grammar; +import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; + +@Rule(key = OAR116PathPatternCheck.KEY) +public class OAR116PathPatternCheck extends BaseCheck { + + public static final String KEY = "OAR116"; + private static final String MESSAGE = "OAR116.error"; + private static final String DEFAULT_PATTERN = "^/"; + + @RuleProperty( + key = "pattern", + description = "Regular expression every API path must match.", + defaultValue = DEFAULT_PATTERN + ) + private String patternStr = DEFAULT_PATTERN; + + private Pattern pattern; + + @Override + public Set subscribedKinds() { + return ImmutableSet.of(OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH, OpenApi32Grammar.PATH); + } + + @Override + protected void visitFile(JsonNode root) { + pattern = Pattern.compile(patternStr != null ? patternStr : DEFAULT_PATTERN); + super.visitFile(root); + } + + @Override + public void visitNode(JsonNode node) { + String path = node.key().getTokenValue(); + if (!pattern.matcher(path).find()) { + addIssue(KEY, translate(MESSAGE, patternStr), node.key()); + } + } +} diff --git a/src/main/resources/messages/errors.properties b/src/main/resources/messages/errors.properties index 94b9fb2d..801c1b5f 100644 --- a/src/main/resources/messages/errors.properties +++ b/src/main/resources/messages/errors.properties @@ -120,6 +120,7 @@ OAR110.error=License information cannot be empty OAR111.error=Contact information cannot be empty OAR113.error=Field or extension {0} must be at the assigned location OAR115.error=This value does not exist, it must be defined in the schema properties +OAR116.error=Path does not match the required pattern: {0} generic.section=Section {0} is mandatory generic.consume=Should indicate the default request media type generic.produce=Should indicate the default response media type diff --git a/src/main/resources/messages/errors_es.properties b/src/main/resources/messages/errors_es.properties index ed00285c..1f5266ac 100644 --- a/src/main/resources/messages/errors_es.properties +++ b/src/main/resources/messages/errors_es.properties @@ -120,6 +120,7 @@ OAR110.error=La información de licencia no puede estar vacía OAR111.error=La información de contacto no puede estar vacía OAR113.error=El campo o extensión {0} debe estar en la ubicación asignada. OAR115.error=Este valor no existe, debe estár definido en las propiedades del esquema +OAR116.error=La ruta no cumple con el patrón requerido: {0} generic.section=La sección {0} es obligatoria generic.consume=Debe indicar el tipo de medio de solicitud predeterminado generic.produce=Debe indicar el tipo de medio de respuesta predeterminado diff --git a/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.html b/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.html new file mode 100644 index 00000000..acbd04d3 --- /dev/null +++ b/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.html @@ -0,0 +1,53 @@ +

Todas las rutas de la API deben cumplir con la expresión regular configurada. Por defecto el patrón solo exige que cada ruta empiece con "/" (^/); el cliente puede sobrescribirlo con una expresión más estricta.

+

Solución no conforme (OpenAPI 2)

+
+swagger: "2.0"
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/}}
+    get:
+      responses:
+        "200":
+          description: Ok
+
+

Solución conforme (OpenAPI 2)

+
+swagger: "2.0"
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  /pets:
+    get:
+      responses:
+        "200":
+          description: Ok
+
+

Solución no conforme (OpenAPI 3)

+
+openapi: 3.0.0
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/}}
+    get:
+      responses:
+        "200":
+          description: Ok
+
+

Solución conforme (OpenAPI 3)

+
+openapi: 3.0.0
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  /pets:
+    get:
+      responses:
+        "200":
+          description: Ok
+
diff --git a/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.json b/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.json new file mode 100644 index 00000000..56ebac61 --- /dev/null +++ b/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/format/OAR116.json @@ -0,0 +1,13 @@ +{ + "title": "OAR116 - PathPattern - Todas las rutas de la API deben cumplir con la expresión regular configurada", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "format" + ], + "defaultSeverity": "MAJOR" +} diff --git a/src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.html b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.html new file mode 100644 index 00000000..4f0b960e --- /dev/null +++ b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.html @@ -0,0 +1,53 @@ +

Every API path must match the configured regular expression. By default the pattern only requires each path to start with "/" (^/); clients can override it with a stricter expression.

+

Noncompliant Solution (OpenAPI 2)

+
+swagger: "2.0"
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/}}
+    get:
+      responses:
+        "200":
+          description: Ok
+
+

Compliant Solution (OpenAPI 2)

+
+swagger: "2.0"
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  /pets:
+    get:
+      responses:
+        "200":
+          description: Ok
+
+

Noncompliant Solution (OpenAPI 3)

+
+openapi: 3.0.0
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/}}
+    get:
+      responses:
+        "200":
+          description: Ok
+
+

Compliant Solution (OpenAPI 3)

+
+openapi: 3.0.0
+info:
+  title: Sample API
+  version: "1.0.0"
+paths:
+  /pets:
+    get:
+      responses:
+        "200":
+          description: Ok
+
diff --git a/src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.json b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.json new file mode 100644 index 00000000..c68e1305 --- /dev/null +++ b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/format/OAR116.json @@ -0,0 +1,13 @@ +{ + "title": "OAR116 - PathPattern - All API paths must match the configured regular expression", + "type": "BUG", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "30min" + }, + "tags": [ + "format" + ], + "defaultSeverity": "MAJOR" +} diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheckTest.java new file mode 100644 index 00000000..9ef1caf7 --- /dev/null +++ b/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR116PathPatternCheckTest.java @@ -0,0 +1,83 @@ +package apiaddicts.sonar.openapi.checks.format; + +import java.lang.reflect.Field; + +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.rule.Severity; +import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.RuleParamType; +import apiaddicts.sonar.openapi.BaseCheckTest; + +public class OAR116PathPatternCheckTest extends BaseCheckTest { + + @Before + public void init() { + ruleName = "OAR116"; + check = new OAR116PathPatternCheck(); + v2Path = getV2Path("format"); + v3Path = getV3Path("format"); + v31Path = getV31Path("format"); + v32Path = getV32Path("format"); + } + + @Test + public void verifyValidV2() { + verifyV2("valid"); + } + @Test + public void verifyValidV3() { + verifyV3("valid"); + } + @Test + public void verifyValidV31() { + verifyV31("valid"); + } + @Test + public void verifyValidV32() { + verifyV32("valid"); + } + + @Test + public void verifyInvalidV2() throws Exception { + setPattern("^/v[0-9]+"); + verifyV2("invalid.yaml"); + } + @Test + public void verifyInvalidV3() throws Exception { + setPattern("^/v[0-9]+"); + verifyV3("invalid.yaml"); + } + @Test + public void verifyInvalidV31() throws Exception { + setPattern("^/v[0-9]+"); + verifyV31("invalid.yaml"); + } + @Test + public void verifyInvalidV32() throws Exception { + setPattern("^/v[0-9]+"); + verifyV32("invalid.yaml"); + } + @Test + public void verifyNullPatternFallsBackToDefault() throws Exception { + setPattern(null); + verifyV3("valid"); + } + + private void setPattern(String pattern) throws Exception { + Field field = OAR116PathPatternCheck.class.getDeclaredField("patternStr"); + field.setAccessible(true); + field.set(check, pattern); + } + + @Override + public void verifyParameters() { + assertNumberOfParameters(1); + assertParameterProperties("pattern", "^/", RuleParamType.STRING); + } + + @Override + public void verifyRule() { + assertRuleProperties("OAR116 - PathPattern - All API paths must match the configured regular expression", RuleType.BUG, Severity.MAJOR, tags("format")); + } +} diff --git a/src/test/resources/checks/v2/format/OAR116/invalid.yaml b/src/test/resources/checks/v2/format/OAR116/invalid.yaml new file mode 100644 index 00000000..1c7023d3 --- /dev/null +++ b/src/test/resources/checks/v2/format/OAR116/invalid.yaml @@ -0,0 +1,15 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Sample API +paths: + /v1/pets: + get: + responses: + "200": + description: Ok + /pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/v[0-9]+}} + get: + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v2/format/OAR116/valid.json b/src/test/resources/checks/v2/format/OAR116/valid.json new file mode 100644 index 00000000..f8df8270 --- /dev/null +++ b/src/test/resources/checks/v2/format/OAR116/valid.json @@ -0,0 +1,33 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.0.0", + "title" : "Sample API" + }, + "paths" : { + "/pets" : { + "get" : { + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + }, + "/pets/{id}" : { + "get" : { + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "type" : "string" + } ], + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + } + } +} diff --git a/src/test/resources/checks/v2/format/OAR116/valid.yaml b/src/test/resources/checks/v2/format/OAR116/valid.yaml new file mode 100644 index 00000000..1026fc76 --- /dev/null +++ b/src/test/resources/checks/v2/format/OAR116/valid.yaml @@ -0,0 +1,20 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Sample API +paths: + /pets: + get: + responses: + "200": + description: Ok + /pets/{id}: + get: + parameters: + - in: path + name: id + required: true + type: string + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v3/format/OAR116/invalid.yaml b/src/test/resources/checks/v3/format/OAR116/invalid.yaml new file mode 100644 index 00000000..8b65f479 --- /dev/null +++ b/src/test/resources/checks/v3/format/OAR116/invalid.yaml @@ -0,0 +1,15 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Sample API +paths: + /v1/pets: + get: + responses: + "200": + description: Ok + /pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/v[0-9]+}} + get: + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v3/format/OAR116/valid.json b/src/test/resources/checks/v3/format/OAR116/valid.json new file mode 100644 index 00000000..8e79badd --- /dev/null +++ b/src/test/resources/checks/v3/format/OAR116/valid.json @@ -0,0 +1,35 @@ +{ + "openapi" : "3.0.0", + "info" : { + "version" : "1.0.0", + "title" : "Sample API" + }, + "paths" : { + "/pets" : { + "get" : { + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + }, + "/pets/{id}" : { + "get" : { + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + } + } +} diff --git a/src/test/resources/checks/v3/format/OAR116/valid.yaml b/src/test/resources/checks/v3/format/OAR116/valid.yaml new file mode 100644 index 00000000..d5504dfc --- /dev/null +++ b/src/test/resources/checks/v3/format/OAR116/valid.yaml @@ -0,0 +1,21 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Sample API +paths: + /pets: + get: + responses: + "200": + description: Ok + /pets/{id}: + get: + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v31/format/OAR116/invalid.yaml b/src/test/resources/checks/v31/format/OAR116/invalid.yaml new file mode 100644 index 00000000..7289b912 --- /dev/null +++ b/src/test/resources/checks/v31/format/OAR116/invalid.yaml @@ -0,0 +1,15 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Sample API +paths: + /v1/pets: + get: + responses: + "200": + description: Ok + /pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/v[0-9]+}} + get: + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v31/format/OAR116/valid.json b/src/test/resources/checks/v31/format/OAR116/valid.json new file mode 100644 index 00000000..c7df1855 --- /dev/null +++ b/src/test/resources/checks/v31/format/OAR116/valid.json @@ -0,0 +1,35 @@ +{ + "openapi" : "3.1.0", + "info" : { + "version" : "1.0.0", + "title" : "Sample API" + }, + "paths" : { + "/pets" : { + "get" : { + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + }, + "/pets/{id}" : { + "get" : { + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + } + } +} diff --git a/src/test/resources/checks/v31/format/OAR116/valid.yaml b/src/test/resources/checks/v31/format/OAR116/valid.yaml new file mode 100644 index 00000000..b098e837 --- /dev/null +++ b/src/test/resources/checks/v31/format/OAR116/valid.yaml @@ -0,0 +1,21 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Sample API +paths: + /pets: + get: + responses: + "200": + description: Ok + /pets/{id}: + get: + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v32/format/OAR116/invalid.yaml b/src/test/resources/checks/v32/format/OAR116/invalid.yaml new file mode 100644 index 00000000..1d638f4e --- /dev/null +++ b/src/test/resources/checks/v32/format/OAR116/invalid.yaml @@ -0,0 +1,15 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Sample API +paths: + /v1/pets: + get: + responses: + "200": + description: Ok + /pets: # Noncompliant {{OAR116: Path does not match the required pattern: ^/v[0-9]+}} + get: + responses: + "200": + description: Ok diff --git a/src/test/resources/checks/v32/format/OAR116/valid.json b/src/test/resources/checks/v32/format/OAR116/valid.json new file mode 100644 index 00000000..3501b947 --- /dev/null +++ b/src/test/resources/checks/v32/format/OAR116/valid.json @@ -0,0 +1,35 @@ +{ + "openapi" : "3.2.0", + "info" : { + "version" : "1.0.0", + "title" : "Sample API" + }, + "paths" : { + "/pets" : { + "get" : { + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + }, + "/pets/{id}" : { + "get" : { + "parameters" : [ { + "in" : "path", + "name" : "id", + "required" : true, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Ok" + } + } + } + } + } +} diff --git a/src/test/resources/checks/v32/format/OAR116/valid.yaml b/src/test/resources/checks/v32/format/OAR116/valid.yaml new file mode 100644 index 00000000..9ef63523 --- /dev/null +++ b/src/test/resources/checks/v32/format/OAR116/valid.yaml @@ -0,0 +1,21 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Sample API +paths: + /pets: + get: + responses: + "200": + description: Ok + /pets/{id}: + get: + parameters: + - in: path + name: id + required: true + schema: + type: string + responses: + "200": + description: Ok From dd8b2ba7e9d623837e0f1b1ec744ffb9ee15f8aa Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 25 Aug 2026 11:51:12 -0500 Subject: [PATCH 05/22] fix: OAR003 resolve chained and per-scope $ref in x-wso2-scopes --- .../apim/wso2/AbstractWso2ScopesCheck.java | 6 ++- .../sonar/openapi/utils/JsonNodeUtils.java | 17 +++++--- ...DefinedWso2ScopesDescriptionCheckTest.java | 43 +++++++++++++++++++ .../OAR003/fail-chained-ref-security.json | 32 ++++++++++++++ .../OAR003/fail-chained-ref-security.yaml | 22 ++++++++++ .../apim/wso2/OAR003/fail-ref-security.json | 29 +++++++++++++ .../apim/wso2/OAR003/fail-ref-security.yaml | 20 +++++++++ .../apim/wso2/OAR003/fail-scopes-as-map.json | 27 ++++++++++++ .../apim/wso2/OAR003/fail-scopes-as-map.yaml | 19 ++++++++ .../OAR003/ref-scope-with-description.json | 33 ++++++++++++++ .../OAR003/ref-scope-with-description.yaml | 23 ++++++++++ .../OAR003/fail-chained-ref-security.json | 32 ++++++++++++++ .../OAR003/fail-chained-ref-security.yaml | 22 ++++++++++ .../OAR003/ref-scope-with-description.json | 33 ++++++++++++++ .../OAR003/ref-scope-with-description.yaml | 23 ++++++++++ .../OAR003/fail-chained-ref-security.json | 32 ++++++++++++++ .../OAR003/fail-chained-ref-security.yaml | 22 ++++++++++ .../OAR003/ref-scope-with-description.json | 33 ++++++++++++++ .../OAR003/ref-scope-with-description.yaml | 23 ++++++++++ .../OAR003/fail-chained-ref-security.json | 32 ++++++++++++++ .../OAR003/fail-chained-ref-security.yaml | 22 ++++++++++ .../OAR003/ref-scope-with-description.json | 33 ++++++++++++++ .../OAR003/ref-scope-with-description.yaml | 23 ++++++++++ 23 files changed, 594 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.json create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.yaml create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.json create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.yaml create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.json create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.yaml create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.json create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.yaml create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.json create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.yaml create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.json create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.yaml create mode 100644 src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.json create mode 100644 src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.yaml create mode 100644 src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.json create mode 100644 src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.yaml create mode 100644 src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.json create mode 100644 src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.yaml create mode 100644 src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.json create mode 100644 src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.yaml diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java index 9d824a48..248d942e 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java @@ -32,9 +32,13 @@ private void visitV2NV3Node(JsonNode node) { JsonNode scopesNode = securityNode.get("apim").get("x-wso2-scopes"); visitScopesNode(scopesNode); if (scopesNode.isMissing() || scopesNode.isNull()) return; - List scopes = scopesNode.isObject() + List rawScopes = scopesNode.isObject() ? new ArrayList<>(scopesNode.propertyMap().values()) : scopesNode.elements(); + List scopes = new ArrayList<>(rawScopes.size()); + for (JsonNode scope : rawScopes) { + scopes.add(JsonNodeUtils.resolve(scope)); + } visitScopes(scopes); scopes.forEach(this::visitScope); } diff --git a/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java b/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java index 1763698f..bf3edf51 100644 --- a/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java +++ b/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java @@ -17,6 +17,7 @@ import java.io.InputStreamReader; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import com.sonar.sslr.api.AstNodeType; @@ -45,17 +46,21 @@ private JsonNodeUtils() { private static String lastFetchedContent = ""; public static JsonNode resolve(JsonNode original) { - - if (original.isRef()) { - String ref = original.get("$ref").getTokenValue(); + JsonNode current = original; + Set visitedRefs = new HashSet<>(); + while (current.isRef()) { + String ref = current.get("$ref").getTokenValue(); if (ref.startsWith("#")) { - return original.resolve(); + if (!visitedRefs.add(ref)) { + return current; + } + current = current.resolve(); } else { JsonNode resolved = resolveExternalRef(ref); - return resolved != null ? resolved : original; + return resolved != null ? resolved : current; } } - return original; + return current; } public static boolean isExternalRef (JsonNode original){ diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java index 2321dfad..4685dcf7 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR003DefinedWso2ScopesDescriptionCheckTest.java @@ -98,6 +98,49 @@ public void verifyInV32WithNullScopeDescription() { verifyV32("with-null-description"); } + @Test + public void verifyInV2WithScopesAsMap() { + verifyV2("fail-scopes-as-map"); + } + @Test + public void verifyInV2WithRefSecurity() { + verifyV2("fail-ref-security"); + } + + @Test + public void verifyInV2WithChainedRefSecurity() { + verifyV2("fail-chained-ref-security"); + } + @Test + public void verifyInV3WithChainedRefSecurity() { + verifyV3("fail-chained-ref-security"); + } + @Test + public void verifyInV31WithChainedRefSecurity() { + verifyV31("fail-chained-ref-security"); + } + @Test + public void verifyInV32WithChainedRefSecurity() { + verifyV32("fail-chained-ref-security"); + } + + @Test + public void verifyInV2WithRefScopeDescription() { + verifyV2("ref-scope-with-description"); + } + @Test + public void verifyInV3WithRefScopeDescription() { + verifyV3("ref-scope-with-description"); + } + @Test + public void verifyInV31WithRefScopeDescription() { + verifyV31("ref-scope-with-description"); + } + @Test + public void verifyInV32WithRefScopeDescription() { + verifyV32("ref-scope-with-description"); + } + @Override public void verifyRule() { assertRuleProperties("OAR003 - DefinedWso2ScopesDescription - WSO2 scope description is recommended", RuleType.VULNERABILITY, Severity.BLOCKER, tags("api-manager", "vulnerability", "wso2")); diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.json b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.json new file mode 100644 index 00000000..ac1b0c48 --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.json @@ -0,0 +1,32 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/securityAlias" + }, + "x-wso2-definitions" : { + "securityAlias" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.yaml b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.yaml new file mode 100644 index 00000000..132eb7ad --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-chained-ref-security.yaml @@ -0,0 +1,22 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/securityAlias" +x-wso2-definitions: + securityAlias: + $ref: "#/x-wso2-definitions/security" + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.json b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.json new file mode 100644 index 00000000..e219f118 --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.json @@ -0,0 +1,29 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "x-wso2-definitions" : { + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.yaml b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.yaml new file mode 100644 index 00000000..cbbd6d45 --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-ref-security.yaml @@ -0,0 +1,20 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/security" +x-wso2-definitions: + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.json b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.json new file mode 100644 index 00000000..b4b5f581 --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.json @@ -0,0 +1,27 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } + } + } + } +} diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.yaml b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.yaml new file mode 100644 index 00000000..e01463b2 --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/fail-scopes-as-map.yaml @@ -0,0 +1,19 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.json b/src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.json new file mode 100644 index 00000000..47b61bd6 --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.json @@ -0,0 +1,33 @@ +{ + "swagger" : "2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { + "$ref" : "#/x-wso2-definitions/writeScope" + } + } + } + }, + "x-wso2-definitions" : { + "writeScope" : { + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE", + "description" : "Allows users to write the catalogue" + } + } +} diff --git a/src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.yaml b/src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.yaml new file mode 100644 index 00000000..3de00aef --- /dev/null +++ b/src/test/resources/checks/v2/apim/wso2/OAR003/ref-scope-with-description.yaml @@ -0,0 +1,23 @@ +swagger: "2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + $ref: "#/x-wso2-definitions/writeScope" +x-wso2-definitions: + writeScope: + name: write + key: write + roles: ROLE_WRITE + description: Allows users to write the catalogue diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.json b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.json new file mode 100644 index 00000000..95e577dd --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.json @@ -0,0 +1,32 @@ +{ + "openapi" : "3.0.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/securityAlias" + }, + "x-wso2-definitions" : { + "securityAlias" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.yaml b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.yaml new file mode 100644 index 00000000..d54bf5d8 --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/fail-chained-ref-security.yaml @@ -0,0 +1,22 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/securityAlias" +x-wso2-definitions: + securityAlias: + $ref: "#/x-wso2-definitions/security" + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.json b/src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.json new file mode 100644 index 00000000..a7922e2b --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.json @@ -0,0 +1,33 @@ +{ + "openapi" : "3.0.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { + "$ref" : "#/x-wso2-definitions/writeScope" + } + } + } + }, + "x-wso2-definitions" : { + "writeScope" : { + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE", + "description" : "Allows users to write the catalogue" + } + } +} diff --git a/src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.yaml b/src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.yaml new file mode 100644 index 00000000..6dfbbe4f --- /dev/null +++ b/src/test/resources/checks/v3/apim/wso2/OAR003/ref-scope-with-description.yaml @@ -0,0 +1,23 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + $ref: "#/x-wso2-definitions/writeScope" +x-wso2-definitions: + writeScope: + name: write + key: write + roles: ROLE_WRITE + description: Allows users to write the catalogue diff --git a/src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.json b/src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.json new file mode 100644 index 00000000..57c857c7 --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.json @@ -0,0 +1,32 @@ +{ + "openapi" : "3.1.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/securityAlias" + }, + "x-wso2-definitions" : { + "securityAlias" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.yaml b/src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.yaml new file mode 100644 index 00000000..0e83ac74 --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/fail-chained-ref-security.yaml @@ -0,0 +1,22 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/securityAlias" +x-wso2-definitions: + securityAlias: + $ref: "#/x-wso2-definitions/security" + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.json b/src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.json new file mode 100644 index 00000000..a957aa90 --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.json @@ -0,0 +1,33 @@ +{ + "openapi" : "3.1.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { + "$ref" : "#/x-wso2-definitions/writeScope" + } + } + } + }, + "x-wso2-definitions" : { + "writeScope" : { + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE", + "description" : "Allows users to write the catalogue" + } + } +} diff --git a/src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.yaml b/src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.yaml new file mode 100644 index 00000000..3525a799 --- /dev/null +++ b/src/test/resources/checks/v31/apim/OAR003/ref-scope-with-description.yaml @@ -0,0 +1,23 @@ +openapi: "3.1.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + $ref: "#/x-wso2-definitions/writeScope" +x-wso2-definitions: + writeScope: + name: write + key: write + roles: ROLE_WRITE + description: Allows users to write the catalogue diff --git a/src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.json b/src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.json new file mode 100644 index 00000000..59284764 --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.json @@ -0,0 +1,32 @@ +{ + "openapi" : "3.2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "$ref" : "#/x-wso2-definitions/securityAlias" + }, + "x-wso2-definitions" : { + "securityAlias" : { + "$ref" : "#/x-wso2-definitions/security" + }, + "security" : { + "apim" : { + "x-wso2-scopes" : [ { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, { # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE" + } ] + } + } + } +} diff --git a/src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.yaml b/src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.yaml new file mode 100644 index 00000000..02656857 --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/fail-chained-ref-security.yaml @@ -0,0 +1,22 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + $ref: "#/x-wso2-definitions/securityAlias" +x-wso2-definitions: + securityAlias: + $ref: "#/x-wso2-definitions/security" + security: + apim: + x-wso2-scopes: + - name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + - name: write # Noncompliant {{OAR003: WSO2 scope 'description' is recommended}} + key: write + roles: ROLE_WRITE diff --git a/src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.json b/src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.json new file mode 100644 index 00000000..e9353150 --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.json @@ -0,0 +1,33 @@ +{ + "openapi" : "3.2.0", + "info" : { + "version" : "1.0.0", + "title" : "Swagger Petstore" + }, + "paths" : { + "/pets" : { } + }, + "x-wso2-security" : { + "apim" : { + "x-wso2-scopes" : { + "read" : { + "name" : "read", + "key" : "read", + "roles" : "ROLE_READ", + "description" : "Allows users to read the catalogue" + }, + "write" : { + "$ref" : "#/x-wso2-definitions/writeScope" + } + } + } + }, + "x-wso2-definitions" : { + "writeScope" : { + "name" : "write", + "key" : "write", + "roles" : "ROLE_WRITE", + "description" : "Allows users to write the catalogue" + } + } +} diff --git a/src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.yaml b/src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.yaml new file mode 100644 index 00000000..eae34366 --- /dev/null +++ b/src/test/resources/checks/v32/apim/OAR003/ref-scope-with-description.yaml @@ -0,0 +1,23 @@ +openapi: "3.2.0" +info: + version: 1.0.0 + title: Swagger Petstore +paths: + /pets: {} + +x-wso2-security: + apim: + x-wso2-scopes: + read: + name: read + key: read + roles: ROLE_READ + description: Allows users to read the catalogue + write: + $ref: "#/x-wso2-definitions/writeScope" +x-wso2-definitions: + writeScope: + name: write + key: write + roles: ROLE_WRITE + description: Allows users to write the catalogue From 27655738d27afe08efa7a19e9118651019afc87a Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 25 Aug 2026 13:20:16 -0500 Subject: [PATCH 06/22] update version and changelog --- CHANGELOG.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36ef6797..779db725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `sonar-openapi` core to `1.2.2-beta-1` (parses the new versions). +- Bump `sonar-openapi` core to `1.3.0-beta-1` (parses the new versions). - `JsonNodeUtils` - `isType`/`getPrimaryType` accept array-form `type` (OpenAPI 3.1). - OAR082 - Accept array-form `type`; accept `contentEncoding`/`contentMediaType` as byte/binary. - OAR029 - Accept array-form `type`. diff --git a/pom.xml b/pom.xml index d2929d5f..22b75648 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 8.7.0.41497 6.7 - 1.2.2-beta-1 + 1.3.0-beta-1 1.22.0.848 20231013 4.13.2 From e0528ab34ea555cf46a04d97a723b8bced40e5bc Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 25 Aug 2026 16:13:50 -0500 Subject: [PATCH 07/22] fix: OAR002 empty array and object validation --- .../apim/wso2/OAR002ValidWso2ScopesCheck.java | 9 ++++++++- .../checks/v2/apim/wso2/OAR002/with-scopes.json | 16 ++++++++++++++++ .../checks/v2/apim/wso2/OAR002/with-scopes.yaml | 14 +++++++++++++- .../checks/v3/apim/wso2/OAR002/with-scopes.json | 16 ++++++++++++++++ .../checks/v3/apim/wso2/OAR002/with-scopes.yaml | 14 +++++++++++++- .../checks/v31/apim/OAR002/with-scopes.json | 16 ++++++++++++++++ .../checks/v31/apim/OAR002/with-scopes.yaml | 14 +++++++++++++- .../checks/v32/apim/OAR002/with-scopes.json | 16 ++++++++++++++++ .../checks/v32/apim/OAR002/with-scopes.yaml | 14 +++++++++++++- 9 files changed, 124 insertions(+), 5 deletions(-) diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java index 71d669f0..d0614309 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java @@ -38,8 +38,15 @@ private void validateProperty(Map properties, String propertyN JsonNode property = properties.get(propertyName); if (!properties.containsKey(propertyName)) { addIssue(KEY, translate(MESSAGE_PROP, propertyName), scope); - } else if(property.isNull() || property.getTokenValue().trim().equals("")) { + } else if (isEmpty(property)) { addIssue(KEY, translate(MESSAGE_PROP, propertyName), property.key()); } } + + private boolean isEmpty(JsonNode property) { + if (property.isNull()) return true; + if (property.isArray()) return property.elements().isEmpty(); + if (property.isObject()) return property.propertyMap().isEmpty(); + return property.getTokenValue().trim().equals(""); + } } diff --git a/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.json b/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.json index c03da0ac..0e04b217 100644 --- a/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.json +++ b/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.json @@ -30,6 +30,22 @@ "key" : "view2", "roles" : "ROLE_VIEW_2", "name" : null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + }, { + "name" : "read3", + "key" : "read3", + "roles" : [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : "read4", + "key" : "read4", + "roles" : {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : [], # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + "key" : "view3", + "roles" : "ROLE_VIEW_3" + }, { + "name" : "view4", + "key" : {}, # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + "roles" : "ROLE_VIEW_4" } ] } } diff --git a/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.yaml b/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.yaml index f574f034..4e436bc8 100644 --- a/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.yaml +++ b/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes.yaml @@ -22,4 +22,16 @@ x-wso2-security: roles: null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} - key: view2 roles: ROLE_VIEW_2 - name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} \ No newline at end of file + name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + - name: read3 + key: read3 + roles: [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: read4 + key: read4 + roles: {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: [] # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + key: view3 + roles: ROLE_VIEW_3 + - name: view4 + key: {} # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + roles: ROLE_VIEW_4 \ No newline at end of file diff --git a/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.json b/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.json index 79789ef4..3683ae6d 100644 --- a/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.json +++ b/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.json @@ -30,6 +30,22 @@ "key" : "view2", "roles" : "ROLE_VIEW_2", "name" : null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + }, { + "name" : "read3", + "key" : "read3", + "roles" : [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : "read4", + "key" : "read4", + "roles" : {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : [], # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + "key" : "view3", + "roles" : "ROLE_VIEW_3" + }, { + "name" : "view4", + "key" : {}, # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + "roles" : "ROLE_VIEW_4" } ] } } diff --git a/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.yaml b/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.yaml index 4c8994cb..29c82171 100644 --- a/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.yaml +++ b/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes.yaml @@ -22,4 +22,16 @@ x-wso2-security: roles: null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} - key: view2 roles: ROLE_VIEW_2 - name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} \ No newline at end of file + name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + - name: read3 + key: read3 + roles: [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: read4 + key: read4 + roles: {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: [] # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + key: view3 + roles: ROLE_VIEW_3 + - name: view4 + key: {} # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + roles: ROLE_VIEW_4 \ No newline at end of file diff --git a/src/test/resources/checks/v31/apim/OAR002/with-scopes.json b/src/test/resources/checks/v31/apim/OAR002/with-scopes.json index 807c5ce0..2794caaf 100644 --- a/src/test/resources/checks/v31/apim/OAR002/with-scopes.json +++ b/src/test/resources/checks/v31/apim/OAR002/with-scopes.json @@ -30,6 +30,22 @@ "key" : "view2", "roles" : "ROLE_VIEW_2", "name" : null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + }, { + "name" : "read3", + "key" : "read3", + "roles" : [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : "read4", + "key" : "read4", + "roles" : {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : [], # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + "key" : "view3", + "roles" : "ROLE_VIEW_3" + }, { + "name" : "view4", + "key" : {}, # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + "roles" : "ROLE_VIEW_4" } ] } } diff --git a/src/test/resources/checks/v31/apim/OAR002/with-scopes.yaml b/src/test/resources/checks/v31/apim/OAR002/with-scopes.yaml index 3c78c8ee..34c40976 100644 --- a/src/test/resources/checks/v31/apim/OAR002/with-scopes.yaml +++ b/src/test/resources/checks/v31/apim/OAR002/with-scopes.yaml @@ -22,4 +22,16 @@ x-wso2-security: roles: null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} - key: view2 roles: ROLE_VIEW_2 - name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} \ No newline at end of file + name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + - name: read3 + key: read3 + roles: [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: read4 + key: read4 + roles: {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: [] # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + key: view3 + roles: ROLE_VIEW_3 + - name: view4 + key: {} # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + roles: ROLE_VIEW_4 \ No newline at end of file diff --git a/src/test/resources/checks/v32/apim/OAR002/with-scopes.json b/src/test/resources/checks/v32/apim/OAR002/with-scopes.json index b5574cef..789a6e48 100644 --- a/src/test/resources/checks/v32/apim/OAR002/with-scopes.json +++ b/src/test/resources/checks/v32/apim/OAR002/with-scopes.json @@ -30,6 +30,22 @@ "key" : "view2", "roles" : "ROLE_VIEW_2", "name" : null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + }, { + "name" : "read3", + "key" : "read3", + "roles" : [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : "read4", + "key" : "read4", + "roles" : {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + }, { + "name" : [], # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + "key" : "view3", + "roles" : "ROLE_VIEW_3" + }, { + "name" : "view4", + "key" : {}, # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + "roles" : "ROLE_VIEW_4" } ] } } diff --git a/src/test/resources/checks/v32/apim/OAR002/with-scopes.yaml b/src/test/resources/checks/v32/apim/OAR002/with-scopes.yaml index c853ee55..62787cc4 100644 --- a/src/test/resources/checks/v32/apim/OAR002/with-scopes.yaml +++ b/src/test/resources/checks/v32/apim/OAR002/with-scopes.yaml @@ -22,4 +22,16 @@ x-wso2-security: roles: null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} - key: view2 roles: ROLE_VIEW_2 - name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} \ No newline at end of file + name: null # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + - name: read3 + key: read3 + roles: [] # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: read4 + key: read4 + roles: {} # Noncompliant {{OAR002: WSO2 scope 'roles' is required}} + - name: [] # Noncompliant {{OAR002: WSO2 scope 'name' is required}} + key: view3 + roles: ROLE_VIEW_3 + - name: view4 + key: {} # Noncompliant {{OAR002: WSO2 scope 'key' is required}} + roles: ROLE_VIEW_4 \ No newline at end of file From ed9c5172a714f4f36492f904f0fc595de2359a0f Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 25 Aug 2026 16:17:54 -0500 Subject: [PATCH 08/22] update changelog and version --- CHANGELOG.md | 5 +++++ pom.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5617c8..019b5315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0-beta-1] - 2026-08-25 + +### Fixed + +- OAR002 - Rewrote to validate the full `x-wso2-scopes` definition (null/empty container and missing/null/blank or empty-array/object `name`/`key`/`roles`) via new `apq-wso2-scopes-valid`. ## [1.5.1] - 2026-08-25 diff --git a/pom.xml b/pom.xml index de86ba15..4aa3e977 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.5.1 + 1.6.0-beta-1 sonar-plugin SonarQube OpenAPI Community Rules From 4ef6ea07c6d510700332bf0d8d9518bdac2a1047 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 25 Aug 2026 19:52:49 -0500 Subject: [PATCH 09/22] fix: OAR044 use possessive quantifiers in media-type regex to prevent ReDoS --- CHANGELOG.md | 7 +++++++ pom.xml | 2 +- .../openapi/checks/format/OAR044MediaTypeCheck.java | 8 ++++---- .../openapi/checks/format/OAR044MediaTypeCheckTest.java | 9 +++++++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5617c8..b9ebd705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0-beta-1] - 2026-08-25 + +### Fixed + +- OAR044 - MediaTypeCheck - Made the media type regex quantifiers possessive to prevent ReDoS with no change to matching. + + ## [1.5.1] - 2026-08-25 ### Changed diff --git a/pom.xml b/pom.xml index de86ba15..4aa3e977 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.5.1 + 1.6.0-beta-1 sonar-plugin SonarQube OpenAPI Community Rules diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheck.java index 39656827..b3446786 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheck.java @@ -45,11 +45,11 @@ public class OAR044MediaTypeCheck extends BaseCheck { private final ExternalRefHandler handleExternalRef = new ExternalRefHandler(); private static final String RESTRICTED_NAME = "[a-zA-Z0-9][a-zA-Z0-9.!#$&^_+\\-]*"; - private static final String OWS = "[ \\t]*"; - private static final String TOKEN = "[a-zA-Z0-9!#$%&'*+\\-.^_`|~]+"; - private static final String QUOTED_STRING = "\"(?:[^\"\\\\]|\\\\.)*\""; + private static final String OWS = "[ \\t]*+"; + private static final String TCHARS = "[a-zA-Z0-9!#$%&'*+\\-.^_`|~]++"; + private static final String QUOTED_STRING = "\"(?:[^\"\\\\]|\\\\.)*+\""; private static final String PARAMETERS = - "(?:" + OWS + ";" + OWS + TOKEN + "=(?:" + TOKEN + "|" + QUOTED_STRING + "))*"; + "(?:" + OWS + ";" + OWS + TCHARS + "=(?:" + TCHARS + "|" + QUOTED_STRING + "))*+"; @VisibleForTesting static final Pattern MIME_TYPE_PATTERN = Pattern.compile( diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheckTest.java index ae71be53..5a70f94a 100644 --- a/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheckTest.java +++ b/src/test/java/apiaddicts/sonar/openapi/checks/format/OAR044MediaTypeCheckTest.java @@ -6,6 +6,8 @@ import org.sonar.api.rules.RuleType; import apiaddicts.sonar.openapi.BaseCheckTest; +import static org.junit.Assert.assertFalse; + public class OAR044MediaTypeCheckTest extends BaseCheckTest { @Before @@ -36,6 +38,13 @@ public void verifyInV32() { verifyV32("media-type"); } + @Test(timeout = 2000) + public void verifyNoBacktrackingOnLongInput() { + String malicious = "application/json;x=\"" + "a".repeat(500_000); + assertFalse(OAR044MediaTypeCheck.MEDIA_RANGE_PATTERN.matcher(malicious).matches()); + assertFalse(OAR044MediaTypeCheck.MIME_TYPE_PATTERN.matcher(malicious).matches()); + } + @Override public void verifyRule() { assertRuleProperties("OAR044 - MediaType - Media types SHOULD conform to the RFC.", RuleType.BUG, Severity.BLOCKER, tags("format")); From f83f55a02a147de16e99997cc58e52fffad62c03 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Fri, 28 Aug 2026 17:46:22 -0500 Subject: [PATCH 10/22] feat: update changelog and version --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd344114..00673e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.6.0-beta-1] - 2026-08-25 +## [1.6.0-beta-1] - 2026-08-28 ### Fixed - OAR002 - Rewrote to validate the full `x-wso2-scopes` definition (null/empty container and missing/null/blank or empty-array/object `name`/`key`/`roles`) via new `apq-wso2-scopes-valid`. - -## [1.6.0-beta-1] - 2026-08-25 +- OAR003 - Resolve a `$ref` on `x-wso2-security` and iterate map-form `x-wso2-scopes` (shared `AbstractWso2ScopesCheck`), so referenced and mapping-keyed scopes are detected. ### Added From 0987dee218748eecc98e74e424105c5fadc55ec8 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Mon, 31 Aug 2026 19:42:27 -0500 Subject: [PATCH 11/22] update changelog and version --- CHANGELOG.md | 5 ++++- pom.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00673e41..8b2e8b1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0-beta-2] - 2026-08-31 + +- OAR003 - Resolve a `$ref` on `x-wso2-security` and iterate map-form `x-wso2-scopes` (shared `AbstractWso2ScopesCheck`), so referenced and mapping-keyed scopes are detected. + ## [1.6.0-beta-1] - 2026-08-28 ### Fixed - OAR002 - Rewrote to validate the full `x-wso2-scopes` definition (null/empty container and missing/null/blank or empty-array/object `name`/`key`/`roles`) via new `apq-wso2-scopes-valid`. -- OAR003 - Resolve a `$ref` on `x-wso2-security` and iterate map-form `x-wso2-scopes` (shared `AbstractWso2ScopesCheck`), so referenced and mapping-keyed scopes are detected. ### Added diff --git a/pom.xml b/pom.xml index 22b75648..8b163ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.6.0-beta-1 + 1.6.0-beta-2 sonar-plugin SonarQube OpenAPI Community Rules From cf0c9a95af1d208e0ef678ccfa9ea42c3b62b7ec Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 1 Sep 2026 11:58:44 -0500 Subject: [PATCH 12/22] feat: bump core to 1.3.0-beta-2 so reports unsupported OpenAPI versions --- CHANGELOG.md | 6 ++++++ pom.xml | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00673e41..a9e668b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0-beta-2] - 2026-09-01 + +### Changed + +- Bump `sonar-openapi` core to `1.3.0-beta-2`: a document declaring an unsupported `openapi` / `swagger` version is now analysed instead of being silently skipped. + ## [1.6.0-beta-1] - 2026-08-28 ### Fixed diff --git a/pom.xml b/pom.xml index 22b75648..5b7bd03f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.6.0-beta-1 + 1.6.0-beta-2 sonar-plugin SonarQube OpenAPI Community Rules @@ -64,7 +64,7 @@ 8.7.0.41497 6.7 - 1.3.0-beta-1 + 1.3.0-beta-2 1.22.0.848 20231013 4.13.2 From a99e42bbae13b106b82a560544a5dca7f586dbfc Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 1 Sep 2026 15:24:04 -0500 Subject: [PATCH 13/22] Merge branch 'develop' into feat/2694/oar060-path-exclusions --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9e668b8..66d0e9a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,7 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - OAR025 - The shared `apq-collection-query-param-required` function now also validates the parameter type for OAR025, keyed by rule code; when `$limit` is present but its type is not `integer`, a distinct type message is emitted. -## [1.5.1-beta-3] - 2026-08-14 +# [1.5.1-beta-3] - 2026-08-14 ### Changed From e56027756e704f099aa3ee06fa527b8348361795 Mon Sep 17 00:00:00 2001 From: Melsy Huamani Date: Tue, 1 Sep 2026 16:17:12 -0500 Subject: [PATCH 14/22] fix: apply OAR060 path-exclusions to $ref-ed query params --- CHANGELOG.md | 6 ++ pom.xml | 2 +- .../OAR060QueryParametersOptionalCheck.java | 72 +++++++++++++++++-- .../rules/openapi/parameters/OAR060.html | 4 ++ .../rules/openapi/parameters/OAR060.html | 4 ++ ...AR060QueryParametersOptionalCheckTest.java | 29 ++++++++ .../parameters/OAR060/ref-excluded-path.yaml | 35 +++++++++ .../parameters/OAR060/ref-excluded-path.yaml | 34 +++++++++ .../v3/parameters/OAR060/ref-external.yaml | 24 +++++++ .../v3/parameters/OAR060/ref-mixed-paths.yaml | 36 ++++++++++ .../parameters/OAR060/ref-no-exclusions.yaml | 20 ++++++ .../parameters/OAR060/ref-excluded-path.yaml | 33 +++++++++ .../parameters/OAR060/ref-excluded-path.yaml | 33 +++++++++ 13 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/checks/v2/parameters/OAR060/ref-excluded-path.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/ref-excluded-path.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/ref-external.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/ref-mixed-paths.yaml create mode 100644 src/test/resources/checks/v3/parameters/OAR060/ref-no-exclusions.yaml create mode 100644 src/test/resources/checks/v31/parameters/OAR060/ref-excluded-path.yaml create mode 100644 src/test/resources/checks/v32/parameters/OAR060/ref-excluded-path.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d0e9a5..1522314d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0-beta-3] - 2026-09-01 + +### Added + +- OAR060 - QueryParametersOptional - New `path-exclusions` rule property (default `/status`): a comma-separated list of exact, case-sensitive paths the rule must not fire on. + ## [1.6.0-beta-2] - 2026-09-01 ### Changed diff --git a/pom.xml b/pom.xml index 5b7bd03f..f0d060c7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.apiaddicts.apitools.dosonarapi sonaropenapi-rules-community - 1.6.0-beta-2 + 1.6.0-beta-3 sonar-plugin SonarQube OpenAPI Community Rules diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java index 0e39099f..9ebf8904 100644 --- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java +++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheck.java @@ -2,6 +2,9 @@ import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -16,12 +19,18 @@ import org.apiaddicts.apitools.dosonarapi.api.v32.OpenApi32Grammar; import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode; +import apiaddicts.sonar.openapi.utils.JsonNodeUtils; + @Rule(key = OAR060QueryParametersOptionalCheck.KEY) public class OAR060QueryParametersOptionalCheck extends AbstractParameterCheck { public static final String KEY = "OAR060"; private static final String MESSAGE = "OAR060.error"; private static final String DEFAULT_EXCLUSION = "/status"; + private static final String PATHS = "paths"; + private static final String PARAMETERS = "parameters"; + private static final String REF = "$ref"; + private static final int MAX_REF_DEPTH = 10; @RuleProperty( key = "path-exclusions", @@ -32,6 +41,8 @@ public class OAR060QueryParametersOptionalCheck extends AbstractParameterCheck { private Set exclusion = Collections.emptySet(); + private Map> refUsages = Collections.emptyMap(); + @Override protected void visitFile(JsonNode root) { exclusion = (exclusionStr == null || exclusionStr.trim().isEmpty()) @@ -40,6 +51,7 @@ protected void visitFile(JsonNode root) { .map(String::trim) .filter(s -> !s.isEmpty()) .collect(Collectors.toSet()); + refUsages = collectRefUsages(root); super.visitFile(root); } @@ -65,10 +77,62 @@ protected void visitParameterNode(JsonNode node) { private boolean isExcludedPath(JsonNode node) { AstNode pathNode = node.getFirstAncestor( OpenApi2Grammar.PATH, OpenApi3Grammar.PATH, OpenApi31Grammar.PATH, OpenApi32Grammar.PATH); - if (pathNode == null) { - return false; + if (pathNode != null) { + return exclusion.contains(((JsonNode) pathNode).key().getTokenValue()); + } + Set usages = refUsages.get(node.getPointer()); + return usages != null && exclusion.containsAll(usages); + } + + private Map> collectRefUsages(JsonNode root) { + JsonNode paths = root.get(PATHS); + if (paths.isMissing() || !paths.isObject()) { + return Collections.emptyMap(); + } + Map> usages = new HashMap<>(); + for (JsonNode pathNode : paths.properties()) { + JsonNode pathKey = pathNode.key(); + if (pathKey.isMissing()) { + continue; + } + String path = pathKey.getTokenValue(); + JsonNode pathItem = resolveLocalRef(pathNode); + collectParameterRefUsages(pathItem.get(PARAMETERS), path, usages); + for (JsonNode operationNode : pathItem.properties()) { + if (JsonNodeUtils.isOperation(operationNode)) { + collectParameterRefUsages(operationNode.get(PARAMETERS), path, usages); + } + } + } + return usages; + } + + private void collectParameterRefUsages(JsonNode parametersNode, String path, Map> usages) { + if (parametersNode.isMissing() || !parametersNode.isArray()) { + return; + } + for (JsonNode parameterNode : parametersNode.elements()) { + JsonNode current = parameterNode; + for (int depth = 0; depth < MAX_REF_DEPTH && current.isRef(); depth++) { + JsonNode resolved = resolveLocalRef(current); + if (resolved == current) { + break; + } + usages.computeIfAbsent(resolved.getPointer(), k -> new HashSet<>()).add(path); + current = resolved; + } + } + } + + private static JsonNode resolveLocalRef(JsonNode node) { + if (!node.isRef()) { + return node; + } + String ref = node.get(REF).getTokenValue(); + if (ref == null || !ref.startsWith("#")) { + return node; } - String path = ((JsonNode) pathNode).key().getTokenValue(); - return exclusion.contains(path); + JsonNode resolved = node.resolve(); + return resolved.isMissing() ? node : resolved; } } diff --git a/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/parameters/OAR060.html b/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/parameters/OAR060.html index 1edc6032..e2049c5b 100644 --- a/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/parameters/OAR060.html +++ b/src/main/resources/org/sonar/l10n/es/openapi/rules/openapi/parameters/OAR060.html @@ -1,4 +1,8 @@

Todos los parámetros en query deben definirse como opcionales

+

Parámetros configurables:

+
    +
  • path-exclusions — lista separada por comas de rutas exactas (sensibles a mayúsculas) en las que esta regla no debe aplicarse (por defecto: /status). La coincidencia es exacta: /status excluye /status, pero no /status/health ni /statuses.
  • +

Ejemplo de código no compatible (OpenAPI 2)

   swagger: "2.0"
diff --git a/src/main/resources/org/sonar/l10n/openapi/rules/openapi/parameters/OAR060.html b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/parameters/OAR060.html
index 514df0ab..227a47aa 100644
--- a/src/main/resources/org/sonar/l10n/openapi/rules/openapi/parameters/OAR060.html
+++ b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/parameters/OAR060.html
@@ -1,4 +1,8 @@
 

All parameters in query must be defined as optional

+

Configurable parameters:

+
    +
  • path-exclusions — comma-separated list of exact, case-sensitive paths this rule must not fire on (default: /status). Matching is exact: /status excludes /status but neither /status/health nor /statuses.
  • +

Noncompliant Code Example (OpenAPI 2)

   swagger: "2.0"
diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java
index 2a713a78..6b9363ff 100644
--- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR060QueryParametersOptionalCheckTest.java
@@ -99,6 +99,35 @@ public void verifyNullExclusionsFlagsEveryPath() throws Exception {
     public void verifyRefParamAndQueryWithoutRequiredIgnored() {
         verifyV3("edge-params.yaml");
     }
+    @Test
+    public void verifyV3SharedParamUsedOnlyByExcludedPath() {
+        verifyV3("ref-excluded-path.yaml");
+    }
+    @Test
+    public void verifyV31SharedParamUsedOnlyByExcludedPath() {
+        verifyV31("ref-excluded-path.yaml");
+    }
+    @Test
+    public void verifyV32SharedParamUsedOnlyByExcludedPath() {
+        verifyV32("ref-excluded-path.yaml");
+    }
+    @Test
+    public void verifyV2SharedParamUsedOnlyByExcludedPath() {
+        verifyV2("ref-excluded-path.yaml");
+    }
+    @Test
+    public void verifyV3SharedParamUsedByExcludedAndNonExcludedPaths() {
+        verifyV3("ref-mixed-paths.yaml");
+    }
+    @Test
+    public void verifyV3SharedParamNotExcludedWithoutExclusions() throws Exception {
+        setExclusions("");
+        verifyV3("ref-no-exclusions.yaml");
+    }
+    @Test
+    public void verifyV3ExternalRefParamIsNotResolved() {
+        verifyV3("ref-external.yaml");
+    }
 
     private void setExclusions(String value) throws Exception {
         Field field = OAR060QueryParametersOptionalCheck.class.getDeclaredField("exclusionStr");
diff --git a/src/test/resources/checks/v2/parameters/OAR060/ref-excluded-path.yaml b/src/test/resources/checks/v2/parameters/OAR060/ref-excluded-path.yaml
new file mode 100644
index 00000000..33913eaa
--- /dev/null
+++ b/src/test/resources/checks/v2/parameters/OAR060/ref-excluded-path.yaml
@@ -0,0 +1,35 @@
+swagger: "2.0"
+info:
+  version: 1.0.0
+  title: OAR060 shared param used only by an excluded path
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: '#/parameters/SharedRefOnly'
+      responses:
+        200:
+          description: Ok
+  /pets:
+    get:
+      parameters:
+        - $ref: '#/parameters/SharedByPets'
+      responses:
+        200:
+          description: Ok
+parameters:
+  SharedRefOnly:
+    in: query
+    name: q
+    type: string
+    required: true
+  SharedByPets:
+    in: query
+    name: p
+    type: string
+    required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+  NeverReferenced:
+    in: query
+    name: unused
+    type: string
+    required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
diff --git a/src/test/resources/checks/v3/parameters/OAR060/ref-excluded-path.yaml b/src/test/resources/checks/v3/parameters/OAR060/ref-excluded-path.yaml
new file mode 100644
index 00000000..1e51ba5a
--- /dev/null
+++ b/src/test/resources/checks/v3/parameters/OAR060/ref-excluded-path.yaml
@@ -0,0 +1,34 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: OAR060 shared param used only by an excluded path
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedRefOnly'
+      responses:
+        "200":
+          description: OK
+    parameters:
+      - $ref: '#/components/parameters/SharedPathLevel'
+components:
+  parameters:
+    SharedRefOnly:
+      in: query
+      name: q
+      required: true
+      schema:
+        type: string
+    SharedPathLevel:
+      in: query
+      name: p
+      required: true
+      schema:
+        type: string
+    NeverReferenced:
+      in: query
+      name: unused
+      required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+      schema:
+        type: string
diff --git a/src/test/resources/checks/v3/parameters/OAR060/ref-external.yaml b/src/test/resources/checks/v3/parameters/OAR060/ref-external.yaml
new file mode 100644
index 00000000..e750d557
--- /dev/null
+++ b/src/test/resources/checks/v3/parameters/OAR060/ref-external.yaml
@@ -0,0 +1,24 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: OAR060 external parameter ref
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: './common.yaml#/components/parameters/External'
+      responses:
+        "200":
+          description: OK
+  /pets:
+    get:
+      parameters:
+        - $ref: './common.yaml#/components/parameters/External'
+        - in: query
+          name: inline
+          required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+          schema:
+            type: string
+      responses:
+        "200":
+          description: OK
diff --git a/src/test/resources/checks/v3/parameters/OAR060/ref-mixed-paths.yaml b/src/test/resources/checks/v3/parameters/OAR060/ref-mixed-paths.yaml
new file mode 100644
index 00000000..91890337
--- /dev/null
+++ b/src/test/resources/checks/v3/parameters/OAR060/ref-mixed-paths.yaml
@@ -0,0 +1,36 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: OAR060 shared params across excluded and non-excluded paths
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedByBoth'
+      responses:
+        "200":
+          description: OK
+  /pets:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedByBoth'
+        - $ref: '#/components/parameters/ChainedAlias'
+      responses:
+        "200":
+          description: OK
+components:
+  parameters:
+    SharedByBoth:
+      in: query
+      name: q
+      required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+      schema:
+        type: string
+    ChainedAlias:
+      $ref: '#/components/parameters/ChainTarget'
+    ChainTarget:
+      in: query
+      name: c
+      required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+      schema:
+        type: string
diff --git a/src/test/resources/checks/v3/parameters/OAR060/ref-no-exclusions.yaml b/src/test/resources/checks/v3/parameters/OAR060/ref-no-exclusions.yaml
new file mode 100644
index 00000000..61cbc298
--- /dev/null
+++ b/src/test/resources/checks/v3/parameters/OAR060/ref-no-exclusions.yaml
@@ -0,0 +1,20 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: OAR060 shared param with no exclusions configured
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedRefOnly'
+      responses:
+        "200":
+          description: OK
+components:
+  parameters:
+    SharedRefOnly:
+      in: query
+      name: q
+      required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+      schema:
+        type: string
diff --git a/src/test/resources/checks/v31/parameters/OAR060/ref-excluded-path.yaml b/src/test/resources/checks/v31/parameters/OAR060/ref-excluded-path.yaml
new file mode 100644
index 00000000..e00a371b
--- /dev/null
+++ b/src/test/resources/checks/v31/parameters/OAR060/ref-excluded-path.yaml
@@ -0,0 +1,33 @@
+openapi: "3.1.0"
+info:
+  version: 1.0.0
+  title: OAR060 shared param used only by an excluded path
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedRefOnly'
+      responses:
+        "200":
+          description: OK
+  /pets:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedByPets'
+      responses:
+        "200":
+          description: OK
+components:
+  parameters:
+    SharedRefOnly:
+      in: query
+      name: q
+      required: true
+      schema:
+        type: string
+    SharedByPets:
+      in: query
+      name: p
+      required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+      schema:
+        type: string
diff --git a/src/test/resources/checks/v32/parameters/OAR060/ref-excluded-path.yaml b/src/test/resources/checks/v32/parameters/OAR060/ref-excluded-path.yaml
new file mode 100644
index 00000000..95085868
--- /dev/null
+++ b/src/test/resources/checks/v32/parameters/OAR060/ref-excluded-path.yaml
@@ -0,0 +1,33 @@
+openapi: "3.2.0"
+info:
+  version: 1.0.0
+  title: OAR060 shared param used only by an excluded path
+paths:
+  /status:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedRefOnly'
+      responses:
+        "200":
+          description: OK
+  /pets:
+    get:
+      parameters:
+        - $ref: '#/components/parameters/SharedByPets'
+      responses:
+        "200":
+          description: OK
+components:
+  parameters:
+    SharedRefOnly:
+      in: query
+      name: q
+      required: true
+      schema:
+        type: string
+    SharedByPets:
+      in: query
+      name: p
+      required: true # Noncompliant {{OAR060: All parameters in query must be defined as optional}}
+      schema:
+        type: string

From c8dc0b85639ba926a0a83786dd635613c5d0b0f1 Mon Sep 17 00:00:00 2001
From: Melsy Huamani 
Date: Mon, 24 Aug 2026 12:41:31 -0500
Subject: [PATCH 15/22] update changelog

---
 CHANGELOG.md | 6 ++++++
 pom.xml      | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 313f1068..a888b64f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 
+## [1.5.1-beta-4] - 2026-08-24
+
+### Added
+
+- OAR116 - PathPattern - New rule: every API path must match a configurable regex `pattern` (default `^/`); unanchored match, dynamic message with the configured pattern.
+
 ## [1.5.1-beta-3] - 2026-08-14
 
 ### Changed
diff --git a/pom.xml b/pom.xml
index 89572cfa..0f9db72d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
   4.0.0
   org.apiaddicts.apitools.dosonarapi
   sonaropenapi-rules-community
-  1.5.1-beta-3
+  1.5.1-beta-4
   sonar-plugin
 
   SonarQube OpenAPI Community Rules

From ace5f79c40e168bcdbd72f3da07f8436f451476a Mon Sep 17 00:00:00 2001
From: Melsy Huamani 
Date: Tue, 1 Sep 2026 17:54:56 -0500
Subject: [PATCH 16/22] fix: OAR002 align sonar on empty scope attributes

---
 CHANGELOG.md                                  |  6 +++
 pom.xml                                       |  2 +-
 .../apim/wso2/AbstractWso2ScopesCheck.java    | 10 +++-
 .../apim/wso2/OAR002ValidWso2ScopesCheck.java | 33 ++++++++++--
 .../sonar/openapi/utils/JsonNodeUtils.java    | 11 ++++
 .../wso2/OAR002ValidWso2ScopesCheckTest.java  | 51 +++++++++++++++++++
 .../OAR002/with-empty-value-container.yaml    | 10 ++++
 .../wso2/OAR002/with-empty-value-scopes.yaml  | 38 ++++++++++++++
 .../apim/wso2/OAR002/with-scopes-as-map.yaml  | 21 ++++++++
 .../OAR002/with-empty-value-container.yaml    | 10 ++++
 .../wso2/OAR002/with-empty-value-scopes.yaml  | 38 ++++++++++++++
 .../apim/wso2/OAR002/with-scopes-as-map.yaml  | 21 ++++++++
 .../OAR002/with-empty-value-container.yaml    | 10 ++++
 .../apim/OAR002/with-empty-value-scopes.yaml  | 38 ++++++++++++++
 .../v31/apim/OAR002/with-scopes-as-map.yaml   | 21 ++++++++
 .../OAR002/with-empty-value-container.yaml    | 10 ++++
 .../apim/OAR002/with-empty-value-scopes.yaml  | 38 ++++++++++++++
 .../v32/apim/OAR002/with-scopes-as-map.yaml   | 21 ++++++++
 18 files changed, 383 insertions(+), 6 deletions(-)
 create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-container.yaml
 create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-scopes.yaml
 create mode 100644 src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes-as-map.yaml
 create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-container.yaml
 create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-scopes.yaml
 create mode 100644 src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes-as-map.yaml
 create mode 100644 src/test/resources/checks/v31/apim/OAR002/with-empty-value-container.yaml
 create mode 100644 src/test/resources/checks/v31/apim/OAR002/with-empty-value-scopes.yaml
 create mode 100644 src/test/resources/checks/v31/apim/OAR002/with-scopes-as-map.yaml
 create mode 100644 src/test/resources/checks/v32/apim/OAR002/with-empty-value-container.yaml
 create mode 100644 src/test/resources/checks/v32/apim/OAR002/with-empty-value-scopes.yaml
 create mode 100644 src/test/resources/checks/v32/apim/OAR002/with-scopes-as-map.yaml

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9e668b8..5a8f9b37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.6.0-beta-3] - 2026-09-01
+
+### Fixed
+
+- OAR002 - Detect an attribute or container written with no value (`roles:`), the `~`/`Null`/`NULL` spellings of null, and anchor map-form scope defects on the scope key.
+
 ## [1.6.0-beta-2] - 2026-09-01
 
 ### Changed
diff --git a/pom.xml b/pom.xml
index 5b7bd03f..f0d060c7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
   4.0.0
   org.apiaddicts.apitools.dosonarapi
   sonaropenapi-rules-community
-  1.6.0-beta-2
+  1.6.0-beta-3
   sonar-plugin
 
   SonarQube OpenAPI Community Rules
diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java
index 248d942e..7a0d5899 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/AbstractWso2ScopesCheck.java
@@ -16,6 +16,8 @@
 
 public abstract class AbstractWso2ScopesCheck extends BaseCheck {
 
+	private JsonNode scopesKeyNode;
+
 	@Override
 	public Set subscribedKinds() {
 		return ImmutableSet.of(OpenApi2Grammar.ROOT, OpenApi3Grammar.ROOT, OpenApi31Grammar.ROOT, OpenApi32Grammar.ROOT);
@@ -26,10 +28,16 @@ public void visitNode(JsonNode node) {
 		visitV2NV3Node(node);
 	}
 
+	protected JsonNode scopesKeyNode() {
+		return scopesKeyNode;
+	}
+
 	private void visitV2NV3Node(JsonNode node) {
 		JsonNode securityNode = node.get("x-wso2-security");
 		if (!securityNode.isMissing()) securityNode = JsonNodeUtils.resolve(securityNode);
-		JsonNode scopesNode = securityNode.get("apim").get("x-wso2-scopes");
+		JsonNode apimNode = securityNode.get("apim");
+		JsonNode scopesNode = apimNode.get("x-wso2-scopes");
+		scopesKeyNode = JsonNodeUtils.propertyKey(apimNode, "x-wso2-scopes");
 		visitScopesNode(scopesNode);
 		if (scopesNode.isMissing() || scopesNode.isNull()) return;
 		List rawScopes = scopesNode.isObject()
diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java
index d0614309..e588b1e0 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheck.java
@@ -1,10 +1,14 @@
 package apiaddicts.sonar.openapi.checks.apim.wso2;
 
+import com.google.common.collect.ImmutableSet;
+import com.sonar.sslr.api.Token;
 import org.sonar.check.Rule;
+import apiaddicts.sonar.openapi.utils.JsonNodeUtils;
 import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 @Rule(key = OAR002ValidWso2ScopesCheck.KEY)
 public class OAR002ValidWso2ScopesCheck extends AbstractWso2ScopesCheck {
@@ -12,12 +16,17 @@ public class OAR002ValidWso2ScopesCheck extends AbstractWso2ScopesCheck {
 	public static final String KEY = "OAR002";
 	private static final String MESSAGE = "OAR002.error";
 	private static final String MESSAGE_PROP = "OAR002.error-property";
+	private static final Set NULL_SPELLINGS = ImmutableSet.of("~", "Null", "NULL");
 
 	private JsonNode scopesNode;
 
 	@Override
 	protected void visitScopesNode(JsonNode scopesNode) {
-		if (scopesNode.isNull()) addIssue(KEY, translate(MESSAGE), scopesNode.key());
+		if (scopesNode.isNull()) {
+			addIssue(KEY, translate(MESSAGE), scopesNode.key());
+		} else if (scopesNode.isMissing() && scopesKeyNode() != null) {
+			addIssue(KEY, translate(MESSAGE), scopesKeyNode());
+		}
 		this.scopesNode = scopesNode;
 	}
 
@@ -36,17 +45,33 @@ protected void visitScope(JsonNode scope) {
 
 	private void validateProperty(Map properties, String propertyName, JsonNode scope) {
 		JsonNode property = properties.get(propertyName);
-		if (!properties.containsKey(propertyName)) {
-			addIssue(KEY, translate(MESSAGE_PROP, propertyName), scope);
+		if (property == null) {
+			addIssue(KEY, translate(MESSAGE_PROP, propertyName), scopeLocation(scope));
+			return;
+		}
+		if (property.isMissing()) {
+			JsonNode key = JsonNodeUtils.propertyKey(scope, propertyName);
+			addIssue(KEY, translate(MESSAGE_PROP, propertyName), key != null ? key : scopeLocation(scope));
 		} else if (isEmpty(property)) {
 			addIssue(KEY, translate(MESSAGE_PROP, propertyName), property.key());
 		}
 	}
 
+	private JsonNode scopeLocation(JsonNode scope) {
+		JsonNode key = scope.key();
+		return key.isMissing() ? scope : key;
+	}
+
 	private boolean isEmpty(JsonNode property) {
-		if (property.isNull()) return true;
+		if (isNullScalar(property)) return true;
 		if (property.isArray()) return property.elements().isEmpty();
 		if (property.isObject()) return property.propertyMap().isEmpty();
 		return property.getTokenValue().trim().equals("");
 	}
+
+	private boolean isNullScalar(JsonNode property) {
+		if (property.isNull()) return true;
+		Token token = property.getToken();
+		return token != null && NULL_SPELLINGS.contains(token.getOriginalValue());
+	}
 }
diff --git a/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java b/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java
index 2bc2d80c..4380a62e 100644
--- a/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java
+++ b/src/main/java/apiaddicts/sonar/openapi/utils/JsonNodeUtils.java
@@ -7,6 +7,7 @@
 import org.apiaddicts.apitools.dosonarapi.openapi.OpenApiConfiguration;
 import org.apiaddicts.apitools.dosonarapi.openapi.parser.OpenApiParser;
 import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.JsonNode;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlGrammar;
 import org.apiaddicts.apitools.dosonarapi.sslr.yaml.grammar.YamlParser;
 
 import java.net.HttpURLConnection;
@@ -153,6 +154,16 @@ public static String getLastFetchedContent() {
         return lastFetchedContent;
     }
 
+    public static JsonNode propertyKey(JsonNode object, String propertyName) {
+        if (object == null || !object.isObject()) return null;
+        JsonNode found = null;
+        for (JsonNode property : object.getJsonChildren(YamlGrammar.BLOCK_PROPERTY, YamlGrammar.FLOW_PROPERTY)) {
+            JsonNode key = property.key();
+            if (!key.isMissing() && propertyName.equals(key.getTokenValue())) found = key;
+        }
+        return found;
+    }
+
     public static JsonNode getType(JsonNode schema) {
         return schema.get(TYPE);
     }
diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheckTest.java
index dbdc0cb9..a6c6d8ac 100644
--- a/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/checks/apim/wso2/OAR002ValidWso2ScopesCheckTest.java
@@ -108,6 +108,57 @@ public void verifyInV32WithoutSecurity() {
         verifyV32("without-security");
     }
 
+    @Test
+    public void verifyInV2WithEmptyValueScopes() {
+        verifyV2("with-empty-value-scopes.yaml");
+    }
+    @Test
+    public void verifyInV3WithEmptyValueScopes() {
+        verifyV3("with-empty-value-scopes.yaml");
+    }
+    @Test
+    public void verifyInV31WithEmptyValueScopes() {
+        verifyV31("with-empty-value-scopes.yaml");
+    }
+    @Test
+    public void verifyInV32WithEmptyValueScopes() {
+        verifyV32("with-empty-value-scopes.yaml");
+    }
+
+    @Test
+    public void verifyInV2WithEmptyValueContainer() {
+        verifyV2("with-empty-value-container.yaml");
+    }
+    @Test
+    public void verifyInV3WithEmptyValueContainer() {
+        verifyV3("with-empty-value-container.yaml");
+    }
+    @Test
+    public void verifyInV31WithEmptyValueContainer() {
+        verifyV31("with-empty-value-container.yaml");
+    }
+    @Test
+    public void verifyInV32WithEmptyValueContainer() {
+        verifyV32("with-empty-value-container.yaml");
+    }
+
+    @Test
+    public void verifyInV2WithScopesAsMap() {
+        verifyV2("with-scopes-as-map.yaml");
+    }
+    @Test
+    public void verifyInV3WithScopesAsMap() {
+        verifyV3("with-scopes-as-map.yaml");
+    }
+    @Test
+    public void verifyInV31WithScopesAsMap() {
+        verifyV31("with-scopes-as-map.yaml");
+    }
+    @Test
+    public void verifyInV32WithScopesAsMap() {
+        verifyV32("with-scopes-as-map.yaml");
+    }
+
     @Override
     public void verifyRule() {
         assertRuleProperties("OAR002 - ValidWso2Scopes - WSO2 scope definition is wrong", RuleType.VULNERABILITY, Severity.BLOCKER, tags("api-manager", "vulnerability", "wso2"));
diff --git a/src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-container.yaml b/src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-container.yaml
new file mode 100644
index 00000000..5f4067ea
--- /dev/null
+++ b/src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-container.yaml
@@ -0,0 +1,10 @@
+swagger: "2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes: # Noncompliant {{OAR002: WSO2 scopes definition is wrong}}
diff --git a/src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-scopes.yaml b/src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-scopes.yaml
new file mode 100644
index 00000000..7fe1d99b
--- /dev/null
+++ b/src/test/resources/checks/v2/apim/wso2/OAR002/with-empty-value-scopes.yaml
@@ -0,0 +1,38 @@
+swagger: "2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      - name: read
+        key: read
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: write
+        key: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        roles: ROLE_WRITE
+      - name: # Noncompliant {{OAR002: WSO2 scope 'name' is required}}
+        key: view
+        roles: ROLE_VIEW
+      - name: tilde
+        key: tilde
+        roles: ~ # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: capitalised
+        key: capitalised
+        roles: Null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: uppercase
+        key: uppercase
+        roles: NULL # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: quoted-null
+        key: quoted-null
+        roles: "null"
+      - name: quoted-tilde
+        key: quoted-tilde
+        roles: "~"
+      - name: duplicated
+        key: duplicated
+        roles: ROLE_FIRST
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
diff --git a/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes-as-map.yaml b/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes-as-map.yaml
new file mode 100644
index 00000000..6db0f78e
--- /dev/null
+++ b/src/test/resources/checks/v2/apim/wso2/OAR002/with-scopes-as-map.yaml
@@ -0,0 +1,21 @@
+swagger: "2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      read: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        name: read
+        roles: ROLE_READ
+      write:
+        name: write
+        key: write
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      manage:
+        name: manage
+        key: manage
+        roles: ROLE_MANAGE
diff --git a/src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-container.yaml b/src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-container.yaml
new file mode 100644
index 00000000..d7fe8cbf
--- /dev/null
+++ b/src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-container.yaml
@@ -0,0 +1,10 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes: # Noncompliant {{OAR002: WSO2 scopes definition is wrong}}
diff --git a/src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-scopes.yaml b/src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-scopes.yaml
new file mode 100644
index 00000000..9fc21704
--- /dev/null
+++ b/src/test/resources/checks/v3/apim/wso2/OAR002/with-empty-value-scopes.yaml
@@ -0,0 +1,38 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      - name: read
+        key: read
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: write
+        key: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        roles: ROLE_WRITE
+      - name: # Noncompliant {{OAR002: WSO2 scope 'name' is required}}
+        key: view
+        roles: ROLE_VIEW
+      - name: tilde
+        key: tilde
+        roles: ~ # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: capitalised
+        key: capitalised
+        roles: Null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: uppercase
+        key: uppercase
+        roles: NULL # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: quoted-null
+        key: quoted-null
+        roles: "null"
+      - name: quoted-tilde
+        key: quoted-tilde
+        roles: "~"
+      - name: duplicated
+        key: duplicated
+        roles: ROLE_FIRST
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
diff --git a/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes-as-map.yaml b/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes-as-map.yaml
new file mode 100644
index 00000000..3d2cf7af
--- /dev/null
+++ b/src/test/resources/checks/v3/apim/wso2/OAR002/with-scopes-as-map.yaml
@@ -0,0 +1,21 @@
+openapi: "3.0.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      read: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        name: read
+        roles: ROLE_READ
+      write:
+        name: write
+        key: write
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      manage:
+        name: manage
+        key: manage
+        roles: ROLE_MANAGE
diff --git a/src/test/resources/checks/v31/apim/OAR002/with-empty-value-container.yaml b/src/test/resources/checks/v31/apim/OAR002/with-empty-value-container.yaml
new file mode 100644
index 00000000..850d6426
--- /dev/null
+++ b/src/test/resources/checks/v31/apim/OAR002/with-empty-value-container.yaml
@@ -0,0 +1,10 @@
+openapi: "3.1.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes: # Noncompliant {{OAR002: WSO2 scopes definition is wrong}}
diff --git a/src/test/resources/checks/v31/apim/OAR002/with-empty-value-scopes.yaml b/src/test/resources/checks/v31/apim/OAR002/with-empty-value-scopes.yaml
new file mode 100644
index 00000000..03732920
--- /dev/null
+++ b/src/test/resources/checks/v31/apim/OAR002/with-empty-value-scopes.yaml
@@ -0,0 +1,38 @@
+openapi: "3.1.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      - name: read
+        key: read
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: write
+        key: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        roles: ROLE_WRITE
+      - name: # Noncompliant {{OAR002: WSO2 scope 'name' is required}}
+        key: view
+        roles: ROLE_VIEW
+      - name: tilde
+        key: tilde
+        roles: ~ # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: capitalised
+        key: capitalised
+        roles: Null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: uppercase
+        key: uppercase
+        roles: NULL # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: quoted-null
+        key: quoted-null
+        roles: "null"
+      - name: quoted-tilde
+        key: quoted-tilde
+        roles: "~"
+      - name: duplicated
+        key: duplicated
+        roles: ROLE_FIRST
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
diff --git a/src/test/resources/checks/v31/apim/OAR002/with-scopes-as-map.yaml b/src/test/resources/checks/v31/apim/OAR002/with-scopes-as-map.yaml
new file mode 100644
index 00000000..5aad43ce
--- /dev/null
+++ b/src/test/resources/checks/v31/apim/OAR002/with-scopes-as-map.yaml
@@ -0,0 +1,21 @@
+openapi: "3.1.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      read: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        name: read
+        roles: ROLE_READ
+      write:
+        name: write
+        key: write
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      manage:
+        name: manage
+        key: manage
+        roles: ROLE_MANAGE
diff --git a/src/test/resources/checks/v32/apim/OAR002/with-empty-value-container.yaml b/src/test/resources/checks/v32/apim/OAR002/with-empty-value-container.yaml
new file mode 100644
index 00000000..f22a310f
--- /dev/null
+++ b/src/test/resources/checks/v32/apim/OAR002/with-empty-value-container.yaml
@@ -0,0 +1,10 @@
+openapi: "3.2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes: # Noncompliant {{OAR002: WSO2 scopes definition is wrong}}
diff --git a/src/test/resources/checks/v32/apim/OAR002/with-empty-value-scopes.yaml b/src/test/resources/checks/v32/apim/OAR002/with-empty-value-scopes.yaml
new file mode 100644
index 00000000..0275ee99
--- /dev/null
+++ b/src/test/resources/checks/v32/apim/OAR002/with-empty-value-scopes.yaml
@@ -0,0 +1,38 @@
+openapi: "3.2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      - name: read
+        key: read
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: write
+        key: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        roles: ROLE_WRITE
+      - name: # Noncompliant {{OAR002: WSO2 scope 'name' is required}}
+        key: view
+        roles: ROLE_VIEW
+      - name: tilde
+        key: tilde
+        roles: ~ # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: capitalised
+        key: capitalised
+        roles: Null # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: uppercase
+        key: uppercase
+        roles: NULL # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      - name: quoted-null
+        key: quoted-null
+        roles: "null"
+      - name: quoted-tilde
+        key: quoted-tilde
+        roles: "~"
+      - name: duplicated
+        key: duplicated
+        roles: ROLE_FIRST
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
diff --git a/src/test/resources/checks/v32/apim/OAR002/with-scopes-as-map.yaml b/src/test/resources/checks/v32/apim/OAR002/with-scopes-as-map.yaml
new file mode 100644
index 00000000..ac70f381
--- /dev/null
+++ b/src/test/resources/checks/v32/apim/OAR002/with-scopes-as-map.yaml
@@ -0,0 +1,21 @@
+openapi: "3.2.0"
+info:
+  version: 1.0.0
+  title: Swagger Petstore
+paths:
+  /pets: {}
+
+x-wso2-security:
+  apim:
+    x-wso2-scopes:
+      read: # Noncompliant {{OAR002: WSO2 scope 'key' is required}}
+        name: read
+        roles: ROLE_READ
+      write:
+        name: write
+        key: write
+        roles: # Noncompliant {{OAR002: WSO2 scope 'roles' is required}}
+      manage:
+        name: manage
+        key: manage
+        roles: ROLE_MANAGE

From da8b3118de0eded5d3f2620c5dfe1aed797ddb03 Mon Sep 17 00:00:00 2001
From: Melsy Huamani 
Date: Wed, 2 Sep 2026 09:24:48 -0500
Subject: [PATCH 17/22] update changelog

---
 CHANGELOG.md | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1dc1cd62..7beb5dc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,12 +5,13 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [1.6.0-beta-3] - 2026-09-01
+## [1.6.0-beta-3] - 2026-09-02
 
 ### Fixed
 
 - OAR002 - Detect an attribute or container written with no value (`roles:`), the `~`/`Null`/`NULL` spellings of null, and anchor map-form scope defects on the scope key.
-- 
+- OAR044 - MediaTypeCheck - Made the media type regex quantifiers possessive to prevent ReDoS with no change to matching.
+
 ### Added
 
 - OAR060 - QueryParametersOptional - New `path-exclusions` rule property (default `/status`): a comma-separated list of exact, case-sensitive paths the rule must not fire on.
@@ -48,13 +49,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - OAR016 / OAR037 / OAR052 / OAR076 - Accept array-form `type` via `AbstractFormatCheck`.
 
 
-## [1.6.0-beta-1] - 2026-08-25
-
-### Fixed
-
-- OAR044 - MediaTypeCheck - Made the media type regex quantifiers possessive to prevent ReDoS with no change to matching.
-
-
 ## [1.5.1] - 2026-08-25
 
 ### Changed

From 935d29fc42d7ff634d8a0e54e9a0a8d5eef31067 Mon Sep 17 00:00:00 2001
From: Melsy Huamani 
Date: Thu, 3 Sep 2026 07:47:14 -0500
Subject: [PATCH 18/22] fix: OAR029 rootProperty crash, OAR108 example typing,
 OAR075 any-of integrity

---
 .../OAR029StandardResponseSchemaCheck.java    | 34 ++++++----
 .../schemas/OAR108SchemaValidatorCheck.java   | 44 ++++++++-----
 .../OAR075StringParameterIntegrityCheck.java  |  4 +-
 .../rules/openapi/security/OAR075.json        |  2 +-
 .../OAR108SchemaValidatorCheckTest.java       | 10 +++
 ...R075StringParameterIntegrityCheckTest.java | 14 +++-
 .../v2/schemas/OAR108/quoted-number.yaml      | 66 +++++++++++++++++++
 .../security/OAR075/single-restriction.yaml   | 36 ++++++++++
 .../checks/v31/schemas/OAR108/array-type.yaml | 56 ++++++++++++++++
 .../v31/security/OAR075/array-type.yaml       | 45 +++++++++++++
 10 files changed, 279 insertions(+), 32 deletions(-)
 create mode 100644 src/test/resources/checks/v2/schemas/OAR108/quoted-number.yaml
 create mode 100644 src/test/resources/checks/v3/security/OAR075/single-restriction.yaml
 create mode 100644 src/test/resources/checks/v31/schemas/OAR108/array-type.yaml
 create mode 100644 src/test/resources/checks/v31/security/OAR075/array-type.yaml

diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java
index 93f7e452..e9bf1a8e 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR029StandardResponseSchemaCheck.java
@@ -16,6 +16,7 @@
 
 import java.util.Arrays;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -128,19 +129,11 @@ private void visitSchemaNode(JsonNode responseNode, String statusCode) {
         Map properties = getAllProperties(schemaNode);
 
         if (rootProperty != null) {
-            if (rootProperty.equals("*")) {
-                rootProperty = properties.entrySet().iterator().next().getKey();
-            }
+            Optional rootNode = resolveRootNode(properties, schemaNode);
+            if (!rootNode.isPresent()) return;
 
-            validateProperty(properties, rootProperty, "object", schemaNode.key()).ifPresent(node -> {
-                Map allProp = getAllProperties(node);
-                if (allProp.isEmpty()) {
-                    addIssue(KEY, translate("OAR029.error-required-one-property", rootProperty), handleExternalRef.getTrueNode(node.key()));
-                }
-            });
-
-            schemaNode = properties.get(rootProperty);
-            properties = getAllProperties(properties.get(rootProperty));
+            schemaNode = rootNode.get();
+            properties = getAllProperties(schemaNode);
         }
 
         if (successCode) {
@@ -151,6 +144,23 @@ private void visitSchemaNode(JsonNode responseNode, String statusCode) {
         validateRootProperties(requiredAlways, properties, schemaNode);
     }
 
+    private Optional resolveRootNode(Map properties, JsonNode schemaNode) {
+        String currentRootProperty = rootProperty;
+        if ("*".equals(currentRootProperty)) {
+            if (properties.isEmpty()) return Optional.empty();
+            currentRootProperty = properties.entrySet().iterator().next().getKey();
+        }
+
+        final String resolvedRootProperty = currentRootProperty;
+        Optional rootNode = validateProperty(properties, resolvedRootProperty, "object", schemaNode.key());
+        rootNode.ifPresent(node -> {
+            if (getAllProperties(node).isEmpty()) {
+                addIssue(KEY, translate("OAR029.error-required-one-property", resolvedRootProperty), handleExternalRef.getTrueNode(node.key()));
+            }
+        });
+        return rootNode;
+    }
+
     private void validateRootProperties(JSONArray requiredPropsArray, Map properties, JsonNode parentNode) {
         if (requiredPropsArray == null || requiredPropsArray.isEmpty()) return;
 
diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java
index 51871ce2..e0787983 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheck.java
@@ -2,6 +2,8 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.sonar.sslr.api.AstNodeType;
+import com.sonar.sslr.api.TokenType;
+import org.apiaddicts.apitools.dosonarapi.sslr.yaml.snakeyaml.parser.Tokens;
 import org.sonar.check.Rule;
 import apiaddicts.sonar.openapi.checks.BaseCheck;
 import apiaddicts.sonar.openapi.utils.JsonNodeUtils;
@@ -20,6 +22,7 @@ public class OAR108SchemaValidatorCheck extends BaseCheck {
 
     public static final String KEY = "OAR108";
     private static final String MESSAGE = "OAR108.error";
+    private static final String TYPE_NULL = "null";
 
     @Override
     public Set subscribedKinds() {
@@ -62,12 +65,19 @@ private void validateTypes(JsonNode schema, JsonNode example, boolean isSwagger)
 
         schemaTypes.forEach((keyName, expectedType) -> {
             String actualType = exampleTypes.getOrDefault(keyName, "unknown");
-            if (!expectedType.equals(actualType)) {
+            if (!isCompatible(expectedType, actualType)) {
                 addIssue(KEY, translate(MESSAGE), example.key());
             }
         });
     }
 
+    private boolean isCompatible(String expectedType, String actualType) {
+        if (expectedType == null) return true;
+        if (TYPE_NULL.equals(actualType)) return true;
+        if (expectedType.equals(actualType)) return true;
+        return "number".equals(expectedType) && "integer".equals(actualType);
+    }
+
     private Map extractSchemaTypes(JsonNode schemaNode) {
         Map schemaTypes = new HashMap<>();
 
@@ -76,9 +86,7 @@ private Map extractSchemaTypes(JsonNode schemaNode) {
             for (Map.Entry entry : propertiesNode.propertyMap().entrySet()) {
                 String propertyName = entry.getKey();
                 JsonNode propertyTypeNode = entry.getValue().get("type");
-                String propertyType = (propertyTypeNode != null && propertyTypeNode.isArray())
-                        ? JsonNodeUtils.getPrimaryType(propertyTypeNode)
-                        : (propertyTypeNode != null ? propertyTypeNode.stringValue() : null);
+                String propertyType = JsonNodeUtils.getPrimaryType(propertyTypeNode);
                 schemaTypes.put(propertyName, propertyType);
             }
         }
@@ -119,22 +127,28 @@ private Map extractExampleTypesSwagger2(JsonNode examplesNode) {
     }
 
     private String determineExampleType(JsonNode node) {
-        String value = node.stringValue().trim();
+        if (node.isObject()) {
+            return "object";
+        }
+        if (node.isArray()) {
+            return "array";
+        }
+        if (node.isNull()) {
+            return TYPE_NULL;
+        }
 
-        if (value.matches("-?\\d+\\.\\d+")) {
-            return "number";
-        } else if (value.matches("-?\\d+")) {
+        TokenType tokenType = node.getToken().getType();
+        if (tokenType == Tokens.INTEGER) {
             return "integer";
         }
-
-        if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+        if (tokenType == Tokens.FLOAT) {
+            return "number";
+        }
+        if (tokenType == Tokens.TRUE || tokenType == Tokens.FALSE) {
             return "boolean";
         }
-
-        if (node.isObject()) {
-            return "object";
-        } else if (node.isArray()) {
-            return "array";
+        if (tokenType == Tokens.NULL) {
+            return TYPE_NULL;
         }
 
         return "string";
diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java
index 4bcd52a7..9a554366 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheck.java
@@ -14,7 +14,7 @@ public class OAR075StringParameterIntegrityCheck extends AbstractTypedParameterI
 
     public static final String KEY = "OAR075";
     private static final String MESSAGE = "OAR075.error";
-    private static final String DEFAULT = "minLength,maxLength,enum,format";
+    private static final String DEFAULT = "minLength,maxLength,pattern,enum";
 
     @RuleProperty(
             key = "parameter_integrity",
@@ -38,7 +38,7 @@ protected void validateTypedNode(JsonNode node,JsonNode typeNode) {
                 .map(String::trim)
                 .collect(Collectors.toSet());
 
-        boolean ok = checks.stream().allMatch(k->{
+        boolean ok = checks.stream().anyMatch(k->{
             JsonNode n = node.get(k);
             return n != null && !n.isMissing();
         });
diff --git a/src/main/resources/org/sonar/l10n/openapi/rules/openapi/security/OAR075.json b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/security/OAR075.json
index 760ee104..5e814abe 100644
--- a/src/main/resources/org/sonar/l10n/openapi/rules/openapi/security/OAR075.json
+++ b/src/main/resources/org/sonar/l10n/openapi/rules/openapi/security/OAR075.json
@@ -1,5 +1,5 @@
 {
-    "title": "OAR075 - StringParameterIntegrityCheck - String parameters should have minLength, maxLength, pattern (regular expression),format or enum restriction",
+    "title": "OAR075 - StringParameterIntegrityCheck - String parameters should have minLength, maxLength, pattern (regular expression) or enum restriction",
     "type": "VULNERABILITY",
     "status": "ready",
     "remediation": {
diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheckTest.java
index 66063382..8921e0f4 100644
--- a/src/test/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/checks/schemas/OAR108SchemaValidatorCheckTest.java
@@ -54,6 +54,16 @@ public void verifyInV32Invalid() {
         verifyV32("invalid");
     }
 
+    @Test
+    public void verifyInV2QuotedNumber() {
+        verifyV2("quoted-number.yaml");
+    }
+
+    @Test
+    public void verifyInV31ArrayType() {
+        verifyV31("array-type.yaml");
+    }
+
     @Override
     public void verifyRule() {
         assertRuleProperties("OAR108 - SchemaValidator - Schema does not match the provided example", RuleType.BUG, Severity.MAJOR, tags("schemas"));
diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheckTest.java
index 35d3d780..f9682140 100644
--- a/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/checks/security/OAR075StringParameterIntegrityCheckTest.java
@@ -46,6 +46,16 @@ public void verifyInV32withRestrictions() {
     public void verifyInV3noRestrictions() {
         verifyV3("no-restrictions");
     }
+
+    @Test
+    public void verifyInV3singleRestriction() {
+        verifyV3("single-restriction.yaml");
+    }
+
+    @Test
+    public void verifyInV31arrayType() {
+        verifyV31("array-type.yaml");
+    }
     @Test
     public void verifyInV31noRestrictions() {
         verifyV31("no-restrictions");
@@ -58,11 +68,11 @@ public void verifyInV32noRestrictions() {
     @Override
     public void verifyParameters() {
         assertNumberOfParameters(1);
-        assertParameterProperties("parameter_integrity", "minLength,maxLength,enum,format", RuleParamType.STRING);
+        assertParameterProperties("parameter_integrity", "minLength,maxLength,pattern,enum", RuleParamType.STRING);
     }
 
     @Override
     public void verifyRule() {
-        assertRuleProperties("OAR075 - StringParameterIntegrityCheck - String parameters should have minLength, maxLength, pattern (regular expression),format or enum restriction", RuleType.VULNERABILITY, Severity.MAJOR, tags("safety"));
+        assertRuleProperties("OAR075 - StringParameterIntegrityCheck - String parameters should have minLength, maxLength, pattern (regular expression) or enum restriction", RuleType.VULNERABILITY, Severity.MAJOR, tags("safety"));
     }
 }
\ No newline at end of file
diff --git a/src/test/resources/checks/v2/schemas/OAR108/quoted-number.yaml b/src/test/resources/checks/v2/schemas/OAR108/quoted-number.yaml
new file mode 100644
index 00000000..bf0acb83
--- /dev/null
+++ b/src/test/resources/checks/v2/schemas/OAR108/quoted-number.yaml
@@ -0,0 +1,66 @@
+swagger: '2.0'
+info:
+  title: Ejemplo de API
+  version: 1.0.0
+paths:
+  /item:
+    get:
+      responses:
+        200:
+          description: OK
+          schema:
+            type: object
+            properties:
+              id:
+                type: integer
+              nombre:
+                type: string
+            required:
+              - id
+              - nombre
+          examples: # Noncompliant {{OAR108: Schema does not match the provided example}}
+            application/json:
+              id: "123"
+              nombre: "Ejemplo"
+  /nulos:
+    get:
+      responses:
+        200:
+          description: OK
+          schema:
+            type: object
+            properties:
+              id:
+                type: integer
+              nombre:
+                type: string
+          examples:
+            application/json:
+              id:
+              nombre: null
+  /enteros:
+    get:
+      responses:
+        200:
+          description: OK
+          schema:
+            type: object
+            properties:
+              total:
+                type: number
+          examples:
+            application/json:
+              total: 5
+  /sin-type:
+    get:
+      responses:
+        200:
+          description: OK
+          schema:
+            type: object
+            properties:
+              sinType:
+                description: sin type
+          examples:
+            application/json:
+              sinType: "algo"
diff --git a/src/test/resources/checks/v3/security/OAR075/single-restriction.yaml b/src/test/resources/checks/v3/security/OAR075/single-restriction.yaml
new file mode 100644
index 00000000..24c83a81
--- /dev/null
+++ b/src/test/resources/checks/v3/security/OAR075/single-restriction.yaml
@@ -0,0 +1,36 @@
+openapi: 3.0.0
+info:
+  title: OAR075 - una sola restriccion basta
+  version: 1.0.0
+paths:
+  /users:
+    get:
+      parameters:
+        - name: soloMinLength
+          in: query
+          schema:
+            type: string
+            minLength: 1
+        - name: soloMaxLength
+          in: query
+          schema:
+            type: string
+            maxLength: 10
+        - name: soloPattern
+          in: query
+          schema:
+            type: string
+            pattern: '^[a-z]+$'
+        - name: soloEnum
+          in: query
+          schema:
+            type: string
+            enum: ['admin', 'user']
+        - name: soloFormat
+          in: query
+          schema:
+            type: string  # Noncompliant {{OAR075: String parameters should have minLength, maxLength, pattern (regular expression), or enum restriction that are defined in the properties}}
+            format: date
+      responses:
+        '200':
+          description: ok
diff --git a/src/test/resources/checks/v31/schemas/OAR108/array-type.yaml b/src/test/resources/checks/v31/schemas/OAR108/array-type.yaml
new file mode 100644
index 00000000..e6a5f1d8
--- /dev/null
+++ b/src/test/resources/checks/v31/schemas/OAR108/array-type.yaml
@@ -0,0 +1,56 @@
+openapi: 3.1.1
+info:
+  title: OAR108 - forma-array de type (JSON Schema 2020-12)
+  version: 1.0.0
+paths:
+  /mismatch:
+    get:
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  nullAlFinal:
+                    type: ["integer", "null"]
+                  nullDelante:
+                    type: ["null", "integer"]
+              example: # Noncompliant {{OAR108: Schema does not match the provided example}}
+                nullAlFinal: "mal"
+                nullDelante: "mal"
+  /match:
+    get:
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  nullAlFinal:
+                    type: ["integer", "null"]
+                  nullDelante:
+                    type: ["null", "integer"]
+                  cadena:
+                    type: ["string", "null"]
+              example:
+                nullAlFinal: 1
+                nullDelante: 2
+                cadena: "ok"
+  /nulos:
+    get:
+      responses:
+        '200':
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: object
+                properties:
+                  id:
+                    type: ["integer", "null"]
+              example:
+                id:
diff --git a/src/test/resources/checks/v31/security/OAR075/array-type.yaml b/src/test/resources/checks/v31/security/OAR075/array-type.yaml
new file mode 100644
index 00000000..d86ee880
--- /dev/null
+++ b/src/test/resources/checks/v31/security/OAR075/array-type.yaml
@@ -0,0 +1,45 @@
+openapi: 3.1.1
+info:
+  title: OAR075 - forma-array de type (JSON Schema 2020-12)
+  version: 1.0.0
+paths:
+  /users:
+    get:
+      parameters:
+        - name: nullAlFinalSinRestricciones
+          in: query
+          schema:
+            type: ["string", "null"]  # Noncompliant {{OAR075: String parameters should have minLength, maxLength, pattern (regular expression), or enum restriction that are defined in the properties}}
+        - name: nullDelanteSinRestricciones
+          in: query
+          schema:
+            type: ["null", "string"]  # Noncompliant {{OAR075: String parameters should have minLength, maxLength, pattern (regular expression), or enum restriction that are defined in the properties}}
+        - name: arrayDeUnElemento
+          in: query
+          schema:
+            type: ["string"]  # Noncompliant {{OAR075: String parameters should have minLength, maxLength, pattern (regular expression), or enum restriction that are defined in the properties}}
+        - name: nullAlFinalConMinLength
+          in: query
+          schema:
+            type: ["string", "null"]
+            minLength: 1
+        - name: nullDelanteConEnum
+          in: query
+          schema:
+            type: ["null", "string"]
+            enum: ['a', 'b']
+        - name: soloNull
+          in: query
+          schema:
+            type: ["null"]
+        - name: nullEscalar
+          in: query
+          schema:
+            type: "null"
+        - name: numerico
+          in: query
+          schema:
+            type: ["integer", "null"]
+      responses:
+        '200':
+          description: ok

From 9c01c3812b2edb1f2134a4763aae6dbe8dcc438a Mon Sep 17 00:00:00 2001
From: Sebastian Diaz Torres 
Date: Fri, 4 Sep 2026 16:06:23 -0500
Subject: [PATCH 19/22] feat: 1.6.0-beta-4

---
 CHANGELOG.md | 8 ++++++++
 pom.xml      | 2 +-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9d86120..a5cc4c7c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.6.0-beta-4] - 2026-09-04
+
+### Fixed
+
+- OAR029 - StandardResponseSchemaCheck - Fixed crash (`NoSuchElementException`) when `rootProperty: "*"` and the schema has no properties; root-node resolution now returns safely instead of throwing.
+- OAR108 - SchemaValidatorCheck - Example type detection rewritten to use the YAML token type instead of string pattern-matching, fixing misclassification of quoted numbers; `integer`/`number` and `null` are now treated as compatible with their schema type.
+- OAR075 - StringParameterIntegrityCheck - Fixed any-of integrity check that required all configured constraints (`allMatch`) instead of at least one (`anyMatch`); default constraints changed from `minLength,maxLength,enum,format` to `minLength,maxLength,pattern,enum`.
+
 ## [1.6.0-beta-3] - 2026-09-02
 
 ### Fixed
diff --git a/pom.xml b/pom.xml
index f0d060c7..a1d135d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
   4.0.0
   org.apiaddicts.apitools.dosonarapi
   sonaropenapi-rules-community
-  1.6.0-beta-3
+  1.6.0-beta-4
   sonar-plugin
 
   SonarQube OpenAPI Community Rules

From db3abbacd6f484790d270b2f2dbe4f80d609837d Mon Sep 17 00:00:00 2001
From: Melsy Huamani 
Date: Wed, 9 Sep 2026 13:37:30 -0500
Subject: [PATCH 20/22] fix oar022 oar025 rule param

---
 CHANGELOG.md                                  | 19 +++++++++++++++++++
 pom.xml                                       |  2 +-
 .../OAR022OrderbyParameterCheck.java          | 12 ++++++++++++
 .../parameters/OAR025LimitParameterCheck.java | 12 ++++++++++++
 .../OAR022OrderbyParameterCheckTest.java      |  3 ++-
 .../OAR025LimitParameterCheckTest.java        |  3 ++-
 6 files changed, 48 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9d86120..f2c07747 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.6.0-beta-5] - 2026-09-09
+
+### Added
+
+- OAR022 - OrderbyParameter - New `parameterName` rule property (default `$orderby`), previously hardcoded; now configurable like OAR020/021/028.
+- OAR025 - LimitParameter - Same as OAR022: new `parameterName` rule property (default `$limit`), previously hardcoded.
+
+## [1.6.0-beta-4] - 2026-09-02
+
+### Fixed
+
+- OAR029 - StandardResponseSchema - Stop the response when a configured `rootProperty` is missing or mistyped, instead of falling through to `getAllProperties(null)` and throwing; the `*` wildcard is resolved into a local rather than written back to the field.
+- OAR108 - SchemaValidator - Derive the example type from the lexer token instead of a regex over its text, and read the schema type through `getPrimaryType`; an untyped property, a null example value and an integer against a `number` schema are no longer mismatches.
+
+### Changed
+
+- OAR075 - StringParameterIntegrity - `allMatch` to `anyMatch`, default `parameter_integrity` to `minLength,maxLength,pattern,enum`, and rule title aligned.
+
+
 ## [1.6.0-beta-3] - 2026-09-02
 
 ### Fixed
diff --git a/pom.xml b/pom.xml
index f0d060c7..a1d135d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
   4.0.0
   org.apiaddicts.apitools.dosonarapi
   sonaropenapi-rules-community
-  1.6.0-beta-3
+  1.6.0-beta-4
   sonar-plugin
 
   SonarQube OpenAPI Community Rules
diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java
index b3426978..a927ce6f 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheck.java
@@ -10,6 +10,13 @@ public class OAR022OrderbyParameterCheck extends AbstractQueryParameterCheck {
     private static final String MESSAGE = "OAR022.error";
     private static final String PARAM_NAME = "$orderby";
 
+    @RuleProperty(
+        key = "parameterName",
+        description = "Name of the query parameter to be checked",
+        defaultValue = PARAM_NAME
+    )
+    private String parameterNameOverride = PARAM_NAME;
+
     @RuleProperty(
         key = "paths",
         description = "List of explicit paths to include/exclude from this rule separated by comma",
@@ -33,6 +40,11 @@ public OAR022OrderbyParameterCheck() {
         );
     }
 
+    @Override
+    protected String getParameterName() {
+        return parameterNameOverride;
+    }
+
     @Override
     protected String getPathsStr() {
         return pathsStr;
diff --git a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java
index 865ac31d..3b55eced 100644
--- a/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java
+++ b/src/main/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheck.java
@@ -12,6 +12,13 @@ public class OAR025LimitParameterCheck extends AbstractQueryParameterCheck {
     private static final String PARAM_NAME = "$limit";
     private static final String EXPECTED_TYPE = "integer";
 
+    @RuleProperty(
+        key = "parameterName",
+        description = "Name of the query parameter to be checked",
+        defaultValue = PARAM_NAME
+    )
+    private String parameterNameOverride = PARAM_NAME;
+
     @RuleProperty(
         key = "paths",
         description = "List of explicit paths to include/exclude from this rule separated by comma",
@@ -35,6 +42,11 @@ public OAR025LimitParameterCheck() {
         );
     }
 
+    @Override
+    protected String getParameterName() {
+        return parameterNameOverride;
+    }
+
     @Override
     protected String getPathsStr() {
         return pathsStr;
diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java
index 6f9e281e..8938c709 100644
--- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR022OrderbyParameterCheckTest.java
@@ -105,7 +105,8 @@ public void verifyRule() {
 
     @Override
     public void verifyParameters() {
-        assertNumberOfParameters(2);
+        assertNumberOfParameters(3);
+        assertParameterProperties("parameterName", "$orderby", RuleParamType.STRING);
         assertParameterProperties("paths", "/examples", RuleParamType.STRING);
         assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING);
     }
diff --git a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java
index a044b481..6b90dbef 100644
--- a/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java
+++ b/src/test/java/apiaddicts/sonar/openapi/checks/parameters/OAR025LimitParameterCheckTest.java
@@ -183,7 +183,8 @@ public void verifyRule() {
 
     @Override
     public void verifyParameters() {
-        assertNumberOfParameters(2);
+        assertNumberOfParameters(3);
+        assertParameterProperties("parameterName", "$limit", RuleParamType.STRING);
         assertParameterProperties("paths", "/examples", RuleParamType.STRING);
         assertParameterProperties("pathValidationStrategy", "/include", RuleParamType.STRING);
     }

From 371cdf59bbdd6f5a137c21648b5b36b1f6845d5b Mon Sep 17 00:00:00 2001
From: Sebastian Diaz Torres 
Date: Wed, 9 Sep 2026 21:34:17 -0500
Subject: [PATCH 21/22] feat: Beta .5

---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index a1d135d2..a057b9bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
   4.0.0
   org.apiaddicts.apitools.dosonarapi
   sonaropenapi-rules-community
-  1.6.0-beta-4
+  1.6.0-beta-5
   sonar-plugin
 
   SonarQube OpenAPI Community Rules

From 52dca6ec09c46b2c8b9d8ca6bc3a9f977d87fbbb Mon Sep 17 00:00:00 2001
From: Sebastian Diaz Torres 
Date: Thu, 10 Sep 2026 11:15:29 -0500
Subject: [PATCH 22/22] RELEASE: 1.6.0

---
 CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++
 pom.xml      |  4 ++--
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2814ab30..4cc4ea57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.6.0] - 2026-09-10
+
+Recopilado de `1.6.0-beta-1` a `1.6.0-beta-5`.
+
+### Added
+
+- OAR060 - QueryParametersOptional - New `path-exclusions` rule property (default `/status`): a comma-separated list of exact, case-sensitive paths the rule must not fire on.
+- OAR116 - PathPattern - New rule: every API path must match a configurable regex `pattern` (default `^/`); unanchored match, dynamic message with the configured pattern.
+- OAR085 - Accept `3.0.4`, `3.1.1`, `3.1.2` in the default valid-versions.
+- OAR022 - OrderbyParameter - New `parameterName` rule property (default `$orderby`), previously hardcoded; now configurable like OAR020/021/028.
+- OAR025 - LimitParameter - Same as OAR022: new `parameterName` rule property (default `$limit`), previously hardcoded.
+
+### Changed
+
+- Bump `sonar-openapi` core to `1.3.0-beta-2`: parses `3.0.4`/`3.1.1`/`3.1.2`, and a document declaring an unsupported `openapi`/`swagger` version is now analysed instead of being silently skipped.
+- `JsonNodeUtils` - `isType`/`getPrimaryType` accept array-form `type` (OpenAPI 3.1).
+- OAR082 - Accept array-form `type`; accept `contentEncoding`/`contentMediaType` as byte/binary.
+- OAR029 / OAR070 / OAR074 / OAR075 / OAR108 / OAR115 - Accept array-form `type`.
+- OAR016 / OAR037 / OAR052 / OAR076 - Accept array-form `type` via `AbstractFormatCheck`.
+- OAR075 - StringParameterIntegrityCheck - Default `parameter_integrity` constraints changed from `minLength,maxLength,enum,format` to `minLength,maxLength,pattern,enum`.
+
+### Fixed
+
+- OAR002 - Rewrote to validate the full `x-wso2-scopes` definition (null/empty container and missing/null/blank or empty-array/object `name`/`key`/`roles`) via new `apq-wso2-scopes-valid`; also detects an attribute or container written with no value (`roles:`), the `~`/`Null`/`NULL` spellings of null, and anchors map-form scope defects on the scope key.
+- OAR044 - MediaTypeCheck - Made the media type regex quantifiers possessive to prevent ReDoS with no change to matching.
+- OAR003 - Resolve a `$ref` on `x-wso2-security` and iterate map-form `x-wso2-scopes` (shared `AbstractWso2ScopesCheck`), so referenced and mapping-keyed scopes are detected.
+- OAR029 - StandardResponseSchemaCheck - Fixed crash (`NoSuchElementException`) when `rootProperty: "*"` and the schema has no properties; root-node resolution now returns safely instead of throwing.
+- OAR108 - SchemaValidatorCheck - Example type detection rewritten to use the YAML token type instead of string pattern-matching, fixing misclassification of quoted numbers; `integer`/`number` and `null` are now treated as compatible with their schema type.
+- OAR075 - StringParameterIntegrityCheck - Fixed any-of integrity check that required all configured constraints (`allMatch`) instead of at least one (`anyMatch`).
+
+
+
 ## [1.6.0-beta-5] - 2026-09-09
 
 ### Added
diff --git a/pom.xml b/pom.xml
index a057b9bb..dc70ccd2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
   4.0.0
   org.apiaddicts.apitools.dosonarapi
   sonaropenapi-rules-community
-  1.6.0-beta-5
+  1.6.0
   sonar-plugin
 
   SonarQube OpenAPI Community Rules
@@ -64,7 +64,7 @@
 
     8.7.0.41497
     6.7
-    1.3.0-beta-2
+    1.3.0
     1.22.0.848
     20231013
     4.13.2