Skip to content

fix(localresource): confine fromRef to the referencing resource's own namespace (#10) - #19

Merged
braghettos merged 1 commit into
mainfrom
fix/10-confine-fromref-to-own-namespace
Sep 18, 2026
Merged

braghettos merged 1 commit into
mainfrom
fix/10-confine-fromref-to-own-namespace

Conversation

@braghettos

Copy link
Copy Markdown

Closes #10.

The shape

LocalResource let its author name any object in any namespace via spec.fromResource.fromRef, and the provider fetched it with its own ServiceAccount — which holds apiGroups:["*"] resources:["*"] verbs:["get"] cluster-wide, precisely because cross-namespace refs were the intent. The object was then serialised in full (WriteK8sResource strips nothing, so Secret.data is included) and committed to a git remote the same CR names.

Namespaced create rights on one CRD therefore conferred cluster-wide read plus an egress path. Confused deputy.

Verified still present on 0.15.3 before writing anything — localresource.go:422-435 used the namespace verbatim, and grep -rniE 'SubjectAccessReview|Impersonat|AuthorizationV1' returned nothing anywhere in the repo.

The change

fromRef must name the referencing LocalResource's own namespace. An empty namespace stays permitted — that's the cluster-scoped read path, not the author-directed escalation this closes.

What it does NOT do — stated, not implied

It removes the author's ability to aim the provider's identity at another namespace. It does not make the read authorized: a same-namespace read still runs as the provider, so whoever may create a LocalResource in a namespace can still read what the provider can read there.

Closing that needs a SubjectAccessReview against the CR's author, which the CR doesn't record. Left as explicit follow-up rather than half-built — and SECURITY.md says so too, so a future reporter isn't told it's already handled.

⚠️ Breaking — but measured first

Breaking for any LocalResource reading across namespaces. On krateo-057: of 42 LocalResources, 0 use fromRef at all. Observed blast radius nil.

That's one cluster, so the refusal names both the rejected and the permitted namespace rather than failing silently or returning an empty read:

fromResource.fromRef.namespace "kube-system" is not permitted: a LocalResource may only
reference resources in its own namespace ("team-a"). This reference is resolved with the
provider's identity, so permitting another namespace would let it read and publish objects
its author may not be entitled to

I deliberately did not add an allow-list or opt-in escape hatch: that's machinery for a use case with zero current users, and an authorization design deserves doing properly when someone needs it.

Tests

The rule is a pure function so it's testable — SyncLocalResources clones a git repository long before reaching it. Covers same-namespace, empty, and two refusals, asserting the message names both namespaces.

Also

Adds SECURITY.md. Private vulnerability reporting is already enabled (the issue predates that); the file points at it and documents the two properties that shape most reports here — the provider acts with its own identity, and it is an egress path.

🤖 Generated with Claude Code

https://claude.ai/code/session_017tUn92TdGikKGEN5opGD7q

… namespace (#10)

LocalResource let its author name any object in any namespace via
spec.fromResource.fromRef, and the provider fetched it with its OWN
ServiceAccount — which holds apiGroups ["*"], resources ["*"], verbs ["get"]
cluster-wide, precisely because cross-namespace refs were the intent. The
object was then serialised in full (WriteK8sResource strips nothing, so
Secret.data is included) and committed to a git remote the same CR names.

So namespaced create rights on one CRD conferred cluster-wide read plus an
egress path for the result. Confused deputy.

fromRef must now name the referencing LocalResource's own namespace. An
empty namespace stays permitted: that is the cluster-scoped read path, not
the author-directed namespace escalation this closes.

What this does NOT do, stated rather than implied: it removes the author's
ability to AIM the provider's identity at another namespace. It does not
make the read authorized. A same-namespace read is still performed as the
provider, so whoever may create a LocalResource in a namespace can still
read what the provider can read THERE. Closing that needs a
SubjectAccessReview against the CR's author, which the CR does not record —
left as explicit follow-up rather than half-built here. SECURITY.md says so
too, so a future reporter is not told it is already handled.

BREAKING for any LocalResource that reads across namespaces. Measured on
krateo-057 first: of 42 LocalResources, 0 use fromRef at all, so the
observed blast radius is nil — but that is one cluster, so the refusal names
both the rejected and the permitted namespace instead of failing silently or
returning an empty read.

The rule is a pure function so it is testable: SyncLocalResources clones a
git repository long before reaching it.

Also adds SECURITY.md. Private vulnerability reporting is already enabled on
the repository; the file points at it and describes the two properties that
shape most reports here — the provider acts with its own identity, and it is
an egress path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017tUn92TdGikKGEN5opGD7q
@braghettos
braghettos merged commit 43feabb into main Sep 18, 2026
3 checks passed
@braghettos
braghettos deleted the fix/10-confine-fromref-to-own-namespace branch September 18, 2026 10:50
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.

Harden LocalResource fromRef: cross-namespace reads use the provider's identity, not the author's

1 participant