Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ build-golang-adk-full: buildx-create
.PHONY: build-skills-init
build-skills-init: ## Build and push the skills-init image
build-skills-init: buildx-create
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) -t $(SKILLS_INIT_IMG) -f docker/skills-init/Dockerfile docker/skills-init
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) -t $(SKILLS_INIT_IMG) -f docker/skills-init/Dockerfile ./go
$(DOCKER_PUSH) $(SKILLS_INIT_IMG)

.PHONY: push
Expand Down
49 changes: 33 additions & 16 deletions docker/skills-init/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,39 @@
### Stage 0: build krane
FROM golang:1.26-alpine AS krane-builder

ENV KRANE_VERSION=v0.21.2
WORKDIR /build

RUN apk add --no-cache git && \
git clone --depth 1 --branch $KRANE_VERSION \
https://github.com/google/go-containerregistry.git

WORKDIR /build/go-containerregistry/cmd/krane

RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /build/krane .

### Stage 0: build the skills-init Go binary
ARG BASE_IMAGE_REGISTRY=cgr.dev
ARG BUILDPLATFORM
FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/go:latest AS builder
ARG TARGETARCH
ARG TARGETOS

WORKDIR /workspace

COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/go/pkg/mod,rw \
--mount=type=cache,target=/root/.cache/go-build,rw \
go mod download

COPY api/ api/
COPY core/ core/
COPY adk/ adk/

ARG LDFLAGS
RUN --mount=type=cache,target=/root/go/pkg/mod,rw \
--mount=type=cache,target=/root/.cache/go-build,rw \
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
go build -a -trimpath -ldflags "$LDFLAGS" -o /skills-init ./core/cmd/skills-init

### Stage 1: runtime
FROM alpine:3.23

ARG PYTHON_UID=1001
ARG PYTHON_GID=1001

RUN apk upgrade --no-cache && apk add --no-cache git jq
COPY --from=krane-builder /build/krane /usr/local/bin/krane
# git is invoked by skills-init via exec.Command with an argv vector — never
# through a shell — so the only attack surface here is git itself. OCI fetch
# uses the in-process go-containerregistry library, so krane and jq are gone.
RUN apk upgrade --no-cache && apk add --no-cache git openssh-client ca-certificates

COPY --from=builder /skills-init /usr/local/bin/skills-init

# Run as the same UID/GID as the main agent container (python user) so that
# files written to the shared /skills volume are readable by the main container.
Expand All @@ -28,3 +43,5 @@ RUN addgroup -g ${PYTHON_GID} pythongroup && \
adduser -u ${PYTHON_UID} -G pythongroup -s /bin/sh -D python

USER ${PYTHON_UID}:${PYTHON_GID}

ENTRYPOINT ["/usr/local/bin/skills-init"]
35 changes: 35 additions & 0 deletions go/core/cmd/skills-init/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Command skills-init is the init container binary that fetches an Agent's
// skills from git repositories and OCI images before the main agent container
// starts.
//
// It reads its configuration from a ConfigMap-mounted JSON file (see the
// skillsinit package for the wire format) and performs all subprocess
// invocations with argv vectors — no user input is ever interpolated into a
// shell, which is the original design defect that motivated this rewrite.
package main

import (
"context"
"log"
"os"

"github.com/kagent-dev/kagent/go/core/internal/skillsinit"
)

func main() {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)

cfg, err := skillsinit.LoadConfig()
if err != nil {
log.Fatalf("skills-init: %v", err)
}

home, _ := os.UserHomeDir()
if home == "" {
home = "/root"
}

if err := skillsinit.Run(context.Background(), cfg, home); err != nil {
log.Fatalf("skills-init: %v", err)
}
}
Loading
Loading