Frontend security

AgentJS is storing information in localStorage, which makes it possible for the page to reload and still be authenticated. I am not aware of its inner workings, but it doesn’t seem very safe to do that. Chome Extensions XSS attacks; Supply chain attacks; all of these can inject javascript, get access to localStorage and then call all the canister methods they want. Correct me if I am wrong.

If you are like “Ohh I don’t use chrome extensions like that”. Let me tell you, pretty much ALL of my extensions right now have permission to inject javascript in any site: Metamask, Plug, Grammarly, React Developer Tools, Redux Tools.

So can I opt-out of localStorage and just keep my keys in locally scoped variables so injected javascript can’t get to them?

Now. I know that won’t solve everything. Watch a minute from that video from the start time I have set. You won’t regret it

So it seems, just hiding keys won’t be really successful unless we use these under-construction Javascript inventions, which Metamask also uses.

Otherwise injected javascript will just rewrite Array and String function or even fetch. Which could result in taking user keys. Or it can be replacing canister call parameters during fetch.

Without taking precautions and ‘hardening’ our dapps. I suppose for that to really blow in our faces, a hacker would have to specifically target a dapp. The dapp needs to be worthwhile their time and have a big honeypot. Then they would go through significant trouble to… for example, inject malicious code in a js package, required by another js package of a very popular Chrome extension or the dapp itself.

Is there another way to set up your dapp, which will shield contracts and require a hardware signature each time user makes a certain update call?

I can’t find anything related in this forum. It seems that this kind of thing will affect all dapps

5 Likes

I found that AgentJs has sign(blob) which may help to make certain canister methods to require hardware signature.
https://erxue-5aaaa-aaaab-qaagq-cai.raw.ic0.app/agent/classes/signidentity.html#sign

Perhaps something like this will work (Any thoughts?):

  1. If caller principal is a canister, then require no signature
  2. if caller principal is Internet Identity, require a signature
  3. Frontend sends request data and retrieves a blob
  4. Frontend uses agentjs to sign the blob and sends it signed to the canister. It opens a tab to the Internet Identity app and prompts the user to push a button and hardware sign
  5. Canister does inter-canister call to Internet Identity and fetches the public key of the caller principal
  6. With the public key, blob, and signed blob, the canister verifies that the transaction is legit

That’s a fair ask. Auth-client is currently the only package that persists info in local storage, in order to prevent the user from having to login every time they visit the page. I don’t think we have an option yet to opt out of local storage, but we certainly could add one

Actually, did not tried out but, according code you can pass a custom storage to the AuthClient constructor that needs to implement the AuthClientStorage interface.

Therefore, spontaneous guessing but, if developer implement an on-memory only “storage” then localstorage might not be use?

Something like:

export class MyStorage implements AuthClientStorage {
   private myState;

   async get(key: string): Promise<string | null> {
        return myState;
   } 

   async set(key: string, value: string): Promise<void> {
      this.myState = value;
   }

   async remove(key: string): Promise<void> {
      this.myState = undefined;
   }
}

(async () => {
    const storage = new MyStorage();
    const authClient = await AuthClient.create({storage});
})();
3 Likes