Relationships
#1101 export const extension can graft methods onto foreign-collective types, undisclosed in extension info / contentMetadata
Opened by kneel · 7/12/2026· Shipped 7/18/2026
Summary
A published extension can use export const extension with a type: pointing at
a model type owned by a different collective (or any type it does not itself
define), grafting new methods/resources onto that foreign type on the consumer's
machine. This grafting is not disclosed in the package's registry
contentMetadata nor in swamp extension info <pkg>, so a consumer cannot see,
from discovery, which foreign types a package mutates. Grafted methods receive the
target type's globalArgs (which for many types carry credentials). Together this
is a supply-chain transparency gap.
What is NOT the issue (already handled well)
Method-name override is correctly blocked. An extension that redefines an existing method throws at load and the whole file fails to bundle:
Failed to load user model "evil.ts":
Error: Method 'queryMetrics' already exists on model type '@keeb/grafana/instance'
-> BundleBuildFailedThe collision also prevents any other method in the same file from registering. So you cannot silently hijack an existing method — this report is not about override, and I'm noting it so the real issue isn't dismissed as "we already block collisions."
The actual concern
Undisclosed foreign-type grafts.
export const extension { type: "@other/x", methods: [...] }adds methods to@other/x, butswamp extension info <pkg>/ registrycontentMetadatalists only the types the package defines, not the foreign types it extends. Concrete example:@shrug/grafana-extgrafts aqueryLogsmethod onto@keeb/grafana/instance, yetswamp extension info @shrug/grafana-extreports only its own@shrug/grafana-admin/instancetype — the graft onto@keebis invisible in the package's advertised content.No consent from the target collective. A package from collective A can graft onto a type owned by collective B (including official
@swamp/*types) with no opt-in from or relationship with B.Grafted methods see the target's credentials. A grafted method's
executereceives the target type'scontext.globalArgs, which for many types includes API tokens / passwords (e.g. a Grafana Bearer key, a cloud credential). A "helper" method grafted onto a popular type could read and exfiltrate these. It requires the victim to invoke the method (so it's not fully silent), but the method can be named to look benign, and per (1) its very existence is not surfaced in the package's content metadata.
Reproduction
swamp extension pull @keeb/grafana- Add a local extension file:
import { z } from "npm:zod@4"; export const extension = { type: "@keeb/grafana/instance", // a foreign type this pkg does not own methods: [{ evilExfil: { description: "brand-new grafted method", arguments: z.object({}), execute: (_a, ctx) => { /* read [HOST-1], POST it out */ return Promise.resolve({ dataHandles: [] }); }, }}], };
swamp doctor extensions-> PASS;swamp model type describe @keeb/grafana/instancenow listsevilExfilalongside keeb's own methods.swamp extension info <your-pkg>-> the output does NOT mention@keeb/grafana/instanceor the grafted method under the package's content.
Also verified via a real published package: @shrug/grafana-ext@2026.07.11.1
grafts queryLogs onto @keeb/grafana/instance (confirmed on a clean pull), but
swamp extension info @shrug/grafana-ext lists only @shrug/grafana-admin/instance.
Verified on swamp 20260709.014917.0-sha.3da10c76.
Suggested mitigations
- Surface foreign-type extensions in
contentMetadata/extension info: per package, listextends: [<type>...]with the grafted method/resource names, so consumers can audit what a package mutates. - Warn at
swamp extension pullwhen a package extends a type it does not own (especially@swamp/*or a different collective), listing what it grafts. - Consider a manifest-declared capability (
extends: [...]) that must match the code, and/or a target opt-in for cross-collective extension. - Optionally flag grafted methods that read credential-typed
globalArgsfields.
Shipped
Click a lifecycle step above to view its details.