Commit 8603269d authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-xss-error-tracking' into 'master'

Stored XSS on the Error Tracking page

Closes #145

See merge request gitlab-org/security/gitlab!563
parents 23f17213 fed0f156
<script> <script>
import { escape } from 'lodash'; import { GlTooltip, GlSprintf } from '@gitlab/ui';
import { GlTooltip } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
...@@ -11,6 +9,7 @@ export default { ...@@ -11,6 +9,7 @@ export default {
ClipboardButton, ClipboardButton,
FileIcon, FileIcon,
Icon, Icon,
GlSprintf,
}, },
directives: { directives: {
GlTooltip, GlTooltip,
...@@ -57,36 +56,6 @@ export default { ...@@ -57,36 +56,6 @@ export default {
collapseIcon() { collapseIcon() {
return this.isExpanded ? 'chevron-down' : 'chevron-right'; return this.isExpanded ? 'chevron-down' : 'chevron-right';
}, },
errorFnText() {
return this.errorFn
? sprintf(
__(`%{spanStart}in%{spanEnd} %{errorFn}`),
{
errorFn: `<strong>${escape(this.errorFn)}</strong>`,
spanStart: `<span class="text-tertiary">`,
spanEnd: `</span>`,
},
false,
)
: '';
},
errorPositionText() {
return this.errorLine
? sprintf(
__(`%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}`),
{
errorLine: `<strong>${this.errorLine}</strong>`,
errorColumn: this.errorColumn ? `:<strong>${this.errorColumn}</strong>` : ``,
spanStart: `<span class="text-tertiary">`,
spanEnd: `</span>`,
},
false,
)
: '';
},
errorInfo() {
return `${this.errorFnText} ${this.errorPositionText}`;
},
}, },
methods: { methods: {
isHighlighted(lineNum) { isHighlighted(lineNum) {
...@@ -132,7 +101,27 @@ export default { ...@@ -132,7 +101,27 @@ export default {
:text="filePath" :text="filePath"
css-class="btn-default btn-transparent btn-clipboard position-static" css-class="btn-default btn-transparent btn-clipboard position-static"
/> />
<span v-html="errorInfo"></span>
<gl-sprintf v-if="errorFn" :message="__('%{spanStart}in%{spanEnd} %{errorFn}')">
<template #span="{content}">
<span class="gl-text-gray-400">{{ content }}&nbsp;</span>
</template>
<template #errorFn>
<strong>{{ errorFn }}&nbsp;</strong>
</template>
</gl-sprintf>
<gl-sprintf :message="__('%{spanStart}at line%{spanEnd} %{errorLine}%{errorColumn}')">
<template #span="{content}">
<span class="gl-text-gray-400">{{ content }}&nbsp;</span>
</template>
<template #errorLine>
<strong>{{ errorLine }}</strong>
</template>
<template #errorColumn>
<strong v-if="errorColumn">:{{ errorColumn }}</strong>
</template>
</gl-sprintf>
</div> </div>
</div> </div>
......
---
title: Stored XSS on the Error Tracking page
merge_request:
author:
type: security
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlSprintf } from '@gitlab/ui';
import StackTraceEntry from '~/error_tracking/components/stacktrace_entry.vue'; import StackTraceEntry from '~/error_tracking/components/stacktrace_entry.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue'; import FileIcon from '~/vue_shared/components/file_icon.vue';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { trimText } from 'helpers/text_helper';
describe('Stacktrace Entry', () => { describe('Stacktrace Entry', () => {
let wrapper; let wrapper;
...@@ -21,6 +23,9 @@ describe('Stacktrace Entry', () => { ...@@ -21,6 +23,9 @@ describe('Stacktrace Entry', () => {
errorLine: 24, errorLine: 24,
...props, ...props,
}, },
stubs: {
GlSprintf,
},
}); });
} }
...@@ -53,7 +58,7 @@ describe('Stacktrace Entry', () => { ...@@ -53,7 +58,7 @@ describe('Stacktrace Entry', () => {
const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: 77 }; const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: 77 };
mountComponent({ expanded: false, lines: [], ...extraInfo }); mountComponent({ expanded: false, lines: [], ...extraInfo });
expect(wrapper.find(Icon).exists()).toBe(false); expect(wrapper.find(Icon).exists()).toBe(false);
expect(findFileHeaderContent()).toContain( expect(trimText(findFileHeaderContent())).toContain(
`in ${extraInfo.errorFn} at line ${extraInfo.errorLine}:${extraInfo.errorColumn}`, `in ${extraInfo.errorFn} at line ${extraInfo.errorLine}:${extraInfo.errorColumn}`,
); );
}); });
...@@ -61,17 +66,17 @@ describe('Stacktrace Entry', () => { ...@@ -61,17 +66,17 @@ describe('Stacktrace Entry', () => {
it('should render only lineNo:columnNO when there is no errorFn ', () => { it('should render only lineNo:columnNO when there is no errorFn ', () => {
const extraInfo = { errorLine: 34, errorFn: null, errorColumn: 77 }; const extraInfo = { errorLine: 34, errorFn: null, errorColumn: 77 };
mountComponent({ expanded: false, lines: [], ...extraInfo }); mountComponent({ expanded: false, lines: [], ...extraInfo });
expect(findFileHeaderContent()).not.toContain(`in ${extraInfo.errorFn}`); const fileHeaderContent = trimText(findFileHeaderContent());
expect(findFileHeaderContent()).toContain(`${extraInfo.errorLine}:${extraInfo.errorColumn}`); expect(fileHeaderContent).not.toContain(`in ${extraInfo.errorFn}`);
expect(fileHeaderContent).toContain(`${extraInfo.errorLine}:${extraInfo.errorColumn}`);
}); });
it('should render only lineNo when there is no errorColumn ', () => { it('should render only lineNo when there is no errorColumn ', () => {
const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: null }; const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: null };
mountComponent({ expanded: false, lines: [], ...extraInfo }); mountComponent({ expanded: false, lines: [], ...extraInfo });
expect(findFileHeaderContent()).toContain( const fileHeaderContent = trimText(findFileHeaderContent());
`in ${extraInfo.errorFn} at line ${extraInfo.errorLine}`, expect(fileHeaderContent).toContain(`in ${extraInfo.errorFn} at line ${extraInfo.errorLine}`);
); expect(fileHeaderContent).not.toContain(`:${extraInfo.errorColumn}`);
expect(findFileHeaderContent()).not.toContain(`:${extraInfo.errorColumn}`);
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment