@@ -143,6 +143,33 @@ export default class Sidebar extends React.PureComponent<Props> { | |||
stats={facets.statuses} | |||
statuses={query.statuses} | |||
/> | |||
<StandardFacet | |||
cwe={query.cwe} | |||
cweOpen={!!openFacets.cwe} | |||
cweStats={facets.cwe} | |||
fetchingCwe={this.props.loadingFacets.cwe === true} | |||
fetchingOwaspTop10={this.props.loadingFacets.owaspTop10 === true} | |||
fetchingSansTop25={this.props.loadingFacets.sansTop25 === true} | |||
fetchingSonarSourceSecurity={this.props.loadingFacets.sonarsourceSecurity === true} | |||
loadSearchResultCount={this.props.loadSearchResultCount} | |||
onChange={this.props.onFilterChange} | |||
onToggle={this.props.onFacetToggle} | |||
open={ | |||
openFacets[STANDARDS] === undefined | |||
? query.types.includes('SECURITY_HOTSPOT') || query.types.includes('VULNERABILITY') | |||
: openFacets[STANDARDS] | |||
} | |||
owaspTop10={query.owaspTop10} | |||
owaspTop10Open={!!openFacets.owaspTop10} | |||
owaspTop10Stats={facets.owaspTop10} | |||
query={query} | |||
sansTop25={query.sansTop25} | |||
sansTop25Open={!!openFacets.sansTop25} | |||
sansTop25Stats={facets.sansTop25} | |||
sonarsourceSecurity={query.sonarsourceSecurity} | |||
sonarsourceSecurityOpen={!!openFacets.sonarsourceSecurity} | |||
sonarsourceSecurityStats={facets.sonarsourceSecurity} | |||
/> | |||
<CreationDateFacet | |||
component={component} | |||
createdAfter={query.createdAfter} | |||
@@ -180,30 +207,6 @@ export default class Sidebar extends React.PureComponent<Props> { | |||
rules={query.rules} | |||
stats={facets.rules} | |||
/> | |||
<StandardFacet | |||
cwe={query.cwe} | |||
cweOpen={!!openFacets.cwe} | |||
cweStats={facets.cwe} | |||
fetchingCwe={this.props.loadingFacets.cwe === true} | |||
fetchingOwaspTop10={this.props.loadingFacets.owaspTop10 === true} | |||
fetchingSansTop25={this.props.loadingFacets.sansTop25 === true} | |||
loadSearchResultCount={this.props.loadSearchResultCount} | |||
onChange={this.props.onFilterChange} | |||
onToggle={this.props.onFacetToggle} | |||
open={!!openFacets[STANDARDS]} | |||
owaspTop10={query.owaspTop10} | |||
owaspTop10Open={!!openFacets.owaspTop10} | |||
owaspTop10Stats={facets.owaspTop10} | |||
query={query} | |||
sansTop25={query.sansTop25} | |||
sansTop25Open={!!openFacets.sansTop25} | |||
sansTop25Stats={facets.sansTop25} | |||
sonarsourceSecurity={ | |||
[ | |||
/* TODO */ | |||
] | |||
} | |||
/> | |||
<TagFacet | |||
component={component} | |||
fetching={this.props.loadingFacets.tags === true} |
@@ -28,20 +28,23 @@ import FacetItem from '../../../components/facet/FacetItem'; | |||
import { | |||
renderOwaspTop10Category, | |||
renderSansTop25Category, | |||
renderCWECategory | |||
renderCWECategory, | |||
renderSonarSourceSecurityCategory | |||
} from '../../securityReports/utils'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import ListStyleFacet from '../../../components/facet/ListStyleFacet'; | |||
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint'; | |||
import { highlightTerm } from '../../../helpers/search'; | |||
import ListStyleFacet from '../../../components/facet/ListStyleFacet'; | |||
import { getStandards } from '../../../helpers/security-standard'; | |||
export interface Props { | |||
interface Props { | |||
cwe: string[]; | |||
cweOpen: boolean; | |||
cweStats: T.Dict<number> | undefined; | |||
fetchingCwe: boolean; | |||
fetchingOwaspTop10: boolean; | |||
fetchingSansTop25: boolean; | |||
fetchingCwe: boolean; | |||
fetchingSonarSourceSecurity: boolean; | |||
loadSearchResultCount: (property: string, changes: Partial<Query>) => Promise<Facet>; | |||
onChange: (changes: Partial<Query>) => void; | |||
onToggle: (property: string) => void; | |||
@@ -54,6 +57,8 @@ export interface Props { | |||
sansTop25Open: boolean; | |||
sansTop25Stats: T.Dict<number> | undefined; | |||
sonarsourceSecurity: string[]; | |||
sonarsourceSecurityOpen: boolean; | |||
sonarsourceSecurityStats: T.Dict<number> | undefined; | |||
} | |||
interface State { | |||
@@ -61,7 +66,7 @@ interface State { | |||
standards: T.Standards; | |||
} | |||
type StatsProp = 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats'; | |||
type StatsProp = 'owaspTop10Stats' | 'cweStats' | 'sansTop25Stats' | 'sonarsourceSecurityStats'; | |||
type ValuesProp = T.StandardType; | |||
export default class StandardFacet extends React.PureComponent<Props, State> { | |||
@@ -80,7 +85,8 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
this.props.open || | |||
this.props.owaspTop10.length > 0 || | |||
this.props.cwe.length > 0 || | |||
this.props.sansTop25.length > 0 | |||
this.props.sansTop25.length > 0 || | |||
this.props.sonarsourceSecurity.length > 0 | |||
) { | |||
this.loadStandards(); | |||
} | |||
@@ -97,20 +103,21 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
} | |||
loadStandards = () => { | |||
import('../../../helpers/standards.json') | |||
.then(x => x.default) | |||
.then( | |||
({ owaspTop10, sansTop25, cwe, sonarsourceSecurity }: T.Standards) => { | |||
if (this.mounted) { | |||
this.setState({ standards: { owaspTop10, sansTop25, cwe, sonarsourceSecurity } }); | |||
} | |||
}, | |||
() => {} | |||
); | |||
getStandards().then( | |||
({ owaspTop10, sansTop25, cwe, sonarsourceSecurity }: T.Standards) => { | |||
if (this.mounted) { | |||
this.setState({ standards: { owaspTop10, sansTop25, cwe, sonarsourceSecurity } }); | |||
} | |||
}, | |||
() => {} | |||
); | |||
}; | |||
getValues = () => { | |||
return [ | |||
...this.props.sonarsourceSecurity.map(item => | |||
renderSonarSourceSecurityCategory(this.state.standards, item, true) | |||
), | |||
...this.props.owaspTop10.map(item => | |||
renderOwaspTop10Category(this.state.standards, item, true) | |||
), | |||
@@ -133,8 +140,18 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
this.props.onToggle('sansTop25'); | |||
}; | |||
handleSonarSourceSecurityHeaderClick = () => { | |||
this.props.onToggle('sonarsourceSecurity'); | |||
}; | |||
handleClear = () => { | |||
this.props.onChange({ [this.property]: [], owaspTop10: [], sansTop25: [], cwe: [] }); | |||
this.props.onChange({ | |||
[this.property]: [], | |||
owaspTop10: [], | |||
sansTop25: [], | |||
cwe: [], | |||
sonarsourceSecurity: [] | |||
}); | |||
}; | |||
handleItemClick = (prop: ValuesProp, itemValue: string, multiple: boolean) => { | |||
@@ -159,6 +176,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
this.handleItemClick('sansTop25', itemValue, multiple); | |||
}; | |||
handleSonarSourceSecurityItemClick = (itemValue: string, multiple: boolean) => { | |||
this.handleItemClick('sonarsourceSecurity', itemValue, multiple); | |||
}; | |||
handleCWESearch = (query: string) => { | |||
return Promise.resolve({ | |||
results: Object.keys(this.state.standards.cwe).filter(cwe => | |||
@@ -170,7 +191,10 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
}; | |||
loadCWESearchResultCount = (categories: string[]) => { | |||
return this.props.loadSearchResultCount('cwe', { cwe: categories }); | |||
const { loadSearchResultCount } = this.props; | |||
return loadSearchResultCount | |||
? loadSearchResultCount('cwe', { cwe: categories }) | |||
: Promise.resolve({}); | |||
}; | |||
renderList = ( | |||
@@ -258,9 +282,39 @@ export default class StandardFacet extends React.PureComponent<Props, State> { | |||
return this.renderHint('sansTop25Stats', 'sansTop25'); | |||
} | |||
renderSonarSourceSecurityList() { | |||
return this.renderList( | |||
'sonarsourceSecurityStats', | |||
'sonarsourceSecurity', | |||
renderSonarSourceSecurityCategory, | |||
this.handleSonarSourceSecurityItemClick | |||
); | |||
} | |||
renderSonarSourceSecurityHint() { | |||
return this.renderHint('sonarsourceSecurityStats', 'sonarsourceSecurity'); | |||
} | |||
renderSubFacets() { | |||
return ( | |||
<> | |||
<FacetBox className="is-inner" property="sonarsourceSecurity"> | |||
<FacetHeader | |||
name={translate('issues.facet.sonarsourceSecurity')} | |||
onClick={this.handleSonarSourceSecurityHeaderClick} | |||
open={this.props.sonarsourceSecurityOpen} | |||
values={this.props.sonarsourceSecurity.map(item => | |||
renderSonarSourceSecurityCategory(this.state.standards, item) | |||
)} | |||
/> | |||
<DeferredSpinner loading={this.props.fetchingSonarSourceSecurity} /> | |||
{this.props.sonarsourceSecurityOpen && ( | |||
<> | |||
{this.renderSonarSourceSecurityList()} | |||
{this.renderSonarSourceSecurityHint()} | |||
</> | |||
)} | |||
</FacetBox> | |||
<FacetBox className="is-inner" property="owaspTop10"> | |||
<FacetHeader | |||
name={translate('issues.facet.owaspTop10')} |
@@ -19,12 +19,48 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import StandardFacet, { Props } from '../StandardFacet'; | |||
import StandardFacet from '../StandardFacet'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import { Query } from '../../utils'; | |||
import { getStandards } from '../../../../helpers/security-standard'; | |||
jest.mock('../../../../helpers/security-standard', () => ({ | |||
getStandards: jest.fn().mockResolvedValue({ | |||
owaspTop10: { | |||
a1: { | |||
title: 'Injection' | |||
}, | |||
a2: { | |||
title: 'Broken Authentication' | |||
} | |||
}, | |||
sansTop25: { | |||
'insecure-interaction': { | |||
title: 'Insecure Interaction Between Components' | |||
} | |||
}, | |||
cwe: { | |||
unknown: { | |||
title: 'No CWE associated' | |||
}, | |||
'1004': { | |||
title: "Sensitive Cookie Without 'HttpOnly' Flag" | |||
} | |||
}, | |||
sonarsourceSecurity: { | |||
'sql-injection': { | |||
title: 'SQL Injection' | |||
}, | |||
'command-injection': { | |||
title: 'Command Injection' | |||
} | |||
} | |||
}) | |||
})); | |||
it('should render closed', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(getStandards).not.toBeCalled(); | |||
}); | |||
it('should toggle standards facet', () => { | |||
@@ -38,7 +74,13 @@ it('should clear standards facet', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange }); | |||
wrapper.children('FacetHeader').prop<Function>('onClear')(); | |||
expect(onChange).toBeCalledWith({ cwe: [], owaspTop10: [], sansTop25: [], standards: [] }); | |||
expect(onChange).toBeCalledWith({ | |||
cwe: [], | |||
owaspTop10: [], | |||
sansTop25: [], | |||
sonarsourceSecurity: [], | |||
standards: [] | |||
}); | |||
}); | |||
it('should render sub-facets', () => { | |||
@@ -53,9 +95,13 @@ it('should render sub-facets', () => { | |||
owaspTop10Stats: { a1: 15, a3: 5 }, | |||
sansTop25: ['risky-resource'], | |||
sansTop25Open: true, | |||
sansTop25Stats: { foo: 12, 'risky-resource': 10 } | |||
sansTop25Stats: { foo: 12, 'risky-resource': 10 }, | |||
sonarsourceSecurity: ['sql-injection'], | |||
sonarsourceSecurityOpen: true, | |||
sonarsourceSecurityStats: { 'sql-injection': 12 } | |||
}) | |||
).toMatchSnapshot(); | |||
expect(getStandards).toBeCalled(); | |||
}); | |||
it('should render empty sub-facet', () => { | |||
@@ -79,12 +125,16 @@ it('should select items', () => { | |||
owaspTop10Stats: { a1: 15, a3: 5 }, | |||
sansTop25: ['risky-resource'], | |||
sansTop25Open: true, | |||
sansTop25Stats: { foo: 12, 'risky-resource': 10 } | |||
sansTop25Stats: { foo: 12, 'risky-resource': 10 }, | |||
sonarsourceSecurity: ['command-injection'], | |||
sonarsourceSecurityOpen: true, | |||
sonarsourceSecurityStats: { 'sql-injection': 10 } | |||
}); | |||
selectAndCheck('owaspTop10', 'a1'); | |||
selectAndCheck('owaspTop10', 'a1', true, ['a1', 'a3']); | |||
selectAndCheck('sansTop25', 'foo'); | |||
selectAndCheck('sonarsourceSecurity', 'sql-injection'); | |||
function selectAndCheck(facet: string, value: string, multiple = false, expectedValue = [value]) { | |||
wrapper | |||
@@ -102,6 +152,8 @@ it('should toggle sub-facets', () => { | |||
expect(onToggle).lastCalledWith('owaspTop10'); | |||
click(wrapper.find('FacetBox[property="sansTop25"]').children('FacetHeader')); | |||
expect(onToggle).lastCalledWith('sansTop25'); | |||
click(wrapper.find('FacetBox[property="sonarsourceSecurity"]').children('FacetHeader')); | |||
expect(onToggle).lastCalledWith('sonarsourceSecurity'); | |||
}); | |||
it('should display correct selection', () => { | |||
@@ -109,9 +161,11 @@ it('should display correct selection', () => { | |||
open: true, | |||
owaspTop10: ['a1', 'a3', 'unknown'], | |||
sansTop25: ['risky-resource', 'foo'], | |||
cwe: ['42', '1111', 'unknown'] | |||
cwe: ['42', '1111', 'unknown'], | |||
sonarsourceSecurity: ['sql-injection'] | |||
}); | |||
checkValues('standards', [ | |||
'SONAR SQL Injection', | |||
'OWASP A1 - a1 title', | |||
'OWASP A3', | |||
'Not OWAPS', | |||
@@ -123,6 +177,7 @@ it('should display correct selection', () => { | |||
]); | |||
checkValues('owaspTop10', ['A1 - a1 title', 'A3', 'Not OWAPS']); | |||
checkValues('sansTop25', ['Risky Resource Management', 'foo']); | |||
checkValues('sonarsourceSecurity', ['SQL Injection']); | |||
function checkValues(property: string, values: string[]) { | |||
expect( | |||
@@ -134,7 +189,7 @@ it('should display correct selection', () => { | |||
} | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
function shallowRender(props: Partial<StandardFacet['props']> = {}) { | |||
const wrapper = shallow( | |||
<StandardFacet | |||
cwe={[]} | |||
@@ -143,6 +198,7 @@ function shallowRender(props: Partial<Props> = {}) { | |||
fetchingCwe={false} | |||
fetchingOwaspTop10={false} | |||
fetchingSansTop25={false} | |||
fetchingSonarSourceSecurity={false} | |||
loadSearchResultCount={jest.fn()} | |||
onChange={jest.fn()} | |||
onToggle={jest.fn()} | |||
@@ -155,16 +211,17 @@ function shallowRender(props: Partial<Props> = {}) { | |||
sansTop25Open={false} | |||
sansTop25Stats={{}} | |||
sonarsourceSecurity={[]} | |||
sonarsourceSecurityOpen={false} | |||
sonarsourceSecurityStats={{}} | |||
{...props} | |||
/>, | |||
// disable loading of standards.json | |||
{ disableLifecycleMethods: true } | |||
/> | |||
); | |||
wrapper.setState({ | |||
standards: { | |||
owaspTop10: { a1: { title: 'a1 title' }, unknown: { title: 'Not OWAPS' } }, | |||
sansTop25: { 'risky-resource': { title: 'Risky Resource Management' } }, | |||
cwe: { 42: { title: 'cwe-42 title' }, unknown: { title: 'Unknown CWE' } } | |||
cwe: { 42: { title: 'cwe-42 title' }, unknown: { title: 'Unknown CWE' } }, | |||
sonarsourceSecurity: { 'sql-injection': { title: 'SQL Injection' } } | |||
} | |||
}); | |||
return wrapper; |
@@ -6,10 +6,10 @@ Array [ | |||
"SeverityFacet", | |||
"ResolutionFacet", | |||
"StatusFacet", | |||
"StandardFacet", | |||
"InjectIntl(CreationDateFacet)", | |||
"Connect(LanguageFacet)", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ProjectFacet", | |||
"DirectoryFacet", | |||
@@ -24,10 +24,10 @@ Array [ | |||
"SeverityFacet", | |||
"ResolutionFacet", | |||
"StatusFacet", | |||
"StandardFacet", | |||
"InjectIntl(CreationDateFacet)", | |||
"Connect(LanguageFacet)", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"FileFacet", | |||
"AssigneeFacet", | |||
@@ -41,10 +41,10 @@ Array [ | |||
"SeverityFacet", | |||
"ResolutionFacet", | |||
"StatusFacet", | |||
"StandardFacet", | |||
"InjectIntl(CreationDateFacet)", | |||
"Connect(LanguageFacet)", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ProjectFacet", | |||
"AssigneeFacet", | |||
@@ -58,10 +58,10 @@ Array [ | |||
"SeverityFacet", | |||
"ResolutionFacet", | |||
"StatusFacet", | |||
"StandardFacet", | |||
"InjectIntl(CreationDateFacet)", | |||
"Connect(LanguageFacet)", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"DirectoryFacet", | |||
"FileFacet", | |||
@@ -76,10 +76,10 @@ Array [ | |||
"SeverityFacet", | |||
"ResolutionFacet", | |||
"StatusFacet", | |||
"StandardFacet", | |||
"InjectIntl(CreationDateFacet)", | |||
"Connect(LanguageFacet)", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"DirectoryFacet", | |||
"FileFacet", | |||
@@ -94,10 +94,10 @@ Array [ | |||
"SeverityFacet", | |||
"ResolutionFacet", | |||
"StatusFacet", | |||
"StandardFacet", | |||
"InjectIntl(CreationDateFacet)", | |||
"Connect(LanguageFacet)", | |||
"RuleFacet", | |||
"StandardFacet", | |||
"TagFacet", | |||
"ProjectFacet", | |||
"AuthorFacet", |
@@ -52,12 +52,50 @@ exports[`should render sub-facets 1`] = ` | |||
open={true} | |||
values={ | |||
Array [ | |||
"SONAR SQL Injection", | |||
"OWASP A3", | |||
"SANS Risky Resource Management", | |||
"CWE-42 - cwe-42 title", | |||
] | |||
} | |||
/> | |||
<FacetBox | |||
className="is-inner" | |||
property="sonarsourceSecurity" | |||
> | |||
<FacetHeader | |||
name="issues.facet.sonarsourceSecurity" | |||
onClick={[Function]} | |||
open={true} | |||
values={ | |||
Array [ | |||
"SQL Injection", | |||
] | |||
} | |||
/> | |||
<DeferredSpinner | |||
loading={false} | |||
timeout={100} | |||
/> | |||
<FacetItemsList> | |||
<FacetItem | |||
active={true} | |||
disabled={false} | |||
halfWidth={false} | |||
key="sql-injection" | |||
loading={false} | |||
name="SQL Injection" | |||
onClick={[Function]} | |||
stat="12" | |||
tooltip="SQL Injection" | |||
value="sql-injection" | |||
/> | |||
</FacetItemsList> | |||
<MultipleSelectionHint | |||
options={1} | |||
values={1} | |||
/> | |||
</FacetBox> | |||
<FacetBox | |||
className="is-inner" | |||
property="owaspTop10" |
@@ -58,6 +58,7 @@ export interface Query { | |||
sansTop25: string[]; | |||
severities: string[]; | |||
sinceLeakPeriod: boolean; | |||
sonarsourceSecurity: string[]; | |||
sort: string; | |||
statuses: string[]; | |||
tags: string[]; | |||
@@ -93,6 +94,7 @@ export function parseQuery(query: RawQuery): Query { | |||
sansTop25: parseAsArray(query.sansTop25, parseAsString), | |||
severities: parseAsArray(query.severities, parseAsString), | |||
sinceLeakPeriod: parseAsBoolean(query.sinceLeakPeriod, false), | |||
sonarsourceSecurity: parseAsArray(query.sonarsourceSecurity, parseAsString), | |||
sort: parseAsSort(query.s), | |||
statuses: parseAsArray(query.statuses, parseAsString), | |||
tags: parseAsArray(query.tags, parseAsString), | |||
@@ -130,6 +132,7 @@ export function serializeQuery(query: Query): RawQuery { | |||
sansTop25: serializeStringArray(query.sansTop25), | |||
severities: serializeStringArray(query.severities), | |||
sinceLeakPeriod: query.sinceLeakPeriod ? 'true' : undefined, | |||
sonarsourceSecurity: serializeStringArray(query.sonarsourceSecurity), | |||
statuses: serializeStringArray(query.statuses), | |||
tags: serializeStringArray(query.tags), | |||
types: serializeStringArray(query.types) |
@@ -52,7 +52,7 @@ export function renderSansTop25Category( | |||
return addPrefix(record ? record.title : category, 'SANS', withPrefix); | |||
} | |||
export function renderSonarSourceCategory( | |||
export function renderSonarSourceSecurityCategory( | |||
standards: T.Standards, | |||
category: string, | |||
withPrefix = false |
@@ -0,0 +1,22 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
export function getStandards(): Promise<T.Standards> { | |||
return import('./standards.json').then(x => x.default); | |||
} |
@@ -743,6 +743,7 @@ issues.facet.mode.effort=Effort | |||
issues.facet.standards=Standard | |||
issues.facet.owaspTop10=OWASP Top 10 | |||
issues.facet.sansTop25=SANS Top 25 | |||
issues.facet.sonarsourceSecurity=SonarSource | |||
issues.facet.cwe=CWE | |||
#------------------------------------------------------------------------------ | |||
@@ -2092,7 +2093,7 @@ organizations_permissions.provisioning.desc=Ability to initialize a project so i | |||
security_reports.more_rules=Additional security-related rules are available but not active in your profiles. | |||
security_reports.owaspTop10.page=OWASP Top 10 | |||
security_reports.sansTop25.page=SANS Top 25 | |||
security_reports.sonarsourceSecurity.page=SonarSource Security | |||
security_reports.sonarsourceSecurity.page=SonarSource | |||
security_reports.owaspTop10.description=Track Vulnerabilities and Security Hotspots corresponding to OWASP Top 10 standard. | |||
security_reports.sansTop25.description=Track Vulnerabilities and Security Hotspots corresponding to SANS Top 25 standard (25 CWE items in three categories). | |||
security_reports.sonarsourceSecurity.description=Track Vulnerabilities and Security Hotspots corresponding to SonarSource Security categories. |