diff --git a/content_lock.api.inc b/content_lock.api.inc
index 4870d89..8f49429 100644
--- a/content_lock.api.inc
+++ b/content_lock.api.inc
@@ -16,6 +16,31 @@
  */
 
 /**
+ * Control protected paths for node edit forms.
+ *
+ * The hook is typically implemented to check if a path should be protected for
+ * CSRF attacks on the node edit forms.
+ *
+ * @param string $path
+ *   The path to check protection for.
+ *
+ * @return
+ *   TRUE is the path should be protected.
+ *   Note: this grant is permissive rather than restrictive.
+ *
+ * @see hook_field_access().
+ */
+function hook_content_lock_path_protected($path) {
+  if (strpos($path, 'node/') === 0) {
+    foreach (array('node/*/edit', 'node/*/revisions/*/revert') as $protected_pattern) {
+      if (drupal_match_path($path, $protected_pattern)) {
+        return TRUE;
+      }
+    }
+  }
+}
+
+/**
  * Determine if locking should be disabled for a given node (e.g. by
  * checking its type or other properties).
  *
diff --git a/content_lock.module b/content_lock.module
index 9c679d0..da4e241 100644
--- a/content_lock.module
+++ b/content_lock.module
@@ -93,6 +93,79 @@ function content_lock_menu() {
 }
 
 /**
+ * Implements hook_menu_site_status_alter().
+ */
+function content_lock_menu_site_status_alter(&$page_callback_result, $path) {
+  if (content_lock_is_path_protected($path)) {
+    if (!isset($_GET['content_lock_token']) || !drupal_valid_token($_GET['content_lock_token'], $path)) {
+      $page_callback_result = MENU_ACCESS_DENIED;
+    }
+  }
+}
+
+/**
+ * Check if an internal Drupal path should be protected with a token.
+ *
+ * Adds requirements that certain path be accessed only through tokenized URIs
+ * which are enforced by this module. This prevents people from being CSRFed
+ * into locking nodes that they can access without meaning to lock them.
+ *
+ * @return bool
+ *   Returns TRUE if the path is protected or FALSE if not protected.
+ */
+function content_lock_is_path_protected($path) {
+  $cache = &drupal_static(__FUNCTION__, array());
+
+  // Check cache.
+  if (isset($cache[$path])) {
+    return $cache[$path];
+  }
+
+  // Invoke hook and collect grants/denies for protected paths.
+  $protected = array();
+  foreach (module_implements('content_lock_path_protected') as $module) {
+    $protected = array_merge($protected, array($module => module_invoke($module, 'content_lock_path_protected', $path)));
+  }
+
+  // Allow other modules to alter the returned grants/denies.
+  drupal_alter('content_lock_path_protected', $protected, $path);
+
+  // If TRUE is returned, path is protected.
+  $cache[$path] = in_array(TRUE, $protected);
+  return $cache[$path];
+}
+
+/**
+ * Implements hook_content_lock_path_protected().
+ */
+function content_lock_content_lock_path_protected($path) {
+  // Always protect node edit and revert paths.
+  if (strpos($path, 'node/') === 0) {
+    foreach (array('node/*/edit', 'node/*/revisions/*/revert') as $protected_pattern) {
+      if (drupal_match_path($path, $protected_pattern)) {
+        return TRUE;
+      }
+    }
+  }
+
+  // Allow extended tests via protection_menu_token module.
+  if (function_exists('protection_menu_token_is_path_protected')) {
+    return protection_menu_token_is_path_protected($path);
+  }
+}
+
+/**
+ * Implements hook_preprocess_link().
+ */
+function content_lock_preprocess_link(&$vars) {
+  if (content_lock_is_path_protected($vars['path'])) {
+    if (!isset($vars['options']['query']['content_lock_token'])) {
+      $vars['options']['query']['content_lock_token'] = drupal_get_token($vars['path']);
+    }
+  }
+}
+
+/**
  * Implement hook_node_validate() to check that the user is
  * maintaining his lock.
  *
@@ -256,11 +329,28 @@ function content_lock_form_alter(&$form, &$form_state, $form_id) {
       // If the form did not get submitted we show it the first time
       // so try to get the lock if possible
       else if ($form_state['submitted'] === FALSE) {
-        // Finally set the lock if everthing passed.
-        if(content_lock_node($nid, $user->uid) == false) {
-          // could not lock node, it's locked by someone else
+        // Refuse to lock if this page loaded is not protected.
+        // Protects against accidental introduction of CSRF
+        // (user locking nodes without knowing it and causing DoS) by any
+        // module rendering the node form at a menu path other than
+        // 'node/%node/edit'.
+        $menu_item = menu_get_item();
+        if (!content_lock_is_path_protected($menu_item['path'])) {
+          $node_title = empty($node->title) ? 'node/' . $nid : $node->title;
+          drupal_set_message(t('Unable to lock !node for editing because this page load was not protected. You still may !edit node using the normal node editing page.', array(
+            '!node' => l(t('%node_title', array('%node_title' => $node_title)), 'node/' . $nid, array('html' => TRUE)),
+            '!edit node' => l(t('edit %node_title', array('%node_title' => $node_title)), 'node/' . $nid . '/edit', array('html' => TRUE)),
+          )), 'error');
+          watchdog('content_lock', 'Attempt to load the node_form form at menu path %path which is not protected from CSRF. Developers who want to create custom node editing pages and protect them with hook_content_lock_path_protected() or use protection_menu_token module to protect this path.', array('%path' => $menu_item['path']), WATCHDOG_WARNING);
           drupal_goto($destination);
         }
+        else {
+          // Finally set the lock if everything passed.
+          if (content_lock_node($nid, $user->uid) == false) {
+            // Could not lock node, it's locked by someone else.
+            drupal_goto($destination);
+          }
+        }
       }
       // else if($form_state['submitted'] === TRUE)
       // if it is a submission, we would not need to lock once again, as we had before.
