Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,6 @@ Please refer to [post-operation-id-contains-url-verb.md](./post-operation-id-con
### PostResponseCodes

Synchronous POST operations must only use 200 with a default response when a response schema is required, or 204 with a default response when no schema is needed. No other response codes are allowed.

Long-running POST (LRO) operations must initially return 202 with a default response and no schema. The final response must be 200 with a schema if one is required, or 204 with no schema if not. No other response codes are permitted.

Please refer to [post-response-codes.md](./post-response-codes.md) for details.
Expand Down Expand Up @@ -1446,3 +1445,9 @@ Please refer to [xms-paths-must-overload-paths.md](./xms-paths-must-overload-pat
The 200 response model for an ARM PUT operation must have x-ms-azure-resource extension set to true in its hierarchy. Operation: '{0}' Model: '{1}'.

Please refer to [xms-resource-in-put-response.md](./xms-resource-in-put-response.md) for details.

### XMSSecretInResponse

When defining the response model for an ARM PUT/GET/POST operation, any property that contains sensitive information (such as passwords, keys, tokens, credentials, or other secrets) must include the `"x-ms-secret": true` annotation. This ensures that secrets are properly identified and handled according to ARM security guidelines.

Please refer to [xms-secret-in-response.md](./xms-secret-in-response.md) for details.
83 changes: 83 additions & 0 deletions docs/xms-secret-in-response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# XMSSecretInResponse

## Category

ARM Error

## Applies to

ARM OpenAPI(swagger) specs

## Related ARM Guideline Code

- RPC-Put-V1-13

## Description

When defining the response model for an ARM PUT/GET/POST operation, any property that contains sensitive information (such as passwords, keys, tokens, credentials, or other secrets) must include the `"x-ms-secret": true` annotation. This ensures that secrets are properly identified and handled according to ARM security guidelines.

## How to fix the violation

To fix this violation, review the response model for your ARM PUT/GET/POST operation and identify all properties that store secrets (such as `password`, `key`, `access`, `credentials`, `token`, `secret`, `auth`). For each of these properties, add the `"x-ms-secret": true` annotation to ensure they are properly marked as secrets. This helps protect sensitive information and complies with ARM guidelines.

Note: `The ARM reviewers have determined that the property is a false positive and have verified that it does not contain any secrets, they can suppress the error.`

## Good example

```json5
"Resource": {
"description": "The resource",
"properties": {
"password": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
},
"accessKey": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
},
"key": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
},
"token": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
},
"credentials": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
},
"secret": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
},
"auth": {
"type": "string",
"description": "secret",
"x-ms-secret": "true"
}
}
}
```

## Bad example

```json5
"Resource": {
"description": "The resource",
"properties": {
"password": {
"type": "string",
"description": "secret"
// No x-ms-secret annotation
}
}
}
```
6 changes: 6 additions & 0 deletions packages/rulesets/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log - @microsoft.azure/openapi-validator-rulesets

## 2.2.0

### Minor changes

- Added rule RPC-V1-13 to check for secrets and mandate the x-ms-secret annotation.

## 2.1.10

### Patches
Expand Down
50 changes: 50 additions & 0 deletions packages/rulesets/generated/spectral/az-arm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3236,6 +3236,45 @@ const xmsPageableForListCalls = (swaggerObj, _opts, paths) => {
];
};

const sensitiveKeywords = ["access", "credential", "secret", "password", "key", "token", "auth", "connection"];
const excludeKeywords = ["publicKey"].map((keyword) => keyword.toLowerCase());
function isPotentialSensitiveProperty(propertyName) {
const lowerName = propertyName.toLowerCase();
return (sensitiveKeywords.some((keyword) => lowerName.endsWith(keyword) && !lowerName.startsWith(keyword)) &&
!excludeKeywords.some((keyword) => lowerName.endsWith(keyword) && !lowerName.startsWith(keyword)));
}
function isKeyValuePairKeyProp(propertiesKeys) {
return propertiesKeys.includes("key") && propertiesKeys.includes("value");
}
const XMSSecretInResponse = (properties, _opts, ctx) => {
if (properties === null || typeof properties !== "object") {
return [];
}
const path = ctx.path || [];
const errors = [];
const propertiesSize = Object.keys(properties).length;
const propertiesKeys = Object.keys(properties);
const keyValuePairCheck = propertiesSize === 2 && isKeyValuePairKeyProp(propertiesKeys);
for (const prpName of propertiesKeys) {
if (prpName === "properties" && typeof properties[prpName] === "object") {
errors.push(...XMSSecretInResponse(properties[prpName], _opts, { ...ctx, path: [...path, prpName] }));
}
else {
if (isPotentialSensitiveProperty(prpName) &&
properties[prpName] &&
properties[prpName]["x-ms-secret"] !== true &&
!keyValuePairCheck &&
properties[prpName].type === "string") {
errors.push({
message: `Property '${prpName}' contains secret keyword and does not have 'x-ms-secret' annotation. To ensure security, must add the 'x-ms-secret' annotation to this property.`,
path: [...path, prpName],
});
}
}
}
return errors;
};

const ruleset = {
extends: [ruleset$1],
rules: {
Expand Down Expand Up @@ -3738,6 +3777,17 @@ const ruleset = {
function: withXmsResource,
},
},
XMSSecretInResponse: {
rpcGuidelineCode: "RPC-Put-V1-13",
description: `When defining the response model for an ARM PUT/GET/POST operation, any property that contains sensitive information (such as passwords, keys, tokens, credentials, or other secrets) must include the "x-ms-secret": true annotation. This ensures that secrets are properly identified and handled according to ARM security guidelines.`,
message: "{{error}}",
severity: "error",
resolved: true,
given: ["$[paths,'x-ms-paths'].*.[put,get,post].responses.*.schema.properties"],
then: {
function: XMSSecretInResponse,
},
},
LocationMustHaveXmsMutability: {
rpcGuidelineCode: "RPC-Put-V1-14",
description: "A tracked resource's location property must have the x-ms-mutability properties set as read, create.",
Expand Down
2 changes: 1 addition & 1 deletion packages/rulesets/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@microsoft.azure/openapi-validator-rulesets",
"version": "2.1.10",
"version": "2.2.0",
"description": "Azure OpenAPI Validator",
"main": "dist/index.js",
"scripts": {
Expand Down
13 changes: 13 additions & 0 deletions packages/rulesets/src/spectral/az-arm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { validatePatchBodyParamProperties } from "./functions/validate-patch-bod
import withXmsResource from "./functions/with-xms-resource"
import verifyXMSLongRunningOperationProperty from "./functions/xms-long-running-operation-property"
import xmsPageableForListCalls from "./functions/xms-pageable-for-list-calls"
import XMSSecretInResponse from "./functions/xms-secret-in-response"

const ruleset: any = {
extends: [common],
Expand Down Expand Up @@ -682,6 +683,18 @@ const ruleset: any = {
function: withXmsResource,
},
},
// RPC Code: RPC-Put-V1-13
XMSSecretInResponse: {
rpcGuidelineCode: "RPC-Put-V1-13",
description: `When defining the response model for an ARM PUT/GET/POST operation, any property that contains sensitive information (such as passwords, keys, tokens, credentials, or other secrets) must include the "x-ms-secret": true annotation. This ensures that secrets are properly identified and handled according to ARM security guidelines.`,
message: "{{error}}",
severity: "error",
resolved: true,
given: ["$[paths,'x-ms-paths'].*.[put,get,post].responses.*.schema.properties"],
then: {
function: XMSSecretInResponse,
},
},
// RPC Code: RPC-Put-V1-14
LocationMustHaveXmsMutability: {
rpcGuidelineCode: "RPC-Put-V1-14",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const sensitiveKeywords = ["access", "credential", "secret", "password", "key", "token", "auth", "connection"]

const excludeKeywords = ["publicKey"].map((keyword) => keyword.toLowerCase())
Copy link
Collaborator

@AkhilaIlla AkhilaIlla Dec 5, 2025

Choose a reason for hiding this comment

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

.map((keyword) => keyword.toLowerCase())

why map to convert to lowercase and not what you ahev for sensitive keywords? #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is to exclude "publicKey" since it is not a secret as suggested by typespec.
To explicitly keep it as "publicKey" for clarity, and this is how it is typically defined in .ts or .json files.


function isPotentialSensitiveProperty(propertyName: string): boolean {
const lowerName = propertyName.toLowerCase()
return (
sensitiveKeywords.some((keyword) => lowerName.endsWith(keyword) && !lowerName.startsWith(keyword)) &&
Copy link
Collaborator

@AkhilaIlla AkhilaIlla Dec 5, 2025

Choose a reason for hiding this comment

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

lowerName.endsWith(keyword) && !lowerName.startsWith(keyword)

why endwith & not equals? #Resolved

Copy link
Collaborator

Choose a reason for hiding this comment

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

nvm

Copy link
Collaborator

@AkhilaIlla AkhilaIlla Dec 5, 2025

Choose a reason for hiding this comment

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

!lowerName.startsWith(keyword)

is it ok to start with these keywords?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, same here—the typespec team introduced some new checks to cut down on all those false positives.

Copy link
Collaborator

Choose a reason for hiding this comment

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

so, passwordXYZ is fine to be defined?

!excludeKeywords.some((keyword) => lowerName.endsWith(keyword) && !lowerName.startsWith(keyword))
)
}

function isKeyValuePairKeyProp(propertiesKeys: any): boolean {
return propertiesKeys.includes("key") && propertiesKeys.includes("value")
}

export const XMSSecretInResponse = (properties: any, _opts: any, ctx: any) => {
if (properties === null || typeof properties !== "object") {
return []
}

const path = ctx.path || []
const errors: any[] = []
const propertiesSize = Object.keys(properties).length
const propertiesKeys = Object.keys(properties)
const keyValuePairCheck = propertiesSize === 2 && isKeyValuePairKeyProp(propertiesKeys)

// Check top-level and deeply nested properties
for (const prpName of propertiesKeys) {
if (prpName === "properties" && typeof properties[prpName] === "object") {
errors.push(...XMSSecretInResponse(properties[prpName], _opts, { ...ctx, path: [...path, prpName] }))
} else {
// Add all conditions for secret detection
if (
isPotentialSensitiveProperty(prpName) && // property name matches sensitive keywords
properties[prpName] && // property exists
properties[prpName]["x-ms-secret"] !== true && // not explicitly marked as secret
!keyValuePairCheck && // not a key-value pair key
Copy link
Collaborator

Choose a reason for hiding this comment

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

!keyValuePairCheck && // not a key-value pair key

what is this additional check for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggestion from typespec team: "Exclude key when used in a model that only has 1 other property value(Get rid of most key instance as its just key value pair)"

Without this check, we were getting a bunch of false positives. That’s why the typespec team came up with all these new checks, and now they’ve added them to their code too to avoid false positives.

Copy link
Collaborator

Choose a reason for hiding this comment

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

and why is this- Exclude key when used in a model that only has 1 other property value(Get rid of most key instance as its just key value pair)?

properties[prpName].type === "string" // property type is string
) {
errors.push({
message: `Property '${prpName}' contains secret keyword and does not have 'x-ms-secret' annotation. To ensure security, must add the 'x-ms-secret' annotation to this property.`,
path: [...path, prpName],
})
}
}
}

return errors
}

export default XMSSecretInResponse
Loading
Loading