diff --git a/src/core/components/auth/oauth2.jsx b/src/core/components/auth/oauth2.jsx
index a4cb6a79db0..a480857e66a 100644
--- a/src/core/components/auth/oauth2.jsx
+++ b/src/core/components/auth/oauth2.jsx
@@ -96,6 +96,7 @@ export default class Oauth2 extends React.Component {
const AuthError = getComponent("authError")
const JumpToPath = getComponent("JumpToPath", true)
const Markdown = getComponent( "Markdown" )
+ const InitializedInput = getComponent("InitializedInput")
const { isOAS3 } = specSelectors
@@ -170,10 +171,10 @@ export default class Oauth2 extends React.Component {
{
isAuthorized ? ******
:
-
@@ -187,8 +188,8 @@ export default class Oauth2 extends React.Component {
{
isAuthorized ? ******
:
-
diff --git a/src/core/components/initialized-input.jsx b/src/core/components/initialized-input.jsx
new file mode 100644
index 00000000000..2f4189c2438
--- /dev/null
+++ b/src/core/components/initialized-input.jsx
@@ -0,0 +1,36 @@
+// This component provides an interface that feels like an uncontrolled input
+// to consumers, while providing a `defaultValue` interface that initializes
+// the input's value using JavaScript value property APIs instead of React's
+// vanilla[0] implementation that uses HTML value attributes.
+//
+// This is useful in situations where we don't want to surface an input's value
+// into the HTML/CSS-exposed side of the DOM, for example to avoid sequential
+// input chaining attacks[1].
+//
+// [0]: https://github.com/facebook/react/blob/baff5cc2f69d30589a5dc65b089e47765437294b/fixtures/dom/src/components/fixtures/text-inputs/README.md
+// [1]: https://github.com/d0nutptr/sic
+
+import React from "react"
+import PropTypes from "prop-types"
+
+export default class InitializedInput extends React.Component {
+ componentDidMount() {
+ // Set the element's `value` property (*not* the `value` attribute)
+ // once, on mount, if an `initialValue` is provided.
+ if(this.props.initialValue) {
+ this.inputRef.value = this.props.initialValue
+ }
+ }
+
+ render() {
+ // Filter out `value` and `defaultValue`, since we have our own
+ // `initialValue` interface that we provide.
+ // eslint-disable-next-line no-unused-vars, react/prop-types
+ const { value, defaultValue, ...otherProps } = this.props
+ return this.inputRef = c} />
+ }
+}
+
+InitializedInput.propTypes = {
+ initialValue: PropTypes.string
+}
diff --git a/src/core/components/providers/markdown.jsx b/src/core/components/providers/markdown.jsx
index d8d31a97aae..d3a2e4826bc 100644
--- a/src/core/components/providers/markdown.jsx
+++ b/src/core/components/providers/markdown.jsx
@@ -51,6 +51,7 @@ export default Markdown
export function sanitizer(str) {
return DomPurify.sanitize(str, {
- ADD_ATTR: ["target"]
+ ADD_ATTR: ["target"],
+ FORBID_TAGS: ["style"],
})
}
diff --git a/src/core/presets/base.js b/src/core/presets/base.js
index 015cd80d21b..6fe8f43c618 100644
--- a/src/core/presets/base.js
+++ b/src/core/presets/base.js
@@ -53,6 +53,7 @@ import Headers from "core/components/headers"
import Errors from "core/components/errors"
import ContentType from "core/components/content-type"
import Overview from "core/components/overview"
+import InitializedInput from "core/components/initialized-input"
import Info, {
InfoUrl,
InfoBasePath
@@ -105,6 +106,7 @@ export default function() {
basicAuth: BasicAuth,
clear: Clear,
liveResponse: LiveResponse,
+ InitializedInput,
info: Info,
InfoContainer,
JumpToPath,
diff --git a/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml b/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml
index 20bd288b07d..98646200dc9 100644
--- a/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml
+++ b/test/e2e-cypress/static/documents/petstore-expanded.openapi.yaml
@@ -13,6 +13,8 @@ info:
url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
- url: http://petstore.swagger.io/api
+security:
+ - Petstore: []
paths:
/pets:
get:
@@ -152,4 +154,13 @@ components:
type: integer
format: int32
message:
- type: string
\ No newline at end of file
+ type: string
+ securitySchemes:
+ Petstore:
+ type: oauth2
+ flows:
+ implicit:
+ authorizationUrl: https://example.com/api/oauth/dialog
+ scopes:
+ write:pets: modify pets in your account
+ read:pets: read your pets
diff --git a/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css b/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css
new file mode 100644
index 00000000000..edc480f6ad8
--- /dev/null
+++ b/test/e2e-cypress/static/documents/security/sequential-import-chaining/injection.css
@@ -0,0 +1,7 @@
+* {
+ color: red !important; /* for humans */
+}
+
+h4 {
+ display: none; /* for machines, used to trace whether this sheet is applied */
+}
diff --git a/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml b/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml
new file mode 100644
index 00000000000..e4e4ade8526
--- /dev/null
+++ b/test/e2e-cypress/static/documents/security/sequential-import-chaining/openapi.yaml
@@ -0,0 +1,10 @@
+openapi: "3.0.0"
+
+info:
+ title: Sequential Import Chaining
+ description: >
+ This h4 would be hidden by the injected CSS
+
+ This document tests the ability of a `
diff --git a/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml b/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml
new file mode 100644
index 00000000000..5f9cc448a92
--- /dev/null
+++ b/test/e2e-cypress/static/documents/security/sequential-import-chaining/swagger.yaml
@@ -0,0 +1,10 @@
+swagger: "2.0"
+
+info:
+ title: Sequential Import Chaining
+ description: >
+ This h4 would be hidden by the injected CSS
+
+ This document tests the ability of a `
diff --git a/test/e2e-cypress/static/documents/xss/oauth2.yaml b/test/e2e-cypress/static/documents/security/xss-oauth2.yaml
similarity index 100%
rename from test/e2e-cypress/static/documents/xss/oauth2.yaml
rename to test/e2e-cypress/static/documents/security/xss-oauth2.yaml
diff --git a/test/e2e-cypress/tests/features/xss/oauth2.js b/test/e2e-cypress/tests/security/oauth2.js
similarity index 90%
rename from test/e2e-cypress/tests/features/xss/oauth2.js
rename to test/e2e-cypress/tests/security/oauth2.js
index 3d7b727aa0a..4d01ba3c660 100644
--- a/test/e2e-cypress/tests/features/xss/oauth2.js
+++ b/test/e2e-cypress/tests/security/oauth2.js
@@ -1,6 +1,6 @@
describe("XSS: OAuth2 authorizationUrl sanitization", () => {
it("should filter out a javascript URL", () => {
- cy.visit("/?url=/documents/xss/oauth2.yaml")
+ cy.visit("/?url=/documents/security/xss-oauth2.yaml")
.window()
.then(win => {
let args = null
diff --git a/test/e2e-cypress/tests/security/sequential-import-chaining.js b/test/e2e-cypress/tests/security/sequential-import-chaining.js
new file mode 100644
index 00000000000..90887def3ad
--- /dev/null
+++ b/test/e2e-cypress/tests/security/sequential-import-chaining.js
@@ -0,0 +1,58 @@
+describe("Security: CSS Sequential Import Chaining", () => {
+ describe("in OpenAPI 3.0", () => {
+ describe("CSS Injection via Markdown", () => {
+ it("should filter