Skip to content

Commit 20ac380

Browse files
authored
Merge pull request #17 from KongZ/fix-buff-overflow
fix possible memory overflow and update skills
2 parents 2f12fc1 + bf4c563 commit 20ac380

7 files changed

Lines changed: 76 additions & 19 deletions

File tree

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ RUN groupadd -g 1000 kubeai && \
7070
chown -R kubeai:kubeai /home/kubeai
7171

7272
COPY --from=builder /src/kubeai-chatbot /bin/kubeai-chatbot
73+
COPY skills/ /home/kubeai/skills/
7374
RUN ln -sf /opt/tools/kubectl/bin/kubectl /bin/kubectl
7475

7576
USER kubeai

charts/kubeai-chatbot/values.yaml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,18 @@ autoscaling:
161161
# targetMemoryUtilizationPercentage: 80
162162

163163
# Additional volumes on the output Deployment definition.
164-
# Example: mount a skills ConfigMap:
164+
# Example: mount a skills ConfigMap as a subfolder inside the built-in skills directory:
165165
# volumes:
166-
# - name: skills
166+
# - name: custom-skills
167167
# configMap:
168-
# name: kubeai-skills
168+
# name: kubeai-custom-skills
169169
volumes: []
170170

171171
# Additional volumeMounts on the output Deployment definition.
172-
# Example: mount a skills ConfigMap at /etc/kubeai/skills:
172+
# Example: mount custom skills into a subfolder so built-in skills are preserved:
173173
# volumeMounts:
174-
# - name: skills
175-
# mountPath: /etc/kubeai/skills
174+
# - name: custom-skills
175+
# mountPath: /home/kubeai/skills/custom
176176
# readOnly: true
177177
volumeMounts: []
178178

@@ -229,11 +229,12 @@ env:
229229
# MCP_SERVERS: ""
230230

231231
## Skills Configuration
232-
## Path to a directory containing skill .md files (YAML frontmatter + instructions).
233-
## Mount a ConfigMap with your skill files and set SKILLS_DIR to the mount path.
234-
## Example: create a ConfigMap with key "debug-crashloop.md" and mount it below.
232+
## Path to the skills directory. Skills are loaded from .md files in this directory
233+
## and one level of subdirectories. Built-in skills ship with the image at this path.
234+
## Mount a ConfigMap into a subdirectory (e.g. /home/kubeai/skills/custom) to add
235+
## custom skills without overwriting the built-in ones.
235236
#
236-
# SKILLS_DIR: "/etc/kubeai/skills"
237+
# SKILLS_DIR: "/home/kubeai/skills"
237238

238239
## Authentication Configuration
239240
#

pkg/agent/systemprompt_template_default.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,14 @@ You can execute commands that modify resources automatically without requesting
101101

102102
{{if .Skills}}
103103
## Available Skills:
104-
The following skills provide specialized guidance for common tasks. Apply the most relevant skill's instructions when the user's request matches:
104+
The following skills provide authoritative, environment-specific guidance. When a skill matches the user's request, you MUST:
105+
- Follow the skill's instructions as the **sole source of truth** for that topic.
106+
- **Do NOT** supplement with general knowledge, training data, or assumptions about the technology.
107+
- **Do NOT** suggest any concept, resource type, CRD, or step that is not explicitly mentioned in the skill.
108+
- If the user's issue is not covered by the skill, say: "This is outside what I have documented for this topic." Do not guess.
109+
105110
{{range .Skills}}- **{{.Name}}**: {{.Description}}
106111
{{end}}
107-
When a skill is relevant, follow its step-by-step instructions to handle the task.
108-
109112
{{end}}
110113
## Command Structuring Guidelines:
111114
**IMPORTANT:**

pkg/skills/loader.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,27 @@ func LoadFromDir(dir string) ([]Skill, error) {
3838

3939
var result []Skill
4040
for _, entry := range entries {
41-
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
41+
if entry.IsDir() {
42+
subEntries, err := os.ReadDir(filepath.Join(dir, entry.Name()))
43+
if err != nil {
44+
return nil, fmt.Errorf("reading skills subdirectory %s: %w", entry.Name(), err)
45+
}
46+
for _, subEntry := range subEntries {
47+
if subEntry.IsDir() || !strings.HasSuffix(subEntry.Name(), ".md") {
48+
continue
49+
}
50+
skill, err := loadFile(filepath.Join(dir, entry.Name(), subEntry.Name()))
51+
if err != nil {
52+
return nil, fmt.Errorf("loading skill %s/%s: %w", entry.Name(), subEntry.Name(), err)
53+
}
54+
if skill.Name == "" {
55+
continue
56+
}
57+
result = append(result, skill)
58+
}
59+
continue
60+
}
61+
if !strings.HasSuffix(entry.Name(), ".md") {
4262
continue
4363
}
4464
skill, err := loadFile(filepath.Join(dir, entry.Name()))

pkg/skills/skills_test.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,25 @@ func TestLoadFromDir_MultipleSkills(t *testing.T) {
133133
assert.Contains(t, names, "skill-b")
134134
}
135135

136-
func TestLoadFromDir_SkipsSubdirectories(t *testing.T) {
136+
func TestLoadFromDir_LoadsSubdirectories(t *testing.T) {
137137
dir := t.TempDir()
138-
subdir := filepath.Join(dir, "subdir")
138+
subdir := filepath.Join(dir, "custom")
139139
require.NoError(t, os.Mkdir(subdir, 0o755))
140140
writeSkillFile(t, subdir, "nested.md", "---\nname: nested\n---\nBody.")
141141

142+
loaded, err := LoadFromDir(dir)
143+
require.NoError(t, err)
144+
require.Len(t, loaded, 1)
145+
assert.Equal(t, "nested", loaded[0].Name)
146+
}
147+
148+
func TestLoadFromDir_SkipsDeepSubdirectories(t *testing.T) {
149+
dir := t.TempDir()
150+
subdir := filepath.Join(dir, "custom")
151+
deepdir := filepath.Join(subdir, "deep")
152+
require.NoError(t, os.MkdirAll(deepdir, 0o755))
153+
writeSkillFile(t, deepdir, "deep.md", "---\nname: deep\n---\nBody.")
154+
142155
loaded, err := LoadFromDir(dir)
143156
require.NoError(t, err)
144157
assert.Empty(t, loaded)
@@ -231,3 +244,4 @@ func TestRegistry_MatchNoMatch(t *testing.T) {
231244
r.Register(Skill{Name: "crashloop", Triggers: []string{"crashloop"}})
232245
assert.Empty(t, r.Match("everything is fine today"))
233246
}
247+

pkg/ui/slack/slack.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ func (s *SlackUI) handleAuthSuccess(w http.ResponseWriter, r *http.Request) {
299299

300300
if identity == nil {
301301
// Re-trigger login if no identity found
302+
r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MB limit for form body (SAML/OIDC POST)
302303
state := r.FormValue("state")
303304
if state == "" {
304305
state = r.FormValue("RelayState")
Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@ triggers:
66
- piggysec
77
- piggy-env
88
- piggy-webhooks
9-
- install-piggy-env
109
- PIGGY_ALLOWED_SA
1110
- secrets injection
1211
- secret manager
12+
- decision not allowed
13+
- install-piggy-env
14+
- piggysec.com
15+
- piggy-initial-delay
16+
- piggy-enforce-integrity
17+
- piggy-ignore-no-env
18+
- piggy-number-of-retry
19+
- piggy-address
20+
- piggy-aws-secret-name
1321
---
1422

1523
Piggy is a Kubernetes mutating admission webhook that injects AWS Secrets Manager secrets into pod environments via a `piggy-env` init container. Follow this step-by-step diagnostic process.
1624

25+
> **Note:** Piggy is configured entirely through **Kubernetes annotations** and **environment variables** on the `piggy-webhooks` deployment. There are no CRDs, no PiggyPolicy resources. All configuration is annotation-based.
26+
1727
## How Piggy Works (Context)
1828

1929
When a pod is created:
@@ -22,7 +32,8 @@ When a pod is created:
2232
2. Piggy Webhooks injects an `install-piggy-env` init container into the pod.
2333
3. At runtime, `piggy-env` starts and contacts Piggy Webhooks over TLS, sending the pod's service account token, pod name, and a command signature (SHA256).
2434
4. Piggy Webhooks validates credentials via the Kubernetes Token Review API, exchanges the service account token for temporary AWS credentials via STS, then fetches secrets from AWS Secrets Manager.
25-
5. `piggy-env` receives the secrets and replaces any environment variable prefixed with `piggy:` with the real value.
35+
5. Piggy Webhooks checks the `PIGGY_ALLOWED_SA` field in the secret. If the pod's service account (`<namespace>:<service-account>`) is listed there, the request is authorized and the secret is returned. If the service account is **not** listed, Piggy Webhooks rejects the request and logs `"decision not allowed"`. The pod (`piggy-env`) receives no secret and retries according to `piggysec.com/piggy-number-of-retry`; retry attempts appear in the pod logs.
36+
6. `piggy-env` receives the secrets and replaces any environment variable prefixed with `piggy:` with the real value.
2637

2738
## Diagnostic Steps
2839

@@ -32,11 +43,17 @@ When a pod is created:
3243
kubectl describe pod <pod-name> -n <namespace> --context <ctx>
3344
```
3445

46+
A pod has been mutated by Piggy if **any** of the following are present:
47+
48+
- The `install-piggy-env` init container appears in `Init Containers:`
49+
- The annotation `piggysec.com/piggy-uid` is set on the pod
50+
- The environment variable `PIGGY_UID` is injected into the application container
51+
3552
Look for the `install-piggy-env` init container in `Init Containers:`:
3653

3754
- `Exit Code: 0` — init container succeeded; continue to Step 2.
3855
- Non-zero `Exit Code` or `State: Waiting` — init container failed; see Troubleshooting below.
39-
- No `install-piggy-env` container at all — pod was never mutated; see "Pod Not Mutated" below.
56+
- No `install-piggy-env` container, no `piggysec.com/piggy-uid` annotation, and no `PIGGY_UID` env var — pod was never mutated; see "Pod Not Mutated" below.
4057

4158
Also check that your application container's `Command` starts with `/piggy/piggy-env`:
4259

0 commit comments

Comments
 (0)