Freecodecamp: Code Locked / Unlocked button is a confusing UX for beginners

Created on 24 Jan 2018  ·  40Comments  ·  Source: freeCodeCamp/freeCodeCamp



Issue Description

The "Code Locked" button on beta shows up on the very first challenge: "Say Hello to HTML Elements". I think this has to do with recent security improvements to prevent javascript injection via the URI? In any case, there is no code in my URI. The URI is https://beta.freecodecamp.org/en/challenges/basic-html-and-html5/say-hello-to-html-elements. I am guessing maybe it's because saved code was found in local storage? Checking local storage for code to be executed is surely way beyond the ability of someone who just started the course?

Being that this the very first challenge, this is definitely confusing and beyond the expertise of a beginner to decide whether or not code is secure. We are asking them to make a decision they have not been trained to make. I was even confused as an experienced developer. What should I be looking for to understand if the code is trusted? Certainly it's not my <h1> element or anything else in the text editor?

Also, it doesn't match the instructions which talk about clicking the "Run tests" button".

How can we improve user experience while still keeping it secure?

Browser Information

  • Browser Name, Version: Chrome, 63
  • Operating System: Ubuntu
  • Mobile, Desktop, or Tablet: Desktop

Screenshot

image

UI critical path

All 40 comments

@tchaffee thanks for the report, closing this in favor of https://github.com/freeCodeCamp/freeCodeCamp/issues/16250

@raisedadead Issue #16250 describes a different problem. My concern is: how does a beginner know whether or not code is safe to execute? The user experience is bad because there is no way a beginner has the information to make that decision.

Okay... this means we would need a step challenge, for on boarding.

@QuincyLarson do you have anything in mind?

Or maybe revisit how we initially provided for this requirement? Is there any risk running code from local storage? I get the warning even when there is no code in the URI, and that seems like an unnecessary warning because that's just my saved work.

I don't know the background for this use case, but I think code in the URI is for sharing? And I'm guessing that's the real security risk?

Maybe we could consider another solution for sharing code rather than asking beginner programmers to decide if code is safe to execute. I think it would be worth it to first understand what the existing solution offers and what the requirements were that lead to our current solution. Or it might be the case that sharing code via URI is just a bad idea all around.

Or maybe revisit how we initially provided for this requirement? Is there any risk running code from local storage? I get the warning even when there is no code in the URI, and that seems like an unnecessary warning because that's just my saved work.

The requirement is well established. We have had to address several XSS issues on production. The most recent one needed blocking the sharing completely. There are several vectors on how an unsafe code can be executed.

A user for example, could even copy paste code from anywhere, and this could in practice lead to such issues. The current fix does not prevent such a vector, but technically limits any execution with the warning. The UX around it of course is a concern, and hence needs a learning path.

Maybe we could consider another solution for sharing code rather than asking beginner programmers to decide if code is safe to execute.

Sure, why not. Feel free to take a look and we would love to have an solution which augments to current fix which is basically a infra level block on the execution. Of course, should you be interested we could use the help with a PR.

Or it might be the case that sharing code via URI is just a bad idea all around.

We have bins in planned, but its not happening soon, as we are getting the infra ready around it. See https://github.com/freeCodeCamp/freeCodeCamp/issues/11263

This was like so, when the User was the sole model, and we had to keep our DB lightweight, we plan to make it more decoupled progressively.

The requirement is well established.

Which one? I understand fetching code from local storage as being one of the requirements. That makes sense to me - Campers shouldn't lose code they have started to work on.

Is there another requirement to run code from the URI? I don't know much about that, it just rings a bell from the security issue I saw a few weeks ago.

They seem like two different requirements with two different goals, and different security risks?

Feel free to take a look and we would love to have an solution which augments to current fix which is basically a infra level block on the execution.

I'm not a security expert, but I would be happy to try to come up with something if I can better understand the requirements. Could someone describe all the requirements in detail from the Camper's perspective, along with the security hole that the latest changes tried to fix? Also, what is an "infra level block"?

We have bins in planned.

The issue doesn't describe that solution in enough detail for me to understand what it is. Could you give me more details on what "bins" are and what the envisioned solution is?

Anyway, I don't want to make this into something bigger than it is. Maybe a step challenge for on-boarding is the right work-around for now. But I'm really curious what that on-boarding would look like. Is it possible to teach Campers right at the beginning which code should be trusted and which shouldn't? If you have something in mind I'd like to see it because maybe it's simpler than I am imagining.

Thanks for your curiosity, about the architecture of the project. I would definitely like to answer all your queries, but I am afraid, explaining everything on this thread will not be enough or justified.

I understand that you are possibly getting started with this, so I'll try to be as clear as possible, but please do drop your queries if there is something unclear.

I'll take this query and try to give you the context:

Could someone describe all the requirements in detail from the Camper's perspective, along with the security hole that the latest changes tried to fix? Also, what is an "infra level block"?

  • First, there is a huge difference on how the code is being evaluated and executed in production (freeCodeCamp.org) and staging (beta.freeCodeCamp.org) on the client side. Discussing that is a little out of scope, so I will advice taking a look at the code. But, just to give you a bird's eye view of the process, its taking the code in the editor and executing it in the browser's context (using eval, refer code) albeit in very different ways.

  • Now, coming to the camper's perspective. Imagine all the scenarios you would do as a camper:

    • You could start editing code in your editor.
    • You could paste code in your editor
    • You could partially edit, move to another challenge and come back.
    • You could visit someone's profile, click the view solution link, or click a link in forum, etc. Otherwise known as the URI.
    • You could ...
      or any combination of the above is possible as well.
  • In all cases, it reaches the same editor where the code will be parsed and evaluated.
  • This parsing can be from the typed code, local-storage or URI.
  • In all cases, some safety checks will be done, for example infinite loop protection, striping off and/or replacing tags while encoding the solution before sending to DB, etc.

  • With the no. of combinations its really complex to handle multiple attack vectors.

  • That's just security.

  • Then, comes the workflow:

    • local storage is needed to have some way, to "auto-save" work, without hitting the DB.
    • This is so, because only submitted and passed solutions should go to the DB.
    • Partial solutions (edited or copy-pasted), i.e (Submitted + NOT Passing Or In work) will have to be on the local storage.
    • Plus, transactions (sharing/viewing URI) between DB and local storage need to be encoded/decode for reasons I mentioned earlier.

Here by transactions I mean getting to a state where, the editor has code, that is there because of a user clicking a shared link from their profile or elsewhere.

The priority of loading solutions for evaluation is Editor > Local Storage > URI/DB

If, you compare the scenarios vs the workflow, you will probably get more idea how complex the logic gets, while keeping the attack vectors at bay.

Hence, a blanket check is to run nothing in the editor on all challenges, without explicit consent, via a Code Unlock method. This is the infra level block I am taking about. This is going no where, until there is a safe way to execute all code in an isolated environment in the browser.

I agree this may be bad for UX. And we obviously do not need to and can't explain the inner workings as above, to campers. Hence the on-boarding could be a way, to show, why this is needed, without getting into too much complexity, and scaring off new users.

Finally, the bins would be something similar like short links, you see else where. Ex: codepen, jsbin etc. Note: I am simplifying the idea here.

These could help us, safely storing/viewing code without the current priority overhead.

SO,

The real UX fix is simply explaining the warning, in an on-boarding challenge.

Please, note that I may have over simplified things here. If you have the interest, I would recommend checking out the code. If stuck, we are open to address any understanding on that front. So please drop in any queries here.

Again just to re-iterate:

  1. Yes, there are multiple requirements, but all have the same point of entry for execution/evaluation and viewing. Hence there is the need for locking/unlocking. They all lead to same security risks.
  2. This is as of now, a blanket check on all incoming paths of the code into the evaluation mechanism.
  3. We definitely need to address the UX as some form of learning:

Is it possible to teach Campers right at the beginning which code should be trusted and which shouldn't? If you have something in mind I'd like to see it because maybe it's simpler than I am imagining.

  1. Code, that camper created for the solution to the challenge (either by typing in manually, or copy/paste from any other editor) is trusted.

  2. Code, that was created by someone else (including links from their own profile), is NOT trusted, and should be reviewed once. They could simply, check that the solution in the editor makes sense to them. Because, if they are unlocking any any random code, without understanding what it is, they maybe risking some possible security safe guards.

We just need to prepare a verbiage around the above two points, and create an on-boarding challenge.

Hope this gives you some context? If not please feel free to add more queries. Happy contributing!

@raisedadead Your detailed explanation helps a lot. I have some more questions / observations which I hope will lead us to the best short-term and long-term implementation.

its taking the code in the editor and executing it in the browser's context (using eval

That's the weak point creating the security issues. I'm not saying it's avoidable, but just noting that it's something to brainstorm around in case someone out there has come up with a more secure way of providing the same functionality. Maybe not because at the end of the day you do have to execute code. But let's keep an open mind about it. I'll commit to asking around.

Let's look a little closer at functionality and requirements:

Partial solutions (edited or copy-pasted), i.e (Submitted + NOT Passing Or In work) will have to be on the local storage.

Code, that camper created for the solution to the challenge (either by typing in manually, or copy/paste from any other editor) is trusted.

From the above it looks to me like code from local storage should be trusted but is not being trusted. Is there a way to distinguish between code that comes from a URI (definitely not trusted) and code I previously typed in that comes from my local storage? That small change alone would make the UX much better. Especially since we could make the message far more specific: "you used a URI with code in it, do you trust the code?"

If it's possible to detect and not trust only code that comes from a URI, then I think it could fit well into the existing workflow and functionality. If code does come from a URI, we would not save any code to local storage until after the user presses the "Code Locked / Unlock?" button. After which the user has indicated they trust the code, so it can safely be put into local storage and then executed without warning in the future when they come back.

Longer term, I am definitely questioning if sending around code in a URI is just a bad idea overall, and if we could find a better way to share solutions and code. But I'll reiterate that's a longer term question because it requires some big changes in the way things are currently done.

They could simply, check that the solution in the editor makes sense to them.

This has me convinced that the on-boarding could be simple enough to be effective. "If you don't recognize or understand the code in the editor, don't trust it."

But if it's possible to distinguish between code that comes from a URI and code in local storage then I am even questioning if the on-boarding is necessary, because warning only in that situation does not seem like a bad UX to me. "You've just used code from a URI, please make sure the code in the editor makes sense before you unlock it."

@raisedadead I don't know if you've met @tchaffee before but he's a prolific contributor to freeCodeCamp. He is an experienced developer, and one of the main people behind our new testable projects.

Okay... this means we would need a step challenge, for on boarding.

We don't want to add more step challenges. If anything, we want to get rid of the step challenges we have.

This is because people don't read.

One of the reasons we try to make challenge lesson text as terse as possible is because the more text there is, the less likely the user will have the patience to read it.

@tchaffee is right - we need to improve the UX of this.

I propose we only lock the code if they've arrived at the challenge through clicking on a URL that contains someone else's code. Otherwise, we shouldn't warn them about running the code.

JSBin, CodePen, I don't think any of these sites warn people about running other people's code like this. I think we can warn them, but only in situations where it's likely that they're running code that isn't theirs. Otherwise, clicking that button is really annoying and will increase attrition.

You could start editing code in your editor.

No lock needed.

You could paste code in your editor

No lock needed (we should assume you already read the code you're pasting in)

You could partially edit, move to another challenge and come back.

No lock needed

You could visit someone's profile, click the view solution link, or click a link in forum, etc.

This is the only situation when the lock is needed imho.

Also, we need to re-word this to where we don't need a hover message. We don't use on-hover messages anywhere else in the codebase, and shouldn't because they don't work on mobile. All the text we want to use should be written on the button itself.

basic_javascript__increment_a_number_with_javascript___freecodecamp_

All the text we want to use should be written on the button itself.

I think the existing button text could work if you also show a link under the button along the lines of "Why is my code locked?". Just a suggestion.

Also, I can try to take on this issue if no one else has time for it. I would need some help with someone pointing me to all the relevant code. But if there is someone more qualified and willing then for sure let them take this issue.

@tchaffee That would be great!

Rather than have a link we just need to figure out a succinct way to explain it in as few words as possible, even if the button is two lines long. "I trust this code. Unlock it."

Again, we only want this button to show up when the code is not the camper's.

@QuincyLarson yes, while I would also like to avoid an on-boarding flow, and only update the label.

That still leaves the fact that per the current client side logic, the implementation of blocking only non-user code has to be implemented.

By this I mean the logic for the button "I trust this code. Unlock it." to be able to displayed only when code comes from URI, etc. still needs to be implemented.

I'll add a PR to update the label and close the other issue https://github.com/freeCodeCamp/freeCodeCamp/issues/16250

That still leaves the fact that per the current client side logic, the implementation of blocking only non-user code has to be implemented.

Not sure where this leaves my offer to implement the new behavior. I can try to implement blocking only non-user code, but it seems like now is not the right time? Can someone please clarify?

I'll have to double check, how this works to give you very specific guideline on what is to be done, meanwhile I'll tag @BerkeleyTrue for his inputs.

By this I mean the logic for the button "I trust this code. Unlock it." to be able to displayed only when code comes from URI, etc. still needs to be implemented.

@raisedadead Yes - I agree with you. Doing so is important and will save thousands of campers their sanity.

I've moved this to the "critical path" on our beta release Kanban: https://github.com/freeCodeCamp/freecodecamp/projects/1?fullscreen=true

I am still happy to give this a go if someone can point me to the parts of the code in question. I'm sure I could find it myself, but if someone is already familiar with the code it will save some time. Could someone let me know what the status of this is?

@tchaffee Thanks for your patience.

@Bouncey Do you know where this logic resides in the codebase? Could you point @tchaffee in the right direction?

@tchaffee

You can check the URI with the pathnameSelector

You use it by passing it into the mapStatetoProps method of the SidePanel component, you can then interact with the ToolPanel component from there

Hope this helps 👍

I started to take a look at this and I have a question. Can someone give me an example of how it's possible to do the following:

You could visit someone's profile, click the view solution link, or click a link in forum, etc. Otherwise known as the URI.

On the beta site I am only seeing a popup for solutions and no URI that loads code. So seemingly this has changed in Beta? Are there any other places where code could be loaded from a URI? Can someone point me to an example URI?

Thanks!

@tchaffee that is correct, this use case is no longer valid:

You could visit someone's profile, click the view solution link, or click a link in forum, etc. Otherwise known as the URI.

It has indeed been replaced with a modal now, making it way safer than having to parse URI in the editor. So I think loading from URI is no longer in picture.

Now, this brings us to Code Locking / Unlocking. That is when the button should be shown.

Here are some scenarios that I can think of:

  1. Campers can re-visit a challenge, from the Map.
  2. In such a case, the code will be loaded in the editor from the local storage, if available.
  3. Or, it will be fetched from the backend, added to the local storage and then placed in the editor.

Now, in an ideal world it is going to be the case, that this code is owned by the camper.

But, for whatsoever possibilities, it is Code that is loaded on the editor, and is executed. This leaves us with a place where un-secure code could be executed? At least that is a vector that I can see.

So, we have to make sure that the Camper are aware of what is to be executed, and is done with there explicit consent.

So, the part of blocking the code from local storage or backend (non-user code) still remains, I guess. But, having the button for it some what un-necessary I think.

It should be possible to lock the non-user code, that is not execute it, if it was other than the original seed code or some thing which was typed in by the camper (user-code).

@Bouncey @BerkeleyTrue Am I correct in this view?

Oh, yes and the just to note that the URI parsing is still available, and going no-where because the challenge editor is agnostic of the fact that we have a react profile view with modal for solutions.

I'll try and share an example URI if I get some time.

But, for whatsoever possibilities, it is Code that is loaded on the editor, and is executed. This leaves us with a place where un-secure code could be executed?

I would say it doesn't. If a camper copy / pastes code from somewhere else, it's on them to make sure they understand the code, and that the code is safe. This approach also rewards honesty and punishes cheating - if you don't understand what copied code does you should never be pasting it. You clearly could not have written it yourself if you don't understand it, so any damage you cause is a result of real cheating.

There are only two "honest" ways of getting code into the editor the first time?

  1. Copy / paste something you understand and could have written yourself.
  2. Type in the code yourself.

At which point it's saved into local storage or the backend?

Any code that comes from local storage or the backend and is placed in the editor, first had to come from one of the methods above. So code from local storage or backend can always be treated as safe.

It should be possible to lock the non-user code, that is not execute it, if it was other than the original seed code or some thing which was typed in by the camper (user-code).

Per above, IMO this is not needed.

the URI parsing is still available, and going no-where because the challenge editor is agnostic of the fact that we have a react profile view with modal for solutions.

This is the only non-safe vector I can think of. If you can give me an example I will try to detect this and show the the "Unlock" button in this case only.

Of course if I've missed something, please correct me.

Yes, the first two points that you state are the lowest case scenarios for locking the code, to which I agree. And while ideally we should be blocking it. Its an costly thing to do.

At which point it's saved into local storage or the backend?

Its saved as soon as any code is available in the editor. So copy pasting and typing are the counted in that.

This is the only non-safe vector I can think of. If you can give me an example I will try to detect this and show the the "Unlock" button in this case only.

This is the only case that needs to be handled. And again this not a priority for now. The use case for this would be say when we have the solutions coming from a third-party integration to our web app via an URI.

So, the check being in place is enough, and as you correctly figured out, we should intelligently block only this.

I'll share a example URI asap. (Stuart if you are able, please feel free beat me to this though)

@raisedadead thanks for the clarification. I agree that we should only lock the button when the user first loads a challenge and there are code parameters in the URL. In all other situations, we shouldn't lock the code, and should just run it when the user clicks the button or hits ctrl+enter the first time.

As soon as someone can give me an example of code in the URL, I can start working on this.

@tchaffee We test for code in the URI here.

You can dispatch an action to lock the code in the if block like this

return Observable.of(
  makeToast({
    message: 'I found code in the URI. Loading now.'
  }),
  storedCodeFound(challenge, finalFiles),
  // lockTheCodeAction()
);

that should keep you productive until we can get you a code URI to play with

@tchaffee Here is an example URL: http://localhost:3000/challenges/Check%20for%20Palindromes?solution=function%20palindrome(str)%20%7B%0A%20%20str%20%3D%20str.toLowerCase().replace(%2F%5B%5CW_%5D%2Fg%2C%20%27%27)%3B%0A%20%20for(var%20i%20%3D%200%2C%20len%20%3D%20str.length%20-%201%3B%20i%20%3C%20len%2F2%3B%20i%2B%2B)%20%7B%0A%20%20%20%20if(str%5Bi%5D%20!%3D%3D%20str%5Blen-i%5D)%20%7B%0A%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20return%20true%3B%0A%7D

This will lead to the http://localhost:3000/challenges/check-for-palindromes URL and populate the following code:

function palindrome(str) {
  str = str.toLowerCase().replace(/[\W_]/g, '');
  for(var i = 0, len = str.length - 1; i < len/2; i++) {
    if(str[i] !== str[len-i]) {
      return false;
    }
  }
  return true;
}

@QuincyLarson Thank you. I was hesitant to start work on this without being able to test my work. I can set aside some time to look at this soon.

@tchaffee Awesome! Glad I could help. Please let me know if I can do anything else to keep you un-blocked :)

The actual URL I needed to use is http://localhost:3000/en/challenges/javascript-algorithms-and-data-structures-projects/palindrome-checker?solution=function%20palindrome(str)%20%7B%0A%20%20str%20%3D%20str.toLowerCase().replace(%2F%5B%5CW_%5D%2Fg%2C%20%27%27)%3B%0A%20%20for(var%20i%20%3D%200%2C%20len%20%3D%20str.length%20-%201%3B%20i%20%3C%20len%2F2%3B%20i%2B%2B)%20%7B%0A%20%20%20%20if(str%5Bi%5D%20!%3D%3D%20str%5Blen-i%5D)%20%7B%0A%20%20%20%20%20%20return%20false%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%20%20return%20true%3B%0A%7D

Just putting this here for documentation. No need to comment.

@Bouncey can you point me to some existing code that dispatches an action? Thanks.

I think all this locking/unlocking isn't the best idea in terms of sandboxing off code. Instead, I think you should evaluate the code in an iframe on a different origin to ensure that there's no way to interact with a Camper's session.

(Aside but slightly relevant): What is the preferred method for reporting potential security issues to freeCodeCamp?

@joker314 feel free to shoot me an email [email protected]

Please note this issue is blocked by issue #16904

I'm closing this issue as stale since it hasn't been active lately. If you think this is still relevant to the newly updated platform, please explain why, then reopen it.

Was this page helpful?
0 / 5 - 0 ratings