dd-trace
Version:
Datadog APM tracing client for JavaScript
170 lines (138 loc) • 4.94 kB
JavaScript
const { LRUCache } = require('../../../../../vendor/dist/lru-cache')
const { keepTrace } = require('../../priority_sampler')
const { reportStackTrace, getCallsiteFrames, canReportStackTrace, STACK_TRACE_NAMESPACES } = require('../stack_trace')
const { ASM } = require('../../standalone/product')
const vulnerabilitiesFormatter = require('./vulnerabilities-formatter')
const { IAST_ENABLED_TAG_KEY, IAST_JSON_TAG_KEY } = require('./tags')
const { getOriginalPathAndLineFromSourceMap } = require('./taint-tracking/rewriter')
const VULNERABILITIES_KEY = 'vulnerabilities'
const VULNERABILITY_HASHES_MAX_SIZE = 1000
const VULNERABILITY_HASHES = new LRUCache({ max: VULNERABILITY_HASHES_MAX_SIZE })
const RESET_VULNERABILITY_CACHE_INTERVAL = 60 * 60 * 1000 // 1 hour
let tracer
let resetVulnerabilityCacheTimer
let deduplicationEnabled = true
let stackTraceEnabled = true
let stackTraceMaxDepth
let maxStackTraces
function canAddVulnerability (vulnerability) {
const hasRequiredFields = vulnerability?.evidence && vulnerability?.type && vulnerability?.location
if (!hasRequiredFields) return false
const isDuplicated = deduplicationEnabled && isDuplicatedVulnerability(vulnerability)
return !isDuplicated
}
function addVulnerability (iastContext, vulnerability, callSiteFrames) {
if (!canAddVulnerability(vulnerability)) return
VULNERABILITY_HASHES.set(`${vulnerability.type}${vulnerability.hash}`, true)
let span = iastContext?.rootSpan
if (!span && tracer) {
span = tracer.startSpan('vulnerability', {
type: 'vulnerability',
})
vulnerability.location.spanId = span.context().toSpanId()
span.addTags({
[IAST_ENABLED_TAG_KEY]: 1,
})
}
if (!span) return
keepTrace(span, ASM)
if (stackTraceEnabled && canReportStackTrace(span, maxStackTraces, STACK_TRACE_NAMESPACES.IAST)) {
const originalCallSiteList = callSiteFrames.map(callsite => replaceCallSiteFromSourceMap(callsite))
reportStackTrace(
span,
vulnerability.location.stackId,
originalCallSiteList,
STACK_TRACE_NAMESPACES.IAST
)
}
if (iastContext?.rootSpan) {
iastContext[VULNERABILITIES_KEY] = iastContext[VULNERABILITIES_KEY] || []
iastContext[VULNERABILITIES_KEY].push(vulnerability)
} else {
sendVulnerabilities([vulnerability], span)
span.finish()
}
}
function isValidVulnerability (vulnerability) {
return vulnerability && vulnerability.type &&
vulnerability.evidence &&
vulnerability.location && vulnerability.location.spanId
}
function sendVulnerabilities (vulnerabilities, span) {
if (vulnerabilities?.length && span?.addTags) {
const validatedVulnerabilities = vulnerabilities.filter(isValidVulnerability)
const jsonToSend = vulnerabilitiesFormatter.toJson(validatedVulnerabilities)
if (jsonToSend.vulnerabilities.length > 0) {
const tags = {
// TODO: Store this outside of the span and set the tag in the exporter.
[IAST_JSON_TAG_KEY]: JSON.stringify(jsonToSend),
}
span.addTags(tags)
}
}
return IAST_JSON_TAG_KEY
}
function clearCache () { // only for test purposes
VULNERABILITY_HASHES.clear()
}
function startClearCacheTimer () {
resetVulnerabilityCacheTimer = setInterval(clearCache, RESET_VULNERABILITY_CACHE_INTERVAL)
resetVulnerabilityCacheTimer.unref?.()
}
function stopClearCacheTimer () {
if (resetVulnerabilityCacheTimer) {
clearInterval(resetVulnerabilityCacheTimer)
resetVulnerabilityCacheTimer = null
}
}
function isDuplicatedVulnerability (vulnerability) {
return VULNERABILITY_HASHES.get(`${vulnerability.type}${vulnerability.hash}`)
}
function getVulnerabilityCallSiteFrames () {
return getCallsiteFrames(stackTraceMaxDepth, getVulnerabilityCallSiteFrames)
}
function replaceCallSiteFromSourceMap (callsite) {
if (callsite) {
const { path, line, column } = getOriginalPathAndLineFromSourceMap(callsite)
if (path) {
callsite.file = path
callsite.path = path
}
if (line) {
callsite.line = line
}
// We send the column in the stack trace but not in the vulnerability location
if (column) {
callsite.column = column
}
}
return callsite
}
function start (config, _tracer) {
deduplicationEnabled = config.iast.deduplicationEnabled
stackTraceEnabled = config.iast.stackTrace.enabled
stackTraceMaxDepth = config.appsec.stackTrace.maxDepth
maxStackTraces = config.appsec.stackTrace.maxStackTraces
vulnerabilitiesFormatter.setRedactVulnerabilities(
config.iast.redactionEnabled,
config.iast.redactionNamePattern,
config.iast.redactionValuePattern
)
if (deduplicationEnabled) {
startClearCacheTimer()
}
tracer = _tracer
}
function stop () {
stopClearCacheTimer()
}
module.exports = {
addVulnerability,
sendVulnerabilities,
getVulnerabilityCallSiteFrames,
replaceCallSiteFromSourceMap,
clearCache,
start,
stop,
}