Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 102 additions & 38 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,84 @@
# syntax=docker/dockerfile:1
FROM ubuntu:25.10 AS base

# Version of actionlint to install: latest, or specific version number WITHOUT 'v' prefix e.g. 1.7.5
ARG ACTIONLINT_VERSION=latest
# Version of hadolint to install: latest, or specific version number e.g. v2.14.0
ARG HADOLINT_VERSION=latest
# Version of shellcheck to install: latest, or specific version number e.g. v0.11.0
ARG SHELLCHECK_VERSION=latest
# Version of shfmt to install: latest, or specific version number e.g. v3.12.0
ARG SHFMT_VERSION=latest
# Version of uv to install: latest, or specific version number e.g. v0.9.17
ARG UV_VERSION=latest
# Version of reviewdog to install: latest, or specific version number e.g. v0.21.0
ARG REVIEWDOG_VERSION=latest
# Version of Snyk to install: stable, latest, or specific version number e.g. v1.1301.1
ARG SNYK_VERSION=stable

# Images which we can directly copy the binaries from
FROM rhysd/actionlint:${ACTIONLINT_VERSION} AS actionlint
FROM hadolint/hadolint:${HADOLINT_VERSION} AS hadolint
FROM koalaman/shellcheck:${SHELLCHECK_VERSION} AS shellcheck
FROM mvdan/shfmt:${SHFMT_VERSION} AS shfmt



# Using debian as base since it's generally stable, compatible and well supported
FROM debian:13 AS base
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The base image uses debian:13, but Debian 13 (Trixie) is currently in testing and not yet released as stable. For production use, consider using the current stable release debian:12 (Bookworm) instead, or explicitly use debian:testing if the testing version is intentional. Using an unreleased version number may cause issues when Debian 13 is officially released with potentially different package versions.

Suggested change
FROM debian:13 AS base
FROM debian:12 AS base

Copilot uses AI. Check for mistakes.

# Docker built-in arg for multi-platform builds
ARG TARGETARCH

# Redeclare args for use in this scope
ARG UV_VERSION
ARG REVIEWDOG_VERSION
ARG SNYK_VERSION

# Environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV JAVA_HOME=/usr/lib/jvm/java-openjdk
ENV LANG=en_US.UTF-8
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LANG environment variable is set to en_US.UTF-8, but the required locales package or locale generation is not visible in the Dockerfile. Debian's minimal images may not have this locale pre-configured. Consider either installing and generating the locale explicitly, or using C.UTF-8 which is typically available by default in Debian containers.

Suggested change
ENV LANG=en_US.UTF-8
ENV LANG=C.UTF-8

Copilot uses AI. Check for mistakes.

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

# Pin 'stable' over 'testing' to prefer 'stable' packages
# 'testing' packages will only be installed when explicitly requested with '-t testing'
COPY <<-EOT /etc/apt/preferences.d/99pin-testing
Package: *
Pin: release a=stable
Pin-Priority: 900

Package: *
Pin: release a=testing
Pin-Priority: 100
EOT

# Temporarily enable 'testing' repo for outdated/unavailable packages in 'stable' repo,
# especially those that are difficult to build/install elsewhere
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list.d/testing.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends --no-install-suggests -t testing \
# Required for pyre vscode extension
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment states watchman is "Required for pyre vscode extension" but pyre is not mentioned elsewhere in the Dockerfile. Consider expanding this comment to clarify if pyre is expected to be installed separately by users, or if there are other dependencies on watchman that should be documented.

Suggested change
# Required for pyre vscode extension
# watchman is required by the Pyre VSCode extension for file watching functionality.
# Note: Pyre itself is not installed by this Dockerfile; users who need Pyre should install it separately.
# Other tools or extensions that rely on watchman may also benefit from its presence.

Copilot uses AI. Check for mistakes.
watchman \
# Disable 'testing' repo afterwards to prevents potential issues
# where only stable packages are expected (e.g. playwright install-deps)
&& sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/testing.list
Comment on lines +59 to +68
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testing repository is temporarily enabled and then disabled using sed. However, the comment on line 58 says packages from testing are "especially those that are difficult to build/install elsewhere" but only watchman is explicitly installed from testing (line 65). Consider documenting which specific packages require the testing repository, as this complex setup may confuse future maintainers who might not understand why this pattern is necessary for a single package.

Copilot uses AI. Check for mistakes.

# https://docs.docker.com/build/cache/optimize/#use-cache-mounts
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y --no-install-recommends --no-install-suggests \
# Required for pyre vscode extension
watchman \
# Required for sonarqube vscode extension
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OpenJDK version was upgraded from 17 to 21. While this is a positive change to use a newer LTS version, consider adding a comment explaining this upgrade, especially if it's driven by specific requirements or compatibility needs, to help future maintainers understand the decision.

Suggested change
# Required for sonarqube vscode extension
# Required for sonarqube vscode extension
# Use OpenJDK 21 (latest LTS) instead of 17 to ensure compatibility with modern Java tools and libraries.

Copilot uses AI. Check for mistakes.
openjdk-17-jre-headless \
openjdk-21-jre-headless \
nodejs \
# Required for shellcheck vscode extension
shellcheck \
# Required for general purpose compilation
gcc \
# General purpose tools
### General purpose tools
curl \
git \
openssh-client \
Expand All @@ -48,40 +104,48 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
# Code counter
tokei \
# Benchmarking tool
hyperfine \
# Linking preferred alternatives
&& ln -s /usr/bin/eza /usr/local/bin/ls \
hyperfine

# Linking preferred alternatives
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The symlinks for eza, batcat, and fdfind are created assuming these binaries exist at the specified paths. While these should exist after the apt install, consider adding a comment indicating these are Debian-specific binary names that need aliasing, as this may not be obvious to maintainers unfamiliar with Debian package naming conventions.

Suggested change
# Linking preferred alternatives
# Linking preferred alternatives
# Debian/Ubuntu package some tools under non-standard binary names:
# - 'eza' (better 'ls') is installed as 'eza'
# - 'bat' (better 'cat') is installed as 'batcat'
# - 'fd' (better 'find') is installed as 'fdfind'
# The following symlinks provide the expected command names for these tools.

Copilot uses AI. Check for mistakes.
RUN ln -s /usr/bin/eza /usr/local/bin/ls \
&& ln -s /usr/bin/batcat /usr/local/bin/bat \
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
# Install uv:
&& curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/usr/local/bin" sh \
# Install Pulumi:
&& curl -fsSL https://get.pulumi.com | sh \
&& mv /root/.pulumi/bin/pulumi /usr/local/bin \
# Install reviewdog:
&& curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh \
# Make sure java runtime is found for sonarqube
&& ln -s "$(dirname "$(dirname "$(readlink -f "$(which java)")")")" "${JAVA_HOME}"
Comment on lines +110 to +114

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's a good practice to use the -f (--force) flag with ln -s in a Dockerfile to make the command idempotent. If the symlinks already exist for any reason (e.g., from the base image or a previous layer), the build won't fail. This ensures the desired links are in place by overwriting any existing files or links at the destination.

RUN ln -sf /usr/bin/eza /usr/local/bin/ls \
    && ln -sf /usr/bin/batcat /usr/local/bin/bat \
    && ln -sf /usr/bin/fdfind /usr/local/bin/fd \
    # Make sure java runtime is found for sonarqube
    && ln -sf "$(dirname "$(dirname "$(readlink -f "$(which java)")")")" "${JAVA_HOME}"


# Install uv
RUN UV_VER="${UV_VERSION#v}" \
&& UV_INSTALL_URL=$([ "${UV_VER}" = "latest" ] \
&& echo "https://astral.sh/uv/install.sh" || \
echo "https://astral.sh/uv/${UV_VER}/install.sh") \
&& curl -LsSf "${UV_INSTALL_URL}" | env UV_INSTALL_DIR="/usr/local/bin" sh
Comment on lines +117 to +121

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current method of installing uv by piping curl to sh from a dynamic URL (https://astral.sh/uv/install.sh) poses a security risk from supply chain attacks. This is inconsistent with the more secure methods used for reviewdog (pinning to a commit hash) and snyk (checksum verification). To mitigate this, I recommend adopting a more secure installation pattern. A better approach would be to download the uv binary directly for the target architecture and verify its checksum against a trusted source, similar to how snyk is installed.

Comment on lines +117 to +121
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UV_VER variable strips the 'v' prefix but this stripping is unnecessary since the ARG comment on line 11 states the version should be specified "WITHOUT 'v' prefix". Consider removing the "${UV_VERSION#v}" operation and using UV_VERSION directly to match the documented expectation.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Installing uv by piping curl output from astral.sh directly into sh executes remote code as root during the image build without any integrity verification. If the download endpoint, its DNS, or TLS is compromised, an attacker could run arbitrary commands in the build environment and backdoor the resulting uv binary used in CI. Instead, download the installer or binary to disk and verify it against a pinned checksum or signature (or use a package manager/pinned image) before executing it.

Copilot uses AI. Check for mistakes.

# Install reviewdog
RUN curl -sfL "https://raw.githubusercontent.com/reviewdog/reviewdog/fd59714416d6d9a1c0692d872e38e7f8448df4fc/install.sh" \
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reviewdog installation uses a hardcoded commit hash (fd59714416d6d9a1c0692d872e38e7f8448df4fc) in the URL, which bypasses the REVIEWDOG_VERSION argument declared on line 14. This means users cannot control the reviewdog version through build arguments. Consider using a version-based approach similar to other tools, or if a specific commit is required for stability, add a comment explaining why the version argument is not being used.

Suggested change
RUN curl -sfL "https://raw.githubusercontent.com/reviewdog/reviewdog/fd59714416d6d9a1c0692d872e38e7f8448df4fc/install.sh" \
RUN REVIEWDOG_INSTALL_REF=$([ "${REVIEWDOG_VERSION}" = "latest" ] && echo "master" || echo "${REVIEWDOG_VERSION}") \
&& curl -sfL "https://raw.githubusercontent.com/reviewdog/reviewdog/${REVIEWDOG_INSTALL_REF}/install.sh" \

Copilot uses AI. Check for mistakes.
| sh -s -- -b /usr/local/bin \
Comment on lines +124 to 125
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewdog is installed by piping a remote install.sh script from GitHub directly into sh, which again runs unverified third-party code as root during the image build. A compromise of the GitHub repository, raw content delivery, or its dependencies could inject malicious commands or ship a trojanized reviewdog binary that later runs in CI with access to repository data and tokens. Prefer downloading a specific, pinned release asset and verifying its checksum (or vendoring the installer script in this repo) instead of executing it directly from the network.

Copilot uses AI. Check for mistakes.
# Make sure java runtime is found for sonarqube:
&& ln -s "$(dirname "$(dirname "$(readlink -f "$(which java)")")")" "$JAVA_HOME" \
# Install other tools:
&& export ACTIONLINT_VERSION=$(curl -s https://api.github.com/repos/rhysd/actionlint/releases/latest | jq -r '.tag_name' | sed "s/v//") \
&& export HADOLINT_VERSION=$(curl -s https://api.github.com/repos/hadolint/hadolint/releases/latest | jq -r '.tag_name') \
&& export SHFMT_VERSION=$(curl -s https://api.github.com/repos/mvdan/sh/releases/latest | jq -r '.tag_name') \
&& if [ "$(uname -m)" = "aarch64" ]; then \
curl -o /usr/local/bin/snyk -L https://static.snyk.io/cli/latest/snyk-linux-arm64 \
&& curl -o /usr/local/bin/hadolint -L https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-linux-arm64 \
&& curl -o /usr/local/bin/shfmt -L https://github.com/mvdan/sh/releases/download/${SHFMT_VERSION}/shfmt_${SHFMT_VERSION}_linux_arm64 \
&& curl -sL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_arm64.tar.gz" | tar -xzf - -C /usr/local/bin actionlint ; \
else \
curl -o /usr/local/bin/snyk -L https://static.snyk.io/cli/latest/snyk-linux \
&& curl -o /usr/local/bin/hadolint -L https://github.com/hadolint/hadolint/releases/download/${HADOLINT_VERSION}/hadolint-linux-x86_64 \
&& curl -o /usr/local/bin/shfmt -L https://github.com/mvdan/sh/releases/download/${SHFMT_VERSION}/shfmt_${SHFMT_VERSION}_linux_amd64 \
&& curl -sL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" | tar -xzf - -C /usr/local/bin actionlint ; \
fi \
&& chmod +x /usr/local/bin/snyk \
&& chmod +x /usr/local/bin/hadolint \
&& chmod +x /usr/local/bin/shfmt \
&& chmod +x /usr/local/bin/actionlint
"$([ "${REVIEWDOG_VERSION}" != "latest" ] && echo "${REVIEWDOG_VERSION}" || echo "")"
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command substitution for REVIEWDOG_VERSION uses echo "" when the version is "latest", which results in passing an empty string as an argument to the install script. While this might work, it's cleaner to conditionally omit the entire argument. Additionally, the condition logic is inverted - it provides the version argument when NOT "latest", but the echo statement on success is empty. Consider restructuring to be more explicit about the intended behavior.

Copilot uses AI. Check for mistakes.

# Install snyk
RUN RELEASE_JSON=$(curl -s "https://downloads.snyk.io/cli/${SNYK_VERSION}/release.json") \
&& BINARY_NAME="snyk-linux$([ "${TARGETARCH}" = "arm64" ] && echo "-arm64" || echo "")" \
&& SNYK_URL=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".url") \
&& SNYK_SHA256=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".sha256" | awk '{print $1}') \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The awk '{print $1}' here appears to be redundant. The jq -r ".assets.\"${BINARY_NAME}\".sha256" command should already extract just the SHA256 hash string from the release.json. Removing the unnecessary awk command will make the line cleaner and more direct.

    && SNYK_SHA256=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".sha256") \

Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The jq command extracts the sha256 value and pipes it through awk to get the first field, but this assumes a specific format in the JSON response. If the sha256 field is already just the hash string (without additional fields), the awk command is redundant. More importantly, if the JSON format is unexpected or the sha256 field is missing, the checksum verification on line 134 could silently pass with an empty string. Consider adding error checking to ensure SNYK_SHA256 is not empty before proceeding with the download and verification.

Suggested change
&& SNYK_SHA256=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".sha256" | awk '{print $1}') \
&& SNYK_SHA256=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".sha256") \
&& if [ -z "${SNYK_SHA256}" ] || [ "${SNYK_SHA256}" = "null" ]; then echo "Error: SNYK_SHA256 is empty or null! Aborting." >&2; exit 1; fi \

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Snyk installation retrieves the download URL from a JSON response but doesn't validate that the URL was successfully extracted. If jq fails to find the expected path or returns null, SNYK_URL could be empty or "null", causing the curl command to fail silently or download from an invalid location. Add validation to ensure SNYK_URL is not empty before attempting the download.

Suggested change
&& SNYK_SHA256=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".sha256" | awk '{print $1}') \
&& SNYK_SHA256=$(echo "${RELEASE_JSON}" | jq -r ".assets.\"${BINARY_NAME}\".sha256" | awk '{print $1}') \
&& if [ -z "${SNYK_URL}" ] || [ "${SNYK_URL}" = "null" ]; then echo "Error: Failed to extract Snyk download URL for ${BINARY_NAME}"; exit 1; fi \

Copilot uses AI. Check for mistakes.
&& curl -o /usr/local/bin/snyk -L "${SNYK_URL}" \
&& echo "${SNYK_SHA256} /usr/local/bin/snyk" | sha256sum -c - \
&& chmod +x /usr/local/bin/snyk

# Install hadolint
COPY --from=hadolint /bin/hadolint /usr/local/bin/hadolint

# Install actionlint
COPY --from=actionlint /usr/local/bin/actionlint /usr/local/bin/actionlint

# Install shellcheck
# Required for shellcheck vscode extension and actionlint
COPY --from=shellcheck /bin/shellcheck /usr/local/bin/shellcheck

# Install shfmt (Shell formatter)
COPY --from=shfmt /bin/shfmt /usr/local/bin/shfmt

WORKDIR /app

Expand Down
4 changes: 0 additions & 4 deletions tests/specs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ commandTests:
command: "snyk"
args: ["--version"]

- name: "pulumi is installed in path"
command: "pulumi"
args: ["version"]

- name: "reviewdog is installed in path"
command: "reviewdog"
args: ["--version"]
Expand Down