Skip to content

Conversation

@hanzel98
Copy link
Contributor

@hanzel98 hanzel98 commented Sep 23, 2025

Description

This PR implements the isRevoked flag feature for permissions stored in profile sync to ensure off-chain storage stays in sync with on-chain contract state after revocation transactions reach finality.

Key Changes:

  1. Added isRevoked flag to the StoredGrantedPermission type that defaults to false when permissions are stored
  2. Implemented new RPC method permissionsProvider_submitRevocation accessible only by the MetaMask origin to update the revocation status
  3. Added validation and on-chain verification to ensure revocations are only processed for delegations that are actually disabled on-chain
  4. Added filter in the method to pull the permissions, it only pulls the ones that are not revoked.
  5. Implemented a validation to skip the transaction to disable a delegation if it is already disabled.

The RPC method validates the delegation hash, fetches the existing permission, verifies the delegation is disabled on-chain via the delegation manager contract, and updates the permission's isRevoked status to true.

Related issues

Fixes: #357

Manual testing steps

  1. Build and run the gator-permissions-snap
  2. Grant a permission through the snap interface
  3. Revoke the permission on-chain through the delegation manager contract
  4. Call the permissionsProvider_submitRevocation RPC method with the delegation hash
  5. Verify the permission's isRevoked flag is set to true in profile sync
  6. Attempt to call the RPC method with an invalid delegation hash and verify it rejects
  7. Attempt to call the RPC method for a delegation that is not disabled on-chain and verify it rejects

Screenshots/Recordings

Before

  • Permissions stored in profile sync had no revocation tracking
  • No mechanism to sync off-chain permission state with on-chain revocations
  • Once granted, permissions appeared active in profile sync even after on-chain revocation

After

  • Permissions now include an isRevoked boolean flag (defaults to false)
  • New RPC method permissionsProvider_submitRevocation allows MetaMask to update revocation status
  • Off-chain permission state accurately reflects on-chain delegation status
  • Proper validation ensures only legitimate revocations are processed

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Additional Technical Details:


Note

Adds isRevoked tracking to stored permissions and a MetaMask-only permissionsProvider_submitRevocation RPC with on-chain validation and filtering support.

  • Profile Sync (src/profileSync/profileSync.ts):
    • Add isRevoked to StoredGrantedPermission; default false when storing.
    • New helpers to update revocation status: updatePermissionRevocationStatus(…) and optimized updatePermissionRevocationStatusWithPermission(…).
    • Introduce on-chain check checkDelegationDisabledOnChain(…) using eth_call; inject ethereumProvider via createProfileSyncManager config.
    • Export generateObjectKey and improve auth error logging.
  • RPC Layer:
    • New method RpcMethod.PermissionsProviderSubmitRevocation and binding in src/index.ts.
    • Restrict access in src/rpc/permissions.ts to metamask origin.
    • Extend getGrantedPermissions(params?) to support filters: isRevoked, siteOrigin, chainId, delegationManager.
    • Implement submitRevocation(params) in src/rpc/rpcHandler.ts: validate params, fetch permission, verify delegation disabled on-chain, then mark isRevoked=true.
  • Validation (src/utils/validate.ts):
    • Add validateRevocationParams (hex permissionContext) with Zod-based errors.
  • Integration:
    • Pass ethereum to createProfileSyncManager in src/index.ts.
  • Tests:
    • Update/add unit and e2e tests for new RPC, filtering, isRevoked persistence, and on-chain check plumbing.

Written by Cursor Bugbot for commit 5059b2b. This will update automatically on new commits. Configure here.

@hanzel98 hanzel98 requested a review from a team as a code owner September 23, 2025 16:14
@hanzel98 hanzel98 marked this pull request as draft September 23, 2025 16:14
@hanzel98 hanzel98 self-assigned this Sep 23, 2025
cursor[bot]

This comment was marked as outdated.

@hanzel98 hanzel98 force-pushed the chore/is-revoke-flag branch from 02f55e2 to d4be06e Compare October 3, 2025 20:13
@hanzel98 hanzel98 force-pushed the chore/is-revoke-flag branch from ef7bcf7 to 52417dc Compare October 24, 2025 20:40
@socket-security
Copy link

socket-security bot commented Oct 25, 2025

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@hanzel98 hanzel98 marked this pull request as ready for review October 26, 2025 02:03
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

// In case of error, assume not disabled to avoid blocking legitimate operations
return false;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Network Mismatch in Delegation Check

The checkDelegationDisabledOnChain function accepts a chainId parameter but doesn't use it in the eth_call request. This can result in the contract call executing on the wrong network, leading to incorrect delegation status validation.

Fix in Cursor Fix in Web

'latest',
chainId,
],
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is definetly a problem. Do we have any function like: findNetworkClientIdByChainId in the snap ? This exists in extension.

const grantedPermission =
await profileSyncManager.getAllGrantedPermissions();
return grantedPermission as Json[];
const getGrantedPermissions = async (params?: Json): Promise<Json> => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a definition for params with Zod and then validate the params like in grantPermission.

signerMeta,
});

// Check if the delegation is actually disabled on-chain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment does not match code


if (!isDelegationDisabled) {
throw new InvalidInputError(
`Delegation ${delegationHash} is not disabled on-chain. Cannot process revocation.`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If its not disabled then its an error? So the logic for this is to only update profile sync and not start a revocation process?


// Check if any delegation is disabled on-chain
// For now, we'll check the first delegation. This might need adjustment based on business logic
const firstDelegation = delegations[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we then just iterate through if there is more then one and validate all?

);
}

logger.debug(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a bit too much logging here?

* @param delegationManagerAddress - The address of the DelegationManager contract.
* @returns True if the delegation is disabled, false otherwise.
*/
async function checkDelegationDisabledOnChain(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually maybe it would be better for this to be in: blockchainMetadataClient which also has a retry and check we are on the correct chain?

* @param isRevoked - The new revocation status.
*/
async function updatePermissionRevocationStatusWithPermission(
existingPermission: StoredGrantedPermission,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove updatePermissionRevocationStatusWithPermission() function?

Consumers of the revocation flag feature are restricted to the MetaMask client and should only need to call updatePermissionRevocationStatus(permissionContext, isRevoked), which are provided as params on the permissionsProvider_submitRevocation rpc method.

When we allow the consumer to update a permission by providing an existingPermission, the consumer could unintentionally corrupt the user storage (i.e., changing other fields in the permission data).

There is also no check to ensure the item is in the store, which morphs the expected behavior of an 'update' function into a proxy for creating a new store entry. The only field the consumer can update is the isRevoked flag, but this function escalates that privilege to allow mutation of other properties.

Copy link
Member

@V00D00-child V00D00-child Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have created a branch against your PR with my suggestions. Here is the commit: c628a5a

* @param params - The parameters for the revocation.
* @returns Success confirmation.
*/
const submitRevocation = async (params: Json): Promise<Json> => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the submitRevocation() function can be simplified further by moving onchain check business logic to the updatePermissionRevocationStatus() function in profile sync and then calling updatePermissionRevocationStatus() here.

We should check onchain revoke status anytime we attempt to update the isRevoke flag on a storage entry in profile sync, not just on rpc calls.

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.

4 participants