• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

juice-shop / juice-shop / 23407291727

22 Mar 2026 04:19PM UTC coverage: 79.319% (-6.8%) from 86.143%
23407291727

push

github

J12934
lint fix

Signed-off-by: Jannik Hollenbach <jannik.hollenbach@owasp.org>

1881 of 3131 branches covered (60.08%)

Branch coverage included in aggregate %.

5874 of 6646 relevant lines covered (88.38%)

305.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.51
/lib/codingChallenges.ts
1
import fs from 'node:fs/promises'
7,464✔
2
import path from 'node:path'
50✔
3
import logger from './logger'
50✔
4

5
export const SNIPPET_PATHS = Object.freeze(['./server.ts', './routes', './lib', './data', './data/static/web3-snippets', './frontend/src/app', './models'])
50✔
6

7
interface FileMatch {
8
  path: string
9
  content: string
10
}
11

12
interface CachedCodeChallenge {
13
  snippet: string
14
  vulnLines: number[]
15
  neutralLines: number[]
16
}
17

18
export const findFilesWithCodeChallenges = async (paths: readonly string[]): Promise<FileMatch[]> => {
50✔
19
  const matches = []
5,150✔
20
  for (const currPath of paths) {
5,150✔
21
    try {
42,699✔
22
      if ((await fs.lstat(currPath)).isDirectory()) {
42,699✔
23
        const files = await fs.readdir(currPath)
5,100✔
24
        const moreMatches = await findFilesWithCodeChallenges(
5,100✔
25
          files.map(file => path.resolve(currPath, file))
42,349✔
26
        )
27
        matches.push(...moreMatches)
5,100✔
28
      } else {
29
        const code = await fs.readFile(currPath, 'utf8')
37,599✔
30
        if (
37,599✔
31
          // strings are split so that it doesn't find itself...
32
          code.includes('// vuln-code' + '-snippet start') ||
74,448✔
33
          code.includes('# vuln-code' + '-snippet start')
34
        ) {
35
          matches.push({ path: currPath, content: code })
800✔
36
        }
37
      }
38
    } catch (e) {
39
      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.`)
×
40
    }
41
  }
42

43
  return matches
5,150✔
44
}
45

46
function getCodeChallengesFromFile (file: FileMatch) {
50✔
47
  const fileContent = file.content
800✔
48

49
  // get all challenges which are in the file by a regex capture group
50
  const challengeKeyRegex = /[/#]{0,2} vuln-code-snippet start (?<challenges>.*)/g
800✔
51
  const challenges = [...fileContent.matchAll(challengeKeyRegex)]
800✔
52
    .flatMap(match => match.groups?.challenges?.split(' ') ?? [])
1,150!
53
    .filter(Boolean)
54

55
  return challenges.map((challengeKey) => getCodingChallengeFromFileContent(fileContent, challengeKey))
1,800✔
56
}
57

58
function getCodingChallengeFromFileContent (source: string, challengeKey: string) {
50✔
59
  const snippets = source.match(`[/#]{0,2} vuln-code-snippet start.*${challengeKey}([^])*vuln-code-snippet end.*${challengeKey}`)
1,800✔
60
  if (snippets == null) {
1,800!
61
    throw new BrokenBoundary('Broken code snippet boundaries for: ' + challengeKey)
×
62
  }
63
  let snippet = snippets[0] // TODO Currently only a single code snippet is supported
1,800✔
64
  snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet start.*[\r\n]{0,2}/g, '')
1,800✔
65
  snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet end.*/g, '')
1,800✔
66
  snippet = snippet.replace(/.*[/#]{0,2} vuln-code-snippet hide-line[\r\n]{0,2}/g, '')
1,800✔
67
  snippet = snippet.replace(/.*[/#]{0,2} vuln-code-snippet hide-start([^])*[/#]{0,2} vuln-code-snippet hide-end[\r\n]{0,2}/g, '')
1,800✔
68
  snippet = snippet.trim()
1,800✔
69

70
  let lines = snippet.split('\r\n')
1,800✔
71
  if (lines.length === 1) lines = snippet.split('\n')
1,800!
72
  if (lines.length === 1) lines = snippet.split('\r')
1,800!
73
  const vulnLines = []
1,800✔
74
  const neutralLines = []
1,800✔
75
  for (let i = 0; i < lines.length; i++) {
1,800✔
76
    if (new RegExp(`vuln-code-snippet vuln-line.*${challengeKey}`).exec(lines[i]) != null) {
80,350✔
77
      vulnLines.push(i + 1)
2,200✔
78
    } else if (new RegExp(`vuln-code-snippet neutral-line.*${challengeKey}`).exec(lines[i]) != null) {
78,150✔
79
      neutralLines.push(i + 1)
4,050✔
80
    }
81
  }
82
  snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet vuln-line.*/g, '')
1,800✔
83
  snippet = snippet.replace(/\s?[/#]{0,2} vuln-code-snippet neutral-line.*/g, '')
1,800✔
84
  return { challengeKey, snippet, vulnLines, neutralLines }
1,800✔
85
}
86

87
class BrokenBoundary extends Error {
50✔
88
  constructor (message: string) {
89
    super(message)
×
90
    this.name = 'BrokenBoundary'
×
91
    this.message = message
×
92
  }
93
}
94

95
// dont use directly, use getCodeChallenges getter
96
let _internalCodeChallenges: Map<string, CachedCodeChallenge> | null = null
50✔
97
export async function getCodeChallenges (): Promise<Map<string, CachedCodeChallenge>> {
100!
98
  if (_internalCodeChallenges === null) {
57✔
99
    _internalCodeChallenges = new Map<string, CachedCodeChallenge>()
50✔
100
    const filesWithCodeChallenges = await findFilesWithCodeChallenges(SNIPPET_PATHS)
50✔
101
    for (const fileMatch of filesWithCodeChallenges) {
50✔
102
      for (const codeChallenge of getCodeChallengesFromFile(fileMatch)) {
800✔
103
        _internalCodeChallenges.set(codeChallenge.challengeKey, {
1,800✔
104
          snippet: codeChallenge.snippet,
105
          vulnLines: codeChallenge.vulnLines,
106
          neutralLines: codeChallenge.neutralLines
107
        })
108
      }
109
    }
110
  }
111
  return _internalCodeChallenges
57✔
112
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc