AngularJS SCE disabled

Disabling AngularJS Strict Contextual Escaping

Description

AngularJS Strict Contextual Escaping (SCE) applies context-specific trust checks to HTML and sensitive URL bindings. For HTML, it sanitizes untrusted values when $sanitize is available and rejects them otherwise. It does not automatically sanitize all CSS or JavaScript. Disabling SCE globally with $sceProvider.enabled(false) may lead to XSS when untrusted values reach bindings such as ng-bind-html. Disabling it in a shared module also affects the whole application.

Official AngularJS support ended in January 2022. This guidance is for maintaining existing AngularJS applications.

Potential impact

  • XSS may execute malicious JavaScript in the document's context, changing the interface or enabling further attacks.
  • Cookies or tokens accessible to the page may be stolen and used to take over a session.
  • Data displayed on or accessible to the page, such as personal information or CSRF tokens, may be sent elsewhere.
  • DOM changes may display fake input or payment forms for phishing.
  • Disabled SCE combined with insufficient input validation, CSP, or other defenses may increase the damage.

Remediation

  • Do not use $sceProvider.enabled(false). Keep SCE enabled by default.
  • Handle dynamic content as follows:
    • Use ng-bind for text to avoid interpreting HTML.
    • If HTML is necessary, sanitize it with $sanitize from ngSanitize. Use $sce.trustAsHtml(cleanHtml) only when required to mark the sanitized value as trusted in that context.
  • Apply allow-list sanitization to user-generated content, such as comments or profiles, on the server and client before storing or displaying it.
  • Restrict the sources of ng-bind-html values and bind only trusted data.
  • Check through code review and static analysis (SAST) that third-party scripts or modules do not disable SCE.
  • Apply Content-Security-Policy (CSP) to limit inline scripts and arbitrary script loading.

Examples

Before

Each block combines excerpts from separate app.js and index.html files.

javascript
<!-- app.js -->
(function(){
  angular.module('shopApp', [])
    .config(['$sceProvider', function($sceProvider){
      // BAD: disable SCE globally
      $sceProvider.enabled(false);
    }])
    .controller('CommentCtrl', ['$location', function($location){
      var vm = this;
      // An attacker can inject through the URL ?c=<img src=x onerror=alert(1)>
      vm.comment = $location.search().c || '';
    }]);
})();

<!-- index.html -->
<div ng-app="shopApp" ng-controller="CommentCtrl as vm">
  <!-- BAD: render HTML directly with SCE disabled -->
  <div ng-bind-html="vm.comment"></div>
</div>

After

javascript
<!-- app.js -->
(function(){
  angular.module('shopApp', ['ngSanitize'])
    // SCE is not disabled; keep the enabled default
    .controller('CommentCtrl', ['$location', '$sanitize', '$sce', function($location, $sanitize, $sce){
      var vm = this;
      var raw = $location.search().c || '';
      // 1) Safest option: display as text only
      vm.commentText = raw;
      // 2) Only when dynamic HTML is necessary: sanitize before marking trusted
      var clean = $sanitize(raw);
      vm.commentHtml = $sce.trustAsHtml(clean);
    }]);
})();

<!-- index.html -->
<div ng-app="shopApp" ng-controller="CommentCtrl as vm">
  <!-- Render text without interpreting HTML -->
  <div ng-bind="vm.commentText"></div>

  <!-- Use only when needed: bind the result of $sanitize + trustAsHtml -->
  <div ng-bind-html="vm.commentHtml"></div>
</div>

Explanation:

  • Before: With SCE checks disabled by $sceProvider.enabled(false), passing URL input to ng-bind-html may render dangerous HTML.
  • After: SCE remains enabled, and ng-bind displays text by default. When HTML is needed, $sanitize processes it first. $sce.trustAsHtml marks a value as trusted; it is not itself a sanitizer.

References