Skip to content

feat: the node knows whose code it profiles (ADR 0059) - #94

Merged
kfeofantov merged 1 commit into
mainfrom
feat/the-node-knows-whose-code-it-profiles
Aug 31, 2026
Merged

kfeofantov merged 1 commit into
mainfrom
feat/the-node-knows-whose-code-it-profiles

Conversation

@kfeofantov

Copy link
Copy Markdown
Contributor

Closes, for the eBPF capture path, the defect #93's ADR named as open.

The defect

The symbol filter classifies a frame by module path: no domain → stdlib or unpublished code, keep; allow-listed prefix → keep; anything else → third-party, redact. A customer's service lives under github.com/acme/web, which has a domain.

So the configured list was never an addition to what the filter knew — it was the only thing standing between the customer's service layer and [filtered]. Name one prefix, run five services, redact four.

The information was already there, in the same process

cmd/agent/node.go runs the scanner, which reads every kept binary's main module and every replace-redirected dependency, per container, every pass. cmd/agent/pipeline.go:60 ran the profiler beside it and built one filter for the whole node, from configuration alone. Two goroutines in one process that never spoke.

  • The scanner now publishes a container → own modules index, replaced wholesale per pass so a container no live process belongs to leaves with it. Same shape internal/targeting uses on the controller, and for the same reason.
  • processWindow — which already grouped by container — builds the filter per group: configured prefixes plus that build's own modules.
  • A container the scanner has not reported yet (the first window can outrun the first pass) falls back to the previous behaviour, and Validate already refuses a profile of nothing but the runtime. No new drop rule for a case the existing one covers.

Two things worth a look

An allow-list entry that cannot be a module path is now refused. Part of the list comes from binaries, so a build declaring its main module as a bare github.com would otherwise admit every module hosted there. The same check applies to a prefix an operator wrote, where the same typo was previously accepted — that is a behaviour change for a misconfigured install and it is named in the ADR rather than left to be found.

This is not a defence against a hostile workload: a binary that lies exposes its own dependency frames to the party the customer already trusts with its profiles. It bounds a plausible typo.

The chart stops requiring allowedModulePrefixes for ebpf. That refusal existed because an empty list produced nothing, forever, silently. That stopped being true. A required value that changes nothing is the inert knob ADR 0025 abolished — so ebpf now installs and produces useful profiles with no profiling configuration at all, which is what inventory + pull has done since #93.

Why nobody saw it

test/e2e/capture_test.go set allowedModulePrefixes: [sampleModulePath] — the e2e was configured around the bug. It now installs with the allow-list absent, so a surviving frame of the sample can only mean the node read the module off the binary. That is the load-bearing change in this PR's test surface.

Also

OwnModules moves into nodescan, so the derivation that both paths depend on exists once instead of once per consumer.

Evidence

  • TestOwnCodeSurvivesWithNothingConfigured ships a profile through processWindow with an empty configured list and asserts the workload's frame survives and the dependency's does not; TestWithoutTheIndexTheSameWindowShipsNothing pins the old behaviour beside it.
  • TestOnlyAPlausibleModulePathIsAnAllowListEntry and TestTheFilterDropsAnImplausibleEntry cover the guard.
  • go test ./... and golangci-lint run ./... pass on host and GOOS=linux, 0 issues.
  • e2e not run locally (Docker is yours).

Still open, and stated in the ADR

The eBPF capture path contributes nothing to collection_coverage. The prober and puller each report what they attempted and why it failed (#92, #93); the profiler's refusals live only in the node's log. So "this workload has no eBPF profile" is still indistinguishable from a broken agent — exactly what ADR 0054 exists to prevent. Separate slice.

🤖 Generated with Claude Code

https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp

Closes for the eBPF capture path the defect ADR 0058 named as open. The symbol
filter classifies a frame by module path, so a service under
github.com/acme/web was redacted as third-party unless someone had named it —
the configured list was not an addition to what the filter knew, it was the
only thing standing between the customer's service layer and the placeholder.

The information was already on the node and in the same process. cmd/agent/
node.go runs the scanner, which reads each container's main module and every
replace-redirected dependency every pass; cmd/agent/pipeline.go ran the
profiler beside it and built one filter for the whole node from configuration
alone. The two goroutines never spoke.

- The scanner publishes a container→own-modules index, replaced wholesale per
  pass so a departed container leaves with it (the targeting.Publisher shape).
- processWindow builds a filter per container: configured prefixes plus that
  build's own modules. A container the scanner has not reported yet falls back
  to the old behaviour, which Validate already covers.
- An allow-list entry that cannot be a module path is refused, on both paths:
  a build declaring a bare "github.com" would otherwise admit every module on
  it. This also rejects the same typo from an operator, which is a behaviour
  change and is stated in the ADR.
- The chart's refusal to install ebpf with an empty allow-list goes: it
  required a value that no longer changes anything.

Why nobody saw it: the capture e2e configured the sample's own module path.
It now installs with nothing configured, so a surviving sample frame can only
mean the node read the module off the binary.

go test ./... and golangci-lint run ./... (host and GOOS=linux) — pass,
0 issues. e2e not run locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YRKuL97KRndaopfpixrhp
@kfeofantov
kfeofantov merged commit efc36f9 into main Aug 31, 2026
3 checks passed
@kfeofantov
kfeofantov deleted the feat/the-node-knows-whose-code-it-profiles branch August 31, 2026 06:45
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.

1 participant