Skip to content

chore(defaultconfig): curate the shipped default + add extension capability - #365

Merged
thomasboni merged 5 commits into
mainfrom
chore/simplify-default-config-embed
Jul 31, 2026
Merged

chore(defaultconfig): curate the shipped default + add extension capability#365
thomasboni merged 5 commits into
mainfrom
chore/simplify-default-config-embed

Conversation

@thomasboni

@thomasboni thomasboni commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Two user-facing improvements to how Plumber is configured.

1. A safer, cleaner default

The shipped default .plumber.yaml is now curated so a project's first scan catches real supply-chain and hardening problems without a flood of false positives. The same default is used everywhere: a zero-config run, plumber config generate, and the plumber config init wizard all produce a consistent posture, so there is no drift between them.

2. Customize without owning the whole file

Until now, changing one setting meant copying the entire configuration and maintaining all of it yourself. You can now keep a small file instead.

Add extends: plumber:default at the top of your .plumber.yaml and list only what you want to change. Everything else is inherited from Plumber's baseline, and new checks that Plumber ships are picked up automatically. A file without extends keeps behaving exactly as before, so existing configs are unaffected.

For trusted-source lists (GitHub Actions and container registries), includePlumberDefaults: true (the default) keeps Plumber's curated list and adds your entries on top. Set it to false to trust only your own entries.

Example:

extends: plumber:default
github:
  controls:
    githubActionMustComeFromAuthorizedSources:
      trustedGithubActions:
        - my-org

New config commands

  • plumber config generate --overlay writes a short starter overlay instead of the full file.
  • plumber config resolve prints the complete effective configuration, useful to review it or commit a fully explicit version.
  • plumber config view --explain shows, per control, whether a value comes from the baseline or from your file.
  • plumber config slim turns an existing full configuration into a minimal overlay.

Closes #375.

@thomasboni thomasboni changed the title chore(defaultconfig): drop unused build-header machinery from embed chore(defaultconfig): simplify embed pipeline + curate the shipped default Jul 28, 2026
Comment thread defaultConfig/.plumber.yaml
Comment thread defaultConfig/.plumber.yaml
Comment thread cmd/init_test.go
@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch 2 times, most recently from f78a0bc to 1af07ef Compare July 28, 2026 13:33
Comment thread cmd/init_test.go
@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch from 1af07ef to 2381aaf Compare July 28, 2026 13:48
Comment thread defaultConfig/.plumber.yaml
Comment thread configuration/plumberconfig.go
Comment thread cmd/config_slim.go
Comment thread cmd/config_slim.go
Comment thread configuration/resolve.go
Comment thread cmd/config_slim.go Fixed
Comment thread cmd/config.go
Comment thread cmd/config_explain.go
Comment thread cmd/config_resolve_test.go Outdated
Comment thread cmd/config_resolve.go
@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch 2 times, most recently from 358caca to 5ed924a Compare July 30, 2026 13:53
Comment thread configuration/merge.go
Comment thread configuration/resolve.go
@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch from 5ed924a to 14a89c1 Compare July 30, 2026 14:37
Comment thread cmd/config_slim.go
@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch from 3ae3ef9 to b129111 Compare July 30, 2026 15:17
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

All Claude review controls completed on the latest revision.

@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch from b129111 to 0fd9b54 Compare July 30, 2026 16:04
@thomasboni

Copy link
Copy Markdown
Contributor Author

@Joseph94m ready for review

@thomasboni
thomasboni force-pushed the chore/simplify-default-config-embed branch from 0fd9b54 to be35c6e Compare July 30, 2026 16:18
@thomasboni thomasboni changed the title chore(defaultconfig): simplify embed pipeline + curate the shipped default chore(defaultconfig): curate the shipped default + add extension capability Jul 30, 2026
@Joseph94m

Joseph94m commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

@thomasboni
First question:
is the extends implemented in a way so that in the future, users will be able to write:
extends: plumber:/local/default/config/path or extends: plumber:/remote/git/path ?

I don't mean that the feature should work now but does the code make it easy or difficult on us to do that change? Because I can see the feature coming.


Second question:
does extends plumber:default refer to the in-memory config only? Or any config that is discovered automatically by plumber such as plumber.yaml or passed through the --config flag ?

If its not just the default config / automatic ones that are taken as bases, then I am not sure if the name should be default or something else like extends plumber:base ?


Third question:

This is just to verify that my understanding is correct.
I have this setup: a default .plumber.yaml an overlay.yaml in the same repo. I run

plumber config view --explain and i get the follow results. It shows that everything comes from an overlay because we didn't specify a --config? So it takes the .plumber.yaml and fully ignores the overlay.yaml ?

image

Alternatively when running ./plumber config view --explain --config overlay.yaml i get :

Which seems to work because i only overlayed the actionsMustBePinnedByCommitSha control

Screenshot 2026-07-31 at 10 16 58

Fourth Question:

plumber config slim is confusing to me. Its doc says

./plumber config slim --help
Collapse a full .plumber.yaml into a small overlay that extends
plumber:default, keeping only the controls whose values differ from the
baseline. Comments are not preserved; the result is a fresh minimal file.
Review it, then commit. Use 'plumber config resolve' to expand it again.

But isn't the .plumber.yaml the default? so shouldn't the output be empty?

@thomasboni

Copy link
Copy Markdown
Contributor Author

@Joseph94m

  1. Current code make this future implementation easy, no refactor will be needed
  2. Extends refer to the in-memory config only, nothing else. We keep the name plumber:default, it means Plumber shipped default. base is more generic and can get ambiguous if multiple extend sources exist
  3. yes for your both questions: it shows that everything comes from an overlay because you didn't specify a --config, so it takes the .plumber.yaml and fully ignores the overlay.yaml
  4. .plumber.yaml is not the default, it's your config. If it's the same to the default, then yes slim's output will be empty. Slim exists for the case where the file is a full copy that has drifted across versions

thomasboni and others added 5 commits July 31, 2026 16:11
The Makefile `embed` target prepended a `# DO NOT EDIT` header before catting
defaultConfig/.plumber.yaml, but no shipping build path uses it: CI, the release
workflow, and the Dockerfile all `cp` the source verbatim before `go build`.
That left embed.go carrying `buildHeader` + strip logic (and a `GetRaw` helper)
solely to undo a header production builds never produced.

Align `make embed` to a plain `cp` so dev and CI generate byte-identical output,
then remove the dead header handling: `Get()` is just `return Config`, delete
`GetRaw()`, drop the unused `bytes` import, and fix the stale doc comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…it` from it

Rework the zero-config baseline (defaultConfig/.plumber.yaml) so a project's
FIRST run catches real supply-chain / hardening issues without a flood of false
positives, then have `plumber config init` derive its wizard defaults from that
same embedded config — one source of truth, no duplication, no drift.

Curated default (draws on the FP-calibrated radar study config, minus
study/radar-specific bits):
- githubActionMustComeFromAuthorizedSources: curated well-known-publisher
  allowlist (~155 orgs + lowercase aliases) + minimumStars: 20000, replacing the
  Plumber-self allowlist (the #1 false-positive source on real repos).
- containerImageMustComeFromAuthorizedSources (GitLab): drop radar-specific
  trustedUrls; enrich with generic official / vendor registries.
- pipelineMustNotIncludeHardcodedJobs (GitLab): disabled by default.
- securityJobsMustNotBeWeakened (GitLab): enable rules/when, keep allow_failure
  off (stock GitLab templates ship allow_failure: true).
- branchMustBeProtected (GitHub): codeOwnerApprovalRequired: false (aligns with
  GitLab; many first-run repos have no CODEOWNERS).
- Remove pipelineMustNotLeakSecretsInConfig (both providers): the control was
  removed from Plumber and warns under --fail-warnings.
- containerImagesMustBePinnedByDigest stays true on both providers (deliberate).

`config init` consistency + de-duplication:
- Add a wizard question for authorized action sources — include Plumber's
  curated trusted orgs (recommended) vs start from an empty allowlist.
- Source ALL wizard defaults (trusted URLs/actions, minimumStars, forbidden
  tags/versions, security-job patterns, cache-poisoning inventories, branch
  patterns, ...) from the embedded default via embeddedDefault(), removing the
  hand-maintained Go copies (~275 lines) and making init↔default drift
  impossible by construction.
- Align GitLab securityJobs rules/when and the GitHub branch code-owner default
  in the wizard to the curated default.
- Lock `config generate` == embedded default; cover wizard curated/scratch paths
  and the sourced-from-default invariant with tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n config

Commit dd75222 ("clean plumber conf") intentionally dropped the gitlab: section
from the repo-root .plumber.yaml — Plumber runs no GitLab CI, so its own
self-scan config is GitHub-only. Two tests still assumed that file declared a
gitlab section and have been red on main since:

- TestSelfScanConfigControlsMatchDefault fataled on the missing gitlab section.
  Now controlKeys() reports section presence and the test skips a provider the
  self-scan config doesn't declare (a malformed section still fails hard). The
  drift guard remains meaningful for every provider the self-scan DOES declare.
- TestFilterFindingsByEnabledControls_ProviderApplicability drove the GitLab
  gate off pc.ControlsFor("gitlab"), which is now empty, so a cross-provider
  control (ISSUE-401) was reported as over-dropped. It now builds a minimal
  GitLab ControlsConfig with pipelineMustNotIncludeHardcodedJobs enabled to probe
  the gate directly, independent of the self-scan file's provider coverage.

No production code changes; both tests again exercise their real invariant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes #375.

Let a .plumber.yaml be a sparse overlay on Plumber's shipped baseline
instead of forcing users to copy and maintain the whole ~1000-line file.

- `extends: plumber:default` opts a file into overlay mode: it is deep-merged
  onto the embedded baseline, so users write only what they change and new
  controls Plumber ships are inherited automatically. Files without `extends`
  keep the legacy full-replace behavior byte-for-byte (asserted by test). A
  section left empty in an overlay inherits the base rather than wiping it.
- `includePlumberDefaults` toggle on the authorized-source controls: true
  (default) unions Plumber's curated trusted list with the user's additions;
  false uses only the user's list.
- New commands: `config resolve` (materialize the full effective config),
  `config generate --overlay` (minimal starter), `config view --explain`
  (per-control base-vs-overlay provenance), `config slim` (collapse a full
  config into a minimal overlay).

Resolution runs inside LoadPlumberConfigFromBytes: overlay bytes are merged
onto the embedded base, then allowlist fields are materialized so the enforced
trust set and the audit report's config hash reflect what actually ran.
`config slim` normalizes v1 and legacy sources and preserves disabled-by-
omission controls, narrowed trust lists, and strict scalar trust fields, so
slim -> resolve never widens trust or re-enables a control.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The capacity hint make([]string, 0, len(cfg.NamePatterns)+1) trips CodeQL's
go/allocation-size-overflow. Drop the +1 (append grows the one extra default-
branch entry on its own); behavior is unchanged. Pre-existing on main, fixed
here so the PR's CodeQL gate is green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Joseph94m
Joseph94m force-pushed the chore/simplify-default-config-embed branch from be35c6e to b02c8be Compare July 31, 2026 14:11
Comment thread configuration/resolve.go
@thomasboni
thomasboni merged commit c6bca30 into main Jul 31, 2026
15 checks passed
thomasboni added a commit that referenced this pull request Jul 31, 2026
…efaults false

Mirror TestLoadPlumberConfigFromBytes_OverlayNarrowsTrustedActions for the
GitLab containerImageMustComeFromAuthorizedSources branch of
materializeAuthorizedSources. Guards the security-critical direction: a
config trusting only its own registries must not get the curated base
trustedUrls unioned back in on resolve.

Addresses the remaining review finding on #365.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thomasboni added a commit that referenced this pull request Jul 31, 2026
…efaults false

Mirror TestLoadPlumberConfigFromBytes_OverlayNarrowsTrustedActions for the
GitLab containerImageMustComeFromAuthorizedSources branch of
materializeAuthorizedSources. Guards the security-critical direction: a
config trusting only its own registries must not get the curated base
trustedUrls unioned back in on resolve.

Addresses the remaining review finding on #365.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thomasboni added a commit that referenced this pull request Jul 31, 2026
…efaults false

Mirror TestLoadPlumberConfigFromBytes_OverlayNarrowsTrustedActions for the
GitLab containerImageMustComeFromAuthorizedSources branch of
materializeAuthorizedSources. Guards the security-critical direction: a
config trusting only its own registries must not get the curated base
trustedUrls unioned back in on resolve.

Addresses the remaining review finding on #365.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thomasboni added a commit that referenced this pull request Aug 3, 2026
…efaults false

Mirror TestLoadPlumberConfigFromBytes_OverlayNarrowsTrustedActions for the
GitLab containerImageMustComeFromAuthorizedSources branch of
materializeAuthorizedSources. Guards the security-critical direction: a
config trusting only its own registries must not get the curated base
trustedUrls unioned back in on resolve.

Addresses the remaining review finding on #365.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thomasboni added a commit that referenced this pull request Aug 3, 2026
…efaults to false

Mirror TestLoadPlumberConfigFromBytes_OverlayNarrowsTrustedActions for the
GitLab containerImageMustComeFromAuthorizedSources branch of
materializeAuthorizedSources. Guards the security-critical direction: a
config trusting only its own registries must not get the curated base
trustedUrls unioned back in on resolve.

Addresses the remaining review finding on #365.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thomasboni added a commit that referenced this pull request Aug 3, 2026
…efaults to false

Mirror TestLoadPlumberConfigFromBytes_OverlayNarrowsTrustedActions for the
GitLab containerImageMustComeFromAuthorizedSources branch of
materializeAuthorizedSources. Guards the security-critical direction: a
config trusting only its own registries must not get the curated base
trustedUrls unioned back in on resolve.

Addresses the remaining review finding on #365.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
plumber-release-bot Bot pushed a commit that referenced this pull request Aug 3, 2026
## [0.4.27](v0.4.26...v0.4.27) (2026-08-03)

### 🐛 Bug Fixes

* **lint:** report all issues instead of golangci-lint's truncated default ([cb5739a](cb5739a))

### ♻️ Refactoring

* remove 50 dead functions unreachable from main ([a28393c](a28393c))

### ✅ Tests

* **config:** cover GitLab trustedUrls narrowing with includePlumberDefaults to false ([46f09ed](46f09ed)), closes [#365](#365)

### 👷 CI/CD

* **lint:** gate on whole-program dead code detection ([0aac54c](0aac54c))
* **release:** pin v0.4.26 refs [skip ci] ([e461736](e461736))
@Joseph94m
Joseph94m deleted the chore/simplify-default-config-embed branch August 6, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Layered .plumber.yaml configuration: sparse overlays without owning the whole file

3 participants