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
19 changes: 15 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
pull_request:
branches: [ main ]
paths:
- 'images/claude/**'
- 'images/**'
- '.github/workflows/build.yml'
Comment on lines 6 to 10
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the workflow file exists and read the pull_request trigger section
cat -n .github/workflows/build.yml | head -30

Repository: opendatahub-io/ai-helpers

Length of output: 780


🏁 Script executed:

# Check the Containerfiles mentioned
ls -la images/*/Containerfile

Repository: opendatahub-io/ai-helpers

Length of output: 222


🏁 Script executed:

# Read the Containerfiles to verify context and COPY statements
echo "=== images/cursor/Containerfile ===" && wc -l images/cursor/Containerfile
echo "=== images/claude/Containerfile ===" && wc -l images/claude/Containerfile

Repository: opendatahub-io/ai-helpers

Length of output: 202


🏁 Script executed:

# Read around lines 50-52 for both Containerfiles
echo "=== images/cursor/Containerfile lines 45-55 ===" && sed -n '45,55p' images/cursor/Containerfile
echo "=== images/claude/Containerfile lines 45-55 ===" && sed -n '45,55p' images/claude/Containerfile

Repository: opendatahub-io/ai-helpers

Length of output: 770


🏁 Script executed:

# Check what files exist in the root and subdirectories that might be in build context
git ls-files | head -50

Repository: opendatahub-io/ai-helpers

Length of output: 1421


🏁 Script executed:

# Check for scripts directory and other files at root that could be copied
ls -la | grep -E '^d|^-' | head -20

Repository: opendatahub-io/ai-helpers

Length of output: 1323


🏁 Script executed:

# Check if there's a scripts directory with files
find . -maxdepth 2 -type f -name "Makefile" -o -path "*/scripts/*" 2>/dev/null | head -20

Repository: opendatahub-io/ai-helpers

Length of output: 192


🏁 Script executed:

# Verify the build context in the workflow - check if there's a docker build step that specifies context
cat -n .github/workflows/build.yml | grep -A 20 "docker build\|context:"

Repository: opendatahub-io/ai-helpers

Length of output: 371


Major: the PR trigger skips files that are copied into the image.

Both Containerfiles build with context: . (line 66) and copy the entire repository into /opt/ai-helpers (line 51 in both images/cursor/Containerfile and images/claude/Containerfile), so a PR that only changes helpers/**, scripts/**, Makefile, categories.yaml, or other files at repository root can modify the image contents while this workflow never runs. The path filter at lines 8-10 only covers images/** and .github/workflows/build.yml. Remove the paths filter or expand it to include all files in the build context.

Remediation
  pull_request:
    branches: [ main ]
-    paths:
-      - 'images/**'
-      - '.github/workflows/build.yml'
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 6 - 10, The pull_request workflow's
'paths' filter only watches 'images/**' and '.github/workflows/build.yml', which
misses changes copied into the image because both Containerfiles use 'context:
.' and copy the repo into '/opt/ai-helpers' (see images/cursor/Containerfile and
images/claude/Containerfile); update the workflow by removing the 'paths' filter
entirely or expanding it to include all files used in the build context (e.g.,
add '**' or explicit entries like 'helpers/**', 'scripts/**', 'Makefile',
'categories.yaml') so that changes affecting the image trigger the workflow.

schedule:
# Run nightly at 2 AM UTC
Expand All @@ -17,12 +17,23 @@ env:
IMAGE_NAME: ${{ github.repository }}

jobs:
build-claude-container:
build-container:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

strategy:
fail-fast: false
matrix:
image:
- name: claude
containerfile: ./images/claude/Containerfile
suffix: ""
- name: cursor
containerfile: ./images/cursor/Containerfile
suffix: "-cursor"

steps:
- name: Checkout code
uses: actions/checkout@v6
Expand All @@ -42,7 +53,7 @@ jobs:
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}${{ matrix.image.suffix }}
tags: |
type=ref,event=branch
type=ref,event=pr
Expand All @@ -53,7 +64,7 @@ jobs:
uses: docker/build-push-action@v7
with:
context: .
file: ./images/claude/Containerfile
file: ${{ matrix.image.containerfile }}
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
Expand Down
20 changes: 14 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ CONTAINER_RUNTIME ?= $(shell command -v podman 2>/dev/null || echo docker)
# claudelint image
CLAUDELINT_IMAGE = ghcr.io/stbenjam/claudelint:main

# AI helpers image
IMAGE_NAME ?=ai-helpers
# Container images
IMAGE_TAG ?= latest
FULL_IMAGE_NAME = $(IMAGE_NAME):$(IMAGE_TAG)
CLAUDE_IMAGE_NAME ?= ai-helpers
CURSOR_IMAGE_NAME ?= ai-helpers-cursor

.PHONY: help
help: ## Show this help message
Expand Down Expand Up @@ -58,9 +58,17 @@ update: ## Update Claude settings and website data
fi

.PHONY: build
build: ## Build Claude container image using Containerfile
@echo "Building Claude container image $(FULL_IMAGE_NAME) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/claude/Containerfile -t $(FULL_IMAGE_NAME) .
build: build-claude build-cursor ## Build all container images

.PHONY: build-claude
build-claude: ## Build Claude CLI container image
@echo "Building Claude container image $(CLAUDE_IMAGE_NAME):$(IMAGE_TAG) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/claude/Containerfile -t $(CLAUDE_IMAGE_NAME):$(IMAGE_TAG) .

.PHONY: build-cursor
build-cursor: ## Build Cursor CLI container image
@echo "Building Cursor container image $(CURSOR_IMAGE_NAME):$(IMAGE_TAG) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/cursor/Containerfile -t $(CURSOR_IMAGE_NAME):$(IMAGE_TAG) .
Comment on lines +65 to +71
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and read the Makefile
fd -name Makefile -type f | head -5

Repository: opendatahub-io/ai-helpers

Length of output: 299


🏁 Script executed:

# Read the Makefile to see the full context
if [ -f Makefile ]; then
  wc -l Makefile
  cat -n Makefile
fi

Repository: opendatahub-io/ai-helpers

Length of output: 3695


Quote the image reference in build targets.

IMAGE_TAG, CLAUDE_IMAGE_NAME, and CURSOR_IMAGE_NAME are overrideable from the environment/CLI. Per the Makefile security guideline, quote shell variables in targets to prevent word-splitting.

Remediation
-	$(CONTAINER_RUNTIME) build -f images/claude/Containerfile -t $(CLAUDE_IMAGE_NAME):$(IMAGE_TAG) .
+	$(CONTAINER_RUNTIME) build -f images/claude/Containerfile -t "$(CLAUDE_IMAGE_NAME):$(IMAGE_TAG)" .

-	$(CONTAINER_RUNTIME) build -f images/cursor/Containerfile -t $(CURSOR_IMAGE_NAME):$(IMAGE_TAG) .
+	$(CONTAINER_RUNTIME) build -f images/cursor/Containerfile -t "$(CURSOR_IMAGE_NAME):$(IMAGE_TAG)" .
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@echo "Building Claude container image $(CLAUDE_IMAGE_NAME):$(IMAGE_TAG) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/claude/Containerfile -t $(CLAUDE_IMAGE_NAME):$(IMAGE_TAG) .
.PHONY: build-cursor
build-cursor: ## Build Cursor CLI container image
@echo "Building Cursor container image $(CURSOR_IMAGE_NAME):$(IMAGE_TAG) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/cursor/Containerfile -t $(CURSOR_IMAGE_NAME):$(IMAGE_TAG) .
`@echo` "Building Claude container image $(CLAUDE_IMAGE_NAME):$(IMAGE_TAG) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/claude/Containerfile -t "$(CLAUDE_IMAGE_NAME):$(IMAGE_TAG)" .
.PHONY: build-cursor
build-cursor: ## Build Cursor CLI container image
`@echo` "Building Cursor container image $(CURSOR_IMAGE_NAME):$(IMAGE_TAG) with $(CONTAINER_RUNTIME)..."
$(CONTAINER_RUNTIME) build -f images/cursor/Containerfile -t "$(CURSOR_IMAGE_NAME):$(IMAGE_TAG)" .
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 65 - 71, Quote shell variables used in the image
reference to prevent word-splitting: update the Claude and Cursor build targets
(the echo lines and the build commands that use $(CONTAINER_RUNTIME),
$(CLAUDE_IMAGE_NAME), $(CURSOR_IMAGE_NAME), and $(IMAGE_TAG)) so the image tags
are quoted, e.g. use "$(CLAUDE_IMAGE_NAME):$(IMAGE_TAG)" and
"$(CURSOR_IMAGE_NAME):$(IMAGE_TAG)" in the -t arguments and in the echo
messages; keep $(CONTAINER_RUNTIME) invocation intact but ensure the image
references are wrapped in double quotes to avoid splitting.


.PHONY: container-build
container-build: build ## Alias for build target
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,53 @@ claude-container() {
}
```

### Running Cursor Agent CLI in a Container

A container image is also available with the Cursor Agent CLI and all development tools pre-installed:
`ghcr.io/opendatahub-io/ai-helpers-cursor:latest`

You can also build it yourself by running:

```bash
podman build -f images/cursor/Containerfile -t ai-helpers-cursor .
```

Or using make:

```bash
make build-cursor
```

To use the Cursor Agent CLI, you need to pass your `CURSOR_API_KEY`:

```bash
podman run -it --rm \
--pull newer \
--userns=keep-id \
-e CURSOR_API_KEY=your-api-key \
-v $(pwd):$(pwd):z \
-w $(pwd) \
ghcr.io/opendatahub-io/ai-helpers-cursor:latest
Comment on lines +215 to +224
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Major: both examples expose CURSOR_API_KEY in host process args (CWE-312/CWE-200).

Inlining the value in podman run β€” including "${CURSOR_API_KEY}" in the helper β€” expands the secret into the spawned command line. That leaks it via ps, and the one-off example also encourages shell-history exposure. Pass the variable name through instead.

Remediation
-  -e CURSOR_API_KEY=your-api-key \
+  -e CURSOR_API_KEY \
...
-    -e CURSOR_API_KEY="${CURSOR_API_KEY}" \
+    -e CURSOR_API_KEY \

Also applies to: 233-241

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 215 - 224, The examples in README.md inline the
actual CURSOR_API_KEY value into the podman run command which leaks secrets via
process args; change both examples (the podman run snippets around the shown
block and the later one at 233-241) to avoid embedding the secret by passing
only the variable name (e.g., use -e CURSOR_API_KEY or --env CURSOR_API_KEY
without "=your-api-key"), or show using an env file (--env-file .env) or
prompting to export CURSOR_API_KEY in the shell beforehand, so the command no
longer contains the secret literal.

```

**Environment Variables:**

- `CURSOR_API_KEY` - Your Cursor API key (required for authentication)

Add this to your `~/.bashrc` for easy launching of the container:

```bash
cursor-container() {
podman run -it --rm \
--pull newer \
--userns=keep-id \
-e CURSOR_API_KEY="${CURSOR_API_KEY}" \
-v "$(pwd):$(pwd):z" \
-w "$(pwd)" \
ghcr.io/opendatahub-io/ai-helpers-cursor:latest "$@"
}
```

## Using with OpenCode.ai

[OpenCode.ai](https://opencode.ai) is an open-source AI coding assistant that supports custom skills and commands. Our helpers can be integrated as OpenCode skills and commands to enhance your development workflow.
Expand Down
69 changes: 69 additions & 0 deletions images/cursor/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
FROM quay.io/fedora/fedora:latest

# Install Node.js, NPM, Python 3, development tools, and RPM tools
RUN dnf install -y nodejs \
npm \
python3 \
python3-devel \
fedora-packager \
fedora-review \
mock \
rpm-build \
rpmdevtools \
curl \
git \
make \
shellcheck \
which \
&& dnf clean all


RUN echo '[google-cloud-cli]' > /etc/yum.repos.d/google-cloud-sdk.repo && \
echo 'name=Google Cloud CLI' >> /etc/yum.repos.d/google-cloud-sdk.repo && \
echo 'baseurl=https://packages.cloud.google.com/yum/repos/cloud-sdk-el10-$basearch' >> /etc/yum.repos.d/google-cloud-sdk.repo && \
Copy link
Contributor

Choose a reason for hiding this comment

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

I think they only support x86_64: https://docs.cloud.google.com/sdk/docs/install-sdk#rpm so the $basearch is not needed here.

echo 'enabled=1' >> /etc/yum.repos.d/google-cloud-sdk.repo && \
echo 'gpgcheck=1' >> /etc/yum.repos.d/google-cloud-sdk.repo && \
echo 'repo_gpgcheck=0' >> /etc/yum.repos.d/google-cloud-sdk.repo && \
echo 'gpgkey=https://packages.cloud.google.com/yum/doc/rpm-package-key-v10.gpg' >> /etc/yum.repos.d/google-cloud-sdk.repo && \
dnf install -y google-cloud-cli && \
dnf clean all

# Install uv for PEP 723 script dependency management
RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR="/usr/local/bin/" INSTALLER_NO_MODIFY_PATH=1 sh

Comment on lines +32 to +33
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | πŸ”΄ Critical

🧩 Analysis chain

🏁 Script executed:

cat -n images/cursor/Containerfile | sed -n '25,65p'

Repository: opendatahub-io/ai-helpers

Length of output: 1850


Critical: uv and Cursor installers execute remote scripts without integrity verification (CWE-829).

Lines 32 and 58 both pipe unverified remote scripts directly to shell interpreters, allowing arbitrary code execution from upstream sources or CDN compromise during build. Version the installer URLs and verify checksums before execution.

Remediation
 # Install uv for PEP 723 script dependency management
-RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR="/usr/local/bin/" INSTALLER_NO_MODIFY_PATH=1 sh
+ARG UV_INSTALL_URL=<versioned-url>
+ARG UV_INSTALL_SHA256=<published-sha256>
+RUN curl -fsSLo /tmp/uv-install.sh "${UV_INSTALL_URL}" \
+    && echo "${UV_INSTALL_SHA256}  /tmp/uv-install.sh" | sha256sum -c - \
+    && UV_INSTALL_DIR="/usr/local/bin/" INSTALLER_NO_MODIFY_PATH=1 sh /tmp/uv-install.sh \
+    && rm -f /tmp/uv-install.sh

 # Install Cursor CLI
-RUN curl -fsSL https://cursor.com/install | bash
+ARG CURSOR_INSTALL_URL=<versioned-url>
+ARG CURSOR_INSTALL_SHA256=<published-sha256>
+RUN curl -fsSLo /tmp/cursor-install.sh "${CURSOR_INSTALL_URL}" \
+    && echo "${CURSOR_INSTALL_SHA256}  /tmp/cursor-install.sh" | sha256sum -c - \
+    && bash /tmp/cursor-install.sh \
+    && rm -f /tmp/cursor-install.sh
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@images/cursor/Containerfile` around lines 32 - 33, The Dockerfile currently
pipes remote installer scripts straight into sh (the RUN curl -LsSf ... | ... sh
invocation for the uv/astral installer using UV_INSTALL_DIR and
INSTALLER_NO_MODIFY_PATH), which risks executing tampered code; change this to
fetch a versioned release artifact, verify its integrity (SHA256 or GPG) before
execution, and only then run it: download the installer to a file (pin a
specific versioned URL), fetch or embed the expected checksum or signature,
validate the file with shasum/gpg, and finally run the verified file (or prefer
installing from a packaged release or distro package) so the build fails if
verification does not match.

# Install OpenShift client (oc)
RUN curl -LsSf https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable/openshift-client-linux.tar.gz | tar xzf - -C /usr/local/bin/ oc \
&& chmod +x /usr/local/bin/oc
Comment on lines +35 to +36
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "Containerfile" -type f

Repository: opendatahub-io/ai-helpers

Length of output: 128


🏁 Script executed:

find . -type f -name "*docker*" -o -name "*compose*" -o -name "*.yml" -o -name "*.yaml" | grep -E "(workflow|action|build)" | head -20

Repository: opendatahub-io/ai-helpers

Length of output: 264


🏁 Script executed:

find . -type f \( -name ".github" -o -name "docker-compose*" \) | head -20

Repository: opendatahub-io/ai-helpers

Length of output: 51


🏁 Script executed:

cd images/cursor && ls -la

Repository: opendatahub-io/ai-helpers

Length of output: 242


🏁 Script executed:

cat -n images/cursor/Containerfile

Repository: opendatahub-io/ai-helpers

Length of output: 2949


🏁 Script executed:

cat -n .github/workflows/build.yml

Repository: opendatahub-io/ai-helpers

Length of output: 2387


🏁 Script executed:

web_search
OpenShift mirror openshift-client-linux artifact naming conventions amd64 arm64

Repository: opendatahub-io/ai-helpers

Length of output: 167


🌐 Web query:

OpenShift client mirror openshift-client-linux-arm64.tar.gz amd64 arm64 artifacts

πŸ’‘ Result:

OpenShift oc client tarballs are published on the public mirror under these patterns (pick either a specific version directory like 4.17.17/ or a moving channel like stable/, stable-4.14/, latest-4.16/, etc.):

Linux amd64 (x86_64)

https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp/<channel-or-version>/openshift-client-linux.tar.gz

Red Hat docs also reference this ${ARCH} form (where ${ARCH} is x86_64, aarch64, etc.). (docs.redhat.com)

Linux arm64 (aarch64)

Commonly published as an explicit arm64 filename:

https://mirror.openshift.com/pub/openshift-v4/clients/ocp/<channel-or-version>/openshift-client-linux-arm64.tar.gz

Examples seen in the wild:

β€œMulti” layout (also used for arch-separated listings)

You may also find arch-separated directories like:

https://mirror.openshift.com/pub/openshift-v4/multi/clients/ocp/stable/amd64/

(mirror.openshift.com)

Citations:


Major: oc binary download must be architecture-aware for multi-arch builds.

The build workflow (.github/workflows/build.yml:68) publishes for both linux/amd64 and linux/arm64, but the Containerfile hardcodes the generic amd64 archive. The OpenShift mirror publishes distinct artifacts: openshift-client-linux.tar.gz (amd64) and openshift-client-linux-arm64.tar.gz (arm64). The arm64 image will receive the amd64 binary, causing runtime failures.

Parameterize the archive name using Docker's TARGETARCH build argument before publishing multi-arch images:

Remediation
+ARG TARGETARCH
-RUN curl -LsSf https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable/openshift-client-linux.tar.gz | tar xzf - -C /usr/local/bin/ oc \
-    && chmod +x /usr/local/bin/oc
+RUN case "${TARGETARCH}" in \
+      amd64) oc_archive="openshift-client-linux.tar.gz" ;; \
+      arm64) oc_archive="openshift-client-linux-arm64.tar.gz" ;; \
+      *) echo "Unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \
+    esac \
+    && curl -fsSLo /tmp/oc.tgz "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/stable/${oc_archive}" \
+    && tar xzf /tmp/oc.tgz -C /usr/local/bin/ oc \
+    && chmod +x /usr/local/bin/oc \
+    && rm -f /tmp/oc.tgz
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@images/cursor/Containerfile` around lines 35 - 36, The Containerfile
currently hardcodes the amd64 OpenShift client archive in the RUN curl ... oc
download line, which breaks arm64 multi-arch images; add a build ARG TARGETARCH
at the top of the Containerfile and use it to choose the correct archive name
when downloading the oc binary (e.g., map TARGETARCH to the archive suffix such
that TARGETARCH=arm64 selects openshift-client-linux-arm64.tar.gz and otherwise
selects openshift-client-linux.tar.gz), and then update the RUN curl ...
/usr/local/bin/oc command to reference that computed archive variable so
multi-arch builds pull the correct binary.


# Install common Python tools
RUN uv pip install --system --no-cache \
pytest \
requests \
pyyaml \
ruff \
tox \
tox-uv

# Create cursor user
RUN useradd -m -u 1000 -s /bin/bash cursor

# Copy ai-helpers repository to /opt/ai-helpers
COPY . /opt/ai-helpers
RUN chown -R cursor:cursor /opt/ai-helpers

# Switch to cursor user
USER cursor

# Install Cursor CLI
RUN curl -fsSL https://cursor.com/install | bash

# Install ai-helpers skills, commands, and agents for Cursor
RUN mkdir -p /home/cursor/.cursor/skills /home/cursor/.cursor/commands && \
ln -s /opt/ai-helpers/helpers/skills/* /home/cursor/.cursor/skills/ && \
ln -s /opt/ai-helpers/helpers/commands/* /home/cursor/.cursor/commands/ && \
ln -s /opt/ai-helpers/helpers/agents/* /home/cursor/.cursor/skills/

# Set Cursor Agent CLI as the default command
# --trust: skip workspace trust prompt
# --print: output to stdout for non-interactive container use
ENTRYPOINT ["/home/cursor/.local/bin/agent", "--trust", "--print"]