Immutability and Upgradability Dilemma in DeWeb

Many hacks in the DeFi ecosystem come from “leaked” master keys (e.g. Raydium), meaning that founders or developers have a key with master power on the smart contract (like pausing, updating or syphoning it), and that this key has been hacked by an attacker (most probably an insider).

DeFi protocols with no master key in their smart contracts are much safer obviously. They can’t be paused or syphoned. However they also can’t be upgraded, which can be useful in some cases.

Uniswap for instance proposed several smart contract versions over the years up to Uniswap v4, with different smart contracts and frontends each time. Updating was social, announcing new versions and their locations, and was pretty smooth.

Frontends have the same dilemma as smart contracts, they should be immutable, otherwise the master key will “get hacked” some day and that day when you use the protocol it will surprisingly empty your wallet (cf all the recent hacks described in our presentation here). And they should be updated, hopefully not too often.

I would like to start these discussions before bad habits are taken in Massa’s DeWeb:

  • what would be the recommended pattern for builders to both have immutability and social upgradability on the DeWeb frontends ?
  • do we need to change anything on the MNS smart contract to help with this ?

One way of doing this could be:

  • when someone books a domain name “domain.massa” on the MNS, it automatically locks extension like “domainv1.massa”, “domainv2.massa”, etc meaning only the same address that owns the original domain can book the versions extensions.
  • social upgrades would be announced by the project (and potentially relayed by the foundation), like “The frontend has been updated for [whatever] reasons, you can see the code here, and it is now available on domainv2.massa so please do not use v1 from now”.

We could even imagine that builders would keep a master key on “domain.massa” making that frontend point to “domainv1.massa” which itself is immutable, and after an upgrade, make “domain.massa” point to “domainv2.massa”. That way, people who don’t care about being hacked by that master key could use the shortcut “domain.massa” and benefit from automatic upgrades, and people caring about their tokens would prefer to bookmark “domainv1.massa” and only switch to “domainv2.massa” after carefully checking that the project announced the switch on their socials, or even to the extreme check the updated frontend code.

1 Like

Proposal for the “full immutability” case

Here is how to achieve full immutability.

MNS domain name immutability

If the domain name target is changed, users will be redirected to a different website.

To make the domain immutable, just set the owner of the MNS domain to “” (empty address). That way, nobody can change that MNS anymore.

This can be easily checked by the client just by ensuring that the owner address of the served MNS is an empty string.

Website data immutability

If the smart contract hosting the website changes the hosted website, or changes its own bytecode to be able to change the website it hosts, it breaks immutability.

The most straightforward way to make the website data immutable is to allow the owner of the smart contract to simply ask the smart contract to make its own bytecode empty (no more smart contract there).

This is an easy check for the client (check that the bytecode of the SC hosting the website is empty) and ensures that the website data can never change.

Website front-end immutability

Even if the website data is immutable, the website HTML, CSS or JS can dynamically load external resources in the browser, and those resources might not be immutable.

To counter that, the client can set the Content Security Policy (CSP) - HTTP | MDN header to:

Content-Security-Policy: default-src 'self';

This will explicitly prevent the page from loading external resources.

To activate the feature, we can simply define a special metadata entry “immutability_headers: 1”. When present, the client will define the Content-Security-Policy header to default-src 'self'; for all served files on that website and ignore any overrides (eg. by different text case of the header name or through per-file header overrides).

To check that a website obeys this immutability factor, the client can simply read the global header immutability_headers and check that it is set to 1.

2 Likes

As I understand, we can already make the MNS and the smart contract immutable.

What about the front-end ?

If the smart contract is immutable, the website stored on-chain cannot be changed anymore. So the website files hosted on Massa will be immutable.
That being said, even if the website files are all immutable, the website can still exhibit mutable behavior if the front-end dynamically asks the user’s browser to fetch data from a non-immutable source on the internet. To prevent that last part, I proposed to add the HTTP header Content-Security-Policy: default-src 'self'; that explicitly tells the user’s browser to refuse requests by the front-end to fetch data from sources that don’t belong to the decentralized website’s domain itself.

Hi,

This is an important question to address.

Many hacks in the DeFi ecosystem come from “leaked” master keys (e.g. Raydium), meaning that founders or developers have a key with master power on the smart contract (like pausing, updating or syphoning it), and that this key has been hacked by an attacker (most probably an insider).

This highlights the primary concern: a malefactor gaining ownership of a .massa domain, its frontend, and smart contracts. They could modify these components to scam users. It’s similar to an attacker hijacking Elon Musk’s X account and posting fraudulent messages on his behalf.

I believe it’s unreasonable to review scenarios where the domain owner is a scammer and intentionally replaces the frontend or smart contract (SC) to deceive users. Instead, we assume the owner has good intentions.

Additionally, we exclude cases where the frontend or SC contains unintentional bugs or is hacked by third parties.

The critical point here is the importance of making the domain, frontend, and SC immutable. If the owner’s keys are compromised, users can think that they’re accessing the same secure resource as before when it’s not true.

For example:

• A domain like wallet.massa offers a MAS wallet.

• One day, the owner’s keys are compromised, and a malefactor transfers ownership to their account, uploading a fraudulent wallet to wallet.massa.

• Users lose their funds, and the original owner is powerless to intervene.

• The scam continues on wallet.massa, and users assume the original owner is responsible for the fraud.

At the same time, it’s crucial to allow for frontend upgrades when necessary. To address these concerns, I propose the following security features:

  1. Implement a command to make a domain non-transferable

This ensures that the owner of wallet.massa can prevent the domain from being transferred to a malefactor. The owner would still retain the ability to upgrade the frontend.

  1. Implement a command to nullify a domain even if it’s non-transferable

If the owner’s keys are compromised, they should have the option to nullify the domain (e.g., wallet.massa), rendering it inaccessible and preventing the malefactor from deploying a scam frontend.

  1. Introduce an option to permanently block frontend upgrades

In some cases, it may be beneficial to disable frontend upgrades entirely, ensuring the interface remains static and trustworthy.

  1. Extend similar functionality to smart contracts

These features should also apply to smart contracts, enabling their immutability or controlled nullification in case of compromise.

Your point 1 is addressed in the proposal by losing ownership of the MNS. This applies to any ownable smart contract as well.

Point 3 is addressed as well in the proposal by deleting the site SC bytecode.

Nullification of immutable contracts is the only missing piece compared to your proposal.
This could be implemented in the following way:

  • ownership transfer and byte code updates need to be disabled a priori
  • an onlyowner function needs to be exposed that wipes the datastore and deletes the bytecode of the smart contract