UNPKG

eslint-plugin-svelte

Version:
84 lines (83 loc) 3.24 kB
import { createRule } from '../utils/index.js'; import { findAttribute, findBindDirective, findShorthandAttribute, getStaticAttributeValue } from '../utils/ast-utils.js'; /** Checks wether the given attr node is target="_blank" */ function isTargetBlank(node) { return node.key.name === 'target' && getStaticAttributeValue(node) === '_blank'; } /** Checks wether the given element node has secure rel="..." */ function hasSecureRel(node, allowReferrer) { const attr = findAttribute(node, 'rel'); if (attr) { const tags = []; for (const value of attr.value) { if (value.type === 'SvelteLiteral') { tags.push(...value.value.toLowerCase().split(' ')); } } return tags && tags.includes('noopener') && (allowReferrer || tags.includes('noreferrer')); } return false; } /** Checks wether the given element node has external link */ function hasExternalLink(node) { return node.attributes.some((attr) => attr.type === 'SvelteAttribute' && attr.key.name === 'href' && attr.value.length >= 1 && attr.value[0].type === 'SvelteLiteral' && /^(?:\w+:|\/\/)/.test(attr.value[0].value)); } /** Checks wether the given element node has dynamic link */ function hasDynamicLink(node) { const attr = findAttribute(node, 'href'); if (attr) { return attr.value.some((v) => v.type === 'SvelteMustacheTag'); } return Boolean(findShorthandAttribute(node, 'href')) || Boolean(findBindDirective(node, 'href')); } export default createRule('no-target-blank', { meta: { docs: { description: 'disallow `target="_blank"` attribute without `rel="noopener noreferrer"`', category: 'Security Vulnerability', recommended: false }, schema: [ { type: 'object', properties: { allowReferrer: { type: 'boolean' }, enforceDynamicLinks: { enum: ['always', 'never'] } }, additionalProperties: false } ], messages: { disallow: 'Using target="_blank" without rel="noopener noreferrer" is a security risk.' }, type: 'problem' }, create(context) { const configuration = context.options[0] || {}; const allowReferrer = Boolean(configuration.allowReferrer) || false; const enforceDynamicLinks = configuration.enforceDynamicLinks || 'always'; return { SvelteAttribute(node) { if (!isTargetBlank(node) || hasSecureRel(node.parent, allowReferrer)) { return; } const hasDangerHref = hasExternalLink(node.parent) || (enforceDynamicLinks === 'always' && hasDynamicLink(node.parent)); if (hasDangerHref) { context.report({ node, message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk.' }); } } }; } });