Let’s say you are running a linter like HTMLhint. It has 27 dependencies, any of those could be malicious. So, when you do npm install -g htmlhint, you are taking a huge risk. And this is not a theoretical risk.

Even big companies like Amazon are falling for it.

A linter, for example, needs just read-only access to the all the files that you want to lint.

  • It does not need access to files outside the current directory
  • It does not need Internet access
  • It does not need to modify any files either, read-only access is sufficient

So, run it inside Docker to mitigate the risk.

Using Docker, you can enforce the following restrictions:

  • βœ… No ability to send data over the Internet
  • βœ… No access to any files outside the current directory
  • βœ… Read-only access to files inside the current directory
Dockerfile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# network=none => no network access
# -v ${PWD}:${PWD} => mount current directory to the same path inside the container
# ro => read-only filesystem access
# Build:
# docker build -t htmlhint .
# Run:
# docker run --rm --network=none -v ${PWD}:${PWD}:ro htmlhint ${PWD}
FROM node:24-alpine3.21
RUN npm install -g htmlhint
ENTRYPOINT ["htmlhint"]

This drastically reduces the attack surface of the code.

You can do this with pretty much any tool.

Consider golangci-lint, the famous meta-linter for Go language.

You can run it inside docker with the following command.

Bash
1
2
3
$ docker run --rm --network=none -v ${PWD}:${PWD}:ro --workdir=${PWD}
  golangci/golangci-lint:latest-alpine golangci-lint run
...

Or you can do a read/write mount for a formatting tool to let it format/modify the files.

Bash
1
2
3
$ docker run --rm --network=none -v ${PWD}:${PWD} --workdir=${PWD}
  golangci/golangci-lint:latest-alpine golangci-lint run --fix
...

I even recommend this technique for running tools on GitHub Actions and have started using this extensively in GitHub Actions Boilerplate Generator.