Compare commits

...

10 Commits

Author SHA1 Message Date
LEI Zongmin
66df7c48d5 Update CHANGELOG.md
Some checks failed
CodeQL / Analyse (push) Has been cancelled
Node.js CI / build (10.x) (push) Has been cancelled
Node.js CI / build (12.x) (push) Has been cancelled
Node.js CI / build (14.x) (push) Has been cancelled
Node.js CI / build (16.x) (push) Has been cancelled
2024-03-03 10:31:06 +08:00
LEI Zongmin
9c92272047 publish: v1.0.15 2024-03-03 10:28:40 +08:00
suji Kim
ae15483e9e feat: add <kbd> to default whitelist (#279) 2024-03-03 10:24:26 +08:00
mdk000
bedb0c09db feat: single-quoted attribute value syntax support (#287) 2024-03-03 10:21:40 +08:00
Adam Zielinski
8884b21308 feat: Allow loading attribute on img (#278)
Signed-off-by: maosmurf <github@maosmurf.com>
2023-03-23 10:19:18 +08:00
Shigma
4c3c7587f0 chore: fix typo (#272) 2023-03-23 10:05:38 +08:00
LEI Zongmin
c339c1f777 publish: v1.0.14 2022-08-16 21:54:05 +08:00
metonym
71c3f25972 fix: add allowList to types (#261) 2022-08-16 21:50:56 +08:00
Sviataslau Shchaurouski
72844ddc6f fix: problem with not closed tag (#262) 2022-08-16 21:50:26 +08:00
LEI Zongmin
c2419c4d14 publish: v1.0.13 2022-06-07 00:05:03 +08:00
11 changed files with 76 additions and 24 deletions

View File

@@ -1,5 +1,19 @@
# CHANGELOG
## v1.0.15 (2024-03-03)
- [feat: add `<kbd>` to default whitelist](https://github.com/leizongmin/js-xss/pull/279) by @rayrny
- [feat: single-quoted attribute value syntax support](https://github.com/leizongmin/js-xss/pull/287) by @mdk000
## v1.0.14 (2022-08-16)
- [fix: problem with not closed tag](https://github.com/leizongmin/js-xss/pull/262) by @slawiko
- [fix: add allowList to types](https://github.com/leizongmin/js-xss/pull/261) by @metonym
## v1.0.13 (2022-06-07)
- [revert: fix: comment has encoded](https://github.com/leizongmin/js-xss/pull/257)
## v1.0.12 (2022-06-04)
- [feat: add eslint:recommended check](https://github.com/leizongmin/js-xss/pull/252) by @lumburr

View File

@@ -280,6 +280,20 @@ function safeAttrValue(tag, name, value) {
}
```
### Customize output attribute value syntax for HTML
By specifying a `singleQuotedAttributeValue`. Use `true` for `'`. Otherwise default `"` will be used
```javascript
var options = {
singleQuotedAttributeValue: true,
};
// With the configuration specified above, the following HTML:
// <a href="#">Hello</a>
// would become:
// <a href='#'>Hello</a>
```
### Customize CSS filter
If you allow the attribute `style`, the value will be processed by [cssfilter](https://github.com/leizongmin/js-css-filter) module. The cssfilter module includes a default css whitelist. You can specify the options for cssfilter module like this:

View File

@@ -240,7 +240,7 @@ function onIgnoreTag(tag, html, options) {
function onIgnoreTagAttr(tag, name, value, isWhiteAttr) {
// 参数说明与onTagAttr相同
// 如果返回一个字符串,则当前属性值将被替换为该字符串
// 如果不返回任何值,则使用默认的处理方法(删除该属)
// 如果不返回任何值,则使用默认的处理方法(删除该属
}
```

27
dist/xss.js vendored
View File

@@ -58,8 +58,9 @@ function getDefaultWhiteList() {
header: [],
hr: [],
i: [],
img: ["src", "alt", "title", "width", "height"],
img: ["src", "alt", "title", "width", "height", "loading"],
ins: ["datetime"],
kbd: [],
li: [],
mark: [],
nav: [],
@@ -160,15 +161,6 @@ function escapeHtml(html) {
return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
}
/**
* default escapeHtml function but dont escape comment
*
* @param {String} html
*/
function escapeHtmlNotComment(html) {
return html.replace(REGEXP_LT_NOT_COMMENT, "&lt;").replace(REGEXP_RT_NOT_COMMENT, "&gt;");
}
/**
* default safeAttrValue function
*
@@ -238,8 +230,6 @@ function safeAttrValue(tag, name, value, cssFilter) {
// RegExp list
var REGEXP_LT = /</g;
var REGEXP_GT = />/g;
var REGEXP_LT_NOT_COMMENT = /<(?!!--)/g;
var REGEXP_RT_NOT_COMMENT = /(?<!--)>/g;
var REGEXP_QUOTE = /"/g;
var REGEXP_QUOTE_2 = /&quot;/g;
var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
@@ -456,7 +446,6 @@ exports.onTagAttr = onTagAttr;
exports.onIgnoreTagAttr = onIgnoreTagAttr;
exports.safeAttrValue = safeAttrValue;
exports.escapeHtml = escapeHtml;
exports.escapeHtmlNotComment = escapeHtmlNotComment;
exports.escapeQuote = escapeQuote;
exports.unescapeQuote = unescapeQuote;
exports.escapeHtmlEntities = escapeHtmlEntities;
@@ -468,6 +457,7 @@ exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
exports.StripTagBody = StripTagBody;
exports.stripCommentTag = stripCommentTag;
exports.stripBlankChar = stripBlankChar;
exports.attributeWrapSign = '"';
exports.cssFilter = defaultCSSFilter;
exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;
@@ -598,7 +588,7 @@ function parseTag(html, onTag, escapeHtml) {
lastPos = currentPos;
continue;
}
if (c === ">") {
if (c === ">" || currentPos === len - 1) {
rethtml += escapeHtml(html.slice(lastPos, tagStart));
currentHtml = html.slice(tagStart, currentPos + 1);
currentTagName = getTagName(currentHtml);
@@ -633,7 +623,7 @@ function parseTag(html, onTag, escapeHtml) {
}
}
}
if (lastPos < html.length) {
if (lastPos < len) {
rethtml += escapeHtml(html.substr(lastPos));
}
@@ -922,12 +912,14 @@ function FilterXSS(options) {
options.whiteList = DEFAULT.whiteList;
}
this.attributeWrapSign = options.singleQuotedAttributeValue === true ? "'" : DEFAULT.attributeWrapSign;
options.onTag = options.onTag || DEFAULT.onTag;
options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
options.escapeHtml = options.escapeHtml || (options.allowCommentTag ? DEFAULT.escapeHtmlNotComment : DEFAULT.escapeHtml);
options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
this.options = options;
if (options.css === false) {
@@ -959,6 +951,7 @@ FilterXSS.prototype.process = function (html) {
var onIgnoreTagAttr = options.onIgnoreTagAttr;
var safeAttrValue = options.safeAttrValue;
var escapeHtml = options.escapeHtml;
var attributeWrapSign = me.attributeWrapSign;
var cssFilter = me.cssFilter;
// remove invisible characters
@@ -1012,7 +1005,7 @@ FilterXSS.prototype.process = function (html) {
// call `safeAttrValue()`
value = safeAttrValue(tag, name, value, cssFilter);
if (value) {
return name + '="' + value + '"';
return name + '=' + attributeWrapSign + value + attributeWrapSign;
} else {
return name;
}

2
dist/xss.min.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -57,8 +57,9 @@ function getDefaultWhiteList() {
header: [],
hr: [],
i: [],
img: ["src", "alt", "title", "width", "height"],
img: ["src", "alt", "title", "width", "height", "loading"],
ins: ["datetime"],
kbd: [],
li: [],
mark: [],
nav: [],
@@ -455,5 +456,6 @@ exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
exports.StripTagBody = StripTagBody;
exports.stripCommentTag = stripCommentTag;
exports.stripBlankChar = stripBlankChar;
exports.attributeWrapSign = '"';
exports.cssFilter = defaultCSSFilter;
exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;

View File

@@ -71,7 +71,7 @@ function parseTag(html, onTag, escapeHtml) {
lastPos = currentPos;
continue;
}
if (c === ">") {
if (c === ">" || currentPos === len - 1) {
rethtml += escapeHtml(html.slice(lastPos, tagStart));
currentHtml = html.slice(tagStart, currentPos + 1);
currentTagName = getTagName(currentHtml);
@@ -106,7 +106,7 @@ function parseTag(html, onTag, escapeHtml) {
}
}
}
if (lastPos < html.length) {
if (lastPos < len) {
rethtml += escapeHtml(html.substr(lastPos));
}

View File

@@ -100,6 +100,8 @@ function FilterXSS(options) {
options.whiteList = DEFAULT.whiteList;
}
this.attributeWrapSign = options.singleQuotedAttributeValue === true ? "'" : DEFAULT.attributeWrapSign;
options.onTag = options.onTag || DEFAULT.onTag;
options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
@@ -137,6 +139,7 @@ FilterXSS.prototype.process = function (html) {
var onIgnoreTagAttr = options.onIgnoreTagAttr;
var safeAttrValue = options.safeAttrValue;
var escapeHtml = options.escapeHtml;
var attributeWrapSign = me.attributeWrapSign;
var cssFilter = me.cssFilter;
// remove invisible characters
@@ -190,7 +193,7 @@ FilterXSS.prototype.process = function (html) {
// call `safeAttrValue()`
value = safeAttrValue(tag, name, value, cssFilter);
if (value) {
return name + '="' + value + '"';
return name + '=' + attributeWrapSign + value + attributeWrapSign;
} else {
return name;
}

View File

@@ -2,7 +2,7 @@
"name": "xss",
"main": "./lib/index.js",
"typings": "./typings/xss.d.ts",
"version": "1.0.12",
"version": "1.0.15",
"description": "Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist",
"author": "Zongmin Lei <leizongmin@gmail.com> (http://ucdok.com)",
"repository": {

View File

@@ -88,6 +88,7 @@ describe("test XSS", function() {
assert.equal(xss("<img src//>"), "<img src />");
assert.equal(xss("<br/>"), "<br />");
assert.equal(xss("<br />"), "<br />");
assert.equal(xss("<img src=x onerror=alert('XSS')"), "<img src>");
// 畸形属性格式
assert.equal(
@@ -132,6 +133,12 @@ describe("test XSS", function() {
),
'<img width="100" height="200" title="xxx" alt="\'yyy\'">'
);
assert.equal(
xss(
'<img loading="lazy">'
),
'<img loading="lazy">'
);
// 使用Tab或换行符分隔的属性
assert.equal(
@@ -421,6 +428,22 @@ describe("test XSS", function() {
);
});
it("#singleQuotedAttributeValue", function() {
assert.equal(xss('<a title="xx">not-defined</a>'), '<a title="xx">not-defined</a>');
assert.equal(
xss('<a title="xx">single-quoted</a>', { singleQuotedAttributeValue: true }),
'<a title=\'xx\'>single-quoted</a>'
);
assert.equal(
xss('<a title="xx">double-quoted</a>', { singleQuotedAttributeValue: false }),
'<a title="xx">double-quoted</a>'
);
assert.equal(
xss('<a title="xx">invalid-value</a>', { singleQuotedAttributeValue: 'invalid' }),
'<a title="xx">invalid-value</a>'
);
})
it("no options mutated", function() {
var options = {};

3
typings/xss.d.ts vendored
View File

@@ -10,6 +10,7 @@ declare module "xss" {
namespace XSS {
export interface IFilterXSSOptions {
allowList?: IWhiteList;
whiteList?: IWhiteList;
onTag?: OnTagHandler;
onTagAttr?: OnTagAttrHandler;
@@ -21,6 +22,7 @@ declare module "xss" {
stripIgnoreTagBody?: boolean | string[];
allowCommentTag?: boolean;
stripBlankChar?: boolean;
singleQuotedAttributeValue?: boolean;
css?: {} | boolean;
}
@@ -194,6 +196,7 @@ declare module "xss" {
export function onIgnoreTagStripAll(): string;
export const stripCommentTag: EscapeHandler;
export const stripBlankChar: EscapeHandler;
export const attributeWrapSign: string;
export const cssFilter: ICSSFilter;
export function getDefaultCSSWhiteList(): ICSSFilter;