Skip to content
Open
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
4 changes: 4 additions & 0 deletions .agents/commit-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Fixes: <CVE-ID-1>, <CVE-ID-2>
- `github.com/docker/docker` → `docker/docker`
- `golang.org/x/oauth2` → `x/oauth2`
- `helm.sh/helm/v3` → `helm/v3`
- `go.opentelemetry.io/otel` → `otel`
- `go.opentelemetry.io/otel/exporters/otlp/*/X` → `otel/X`
- `google.golang.org/grpc` → `grpc`
- `sigs.k8s.io/controller-runtime` → `controller-runtime`
- Keep `k8s.io/` prefix

**Examples:**
Expand Down
11 changes: 11 additions & 0 deletions .agents/workflows/cve-fix.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
#### Fixing CVEs

**Automated:** Use `/cve-fix` in Claude Code or `make cve-fix` from shipyard:

```bash
/cve-fix release-0.23 ../submariner-operator
make cve-fix REPO=../submariner BRANCH=release-0.23
```

The steps below are the manual process for reference.

---

**All commands should be run from the repository root directory.**

**Before starting, user must:**
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ jobs:
- name: Test the compile.sh script
run: make script-test SCRIPT_TEST_ARGS="test/scripts/compile/test.sh"

- name: Test the CVE fix scripts
run: make script-test SCRIPT_TEST_ARGS="test/scripts/cve/test.sh"

deployment:
name: Deployment
needs: images
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LOCAL_COMPONENTS := submariner-metrics-proxy
MULTIARCH_IMAGES ?= $(IMAGES)
EXTRA_PRELOAD_IMAGES := $(PRELOAD_IMAGES)
PLATFORMS ?= linux/amd64,linux/arm64
NON_DAPPER_GOALS += images multiarch-images
NON_DAPPER_GOALS += images multiarch-images cve-fix cve-clean
PLUGIN ?=

export LOCAL_COMPONENTS
Expand Down Expand Up @@ -65,6 +65,13 @@ deploy deploy-latest e2e upgrade-e2e: package/.image.nettest

include Makefile.dapper

# CVE fix scripts
cve-fix:
./scripts/cve/fix-all.sh "$(or $(REPO),.)" "$(or $(BRANCH),$(shell git branch --show-current))"

cve-clean:
./scripts/cve/clean.sh

# Make sure linting goals have up-to-date linting image
$(LINTING_GOALS): package/.image.shipyard-linting

Expand Down
5 changes: 5 additions & 0 deletions scripts/cve/clean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
# Kill orphaned CVE fix processes and containers.
# Safe to run anytime — only targets fix-all.sh, grype, and dapper fix containers.
pkill -f fix-all.sh 2>/dev/null || true
docker ps --format '{{.ID}} {{.Image}}' 2>/dev/null | grep -E 'grype:latest|fix-.*-cves-' | awk '{print $1}' | xargs -r docker kill 2>/dev/null || true
162 changes: 162 additions & 0 deletions scripts/cve/detect.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/bin/bash
# Detect repository configuration for CVE fixing and optionally set up fix branch.
# Usage: detect.sh [repo] [branch] [--setup-branch]
# Prints state file path as last line of stdout.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source-path=SCRIPTDIR
# shellcheck source=lib.sh
source "$SCRIPT_DIR/lib.sh"

# --- Argument parsing (order-independent) ---
REPO=""
BRANCH=""
SETUP_BRANCH=false

for arg in "$@"; do
case "$arg" in
--setup-branch) SETUP_BRANCH=true ;;
*)
# Expand tilde
arg="${arg/#\~/$HOME}"
# Try sibling directory for bare names (e.g., "subctl" -> "../subctl")
if [[ "$arg" != */* ]] && [[ -d "../$arg" ]]; then
arg="../$arg"
fi
if [[ "$arg" == /* ]] || [[ "$arg" == ./* ]] || [[ "$arg" == ../* ]] || [[ -d "$arg" ]]; then
[[ -n "$REPO" ]] && { echo "ERROR: Multiple repositories specified" >&2; exit 1; }
REPO="$arg"
else
[[ -n "$BRANCH" ]] && { echo "ERROR: Multiple branches specified" >&2; exit 1; }
BRANCH="$arg"
fi
;;
esac
done

REPO="${REPO:-.}"

# --- Validate repo ---
[[ -d "$REPO" ]] || { echo "ERROR: Repository not found: $REPO" >&2; exit 1; }
git -C "$REPO" rev-parse --git-dir &>/dev/null || { echo "ERROR: Not a git repository: $REPO" >&2; exit 1; }

# --- Resolve branch ---
if [[ -z "$BRANCH" ]]; then
BRANCH=$(git -C "$REPO" branch --show-current 2>/dev/null)
[[ -n "$BRANCH" ]] || { echo "ERROR: Not on a branch (detached HEAD). Specify branch explicitly." >&2; exit 1; }
echo "Using current branch: $BRANCH"
fi

# Normalize short version (0.23 -> release-0.23)
if [[ "$BRANCH" =~ ^[0-9]+\.[0-9]+$ ]]; then
BRANCH="release-${BRANCH}"
echo "Normalized to branch: $BRANCH"
fi

# --- Change to repo (absolute path) ---
REPO="$(cd "$REPO" && pwd)"
cd "$REPO"

REPO_NAME=$(basename "$REPO")
echo ""
echo "=== CVE Fix: $REPO_NAME/$BRANCH ==="

# --- Detect config ---
HAS_TOOLS_GOMOD=false
test -f tools/go.mod && HAS_TOOLS_GOMOD=true

GENERATED_FILE=""
DIFF_IGNORE_ARGS=""
if grep -rql "controller-gen.kubebuilder.io/version" --include="*.go" . 2>/dev/null; then
DIFF_IGNORE_ARGS="-Icontroller-gen.kubebuilder.io/version"
GENERATED_FILE=$(grep -rl "controller-gen.kubebuilder.io/version" --include="*.go" . 2>/dev/null | head -1)
elif find . -name "*.pb.go" -type f 2>/dev/null | head -1 | grep -q .; then
DIFF_IGNORE_ARGS="-I^//"
GENERATED_FILE=$(find . -name "*.pb.go" -type f 2>/dev/null | head -1)
fi

NEEDS_BUILD_FOR_SCAN=false
grep -q '^build:' Makefile 2>/dev/null && NEEDS_BUILD_FOR_SCAN=true

CONTAINER_CMD=$(detect_container_cmd)

HAS_LOCAL_GRYPE=false
command -v grype &>/dev/null && HAS_LOCAL_GRYPE=true

# Shipyard build image
if [[ "$BRANCH" == "devel" ]]; then
SHIPYARD_TAG="devel"
elif [[ "$BRANCH" =~ ^release- ]]; then
SHIPYARD_TAG="$BRANCH"
else
echo "WARNING: Unknown branch pattern, assuming devel build image"
SHIPYARD_TAG="devel"
fi

SHIPYARD_IMAGE="quay.io/submariner/shipyard-dapper-base:${SHIPYARD_TAG}"
SHIPYARD_GO_VERSION="unknown"
if [[ -n "$CONTAINER_CMD" ]]; then
if $CONTAINER_CMD pull "$SHIPYARD_IMAGE" >/dev/null 2>&1; then
SHIPYARD_GO_VERSION=$($CONTAINER_CMD run --rm "$SHIPYARD_IMAGE" go version 2>/dev/null || echo "unknown")
fi
fi

# --- Branch setup (optional) ---
ORIGINAL_REF=""
FIX_BRANCH=""
FETCH_FAILED=false

if [[ "$SETUP_BRANCH" == "true" ]]; then
if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then
echo "ERROR: Working tree has uncommitted changes. Commit or stash first." >&2
exit 1
fi

ORIGINAL_REF=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
[[ "$ORIGINAL_REF" == "HEAD" ]] && ORIGINAL_REF=$(git rev-parse HEAD)

if ! GIT_SSH_COMMAND="ssh -o BatchMode=yes" git fetch 2>/dev/null; then
echo "WARNING: git fetch failed. Continuing with cached remote state."
FETCH_FAILED=true
fi

DATE=$(date +%Y-%m-%d)
VERSION="${BRANCH//release-/}"
FIX_BRANCH="fix-${VERSION}-cves-${DATE}"

SUFFIX=""
while git show-ref --verify --quiet "refs/heads/${FIX_BRANCH}${SUFFIX}"; do
if [[ -z "$SUFFIX" ]]; then SUFFIX="-v2"; else NUM=${SUFFIX#-v}; SUFFIX="-v$((NUM+1))"; fi
done
FIX_BRANCH="${FIX_BRANCH}${SUFFIX}"

if ! git checkout -b "$FIX_BRANCH" "origin/$BRANCH" 2>/dev/null; then
echo "ERROR: Could not create fix branch from origin/$BRANCH" >&2
exit 1
fi
echo "Fix branch: $FIX_BRANCH"
fi

# --- Write state file ---
STATE_FILE=$(state_file_path "$REPO" "$BRANCH")
cat > "$STATE_FILE" <<EOF
STATE_FILE="$STATE_FILE"
REPO="$REPO"
BRANCH="$BRANCH"
FIX_BRANCH="$FIX_BRANCH"
ORIGINAL_REF="$ORIGINAL_REF"
HAS_TOOLS_GOMOD=$HAS_TOOLS_GOMOD
GENERATED_FILE="$GENERATED_FILE"
DIFF_IGNORE_ARGS="$DIFF_IGNORE_ARGS"
NEEDS_BUILD_FOR_SCAN=$NEEDS_BUILD_FOR_SCAN
CONTAINER_CMD="$CONTAINER_CMD"
HAS_LOCAL_GRYPE=$HAS_LOCAL_GRYPE
SHIPYARD_IMAGE="$SHIPYARD_IMAGE"
SHIPYARD_GO_VERSION="$SHIPYARD_GO_VERSION"
FETCH_FAILED=$FETCH_FAILED
CVE_SCRIPTS="$SCRIPT_DIR"
EOF
Comment on lines +143 to +159
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Shell-escape values before writing the sourced state file.

This file is later sourced by scan.sh, fix-all.sh, review.sh, and others. Writing raw values inside double quotes means a repo path or branch containing ", backticks, or $(...) can break parsing or execute commands when the state file is loaded.

🛡️ Proposed hardening
 STATE_FILE=$(state_file_path "$REPO" "$BRANCH")
-cat > "$STATE_FILE" <<EOF
-STATE_FILE="$STATE_FILE"
-REPO="$REPO"
-BRANCH="$BRANCH"
-FIX_BRANCH="$FIX_BRANCH"
-ORIGINAL_REF="$ORIGINAL_REF"
-HAS_TOOLS_GOMOD=$HAS_TOOLS_GOMOD
-GENERATED_FILE="$GENERATED_FILE"
-DIFF_IGNORE_ARGS="$DIFF_IGNORE_ARGS"
-NEEDS_BUILD_FOR_SCAN=$NEEDS_BUILD_FOR_SCAN
-CONTAINER_CMD="$CONTAINER_CMD"
-HAS_LOCAL_GRYPE=$HAS_LOCAL_GRYPE
-SHIPYARD_IMAGE="$SHIPYARD_IMAGE"
-SHIPYARD_GO_VERSION="$SHIPYARD_GO_VERSION"
-FETCH_FAILED=$FETCH_FAILED
-CVE_SCRIPTS="$SCRIPT_DIR"
-EOF
+{
+  printf 'STATE_FILE=%q\n' "$STATE_FILE"
+  printf 'REPO=%q\n' "$REPO"
+  printf 'BRANCH=%q\n' "$BRANCH"
+  printf 'FIX_BRANCH=%q\n' "$FIX_BRANCH"
+  printf 'ORIGINAL_REF=%q\n' "$ORIGINAL_REF"
+  printf 'HAS_TOOLS_GOMOD=%q\n' "$HAS_TOOLS_GOMOD"
+  printf 'GENERATED_FILE=%q\n' "$GENERATED_FILE"
+  printf 'DIFF_IGNORE_ARGS=%q\n' "$DIFF_IGNORE_ARGS"
+  printf 'NEEDS_BUILD_FOR_SCAN=%q\n' "$NEEDS_BUILD_FOR_SCAN"
+  printf 'CONTAINER_CMD=%q\n' "$CONTAINER_CMD"
+  printf 'HAS_LOCAL_GRYPE=%q\n' "$HAS_LOCAL_GRYPE"
+  printf 'SHIPYARD_IMAGE=%q\n' "$SHIPYARD_IMAGE"
+  printf 'SHIPYARD_GO_VERSION=%q\n' "$SHIPYARD_GO_VERSION"
+  printf 'FETCH_FAILED=%q\n' "$FETCH_FAILED"
+  printf 'CVE_SCRIPTS=%q\n' "$SCRIPT_DIR"
+} > "$STATE_FILE"
📝 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
cat > "$STATE_FILE" <<EOF
STATE_FILE="$STATE_FILE"
REPO="$REPO"
BRANCH="$BRANCH"
FIX_BRANCH="$FIX_BRANCH"
ORIGINAL_REF="$ORIGINAL_REF"
HAS_TOOLS_GOMOD=$HAS_TOOLS_GOMOD
GENERATED_FILE="$GENERATED_FILE"
DIFF_IGNORE_ARGS="$DIFF_IGNORE_ARGS"
NEEDS_BUILD_FOR_SCAN=$NEEDS_BUILD_FOR_SCAN
CONTAINER_CMD="$CONTAINER_CMD"
HAS_LOCAL_GRYPE=$HAS_LOCAL_GRYPE
SHIPYARD_IMAGE="$SHIPYARD_IMAGE"
SHIPYARD_GO_VERSION="$SHIPYARD_GO_VERSION"
FETCH_FAILED=$FETCH_FAILED
CVE_SCRIPTS="$SCRIPT_DIR"
EOF
{
printf 'STATE_FILE=%q\n' "$STATE_FILE"
printf 'REPO=%q\n' "$REPO"
printf 'BRANCH=%q\n' "$BRANCH"
printf 'FIX_BRANCH=%q\n' "$FIX_BRANCH"
printf 'ORIGINAL_REF=%q\n' "$ORIGINAL_REF"
printf 'HAS_TOOLS_GOMOD=%q\n' "$HAS_TOOLS_GOMOD"
printf 'GENERATED_FILE=%q\n' "$GENERATED_FILE"
printf 'DIFF_IGNORE_ARGS=%q\n' "$DIFF_IGNORE_ARGS"
printf 'NEEDS_BUILD_FOR_SCAN=%q\n' "$NEEDS_BUILD_FOR_SCAN"
printf 'CONTAINER_CMD=%q\n' "$CONTAINER_CMD"
printf 'HAS_LOCAL_GRYPE=%q\n' "$HAS_LOCAL_GRYPE"
printf 'SHIPYARD_IMAGE=%q\n' "$SHIPYARD_IMAGE"
printf 'SHIPYARD_GO_VERSION=%q\n' "$SHIPYARD_GO_VERSION"
printf 'FETCH_FAILED=%q\n' "$FETCH_FAILED"
printf 'CVE_SCRIPTS=%q\n' "$SCRIPT_DIR"
} > "$STATE_FILE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/cve/detect.sh` around lines 137 - 153, The heredoc that writes the
sourced state file in scripts/cve/detect.sh currently injects raw variable
values (STATE_FILE, REPO, BRANCH, FIX_BRANCH, ORIGINAL_REF, HAS_TOOLS_GOMOD,
GENERATED_FILE, DIFF_IGNORE_ARGS, NEEDS_BUILD_FOR_SCAN, CONTAINER_CMD,
HAS_LOCAL_GRYPE, SHIPYARD_IMAGE, SHIPYARD_GO_VERSION, FETCH_FAILED, CVE_SCRIPTS)
unescaped, so values containing quotes or command substitution can break or be
executed when the file is sourced; change the writer to shell-escape each value
before emitting it (for example, generate each line using a safe escape routine
such as printf '%q' or by safely single-quoting and escaping internal single
quotes) and write the escaped results into "$STATE_FILE" instead of the raw
expansions in the heredoc so the resulting file can be safely sourced by
scan.sh, fix-all.sh, review.sh, etc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Won't fix. All values are controlled (paths from pwd, git branch names, hardcoded strings like docker/true/false). No user-supplied input reaches the state file.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@dfarrell07, fair point — looking at the concrete values: pwd-derived paths, git ref names (which cannot contain " or $()), true/false literals, hardcoded image strings, and go version output. None of these realistically carry shell-metacharacter payloads, so the heredoc approach is safe in practice here. Acknowledged, moving on.

😔


# Last line is the state file path (captured by callers)
echo "$STATE_FILE"
Loading
Loading