@mrciphersmith/keryx 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +7310 -2471
- package/dist/core.js +116 -10
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +349 -2
- package/src/gdskills/bundled/rules/core/model-selection.mdc +18 -0
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-comments/SKILL.md +184 -0
- package/src/gdskills/bundled/skills/review/review-jev-contract/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/review/review-jev-docs/SKILL.md +189 -0
- package/src/gdskills/bundled/skills/review/review-jev-risk/SKILL.md +190 -0
- package/src/gdskills/bundled/skills/review/review-jev-scenarios/SKILL.md +187 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +88 -15
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +4 -4
- package/src/gdskills/bundled/stacks/c-cpp/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/c-cpp/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/c-cpp/pack.json +42 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/patterns.mdc +87 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/c-cpp/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/c-cpp/skills/c-cpp-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/eval.json +1295 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/governance/scout.json +26 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/patterns.mdc +77 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/rules/security.mdc +144 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/ci-github-gitlab/skills/ci-pipeline-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/eval.json +865 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/governance/scout.json +16 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/pack.json +46 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/coding-style.mdc +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/patterns.mdc +81 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/security.mdc +146 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/rules/testing.mdc +61 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/docker-k8s-terraform/skills/docker-k8s-terraform-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/php-laravel/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/php-laravel/pack.json +41 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/patterns.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/security.mdc +80 -0
- package/src/gdskills/bundled/stacks/php-laravel/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/php-laravel/skills/php-laravel-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ruby-rails/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/eval.json +1673 -0
- package/src/gdskills/bundled/stacks/ruby-rails/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/ruby-rails/pack.json +42 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/patterns.mdc +93 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/ruby-rails/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/SKILL.md +125 -0
- package/src/gdskills/bundled/stacks/ruby-rails/skills/ruby-rails-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/eval.json +1829 -0
- package/src/gdskills/bundled/stacks/sql-db/governance/scout.json +30 -0
- package/src/gdskills/bundled/stacks/sql-db/pack.json +40 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/patterns.mdc +134 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/security.mdc +74 -0
- package/src/gdskills/bundled/stacks/sql-db/rules/testing.mdc +83 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/SKILL.md +153 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/sql-db/skills/sql-db-testing/evals.json +73 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"query": "Use when reviewing a Dockerfile, Docker Compose file, Kubernetes/Helm manifest, or Terraform change for security and config-authoring risks -- root containers, unpinned base images, secrets baked into image layers, missing Kubernetes securityContext/NetworkPolicy, and Terraform state/secrets handling. Read-only, no edits.",
|
|
4
|
+
"decision": "create",
|
|
5
|
+
"topMatch": "docker-k8s-terraform/docker-k8s-terraform-build-fix",
|
|
6
|
+
"recordedAt": "2026-09-25T15:30:30.700Z",
|
|
7
|
+
"skillName": "docker-k8s-terraform-review"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"query": "Use when a Docker build fails, a Kubernetes/Helm manifest is rejected by validation or the API server, or a Terraform validate/plan fails -- resolves the actual root cause (a bad COPY path, an ownership/permission mismatch after switching to a non-root USER, a schema-invalid manifest, a Terraform state/address mismatch) with the smallest fix, never a suppression.",
|
|
11
|
+
"decision": "create",
|
|
12
|
+
"topMatch": "ci-github-gitlab/ci-pipeline-build-fix",
|
|
13
|
+
"recordedAt": "2026-09-25T15:30:36.249Z",
|
|
14
|
+
"skillName": "docker-k8s-terraform-build-fix"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "docker-k8s-terraform",
|
|
3
|
+
"family": "tool",
|
|
4
|
+
"modules": ["docker-k8s-terraform-rules", "docker-k8s-terraform-skills"],
|
|
5
|
+
"detectionMarkers": ["docker", "docker-compose", "terraform"],
|
|
6
|
+
"provenance": {
|
|
7
|
+
"origin": "authored",
|
|
8
|
+
"sourceRef": "flow 338, Wave 4 batch 6"
|
|
9
|
+
},
|
|
10
|
+
"stability": "experimental",
|
|
11
|
+
"skills": {
|
|
12
|
+
"implement": [],
|
|
13
|
+
"test": [],
|
|
14
|
+
"review": ["docker-k8s-terraform-review"],
|
|
15
|
+
"build-fix": ["docker-k8s-terraform-build-fix"],
|
|
16
|
+
"migrate": []
|
|
17
|
+
},
|
|
18
|
+
"notes": {
|
|
19
|
+
"implement": "No implement skill authored in this batch. W1-stack-catalog.md's target-stack table gives the reason as \"config authoring lives in the deploy quality skill\" -- that does not hold up: `src/gdskills/bundled/skills/quality/deploy/SKILL.md` is scoped to running a deployment pipeline/release (\"Use when deploying a release to any environment\"), not authoring Dockerfiles/K8s manifests/Terraform configs. No existing catalog skill actually covers authoring these files. This is a genuine, honestly-deferred gap, not a covered case -- left for a future batch, and W1-stack-catalog.md's table entry needs its own correction (tracked in that doc's flow 338 review-round notes).",
|
|
20
|
+
"test": "No separate test skill in this batch. This is a scope deferral, not an absence of a real concept: Terraform has `terraform test` (`.tftest.hcl` run blocks) and Helm has `helm test` (chart-defined test hooks) plus the community `helm-unittest` plugin (template unit tests with no cluster required) -- both are genuine, analogous to what go-testing/python-testing cover for their stacks. `docker-k8s-terraform-build-fix`'s reproduce-and-classify step covers running an EXISTING validator (hadolint, kubeconform/kubeval, `terraform validate`/`plan`) to diagnose a failure, which is a different job from writing/extending `.tftest.hcl` or helm-unittest test suites. Left for a future batch.",
|
|
21
|
+
"migrate": "No migrate skill in this batch. This is a scope deferral, not an absence of real migration concepts: Kubernetes API version deprecations (e.g. a manifest still on `apps/v1beta1` needing `apps/v1`), a Terraform provider major-version upgrade with its own breaking-change/state-migration steps, and a Helm chart's own major-version upgrade path are all genuine, recurring migration shapes for this family -- none was concrete/scoped enough to write a focused skill and eval suite around in this batch. Left for a future batch, not claimed as nonexistent."
|
|
22
|
+
},
|
|
23
|
+
"agentProfile": {
|
|
24
|
+
"displayName": "Docker / Kubernetes / Terraform",
|
|
25
|
+
"auditFocus": [
|
|
26
|
+
"a container image actually running as root -- no USER instruction AND no evidence the base image already defaults to a non-root user (see rules/patterns.mdc), or an explicit USER root reintroduced after a non-root user was already set -- or a Kubernetes securityContext missing runAsNonRoot/allowPrivilegeEscalation: false",
|
|
27
|
+
"a base image pinned only by a mutable tag (`FROM node:20`) instead of an immutable digest (`FROM node:20@sha256:...`)",
|
|
28
|
+
"a secret (API key, password, token) baked into an image layer via ARG/ENV/COPY instead of a build-time secret mount or runtime injection",
|
|
29
|
+
"a Kubernetes Pod/Deployment with no resource requests/limits, no readOnlyRootFilesystem, or capabilities not dropped to the minimum the container needs",
|
|
30
|
+
"a namespace or workload with no default-deny NetworkPolicy, or one scoped too broadly to serve as a real boundary",
|
|
31
|
+
"a Terraform resource or module with a hardcoded credential/connection string in `.tf`/`.tfvars`, a `sensitive = true` value treated as if that alone encrypted it, or state configured with a local/unencrypted backend"
|
|
32
|
+
],
|
|
33
|
+
"buildCommands": [
|
|
34
|
+
"hadolint <Dockerfile> (when hadolint is available)",
|
|
35
|
+
"docker build . (or docker compose build for a compose project)",
|
|
36
|
+
"kubeconform -strict -summary <manifest-or-dir> (or kubeval, whichever the project already uses)",
|
|
37
|
+
"terraform validate && terraform plan (only plan, never apply, from this skill)"
|
|
38
|
+
],
|
|
39
|
+
"fixGuardrails": [
|
|
40
|
+
"Never remove a USER/securityContext non-root setting, drop a NetworkPolicy, or relax a capability drop just to make a build/deploy pass.",
|
|
41
|
+
"Never bake a secret into an image layer (ARG/ENV/COPY of a credential) to route around a missing runtime secret — use a build-time secret mount or say the runtime wiring is still needed.",
|
|
42
|
+
"Never commit or read a `.tfstate` file as the source of truth for a fix, and never suggest disabling remote state locking to unblock a run.",
|
|
43
|
+
"Never widen a Terraform `sensitive = true` removal or a K8s NetworkPolicy loosening to silence a validator finding without saying so in the report."
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/Dockerfile", "**/Dockerfile.*", "**/*.Dockerfile", "**/*.dockerfile", "**/Containerfile", "**/docker-compose*.yml", "**/docker-compose*.yaml", "**/compose.yml", "**/compose.yaml", "k8s/**/*.yaml", "k8s/**/*.yml", "manifests/**/*.yaml", "manifests/**/*.yml", "deploy/**/*.yaml", "deploy/**/*.yml", "overlays/**/*.yaml", "**/kustomization.yaml", "**/kustomization.yml", "helm/**/*.yaml", "helm/**/templates/*.yaml", "charts/**/*.yaml", "charts/**/templates/*.yaml", "charts/*/values.yaml", "**/*.tf", "**/*.tfvars"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Docker / Kubernetes / Terraform coding style
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic style rules to the file shapes
|
|
11
|
+
this pack owns: Dockerfiles, Docker Compose files, Kubernetes/Helm YAML, and
|
|
12
|
+
Terraform. Applies only to those file types — not to application source code
|
|
13
|
+
under any of these directories.
|
|
14
|
+
|
|
15
|
+
## Dockerfile
|
|
16
|
+
|
|
17
|
+
- Order instructions from least to most frequently changing (base image,
|
|
18
|
+
system packages, dependency manifests, dependency install, then source
|
|
19
|
+
copy) so Docker's build cache is invalidated only by the layer that
|
|
20
|
+
actually changed, not by every layer after a source edit.
|
|
21
|
+
- Use multi-stage builds (`FROM ... AS builder`, then a slim final stage
|
|
22
|
+
that copies only the built artifact) for any compiled or bundled
|
|
23
|
+
language — the build toolchain never belongs in the shipped image.
|
|
24
|
+
- Prefer a minimal, actively maintained base image (`-slim`/`-alpine`, or a
|
|
25
|
+
distroless/hardened image) over a full OS image unless the project
|
|
26
|
+
genuinely needs the extra tooling at runtime.
|
|
27
|
+
- One logical concern per `RUN` line where it aids readability, but chain
|
|
28
|
+
related package-manager commands (`apt-get update && apt-get install
|
|
29
|
+
-y ... && rm -rf /var/lib/apt/lists/*`) into a single `RUN` so the
|
|
30
|
+
package cache does not persist as a separate layer.
|
|
31
|
+
- Ship a `.dockerignore` alongside the Dockerfile that excludes `.git/`,
|
|
32
|
+
`node_modules/`/`vendor/` (rebuilt inside the image), and any local
|
|
33
|
+
env/secret file — an unfiltered build context both bloats the image and
|
|
34
|
+
risks copying something that should never leave the host.
|
|
35
|
+
|
|
36
|
+
## Docker Compose
|
|
37
|
+
|
|
38
|
+
- Pin every service's `image:` the same way a Dockerfile's `FROM` is
|
|
39
|
+
pinned (a tag, or a digest for anything security-sensitive) — never
|
|
40
|
+
`image: latest` in a committed compose file.
|
|
41
|
+
- Keep secrets and per-environment values in `.env`/`environment:` sourced
|
|
42
|
+
from the shell or a secrets file that is itself gitignored, not
|
|
43
|
+
hardcoded into `docker-compose.yml`.
|
|
44
|
+
- Name every custom network and volume explicitly rather than relying on
|
|
45
|
+
Compose's default naming, so multi-project setups do not collide.
|
|
46
|
+
|
|
47
|
+
## Kubernetes and Helm
|
|
48
|
+
|
|
49
|
+
- One resource kind's concerns per manifest file where the project's
|
|
50
|
+
layout allows it (a Deployment, its Service, its ConfigMap as separate
|
|
51
|
+
files or a clearly delimited multi-document YAML) rather than one
|
|
52
|
+
sprawling file mixing unrelated workloads.
|
|
53
|
+
- Set explicit `resources.requests`/`resources.limits` on every container
|
|
54
|
+
— an unbounded container can starve its node's other workloads, and the
|
|
55
|
+
scheduler cannot place it sensibly without a request.
|
|
56
|
+
- Use a Helm `values.yaml` (or Kustomize overlay) for anything that varies
|
|
57
|
+
per environment — a hardcoded namespace, hostname, or replica count
|
|
58
|
+
inside a template defeats the point of templating.
|
|
59
|
+
- Label and select consistently (`app.kubernetes.io/name`,
|
|
60
|
+
`app.kubernetes.io/instance`) so Services, NetworkPolicies, and
|
|
61
|
+
`kubectl` tooling can all select the same workload by the same labels.
|
|
62
|
+
|
|
63
|
+
## Terraform
|
|
64
|
+
|
|
65
|
+
- Organize a module around `main.tf`/`variables.tf`/`outputs.tf` (plus
|
|
66
|
+
`versions.tf` pinning the provider and Terraform version) rather than
|
|
67
|
+
one large file mixing resource definitions with variable declarations.
|
|
68
|
+
- Name resources and variables for what they are (`aws_db_instance.orders`,
|
|
69
|
+
not `aws_db_instance.db1`), and give every variable a `description` and,
|
|
70
|
+
where one exists, a `type` constraint — an untyped variable defers a
|
|
71
|
+
typo to `apply` time instead of `plan`/`validate` time.
|
|
72
|
+
- Prefer a module for any resource group reused across environments
|
|
73
|
+
(`modules/network`, `modules/service`) over copy-pasted `.tf` blocks per
|
|
74
|
+
environment directory.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/Dockerfile", "**/Dockerfile.*", "**/*.Dockerfile", "**/*.dockerfile", "**/Containerfile", "**/docker-compose*.yml", "**/docker-compose*.yaml", "**/compose.yml", "**/compose.yaml", "k8s/**/*.yaml", "k8s/**/*.yml", "manifests/**/*.yaml", "manifests/**/*.yml", "deploy/**/*.yaml", "deploy/**/*.yml", "overlays/**/*.yaml", "**/kustomization.yaml", "**/kustomization.yml", "helm/**/*.yaml", "helm/**/templates/*.yaml", "charts/**/*.yaml", "charts/**/templates/*.yaml", "charts/*/values.yaml", "**/*.tf", "**/*.tfvars"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Docker / Kubernetes / Terraform patterns
|
|
9
|
+
|
|
10
|
+
Structural patterns for building, orchestrating, and provisioning
|
|
11
|
+
containerized workloads with this pack's three file types. Complements
|
|
12
|
+
`rules/coding-style.mdc` (formatting/organization) with the shape decisions
|
|
13
|
+
that are harder to fix after the fact.
|
|
14
|
+
|
|
15
|
+
## Multi-stage build as the default shape
|
|
16
|
+
|
|
17
|
+
A build stage that needs a compiler, a package manager's dev dependencies,
|
|
18
|
+
or test tooling should never be the stage that ships. Structure:
|
|
19
|
+
|
|
20
|
+
```dockerfile
|
|
21
|
+
FROM node:22-slim AS build
|
|
22
|
+
WORKDIR /app
|
|
23
|
+
COPY package.json package-lock.json ./
|
|
24
|
+
RUN npm ci
|
|
25
|
+
COPY . .
|
|
26
|
+
RUN npm run build && npm run test
|
|
27
|
+
|
|
28
|
+
FROM node:22-slim AS runtime
|
|
29
|
+
WORKDIR /app
|
|
30
|
+
COPY --from=build /app/dist ./dist
|
|
31
|
+
COPY --from=build /app/node_modules ./node_modules
|
|
32
|
+
CMD ["node", "dist/index.js"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The `runtime` stage never sees the source tree's dev dependencies, test
|
|
36
|
+
files, or build toolchain — only what `COPY --from=build` explicitly
|
|
37
|
+
carries over.
|
|
38
|
+
|
|
39
|
+
## Config and code stay separate
|
|
40
|
+
|
|
41
|
+
A Kubernetes `ConfigMap`/`Secret` or a Terraform variable exists so the same
|
|
42
|
+
image/module deploys to more than one environment without a rebuild or a
|
|
43
|
+
hand edit. A hardcoded hostname, replica count, or connection string inside
|
|
44
|
+
a Deployment template or a `.tf` resource block is a sign the value belongs
|
|
45
|
+
in a `values.yaml`, a `ConfigMap`, or a `variable` block instead.
|
|
46
|
+
|
|
47
|
+
## Least-privilege as the default, not an add-on
|
|
48
|
+
|
|
49
|
+
- A container's Dockerfile ends with a non-root `USER` instruction (or the
|
|
50
|
+
base image's own non-root default is kept, not overridden back to root)
|
|
51
|
+
— see `rules/security.mdc` for the full non-root/capability rationale.
|
|
52
|
+
- A Kubernetes workload requests only the RBAC verbs/resources it actually
|
|
53
|
+
calls — a ServiceAccount bound to a namespace-wide `edit`/`cluster-admin`
|
|
54
|
+
role because the specific permission set was not worked out is a pattern
|
|
55
|
+
to flag, not a shortcut to accept.
|
|
56
|
+
- A Terraform IAM/role resource is scoped to the specific actions and
|
|
57
|
+
resource ARNs the workload needs, not a wildcard `"*"` action or
|
|
58
|
+
resource granted because narrowing it took more iteration.
|
|
59
|
+
|
|
60
|
+
## Idempotent, declarative infrastructure
|
|
61
|
+
|
|
62
|
+
- A Terraform module should be safely re-appliable: no `null_resource` or
|
|
63
|
+
`local-exec` provisioner standing in for a resource Terraform's provider
|
|
64
|
+
already models directly, and no resource whose creation depends on
|
|
65
|
+
something outside Terraform's own state (a manually-created bucket
|
|
66
|
+
referenced by name instead of imported or created by the module).
|
|
67
|
+
- A Kubernetes manifest describes the desired end state (replica count,
|
|
68
|
+
image tag, resource limits) rather than an imperative sequence of
|
|
69
|
+
`kubectl` commands baked into a CI script — prefer `kubectl apply -f`
|
|
70
|
+
(or a GitOps controller) over a hand-run sequence of `create`/`patch`
|
|
71
|
+
commands that drift from the checked-in manifest.
|
|
72
|
+
|
|
73
|
+
## Module and environment layout
|
|
74
|
+
|
|
75
|
+
Prefer one Terraform module per reusable resource group (`modules/vpc`,
|
|
76
|
+
`modules/eks-cluster`) instantiated per environment (`environments/staging`,
|
|
77
|
+
`environments/production`) with environment-specific variables, over a
|
|
78
|
+
single flat set of `.tf` files gated by `count`/`var.environment ==
|
|
79
|
+
"production"` conditionals scattered through the resources themselves — the
|
|
80
|
+
conditional form makes it easy for one environment's change to silently
|
|
81
|
+
affect another's plan.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/Dockerfile", "**/Dockerfile.*", "**/*.Dockerfile", "**/*.dockerfile", "**/Containerfile", "**/docker-compose*.yml", "**/docker-compose*.yaml", "**/compose.yml", "**/compose.yaml", "k8s/**/*.yaml", "k8s/**/*.yml", "manifests/**/*.yaml", "manifests/**/*.yml", "deploy/**/*.yaml", "deploy/**/*.yml", "overlays/**/*.yaml", "**/kustomization.yaml", "**/kustomization.yml", "helm/**/*.yaml", "helm/**/templates/*.yaml", "charts/**/*.yaml", "charts/**/templates/*.yaml", "charts/*/values.yaml", "**/*.tf", "**/*.tfvars"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Docker / Kubernetes / Terraform security
|
|
9
|
+
|
|
10
|
+
Narrows `core-common-rules`' stack-agnostic security rules to the risks
|
|
11
|
+
specific to building images, running them on Kubernetes, and provisioning
|
|
12
|
+
infrastructure with Terraform. This skill's scope is the source files
|
|
13
|
+
(Dockerfile, compose, manifests, `.tf`) — a built image's own vulnerability
|
|
14
|
+
scan is `security-audit`'s job, not this rule's.
|
|
15
|
+
|
|
16
|
+
## Non-root by default
|
|
17
|
+
|
|
18
|
+
- Every Dockerfile either ends with a `USER` instruction naming a non-root
|
|
19
|
+
user (one created with `RUN useradd`, or the base image's own built-in
|
|
20
|
+
unprivileged user re-selected explicitly, e.g. `USER nginx`), or keeps a
|
|
21
|
+
base image's own non-root default un-overridden. A missing `USER`
|
|
22
|
+
instruction is not by itself proof of root — some base images (e.g.
|
|
23
|
+
`nginxinc/nginx-unprivileged`) already default to a non-root user with
|
|
24
|
+
no `USER` line needed — but it is not proof of non-root either; confirm
|
|
25
|
+
what the base image's own default actually is (its Dockerfile/docs, or
|
|
26
|
+
`docker run --rm <image> whoami`) rather than assuming either way from
|
|
27
|
+
the absence of a `USER` line alone.
|
|
28
|
+
- The matching Kubernetes `securityContext` sets `runAsNonRoot: true`,
|
|
29
|
+
`allowPrivilegeEscalation: false`, and drops all capabilities
|
|
30
|
+
(`capabilities.drop: ["ALL"]`), adding back only the specific ones the
|
|
31
|
+
container actually needs (e.g. `NET_BIND_SERVICE` to bind a low port).
|
|
32
|
+
A `privileged: true` container is a last resort that needs its own
|
|
33
|
+
justification in the change, never a default reached for to make a
|
|
34
|
+
permission error disappear.
|
|
35
|
+
- Set `readOnlyRootFilesystem: true` where the workload allows it, mounting
|
|
36
|
+
a scoped `emptyDir`/`tmpfs` for the specific paths that need to be
|
|
37
|
+
writable at runtime (`/tmp`, a cache directory) rather than leaving the
|
|
38
|
+
whole filesystem writable because one path needs to be.
|
|
39
|
+
|
|
40
|
+
## Pin base images by digest, not tag alone
|
|
41
|
+
|
|
42
|
+
A mutable tag (`FROM node:22`, `FROM node:22-slim`) can point at a
|
|
43
|
+
different image tomorrow than it does today — the base image is not
|
|
44
|
+
actually pinned until the digest is. For anything security-sensitive
|
|
45
|
+
(a production image, a CI build step that must be reproducible), pin with
|
|
46
|
+
the immutable digest:
|
|
47
|
+
|
|
48
|
+
```dockerfile
|
|
49
|
+
FROM node:22-slim@sha256:<digest>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A tag alone is acceptable for local/throwaway development; flag a
|
|
53
|
+
digest-less `FROM` in a Dockerfile that is clearly meant to ship.
|
|
54
|
+
|
|
55
|
+
## Never bake a secret into an image layer
|
|
56
|
+
|
|
57
|
+
`ARG`/`ENV` values, and anything `COPY`'d into an image, persist in that
|
|
58
|
+
layer even if a later layer deletes or overwrites the file — a secret
|
|
59
|
+
baked in during an early `RUN`/`COPY` is still recoverable from the image's
|
|
60
|
+
layer history. Use BuildKit's build-time secret mount instead, which never
|
|
61
|
+
writes the secret into a layer:
|
|
62
|
+
|
|
63
|
+
```dockerfile
|
|
64
|
+
# syntax=docker/dockerfile:1
|
|
65
|
+
RUN --mount=type=secret,id=npm_token \
|
|
66
|
+
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Runtime secrets (a database password, an API key a running container
|
|
70
|
+
needs) belong in a Kubernetes `Secret` (or the platform's secret store),
|
|
71
|
+
mounted as a file or environment variable at run time — never baked into
|
|
72
|
+
the image `ENV` at build time.
|
|
73
|
+
|
|
74
|
+
## Kubernetes network boundaries
|
|
75
|
+
|
|
76
|
+
A namespace with no `NetworkPolicy` allows every pod to reach every other
|
|
77
|
+
pod by default — that is Kubernetes' actual default, not a hardened one.
|
|
78
|
+
Start from a default-deny policy and open only the specific paths a
|
|
79
|
+
workload needs:
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
apiVersion: networking.k8s.io/v1
|
|
83
|
+
kind: NetworkPolicy
|
|
84
|
+
metadata:
|
|
85
|
+
name: default-deny
|
|
86
|
+
spec:
|
|
87
|
+
podSelector: {}
|
|
88
|
+
policyTypes: ["Ingress", "Egress"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
then a second, narrower `NetworkPolicy` per workload allowing only the
|
|
92
|
+
`podSelector`/`namespaceSelector` traffic it actually needs. Flag a
|
|
93
|
+
namespace shipping workload manifests with no accompanying `NetworkPolicy`
|
|
94
|
+
at all, and flag a per-workload `NetworkPolicy` broad enough to defeat the
|
|
95
|
+
point of having one -- a `spec.ingress` entry that is a single EMPTY rule
|
|
96
|
+
object (`ingress: [{}]`, no `from`/`ports` inside it) matches every source
|
|
97
|
+
on every port, i.e. allow-all, not the deny-all the pattern above depends
|
|
98
|
+
on (NetworkPolicy is fail-closed: `policyTypes: ["Ingress"]` with `ingress`
|
|
99
|
+
omitted or `ingress: []` means NO ingress rule matches, which is deny-all
|
|
100
|
+
-- do not confuse an EMPTY `ingress:` array with an array containing one
|
|
101
|
+
EMPTY rule object; they are opposite policies), or a `podSelector: {}` on a
|
|
102
|
+
policy meant to be workload-scoped (matches every pod in the namespace,
|
|
103
|
+
not just the intended workload).
|
|
104
|
+
|
|
105
|
+
## Terraform state and secrets
|
|
106
|
+
|
|
107
|
+
- Configure a remote backend with encryption at rest and state locking
|
|
108
|
+
(e.g. an S3 backend with `encrypt = true` and `use_lockfile = true` for
|
|
109
|
+
native S3-object-based locking — the current recommended approach,
|
|
110
|
+
superseding a separate DynamoDB lock table for new configurations,
|
|
111
|
+
though an existing DynamoDB-locked backend is not itself a problem to
|
|
112
|
+
flag on its own; or an HCP Terraform/Terraform Cloud workspace, which
|
|
113
|
+
encrypts state and locks automatically) — a local `terraform.tfstate` is
|
|
114
|
+
both unencrypted on disk and a single point of loss/corruption with no
|
|
115
|
+
locking against concurrent runs.
|
|
116
|
+
- Renaming or moving a resource/module block in configuration changes its
|
|
117
|
+
address, which makes an unrelated `terraform plan` show a destroy-then-
|
|
118
|
+
recreate for a resource nothing actually changed about (state still has
|
|
119
|
+
it under the old address) — fix with a `moved` block (`moved { from =
|
|
120
|
+
<old_address> to = <new_address> }`, declarative, stays in the
|
|
121
|
+
configuration for the next person who reads it) or the equivalent
|
|
122
|
+
`terraform state mv <old_address> <new_address>` (imperative, a one-off
|
|
123
|
+
CLI operation) — never by applying the destroy/recreate plan as-is for a
|
|
124
|
+
resource holding real data.
|
|
125
|
+
- A `force_destroy = true` on a resource that can hold data (an S3 bucket,
|
|
126
|
+
in particular) lets `terraform destroy`/a replacement delete a
|
|
127
|
+
non-empty bucket without the safety error Terraform would otherwise
|
|
128
|
+
raise — flag it on any resource whose data matters, especially paired
|
|
129
|
+
with a `moved`/`state mv` scenario where the goal is to KEEP the
|
|
130
|
+
existing data, not clear the way for a fresh create.
|
|
131
|
+
- Never commit a `.tfstate`/`.tfstate.backup` file to version control —
|
|
132
|
+
state routinely contains resource attributes in plaintext (a generated
|
|
133
|
+
password, a connection string) even when the variable that produced them
|
|
134
|
+
was declared `sensitive`.
|
|
135
|
+
- Mark any variable or output that carries a credential, connection
|
|
136
|
+
string, or key `sensitive = true` so Terraform redacts it from CLI plan
|
|
137
|
+
output and logs — but treat `sensitive = true` as a **display**
|
|
138
|
+
protection, not an encryption one: the value is still written to state
|
|
139
|
+
in plain form, and `terraform output <name>` still prints a sensitive
|
|
140
|
+
output's value by name. Encryption-at-rest for that value comes from the
|
|
141
|
+
backend configuration above, not from the `sensitive` flag.
|
|
142
|
+
- Never hardcode a credential directly in a `.tf`/`.tfvars` file — source
|
|
143
|
+
it from a secrets manager data source (Vault, AWS Secrets Manager/SSM
|
|
144
|
+
Parameter Store) or an injected environment variable, the same rule
|
|
145
|
+
`core-common-rules` already states for application source code, applied
|
|
146
|
+
here to infrastructure code.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
extends: common
|
|
3
|
+
paths: ["**/Dockerfile", "**/Dockerfile.*", "**/*.Dockerfile", "**/*.dockerfile", "**/Containerfile", "**/docker-compose*.yml", "**/docker-compose*.yaml", "**/compose.yml", "**/compose.yaml", "k8s/**/*.yaml", "k8s/**/*.yml", "manifests/**/*.yaml", "manifests/**/*.yml", "deploy/**/*.yaml", "deploy/**/*.yml", "overlays/**/*.yaml", "**/kustomization.yaml", "**/kustomization.yml", "helm/**/*.yaml", "helm/**/templates/*.yaml", "charts/**/*.yaml", "charts/**/templates/*.yaml", "charts/*/values.yaml", "**/*.tf", "**/*.tfvars"]
|
|
4
|
+
metadata:
|
|
5
|
+
origin: authored
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Docker / Kubernetes / Terraform validation
|
|
9
|
+
|
|
10
|
+
This pack ships no separate `test` skill yet (see `pack.json`'s
|
|
11
|
+
`notes.test` — genuine test-writing concepts exist for this family,
|
|
12
|
+
`terraform test`/`.tftest.hcl` and `helm test`/helm-unittest chief among
|
|
13
|
+
them; the deferral is a scope decision for this batch, not a claim that no
|
|
14
|
+
such concept exists). What this rule covers instead is VALIDATION: a
|
|
15
|
+
static, tool-driven check that an existing file is well-formed and
|
|
16
|
+
schema-correct, exercised from `docker-k8s-terraform-build-fix`'s own
|
|
17
|
+
reproduce-and-classify step. This rule states what "validated" means for
|
|
18
|
+
each file type so that step — and any manual check — has a concrete bar.
|
|
19
|
+
|
|
20
|
+
## Dockerfile and Compose
|
|
21
|
+
|
|
22
|
+
- `hadolint <Dockerfile>` when the project has hadolint configured (a
|
|
23
|
+
`.hadolint.yaml` present, or the tool available on PATH) — catches
|
|
24
|
+
missing pins, `apt-get` without `--no-install-recommends`, and other
|
|
25
|
+
Dockerfile-specific lint findings before a build is even attempted.
|
|
26
|
+
- `docker build .` (or `docker compose build` for a multi-service project)
|
|
27
|
+
must exit 0 with no `--no-cache` needed to reproduce a passing state —
|
|
28
|
+
a build that only succeeds from a stale cache is not actually validated.
|
|
29
|
+
- For a compose project, `docker compose config` validates the merged
|
|
30
|
+
Compose file parses and every referenced `.env` variable resolves,
|
|
31
|
+
before attempting `up`.
|
|
32
|
+
|
|
33
|
+
## Kubernetes and Helm
|
|
34
|
+
|
|
35
|
+
- `kubeconform -strict -summary <manifest-or-dir>` (or `kubeval`, whichever
|
|
36
|
+
the project already has configured) validates every manifest against the
|
|
37
|
+
Kubernetes API schema for the cluster's target version — a manifest that
|
|
38
|
+
parses as YAML can still fail this (unknown field, wrong type) and
|
|
39
|
+
should not be treated as validated on YAML-parse success alone.
|
|
40
|
+
- `helm template <chart> | kubeconform -strict` for a Helm chart — template
|
|
41
|
+
rendering can itself fail (a missing `values.yaml` key with no default)
|
|
42
|
+
independently of the rendered manifests' own schema validity, so both
|
|
43
|
+
failure modes need to be distinguished when reporting a finding.
|
|
44
|
+
- `kubectl apply --dry-run=server -f <manifest>` (or `--dry-run=client` when
|
|
45
|
+
no live cluster is reachable) catches a resource the API server would
|
|
46
|
+
reject that schema validation alone does not (an immutable field on an
|
|
47
|
+
existing resource, a missing CRD).
|
|
48
|
+
|
|
49
|
+
## Terraform
|
|
50
|
+
|
|
51
|
+
- `terraform fmt -check` and `terraform validate` are the syntax/internal-
|
|
52
|
+
consistency floor — both must pass before a `terraform plan` is
|
|
53
|
+
attempted, since `validate` catches type errors and missing required
|
|
54
|
+
arguments that `plan` would otherwise fail on less legibly.
|
|
55
|
+
- `terraform plan` (never `apply` from this pack's skills) shows the actual
|
|
56
|
+
proposed change; a plan showing an unexpected destroy/replace on a
|
|
57
|
+
resource the change was not meant to touch is a validation failure in
|
|
58
|
+
its own right, not just a formality to skip past.
|
|
59
|
+
- A policy check (`conftest`/OPA, or the project's own policy tool) when
|
|
60
|
+
one is configured — treat its findings the same as a schema-validator
|
|
61
|
+
finding, not as optional advice to skip under time pressure.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docker-k8s-terraform-build-fix
|
|
3
|
+
description: "Use when a Docker build fails, a Kubernetes/Helm manifest is rejected by validation or the API server, or a Terraform validate/plan fails -- resolves the actual root cause (a bad COPY path, an ownership/permission mismatch after switching to a non-root USER, a schema-invalid manifest, a Terraform state/address mismatch) with the smallest fix, never a suppression. Not for an application-code build failure unrelated to these config files (a Go/TypeScript/Python compile or test error) -- that is the language's own build-fix skill's territory, even when this pack's Dockerfile happens to build that language's code."
|
|
4
|
+
triggers:
|
|
5
|
+
- "docker build is failing"
|
|
6
|
+
- "fix this Kubernetes manifest validation error"
|
|
7
|
+
- "terraform plan wants to destroy and recreate this resource"
|
|
8
|
+
- "terraform validate is failing"
|
|
9
|
+
- "this Dockerfile permission denied error"
|
|
10
|
+
- "kubectl apply is rejecting this manifest"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: build-fix
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Docker / Kubernetes / Terraform build fix
|
|
20
|
+
|
|
21
|
+
Resolve a `docker build` failure, a Kubernetes/Helm manifest rejected by
|
|
22
|
+
schema validation or the API server, or a `terraform validate`/`plan`
|
|
23
|
+
failure — with the smallest change that fixes the actual root cause.
|
|
24
|
+
`rules/coding-style.mdc` and `rules/security.mdc` govern what a "correct"
|
|
25
|
+
fix looks like; this skill never reaches for a suppression or a shortcut
|
|
26
|
+
that undoes a prior security fix (reverting to root, disabling a lint rule,
|
|
27
|
+
forcing an unreviewed apply) instead of fixing what actually broke.
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### Step 1: Reproduce and classify
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
docker build . # or: docker compose build
|
|
35
|
+
kubeconform -strict -summary <manifest> # or: kubectl apply --dry-run=server -f <manifest>
|
|
36
|
+
terraform validate && terraform plan
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Read the exact error text and classify it:
|
|
40
|
+
|
|
41
|
+
- **Docker build error** (a `COPY`/`ADD` path that does not exist in the
|
|
42
|
+
build context, a missing `.dockerignore` exclusion breaking a needed
|
|
43
|
+
file, a package-manager install failure, a permission-denied write after
|
|
44
|
+
switching to a non-root `USER`).
|
|
45
|
+
- **Kubernetes/Helm validation error** (an unknown field, a wrong type, a
|
|
46
|
+
Helm template rendering failure from a missing `values.yaml` key, or a
|
|
47
|
+
live API-server rejection such as an immutable field on an existing
|
|
48
|
+
resource).
|
|
49
|
+
- **Terraform error** (a type mismatch or missing required argument
|
|
50
|
+
`validate` catches, a provider/version constraint conflict, or a `plan`
|
|
51
|
+
showing an unexpected destroy/replace after a resource rename or
|
|
52
|
+
refactor).
|
|
53
|
+
|
|
54
|
+
### Step 2: Fix by category
|
|
55
|
+
|
|
56
|
+
**Docker build error:** for a missing `COPY`/`ADD` source, check the build
|
|
57
|
+
context and `.dockerignore` before assuming the file is genuinely absent —
|
|
58
|
+
a `.dockerignore` entry that is too broad is a common real cause. For a
|
|
59
|
+
permission-denied write after a `USER` switch to a non-root user, fix
|
|
60
|
+
ownership at copy time (`COPY --chown=<user>:<group> ...`) or with an
|
|
61
|
+
explicit `RUN chown`/`mkdir -p` step **before** the `USER` instruction
|
|
62
|
+
switches away from root — never fix it by reverting to `USER root` or by
|
|
63
|
+
running `chmod 777` on the directory; both undo the non-root hardening
|
|
64
|
+
this pack's security rule requires instead of fixing the actual ownership
|
|
65
|
+
mismatch.
|
|
66
|
+
|
|
67
|
+
**Kubernetes/Helm validation error:** for a schema error, fix the field
|
|
68
|
+
name/type the validator names — do not delete the offending field to make
|
|
69
|
+
the validator stop complaining if the field is actually required for the
|
|
70
|
+
workload to function correctly. For a live API-server rejection on an
|
|
71
|
+
immutable field (e.g. a Deployment's selector), the fix is usually to
|
|
72
|
+
recreate the resource under change management (a new name, or a deliberate
|
|
73
|
+
delete+recreate the team has agreed to), not to strip the field or force
|
|
74
|
+
an update that the API server is correctly refusing.
|
|
75
|
+
|
|
76
|
+
**Terraform error:** for a type/argument error, fix the resource/variable
|
|
77
|
+
declaration `validate` names. For an unexpected destroy+recreate after a
|
|
78
|
+
rename or a module refactor, relink the existing real resource to its new
|
|
79
|
+
configuration address — either a `moved` block (`moved { from =
|
|
80
|
+
<old_address> to = <new_address> }`, declarative, stays in the
|
|
81
|
+
configuration) or the equivalent `terraform state mv <old_address>
|
|
82
|
+
<new_address>` (imperative, a one-off CLI operation) — never accept the
|
|
83
|
+
destroy+recreate plan with `-auto-approve` (or any unreviewed apply) for a
|
|
84
|
+
stateful resource just to make the plan "go through"; that actually
|
|
85
|
+
destroys and rebuilds the real infrastructure the rename was never meant
|
|
86
|
+
to touch.
|
|
87
|
+
|
|
88
|
+
### Step 3: Verify
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
docker build . # or: docker compose build
|
|
92
|
+
kubeconform -strict -summary <manifest>
|
|
93
|
+
terraform validate && terraform plan
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Re-run the specific command whose failure this fix addresses, plus any
|
|
97
|
+
project-configured validator (`hadolint`, `helm template | kubeconform`).
|
|
98
|
+
All must exit 0 — and for the Terraform case, `terraform plan` must show
|
|
99
|
+
no destroy/replace on the resource the fix was meant to preserve — before
|
|
100
|
+
reporting done.
|
|
101
|
+
|
|
102
|
+
### Step 4: Report
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Fixed: Dockerfile permission-denied write to /app after switching to
|
|
106
|
+
USER app
|
|
107
|
+
- Root cause: files were COPY'd while still root, so `app` had no
|
|
108
|
+
write access to /app
|
|
109
|
+
- Fix: added --chown=app:app to the COPY instruction (no chmod, no
|
|
110
|
+
reverting to root)
|
|
111
|
+
- docker build . passes
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
State the root cause in one sentence, not just "fixed the error."
|
|
115
|
+
|
|
116
|
+
## Rules
|
|
117
|
+
|
|
118
|
+
- Find and fix the smallest change that addresses the actual root cause —
|
|
119
|
+
never widen a fix beyond what the failure requires.
|
|
120
|
+
- NEVER fix a permission-denied error by running `chmod 777` or reverting
|
|
121
|
+
to `USER root` — fix ownership at the actual `COPY`/`RUN` step instead.
|
|
122
|
+
- NEVER accept an unexpected Terraform destroy/replace with
|
|
123
|
+
`-auto-approve` (or any unreviewed apply) — use a `moved` block or
|
|
124
|
+
`terraform state mv` (or another state-preserving fix) when the
|
|
125
|
+
resource itself was not meant to change.
|
|
126
|
+
- NEVER delete a required Kubernetes manifest field just to make a
|
|
127
|
+
validator stop complaining.
|
|
128
|
+
- NEVER delete or skip a failing validation step to reach a green build.
|
|
129
|
+
|
|
130
|
+
## Red Flags
|
|
131
|
+
|
|
132
|
+
| Rationalization | Why it is wrong |
|
|
133
|
+
|---|---|
|
|
134
|
+
| "I'll just `chmod 777` the directory, it's only a build image" | Undoes the non-root hardening this pack's security rule requires and grants write access to everyone, not just the user that actually needs it; fix ownership at the COPY/RUN step instead |
|
|
135
|
+
| "The rename makes Terraform want to destroy and recreate it, but the resource is basically the same, so `-auto-approve` should be fine" | Terraform's plan is not wrong about what it will do — accepting an unreviewed destroy+recreate on a stateful resource can genuinely lose data; a `moved` block or `terraform state mv` fixes the address without touching real infrastructure |
|
|
136
|
+
| "This field keeps failing validation, I'll just remove it" | If the field is actually required for the workload (a probe, a resource limit), removing it trades a caught validation error for a runtime failure later; fix the field's value instead |
|
|
137
|
+
| "I'll switch back to `USER root` just to unblock this build, we can fix permissions properly later" | Reintroduces a root container to solve an ownership problem that a scoped `--chown`/`chown` step solves without giving up the non-root hardening |
|
|
138
|
+
|
|
139
|
+
## Verification
|
|
140
|
+
|
|
141
|
+
Do not report the fix done until all of the following hold:
|
|
142
|
+
|
|
143
|
+
- The specific failing command (`docker build`, `kubeconform`/`kubectl
|
|
144
|
+
apply --dry-run`, `terraform validate`/`plan`) now exits 0.
|
|
145
|
+
- For a Terraform fix: `terraform plan` shows no destroy/replace on the
|
|
146
|
+
resource the fix was meant to preserve.
|
|
147
|
+
- The change is the smallest one that addresses the stated root cause —
|
|
148
|
+
no unrelated files touched, and no security hardening (non-root user,
|
|
149
|
+
digest pin, `NetworkPolicy`) reverted to reach a green build.
|
|
150
|
+
- The report states the root cause in one sentence, not just "build now
|
|
151
|
+
passes."
|