[Techworld with Nana] DevSecOps Bootcamp [2024, ENG]: Application Vulnerability Scanning


Делаю:
2026.01.14


01 - Build a Continuous Integration Pipeline (3 - Application Vulnerability Scanning)


package.json

в devDependencies


Добавить

"@types/minimatch": "^5.1.2",


karma.conf.js

    // 1. Указываем использовать новый лаунчер по умолчанию
    browsers: ['ChromeHeadlessNoSandbox'],
    // 2. Определяем настройки для работы в Docker (без песочницы)
    customLaunchers: {
      ChromeHeadlessNoSandbox: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox', '--disable-setuid-sandbox']
      }
    },


Settings -> CI/CD -> Variables

DOCKER_USER
DOCKER_PASS


.gitlab-ci.yml


variables:
  IMAGE_NAME: webmakaka/demo-app
  IMAGE_TAG: juice-shop-1.1

stages:
  - test
  - build

yarn_test:
  stage: test
  image: node:22-bullseye
  before_script:
    - apt-get update && apt-get install -y wget curl
    - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
    - apt-get install -y ./google-chrome-stable_current_amd64.deb
    - export CHROME_BIN=/usr/bin/google-chrome
  script:
    - yarn install
    - yarn test

build_image:
  stage: build
  image: docker:24.0.5
  services:
    - name: docker:24.0.5-dind
      # Принудительно запускаем без TLS на порту 2375
      command: ['--tls=false']
  variables:
    # В K8s контейнеры внутри одного пода всегда видят друг друга на localhost
    DOCKER_HOST: tcp://localhost:2375
    DOCKER_TLS_CERTDIR: ''

  before_script:
    # КРИТИЧНО: Ждем, пока Docker поднимется, прежде чем слать команды
    - |
      for i in $(seq 1 30); do
        if nc -z localhost 2375; then
          echo "Docker is up and running!"
          break
        fi
        echo "Waiting for Docker daemon..."
        sleep 1
      done
    - echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
  script:
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker push $IMAGE_NAME:$IMAGE_TAG


Вариант с кешированием


variables:
  IMAGE_NAME: webmakaka/demo-app
  IMAGE_TAG: juice-shop-1.1

stages:
  - cache
  - test
  - build

create_cache:
  image: node:22-bullseye
  stage: cache
  script:
    - yarn install
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules/
      - yarn.lock
      - .yarn
    policy: pull-push

yarn_test:
  stage: test
  image: node:22-bullseye
  before_script:
    - apt-get update && apt-get install -y wget curl
    - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
    - apt-get install -y ./google-chrome-stable_current_amd64.deb
    - export CHROME_BIN=/usr/bin/google-chrome
  script:
    - yarn install
    - yarn test
  cache:
    key:
      files:
        - yarn.lock
    paths:
      - node_modules/
      - yarn.lock
      - .yarn
    policy: pull

build_image:
  stage: build
  image: docker:24.0.5
  services:
    - name: docker:24.0.5-dind
      # Принудительно запускаем без TLS на порту 2375
      command: ['--tls=false']
  variables:
    # В K8s контейнеры внутри одного пода всегда видят друг друга на localhost
    DOCKER_HOST: tcp://localhost:2375
    DOCKER_TLS_CERTDIR: ''

  before_script:
    # КРИТИЧНО: Ждем, пока Docker поднимется, прежде чем слать команды
    - |
      for i in $(seq 1 30); do
        if nc -z localhost 2375; then
          echo "Docker is up and running!"
          break
        fi
        echo "Waiting for Docker daemon..."
        sleep 1
      done
    - echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
  script:
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker push $IMAGE_NAME:$IMAGE_TAG


04 - Pre-commit Hook for Secret Scanning & Integrating GitLeaks in CI Pipeline


.gitlab-ci.yml


variables:
  IMAGE_NAME: webmakaka/demo-app
  IMAGE_TAG: juice-shop-1.1

stages:
  - test
  - build

yarn_test:
  stage: test
  image: node:22-bullseye
  before_script:
    - apt-get update && apt-get install -y wget curl
    - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
    - apt-get install -y ./google-chrome-stable_current_amd64.deb
    - export CHROME_BIN=/usr/bin/google-chrome
  script:
    - yarn install
    - yarn test

gitleaks:
  stage: test
  image:
    name: zricethezav/gitleaks
    entrypoint: ['']
  script:
    - gitleaks detect --verbose --source .
  allow_failure: true

build_image:
  stage: build
  image: docker:24.0.5
  services:
    - name: docker:24.0.5-dind
      # Принудительно запускаем без TLS на порту 2375
      command: ['--tls=false']
  variables:
    # В K8s контейнеры внутри одного пода всегда видят друг друга на localhost
    DOCKER_HOST: tcp://localhost:2375
    DOCKER_TLS_CERTDIR: ''

  before_script:
    # КРИТИЧНО: Ждем, пока Docker поднимется, прежде чем слать команды
    - |
      for i in $(seq 1 30); do
        if nc -z localhost 2375; then
          echo "Docker is up and running!"
          break
        fi
        echo "Waiting for Docker daemon..."
        sleep 1
      done
    - echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
  script:
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker push $IMAGE_NAME:$IMAGE_TAG


Запуск локально, чтобы пофиксить ошибки

$ cd ~/tmp/
$ git clone https://github.com/juice-shop/juice-shop
$ cd juice-shop


$ vi .git/hooks/pre-commit
docker pull zricethezav/gitleaks:latest
export path_to_host_folder_to_scan=/home/marley/tmp/juice-shop
docker run -v ${path_to_host_folder_to_scan}:/path zricethezav/gitleaks:latest detect --source="/path" --verbose


$ chmod +x .git/hooks/pre-commit


В файле .gitleaks.toml можно добавить правила.

[extend]
useDefault = true

[allowlist]
paths = ['test', '.*\/test\/.*']


06 - Integrate SAST Scans in Release Pipeline


.gitlab-ci.yml


variables:
  IMAGE_NAME: webmakaka/demo-app
  IMAGE_TAG: juice-shop-1.1

stages:
  - test
  - build

yarn_test:
  stage: test
  image: node:22-bullseye
  before_script:
    - apt-get update && apt-get install -y wget curl
    - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
    - apt-get install -y ./google-chrome-stable_current_amd64.deb
    - export CHROME_BIN=/usr/bin/google-chrome
  script:
    - yarn install
    - yarn test

gitleaks:
  stage: test
  image:
    name: zricethezav/gitleaks
    entrypoint: ['']
  script:
    - gitleaks detect --verbose --source .
  allow_failure: true

njsscan:
  stage: test
  image: python:3.12
  before_script:
    - pip3 install --upgrade njsscan
  script:
    - njsscan --exit-warning .
  allow_failure: true

semgrep:
  stage: test
  image: semgrep/semgrep
  variables:
    SEMGREP_RULES: p/javascript
  script:
    - semgrep ci
  allow_failure: true

build_image:
  stage: build
  image: docker:24.0.5
  services:
    - name: docker:24.0.5-dind
      # Принудительно запускаем без TLS на порту 2375
      command: ['--tls=false']
  variables:
    # В K8s контейнеры внутри одного пода всегда видят друг друга на localhost
    DOCKER_HOST: tcp://localhost:2375
    DOCKER_TLS_CERTDIR: ''

  before_script:
    # КРИТИЧНО: Ждем, пока Docker поднимется, прежде чем слать команды
    - |
      for i in $(seq 1 30); do
        if nc -z localhost 2375; then
          echo "Docker is up and running!"
          break
        fi
        echo "Waiting for Docker daemon..."
        sleep 1
      done
    - echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
  script:
    - docker build -t $IMAGE_NAME:$IMAGE_TAG .
    - docker push $IMAGE_NAME:$IMAGE_TAG


Для semgrep, чтобы исключить файлы, можно использовать .semgrepignore