Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
echo "EOF" >> $GITHUB_OUTPUT
- name: Create the release
if: steps.changelog.outputs.changelog_content != ''
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
name: ${{ github.ref_name }}
body: '${{ steps.changelog.outputs.changelog_content }}'
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

This project adheres to [Semantic Versioning](https://semver.org/).

## 8.5.10

- Fixed XSS via unescaped `</style>` in non-bundler cases (by @TharVid).

## 8.5.9

- Speed up source map encoding paring in case of the error.
Expand Down
65 changes: 52 additions & 13 deletions lib/lazy-result.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,13 @@ class LazyResult {
}
}
this.hasListener = Object.keys(this.listeners).length > 0
this.hasKeyListeners = false
for (let key in this.listeners) {
if (key.includes('-')) {
this.hasKeyListeners = true
break
}
}
}

async runAsync() {
Expand Down Expand Up @@ -517,19 +524,51 @@ class LazyResult {

walkSync(node) {
node[isClean] = true
let events = getEvents(node)
for (let event of events) {
if (event === CHILDREN) {
if (node.nodes) {
node.each(child => {
if (!child[isClean]) this.walkSync(child)
})
}
} else {
let visitors = this.listeners[event]
if (visitors) {
if (this.visitSync(visitors, node.toProxy())) return
}
let type = TYPE_TO_CLASS_NAME[node.type]
let listeners = this.listeners
let proxy
let visitors
let key

if (this.hasKeyListeners) {
if (node.type === 'decl') {
key = node.prop.toLowerCase()
} else if (node.type === 'atrule') {
key = node.name.toLowerCase()
}
}

visitors = listeners[type]
if (visitors) {
proxy = node.toProxy()
if (this.visitSync(visitors, proxy)) return
}

if (key) {
visitors = listeners[type + '-' + key]
if (visitors) {
if (!proxy) proxy = node.toProxy()
if (this.visitSync(visitors, proxy)) return
}
}

if (node.nodes) {
node.each(child => {
if (!child[isClean]) this.walkSync(child)
})
}

visitors = listeners[type + 'Exit']
if (visitors) {
if (!proxy) proxy = node.toProxy()
if (this.visitSync(visitors, proxy)) return
}

if (key) {
visitors = listeners[type + 'Exit-' + key]
if (visitors) {
if (!proxy) proxy = node.toProxy()
this.visitSync(visitors, proxy)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let Root = require('./root')

class Processor {
constructor(plugins = []) {
this.version = '8.5.9'
this.version = '8.5.10'
this.plugins = this.normalize(plugins)
}

Expand Down
33 changes: 25 additions & 8 deletions lib/stringifier.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
'use strict'

// Escapes sequences that could break out of an HTML <style> context.
// Uses CSS unicode escaping (\3c = '<') which is valid CSS and parsed
// correctly by all compliant CSS consumers.
const STYLE_TAG = /(<)(\/?style\b)/gi
const COMMENT_OPEN = /(<)(!--)/g

function escapeHTMLInCSS(str) {
if (typeof str !== 'string') return str
if (!str.includes('<')) return str
return str.replace(STYLE_TAG, '\\3c $2').replace(COMMENT_OPEN, '\\3c $2')
}

const DEFAULT_RAW = {
after: '\n',
beforeClose: '\n',
Expand Down Expand Up @@ -38,7 +50,7 @@ class Stringifier {
this.block(node, name + params)
} else {
let end = (node.raws.between || '') + (semicolon ? ';' : '')
this.builder(name + params + end, node)
this.builder(escapeHTMLInCSS(name + params + end), node)
}
}

Expand Down Expand Up @@ -73,7 +85,7 @@ class Stringifier {

block(node, start) {
let between = this.raw(node, 'between', 'beforeOpen')
this.builder(start + between + '{', node, 'start')
this.builder(escapeHTMLInCSS(start + between) + '{', node, 'start')

let after
if (node.nodes && node.nodes.length) {
Expand All @@ -83,7 +95,7 @@ class Stringifier {
after = this.raw(node, 'after', 'emptyBody')
}

if (after) this.builder(after)
if (after) this.builder(escapeHTMLInCSS(after))
this.builder('}', node, 'end')
}

Expand All @@ -95,18 +107,19 @@ class Stringifier {
}

let semicolon = this.raw(node, 'semicolon')
let isDocument = node.type === 'document'
for (let i = 0; i < node.nodes.length; i++) {
let child = node.nodes[i]
let before = this.raw(child, 'before')
if (before) this.builder(before)
if (before) this.builder(isDocument ? before : escapeHTMLInCSS(before))
this.stringify(child, last !== i || semicolon)
}
}

comment(node) {
let left = this.raw(node, 'left', 'commentLeft')
let right = this.raw(node, 'right', 'commentRight')
this.builder('/*' + left + node.text + right + '*/', node)
this.builder(escapeHTMLInCSS('/*' + left + node.text + right + '*/'), node)
}

decl(node, semicolon) {
Expand All @@ -118,7 +131,7 @@ class Stringifier {
}

if (semicolon) string += ';'
this.builder(string, node)
this.builder(escapeHTMLInCSS(string), node)
}

document(node) {
Expand Down Expand Up @@ -324,13 +337,17 @@ class Stringifier {

root(node) {
this.body(node)
if (node.raws.after) this.builder(node.raws.after)
if (node.raws.after) {
let after = node.raws.after
let isDocument = node.parent && node.parent.type === 'document'
this.builder(isDocument ? after : escapeHTMLInCSS(after))
}
}

rule(node) {
this.block(node, this.rawValue(node, 'selector'))
if (node.raws.ownSemicolon) {
this.builder(node.raws.ownSemicolon, node, 'end')
this.builder(escapeHTMLInCSS(node.raws.ownSemicolon), node, 'end')
}
}

Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "postcss",
"version": "8.5.9",
"version": "8.5.10",
"description": "Tool for transforming styles with JS plugins",
"keywords": [
"css",
Expand Down Expand Up @@ -95,10 +95,10 @@
},
"devDependencies": {
"@logux/eslint-config": "^57.1.0",
"@logux/oxc-configs": "^0.2.2",
"@size-limit/preset-small-lib": "^12.0.1",
"@types/node": "^25.5.2",
"actions-up": "^1.12.1",
"@logux/oxc-configs": "^0.3.3",
"@size-limit/preset-small-lib": "^12.1.0",
"@types/node": "^25.6.0",
"actions-up": "^1.13.0",
"c8": "^11.0.0",
"check-dts": "^0.9.0",
"clean-publish": "^6.0.5",
Expand All @@ -107,10 +107,10 @@
"multiocular": "^0.8.2",
"nanodelay": "^1.0.8",
"nanospy": "^1.0.0",
"oxfmt": "^0.43.0",
"oxfmt": "^0.45.0",
"postcss-parser-tests": "^8.9.0",
"simple-git-hooks": "^2.13.1",
"size-limit": "^12.0.1",
"size-limit": "^12.1.0",
"strip-ansi": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.3",
Expand Down
Loading