From 08567e86d3a98a23a34c9663832e8f4faeaafed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CSebastian?= <64795732+slegarraga@users.noreply.github.com> Date: Fri, 22 May 2026 10:09:48 -0400 Subject: [PATCH] feat(oas): add object-with-properties-requires-type-object rule Adds an opt-in builtin rule for schemas that define properties without declaring type: object, addressing a common OpenAPI footgun. Closes #2203 --- docs/reference/openapi-rules.md | 34 ++++++ ...th-properties-requires-type-object.test.ts | 115 ++++++++++++++++++ packages/rulesets/src/oas/index.ts | 20 +++ 3 files changed, 169 insertions(+) create mode 100644 packages/rulesets/src/oas/__tests__/object-with-properties-requires-type-object.test.ts diff --git a/docs/reference/openapi-rules.md b/docs/reference/openapi-rules.md index 7b2e51187..c953a6ed7 100644 --- a/docs/reference/openapi-rules.md +++ b/docs/reference/openapi-rules.md @@ -68,6 +68,40 @@ TheBadModel: - 2 ``` + +### object-with-properties-requires-type-object + +Schemas that define `properties` must also declare `type: object`. Without it, JSON Schema validation does not restrict instances to objects, so strings and other primitives can pass unexpectedly. + +**Recommended:** No + +**Good Example** + +```yaml +Address: + type: object + properties: + number: + type: number + street_name: + type: string + required: + - number +``` + +**Bad Example** + +```yaml +Address: + properties: + number: + type: number + street_name: + type: string + required: + - number +``` + ### info-contact Info object should contain `contact` object. diff --git a/packages/rulesets/src/oas/__tests__/object-with-properties-requires-type-object.test.ts b/packages/rulesets/src/oas/__tests__/object-with-properties-requires-type-object.test.ts new file mode 100644 index 000000000..a0952af23 --- /dev/null +++ b/packages/rulesets/src/oas/__tests__/object-with-properties-requires-type-object.test.ts @@ -0,0 +1,115 @@ +import { DiagnosticSeverity } from '@stoplight/types'; +import testRule from '../../__tests__/__helpers__/tester'; + +testRule('object-with-properties-requires-type-object', [ + { + name: 'oas3: valid object schema with properties', + document: { + openapi: '3.0.0', + paths: {}, + components: { + schemas: { + Address: { + type: 'object', + properties: { + number: { type: 'number' }, + street_name: { type: 'string' }, + }, + required: ['number'], + }, + }, + }, + }, + errors: [], + }, + + { + name: 'oas3: properties without type is invalid', + document: { + openapi: '3.0.0', + paths: {}, + components: { + schemas: { + Address: { + properties: { + number: { type: 'number' }, + street_name: { type: 'string' }, + }, + required: ['number'], + }, + }, + }, + }, + errors: [ + { + message: 'Schemas with "properties" must declare "type: object".', + path: ['components', 'schemas', 'Address'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'oas3: properties with non-object type is invalid', + document: { + openapi: '3.0.0', + paths: {}, + components: { + schemas: { + Address: { + type: 'string', + properties: { + number: { type: 'number' }, + }, + }, + }, + }, + }, + errors: [ + { + message: 'Schemas with "properties" must declare "type: object".', + path: ['components', 'schemas', 'Address', 'type'], + severity: DiagnosticSeverity.Error, + }, + ], + }, + + { + name: 'oas2: valid definition with type object', + document: { + swagger: '2.0', + paths: {}, + definitions: { + Pet: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + errors: [], + }, + + { + name: 'oas2: definition with properties but missing type', + document: { + swagger: '2.0', + paths: {}, + definitions: { + Pet: { + properties: { + name: { type: 'string' }, + }, + }, + }, + }, + errors: [ + { + message: 'Schemas with "properties" must declare "type: object".', + path: ['definitions', 'Pet'], + severity: DiagnosticSeverity.Error, + }, + ], + }, +]); diff --git a/packages/rulesets/src/oas/index.ts b/packages/rulesets/src/oas/index.ts index 9505dc9c7..492b548a2 100644 --- a/packages/rulesets/src/oas/index.ts +++ b/packages/rulesets/src/oas/index.ts @@ -171,6 +171,26 @@ const ruleset = { }, }, }, + 'object-with-properties-requires-type-object': { + description: 'Schemas with "properties" must declare "type: object".', + message: 'Schemas with "properties" must declare "type: object".', + severity: 0, + recommended: false, + resolved: false, + given: '$..[?(@ && @.properties)]', + then: { + function: schema, + functionOptions: { + dialect: 'draft7', + schema: { + required: ['type'], + properties: { + type: { enum: ['object'] }, + }, + }, + }, + }, + }, 'info-contact': { description: 'Info object must have "contact" object.', recommended: true,