klinux / klinux/gocdnext

hardening(parser): extends drops child deploy/cluster + non-presence-aware clear (slice of #209)

Open
#219 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
7
Forks
3
Avg merge
2h 12m
Merged PRs (30d)
30

Description

Carved out of #209 ("definition-drift safety") so the extends merge bug can be fixed in isolation, smallest-first. The rest of #209 (snapshot-vs-current def, admission_fingerprint fence, revival-set validation, native-takeover fence, drifted-gate reconciliation) stays there as separate steps and is not in scope here.

Problem

mergeJobDef (server/pkg/parser/extends.go:97) starts out := base and then overrides field-by-field. Three job fields are handled incorrectly:

  • Deploy and Cluster have NO merge branch — so out keeps base.Deploy/base.Cluster and a child's own deploy:/cluster: is silently dropped. A child that extends a base and declares its own deploy: loses it; a child that sets cluster: to override the template's is ignored.
  • Cluster is a plain string (JobDef.Cluster, checked != ""), so cluster: null, cluster: "" and an absent key all collapse to "" — there is no way to clear an inherited cluster. Combined with the missing merge branch, cluster: null on a child silently inherits the template's (possibly prod) cluster instead of clearing it. This is a footgun: it can point a job at the wrong cluster.
  • Deploy is *DeploySpec (nil = absent), so deploy: null collapses to nil and cannot be distinguished from "absent" — no explicit clear.
  • Environment (#206) is currently child-wins-or-zero (extends.go:171-178: out.Environment = yaml.Node{}, then the child's if present) — it deliberately does NOT inherit from the base. The #206 comment states this was left for #209: "base inheritance is deliberately NOT added (that's #209, with deploy:/cluster:)."

Net effect: extends is unusable for sharing a deploy config, and cluster: null is a silent footgun.

Contract to settle (the key design decision)

Presence-aware three-state semantics for Deploy / Cluster / Environment:

state Option A — inherit (recommended) Option B — safe (today's env behaviour)
absent inherit base zero (no inherit)
explicit value override override
null / "" clear (never inherit) zero

Both options agree: explicit child value overrides, and null/"" clears (this is what kills the cluster: null footgun). They differ only on absent.

Recommendation: Option A, uniform for all three. It is what extends already means for every other field (image:, script:), a base sharing deploy:/cluster: is legitimate DRY, and the removal of the temporary env-non-inherit is exactly what #206 deferred to here.

Security note (must be decided consciously): Option A makes a base template's deploy:/environment: inheritable, so freeze governance (#202/#206) then applies to children that inherit a governed environment. That is correct when the inheritance is intended, and it is surfaced rather than silent (extending the base is an explicit choice; null clears). If the preferred posture is that deploy/environment must never inherit, choose Option B, or a hybrid (cluster inherits like other config; deploy/environment do not). Whichever is chosen, the presence-aware clear and explicit-override halves ship regardless.

Proposed direction

  • Make Cluster presence-aware — a yaml.Node (mirroring Environment) or *string + presence — so absent / null / value are distinct and an explicit clear is possible.
  • Make Deploy distinguish deploy: null (explicit clear) from absent (a presence flag alongside the *DeploySpec, or a node wrapper). If deploy: null clear is judged unnecessary, state that explicitly and keep nil = absent.
  • Rewrite the Deploy/Cluster/Environment block of mergeJobDef to the agreed contract, uniform and presence-aware.
  • Presence-aware JobDef.UnmarshalYAML recording presence/tag for these fields, preserving KnownFields (strict unknown-key detection must still fire).
  • Post-merge validation (runs AFTER the merge, so a child that clears or overrides is checked, not the pre-merge shape): deploy × environment coherence, cluster only meaningful with a deploy/target, and the existing invalid-cluster-name check (parse_job.go:26).
  • Emit round-trip (server/pkg/parser/emit.go): Environment already round-trips (:175-178); Cluster/Deploy must round-trip presence (absent stays absent, explicit clear stays clear, value stays value).

Tests

  • extends chain (grandparent → base → child) inheriting / overriding / clearing each of deploy/cluster/environment;
  • explicit clear: cluster: null (and deploy: null if supported) on a child extending a base that sets them → cleared, NOT inherited;
  • child explicit value wins; absent behaves per the chosen contract;
  • round-trip (parse → emit → parse) preserves absent vs explicit-clear vs value;
  • freeze governance under Option A: a child inheriting a base's governed environment is held by a freeze on that env (guards the security-sensitive path).

Baseline: origin/main (v0.83.0). Verify line numbers against current origin/main.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at mergeJobDef in server/pkg/parser/extends.go:97, then inspect JobDef.UnmarshalYAML, server/pkg/parser/emit.go, and parse_job.go:26. First settle the absent/value/clear contract for Deploy, Cluster, and Environment. Done means post-merge validation, round-trip preservation, and the listed inheritance, clearing, override, and freeze-governance tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, yaml
Domain
backend, ci-cd
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.