mirror of
https://github.com/KeygraphHQ/juice-shop.git
synced 2026-11-08 02:48:20 -10:00
113 lines
4.3 KiB
TypeScript
113 lines
4.3 KiB
TypeScript
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import logger from './logger'
|
|
|
|
export const SNIPPET_PATHS = Object.freeze(['./server.ts', './routes', './lib', './data', './data/static/web3-snippets', './frontend/src/app', './models'])
|
|
|
|
interface FileMatch {
|
|
path: string
|
|
content: string
|
|
}
|
|
|
|
interface CachedCodeChallenge {
|
|
snippet: string
|
|
vulnLines: number[]
|
|
neutralLines: number[]
|
|
}
|
|
|
|
export const findFilesWithCodeChallenges = async (paths: readonly string[]): Promise<FileMatch[]> => {
|
|
const matches = []
|
|
for (const currPath of paths) {
|
|
if ((await fs.lstat(currPath)).isDirectory()) {
|
|
const files = await fs.readdir(currPath)
|
|
const moreMatches = await findFilesWithCodeChallenges(
|
|
files.map(file => path.resolve(currPath, file))
|
|
)
|
|
matches.push(...moreMatches)
|
|
} else {
|
|
try {
|
|
const code = await fs.readFile(currPath, 'utf8')
|
|
if (
|
|
// strings are split so that it doesn't find itself...
|
|
code.includes('// vuln-code' + '-snippet start') ||
|
|
code.includes('# vuln-code' + '-snippet start')
|
|
) {
|
|
matches.push({ path: currPath, content: code })
|
|
}
|
|
} catch (e) {
|
|
logger.warn(`File ${currPath} could not be read. it might have been moved or deleted. If coding challenges are contained in the file, they will not be available.`)
|
|
}
|
|
}
|
|
}
|
|
|
|
return matches
|
|
}
|
|
|
|
function getCodeChallengesFromFile (file: FileMatch) {
|
|
const fileContent = file.content
|
|
|
|
// get all challenges which are in the file by a regex capture group
|
|
const challengeKeyRegex = /[/#]{0,2} vuln-code-snippet start (?<challenges>.*)/g
|
|
const challenges = [...fileContent.matchAll(challengeKeyRegex)]
|
|
.flatMap(match => match.groups?.challenges?.split(' ') ?? [])
|
|
.filter(Boolean)
|
|
|
|
return challenges.map((challengeKey) => getCodingChallengeFromFileContent(fileContent, challengeKey))
|
|
}
|
|
|
|
function getCodingChallengeFromFileContent (source: string, challengeKey: string) {
|
|
const snippets = source.match(`[/#]{0,2} vuln-code-snippet start.*${challengeKey}([^])*vuln-code-snippet end.*${challengeKey}`)
|
|
if (snippets == null) {
|
|
throw new BrokenBoundary('Broken code snippet boundaries for: ' + challengeKey)
|
|
}
|
|
let snippet = snippets[0] // TODO Currently only a single code snippet is supported
|
|
snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet start.*[\r\n]{0,2}/g, '')
|
|
snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet end.*/g, '')
|
|
snippet = snippet.replace(/.*[/#]{0,2} vuln-code-snippet hide-line[\r\n]{0,2}/g, '')
|
|
snippet = snippet.replace(/.*[/#]{0,2} vuln-code-snippet hide-start([^])*[/#]{0,2} vuln-code-snippet hide-end[\r\n]{0,2}/g, '')
|
|
snippet = snippet.trim()
|
|
|
|
let lines = snippet.split('\r\n')
|
|
if (lines.length === 1) lines = snippet.split('\n')
|
|
if (lines.length === 1) lines = snippet.split('\r')
|
|
const vulnLines = []
|
|
const neutralLines = []
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if (new RegExp(`vuln-code-snippet vuln-line.*${challengeKey}`).exec(lines[i]) != null) {
|
|
vulnLines.push(i + 1)
|
|
} else if (new RegExp(`vuln-code-snippet neutral-line.*${challengeKey}`).exec(lines[i]) != null) {
|
|
neutralLines.push(i + 1)
|
|
}
|
|
}
|
|
snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet vuln-line.*/g, '')
|
|
snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet neutral-line.*/g, '')
|
|
return { challengeKey, snippet, vulnLines, neutralLines }
|
|
}
|
|
|
|
class BrokenBoundary extends Error {
|
|
constructor (message: string) {
|
|
super(message)
|
|
this.name = 'BrokenBoundary'
|
|
this.message = message
|
|
}
|
|
}
|
|
|
|
// dont use directly, use getCodeChallenges getter
|
|
let _internalCodeChallenges: Map<string, CachedCodeChallenge> | null = null
|
|
export async function getCodeChallenges (): Promise<Map<string, CachedCodeChallenge>> {
|
|
if (_internalCodeChallenges === null) {
|
|
_internalCodeChallenges = new Map<string, CachedCodeChallenge>()
|
|
const filesWithCodeChallenges = await findFilesWithCodeChallenges(SNIPPET_PATHS)
|
|
for (const fileMatch of filesWithCodeChallenges) {
|
|
for (const codeChallenge of getCodeChallengesFromFile(fileMatch)) {
|
|
_internalCodeChallenges.set(codeChallenge.challengeKey, {
|
|
snippet: codeChallenge.snippet,
|
|
vulnLines: codeChallenge.vulnLines,
|
|
neutralLines: codeChallenge.neutralLines
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return _internalCodeChallenges
|
|
}
|