be01c866 node sast

Static Application Security for Nodejs (with Gitlab CI)


SAST. Its a thing. Take the test to see if you need it. 🙂 OK, not that SAST, the one that relates to security silly. Let’s talk about integrating static application security for nodejs from our Gitlab CI as part of a Defense In Depth strategy.

So I’ve been using clair from coreos. Its pretty awesome, but, to my chagrin, it does not cover python / node / go / ruby / …, the majority of the upstream culprits. (It focuses on apk/rpm/deb). So you can get a false sense of security by running it. It shocked me when I did my wiki, but then I fixed those issues and moved on and forgot. So when it came time to do my first node.js ‘express’ app, I ran it, got no hits, and was pleased with myself. Static application security for nodejs achieved?

Not so fast. Turns out you need to look a bit harder, tools like snykretireaudit. So I picked two (retire, audit) and added them to my CI pipeline, as below:

stage: scan
  artifacts:
    name: "$CI_PROJECT_PATH-$CI_COMMIT_REF_NAME"
    paths:
      - reports/
  script: |
    echo Analyse container $CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHA for vulnerability
    docker tag $CI_REGISTRY/$CI_PROJECT_PATH:$CI_COMMIT_SHA $CI_PROJECT_PATH:$CI_COMMIT_SHA
    clairctl analyze -l -n --log-level debug $CI_PROJECT_PATH:$CI_COMMIT_SHA
    echo Generate JSON report
    clairctl report -l -f json $CI_PROJECT_PATH:$CI_COMMIT_SHA
    echo Generate HTML report
    clairctl report -l -f html $CI_PROJECT_PATH:$CI_COMMIT_SHA
    docker tag $CI_PROJECT_PATH:$CI_COMMIT_SHA scannee
    docker build -t scanner -f Dockerfile.scan .
    docker run --rm scanner retire --path /usr/src/app > reports/retire.js.txt 2>&1 || true
    docker run --rm scanner retire --path /usr/local/lib/node_modules/npm >> reports/retire.js.txt 2>&1 || true
    docker run -w /usr/src/app --rm scanner auditjs -r >> reports/audit.js.txt || true

Now, unlike clair, these actually need to run in the image, or maybe with it mounted somehow. So I created this very simple Dockerfile. It inherits from ‘scannee’, which I tagged the main image as above.

Now, something else I found ‘exciting’. You see that ‘USER root’? Well the origin image (scannee) has a ‘USER nonprivileged’. So here I am increasing my privilege, I did not think you could do that. Hmm.

FROM scannee
ENV NODE_ENV "production"

WORKDIR /usr/src/app

USER root

# See https://github.com/npm/uid-number/issues/3 for why the 'set unsafe-perm'
RUN npm config set unsafe-perm true  \
 && npm install -g retire \
 && npm install -g auditjs

Now my CI stage above scans (with clair), then builds a child container augmenting with retire and audit, then runs them. The original layer is unchanged, untouched. I can do my static application security for nodejs as-is.

I thought this was neat, you probably thought it was old hat.

But, it found me another hundred or so issues to dig into. And this is from node:9.11.0-alpine, its not like that is old!