You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #1689 / PR #1971, deferred deliberately there so the authorization fix stayed reviewable. Raised independently by otReviewAgent on PR #1971, which reached the same conclusion from the diff alone.
What changed, and why that matters here
PR #1971 makes KnowledgeAssetsLifecycle._executePublishCore authorize a publish when either the paying principal (msg.sender) or the EIP-712-attested author is an authorized publisher for the CG. That fixes #1689 — a curated-CG curator can finally delegate payment to a funded operational wallet.
It also promotes the author attestation from an attribution artifact to an authorization credential. The attestation was never designed to carry that weight.
no deadline — the contract's own doc states the only replay defense is one-shot consumption of reservedKaId, with no signedAtBlock window (RFC-001 §3.2)
So a seal is a bearer token, valid indefinitely until its slot is minted, and the holder — not the author — chooses the destination CG, epochs, and isImmutable in calldata.
Reachability
The seal is persisted to the CG's _meta graph at finalize time (dkg-agent-publish.ts → buildAssertionSealQuads, fields authorAttestationR / authorAttestationVS) and replicated peer-to-peer over the durable _meta sync lane. The responder serves _meta rows with no predicate filtering, so the signature ships verbatim to any authorized syncing peer. For a public CG, authorizeSyncRequest returns true unconditionally — the attestation is world-readable. For a private CG the barrier is CG membership, i.e. exactly the co-tenants best placed to grief a curator.
StorageACKs are not a barrier: core nodes issue them with no authorization check on the requester.
Impact — bounded, but the sharp edges are permanent
A holder of a seal can pay to publish that content into a CG where the author is authorized:
The reservedKaId slot is consumed permanently and kaToContextGraph binding is irreversible, so the author can never republish that slot to the CG they intended.
The attacker chooses isImmutable, which can permanently freeze the KA against all future updates.
The ERC-721 still mints to the author. This is griefing and false publisher-of-record attribution, not asset theft.
The redirect target is not arbitrary: for a curated CG it must be one where the author is the stored authority, i.e. a CG the author themselves curates.
A scoped delegation/consent record — cleanest model, but needs new storage, i.e. redeploying ContextGraphStorage and migrating live CG state.
Withhold authorAttestationR/VS from the durable-_meta sync responder — orthogonal, reduces reachability, and can ship independently of any contract change.
Timing argument for (1) or (2):#1971 already requires a KnowledgeAssetsLifecycle redeploy, and rotation invalidates every outstanding attestation regardless (the KAL address is in the EIP-712 domain separator). That makes it the cheapest moment to change the attestation shape — the migration cost is already being paid.
Related, found during the same analysis
CG escrow / PCA discount when payer ≠ CG owner._useCgEscrow is owner-scoped and the PCA discount reads agentToAccountId(msg.sender), so the operators fix(protocol): authorize curated publish by attested author OR payer (#1689) #1971 unblocks silently forfeit their prepaid escrow and discount. Left on msg.sender deliberately — re-keying to the author would let a seal holder spend the author's funds — but the silent full-price fall-through deserves a warning surface.
StorageACK requester authorization, and the swmGraphId != contextGraphId remap that lets a requester read data under one CG while targeting another in the ACK digest.
Follow-up to #1689 / PR #1971, deferred deliberately there so the authorization fix stayed reviewable. Raised independently by
otReviewAgenton PR #1971, which reached the same conclusion from the diff alone.What changed, and why that matters here
PR #1971 makes
KnowledgeAssetsLifecycle._executePublishCoreauthorize a publish when either the paying principal (msg.sender) or the EIP-712-attested author is an authorized publisher for the CG. That fixes #1689 — a curated-CG curator can finally delegate payment to a funded operational wallet.It also promotes the author attestation from an attribution artifact to an authorization credential. The attestation was never designed to carry that weight.
The gap
AuthorAttestationbinds exactly four fields:"AuthorAttestation(bytes32 merkleRoot,address authorAddress,uint8 schemeVersion,uint256 reservedKaId)"plus the EIP-712 domain (
chainId,verifyingContract). It binds:contextGraphId— removed by Clarify knowledge asset lifecycle when SWM share succeeds without a seal but VM publish requires finalization #1116 so seals could be produced before CG registrationreservedKaId, with nosignedAtBlockwindow (RFC-001 §3.2)So a seal is a bearer token, valid indefinitely until its slot is minted, and the holder — not the author — chooses the destination CG,
epochs, andisImmutablein calldata.Reachability
The seal is persisted to the CG's
_metagraph at finalize time (dkg-agent-publish.ts→buildAssertionSealQuads, fieldsauthorAttestationR/authorAttestationVS) and replicated peer-to-peer over the durable_metasync lane. The responder serves_metarows with no predicate filtering, so the signature ships verbatim to any authorized syncing peer. For a public CG,authorizeSyncRequestreturnstrueunconditionally — the attestation is world-readable. For a private CG the barrier is CG membership, i.e. exactly the co-tenants best placed to grief a curator.StorageACKs are not a barrier: core nodes issue them with no authorization check on the requester.
Impact — bounded, but the sharp edges are permanent
A holder of a seal can pay to publish that content into a CG where the author is authorized:
reservedKaIdslot is consumed permanently andkaToContextGraphbinding is irreversible, so the author can never republish that slot to the CG they intended.isImmutable, which can permanently freeze the KA against all future updates.epochs.Bounding it honestly:
transferFrom, CG escrow, PCA discount) is keyed tomsg.sender, deliberately unchanged by fix(protocol): authorize curated publish by attested author OR payer (#1689) #1971. There is no victim-funded path.publishPolicy == 1, which authorizes any non-zero address) the equivalent exposure is pre-existing via the payer branch and is not introduced by fix(protocol): authorize curated publish by attested author OR payer (#1689) #1971.Net incremental exposure: curated/PCA CGs where the author is the authority or an affiliated agent.
Options
deadlinetoAuthorAttestation— cheapest meaningful mitigation. Bounds the bearer window without reversing Clarify knowledge asset lifecycle when SWM share succeeds without a seal but VM publish requires finalization #1116's CG-independence. Changes the typehash, so it needs client/contract lockstep.contextGraphId— closes it completely, but reverses Clarify knowledge asset lifecycle when SWM share succeeds without a seal but VM publish requires finalization #1116, whose motivation was sealing before CG registration.ContextGraphStorageand migrating live CG state.authorAttestationR/VSfrom the durable-_metasync responder — orthogonal, reduces reachability, and can ship independently of any contract change.Timing argument for (1) or (2): #1971 already requires a
KnowledgeAssetsLifecycleredeploy, and rotation invalidates every outstanding attestation regardless (the KAL address is in the EIP-712 domain separator). That makes it the cheapest moment to change the attestation shape — the migration cost is already being paid.Related, found during the same analysis
_useCgEscrowis owner-scoped and the PCA discount readsagentToAccountId(msg.sender), so the operators fix(protocol): authorize curated publish by attested author OR payer (#1689) #1971 unblocks silently forfeit their prepaid escrow and discount. Left onmsg.senderdeliberately — re-keying to the author would let a seal holder spend the author's funds — but the silent full-price fall-through deserves a warning surface.swmGraphId != contextGraphIdremap that lets a requester read data under one CG while targeting another in the ACK digest.UpdateAuthorAttestationbinds no nonce or version, andDKGKnowledgeAssetspushes merkle roots with no duplicate/monotonicity guard. Pre-existing, independent of fix(protocol): authorize curated publish by attested author OR payer (#1689) #1971.