safevalues

repository·main·Indexed 40 days ago

https://github.com/google/safevalues

A security-focused library for TypeScript and JavaScript designed to prevent Cross-Site Scripting (XSS) vulnerabilities. It provides safe-by-construction values through escaping and sanitization, integrates with Trusted Types for runtime protection, and offers DOM sink wrappers (via safevalues/dom) to safely handle dangerous APIs like innerHTML and location.href. Version 1.2.0 includes a default allowlist-based HTML sanitizer and builders for SafeHtml, SafeScript, TrustedResourceUrl, and SafeStyleSheet.

Tokens
13.3K
Snippets
40
Records
48
Agent score
100%

What's inside safevalues

  1. What is safevalues?

    main

    safevalues is a security library designed to prevent Cross-Site Scripting (XSS) vulnerabilities in TypeScript and JavaScript. It works by providing safe-by-construction values through escaping or sanitization, making security decisions so developers don't have to.

    Key capabilities include:

    • Trusted Types Integration: It defines policies to produce Trusted Types when the browser API is available, providing runtime protection.
    • Extended Sink Protection: It provides types and builders for DOM APIs that are not covered by the standard Trusted Types specification.
    • DOM Sink Wrappers: It offers wrappers for dangerous DOM APIs (e.g., setLocationHref from safevalues/dom) that sanitize arguments before assignment.
    • Transparent Polyfilling: If Trusted Types is not supported by the browser, safevalues transparently uses its own internal types so the application continues to function.
  2. How the default HTML sanitization policy works

    main

    The default policy is allowlist-based. A tag or attribute is only preserved if it meets specific criteria defined in the sanitizer_table:

    Tags: A tag is allowed if it is part of allowedElements OR has a corresponding entry in elementPolicies.

    Attributes: An attribute is allowed if it is [globally allowed], has a [global attribute policy], OR is [allowed for the specific element] being processed.

    Attribute values are handled according to an AttributePolicyAction, which can preserve, sanitize/normalize, or drop the value.

  3. Enforce restrictions on restricted functions with tsec

    main
    Functions in the safevalues/restricted/ modules do not provide the same safety guarantees as the rest of the safevalues package. Because they are designed to be restricted and require human review, it is highly recommended to use a tool like tsec to help enforce usage restrictions and ensure that justifications are provided where necessary.
  4. Handle safety-web false positives with setElementAttribute

    main

    If safety-web flags an assignment (e.g., myImg.src = value) because it cannot determine the element type, use setElementAttribute from safevalues/dom. This function performs a runtime check on the element and attribute name to ensure the assignment is safe.

    Important: You must still use the correct safe type for the specific attribute. For example, a <script> element's src attribute requires a TrustedResourceUrl, not a SafeScript or a plain string.

    import {setElementAttribute} from 'safevalues/dom';
    import {trustedResourceUrl} from 'safevalues';
    
    // Use when element type is unknown to avoid safety-web warnings
    declare const myImg: unknown;
    setElementAttribute(myImg, 'src', value);
    
    // Correct usage for a script element
    const myScript = document.createElement('script');
    setElementAttribute(myScript, 'src', trustedResourceUrl`https://myapp.com/script.js`);
  5. Use reviewed conversions for custom Trusted Type needs

    main

    When standard library builders do not meet your application's specific needs, but the use case is narrow and safe, you can use safevalues/restricted/reviewed.

    If you use tsec, you can use reviewed conversion functions which create a polyfilled value. These functions require a justification object to document why the operation is safe, helping to enforce human review and prevent misuse.

    If you are in a browser with native Trusted Types support, you should instead create a new policy, add it to your headers, and include an extensive comment explaining the safety rationale.

    import {setScriptSrc} from 'safevalues/dom';
    import {scriptSafeByReview} from 'safevalues/restricted/reviewed';
    
    if (document.domain === '') {
        const scriptText = scriptSafeByReview(
            userInput,
            {
                justification: `Even though the input is user controller, the wrapping if statement
             ensures that this code is only ever run in a sandboxed origin`
            }
        );
        setScriptSrc(scriptEl, scriptText);
    }
  6. Set attributes safely using setAttribute alternatives

    main

    Direct use of Element#setAttribute is discouraged as it can bypass security checks. Use these alternatives:

    1. Specific Setters: Use dedicated functions like setAnchorHref for known attributes.
    2. Prefixed Attributes: If attribute names are dynamic but follow a known pattern (e.g., aria-*), use setElementPrefixedAttribute with safeAttrPrefix to restrict the allowed prefixes.
    3. Dataset API: For data-* attributes, use the standard Element.dataset API.
    import {setAnchorHref, setElementPrefixedAttribute} from 'safevalues/dom';
    import {safeAttrPrefix} from 'safevalues';
    
    // 1. Use specific setters
    setAnchorHref(anchor, safeUrl`https://example.com`);
    
    // 2. Use prefixed attributes for dynamic names (e.g., aria-* or role*)
    setElementPrefixedAttribute(
        [safeAttrPrefix`aria-`, safeAttrPrefix`role`], 
        elem, 
        attr, 
        value
    );
    
    // 3. Use dataset for data-* attributes
    elem.dataset['safe'] = true;
  7. Avoid false positives in setTimeout and setInterval static analysis

    main

    Static checkers may flag indirect calls to setTimeout or setInterval as potential eval-like vulnerabilities. To resolve this, wrap the call in an arrow function with a restrictive signature. This informs the checker that the first argument is a function and not a string.

    // Instead of assigning the function directly:
    // const setTimeoutIndirect = setTimeout; 
    
    // Use a wrapper with a restrictive signature:
    const setTimeoutIndirect = (cb: () => void, timeout: number) => {
      setTimeout(cb, timeout);
    };
    
    setTimeoutIndirect(callback, 10);
  8. Execute code safely with SafeScript and globalEval

    main

    To execute JavaScript code snippets safely, use SafeScript instances.

    1. Setting script content: Use setScriptTextContent to set the text of a <script> tag. This is preferred over .textContent or .innerHTML because it propagates security nonces for CSP compliance.
    2. Dynamic evaluation: Instead of using eval(), convert your content to a SafeScript and use globalEval() to execute it.
    import {setScriptTextContent, globalEval} from 'safevalues/dom';
    
    // Setting script content safely
    setScriptTextContent(script, safeScriptValue);
    
    // Safe alternative to eval()
    globalEval(safeScriptValue);
  9. Strip development code and error messages from production builds

    main

    To minimize the production bundle size, safevalues strips out error messages and development-time checks using the process.env.NODE_ENV !== 'production' compile-time constant from the environment.ts module.

    To ensure the compiler successfully removes this code, you must follow two rules:

    1. Use dedicated if statements: Always use the constant in a standalone if statement without combining it with other conditions (e.g., do not use if (otherVar && process.env.NODE_ENV !== 'production')).
    2. Proximity to API: Place these if statements as close to the public API surface as possible to maximize code stripping.

    Error Messages

    Only include verbose error messages inside a development-mode check so they are removed in production.

    Development-time Checks

    Security or development checks that use compile-time assertions or have no downstream effects should be wrapped in a development-mode check.

    // Example: Stripping an error message
    import {process.env.NODE_ENV !== 'production'} from './environment';
    
    let message = '';
    if (process.env.NODE_ENV !== 'production') {
      message = 'Verbose error message';
    }
    throw new Error(message);
    
    // Example: Stripping a development-time check
    import {process.env.NODE_ENV !== 'production'} from './environment';
    
    if (process.env.NODE_ENV !== 'production') {
      assertIsTemplateObject(input);
    }
  10. Remediate code loading vulnerabilities using TrustedResourceUrl

    main

    When loading external resources (scripts, iframes, workers, etc.), avoid assigning raw strings to .src or .data properties. Use safevalues/dom functions that accept a TrustedResourceUrl instance to ensure the URL is safe.

    import {setScriptSrc, setObjectData, setIframeSrc, createWorker, createSharedWorker, workerGlobalScopeImportScripts, serviceWorkerContainerRegister} from 'safevalues/dom';
    
    // Safe alternatives for resource loading:
    setScriptSrc(scriptEl, trustedResourceUrl);
    setObjectData(objectEl, trustedResourceUrl);
    setIframeSrc(iframe, trustedResourceUrl);
    const worker = createWorker(trustedResourceUrl);
    const sharedWorker = createSharedWorker(trustedResourceUrl);
    workerGlobalScopeImportScripts(self, trustedResourceUrl);
    serviceWorkerContainerRegister(self, trustedResourceUrl);
  11. Remediate HTML injection in DOM APIs using SafeHtml

    main

    To prevent XSS when rendering HTML, avoid direct assignments to risky DOM properties like .innerHTML, .outerHTML, or .srcdoc. Instead, use safevalues/dom setter functions that require a SafeHtml instance. If the content is plain text and does not require HTML parsing, use .textContent or .insertAdjacentText instead.

    import {setElementInnerHtml, setElementOuterHtml, setIframeSrcdoc, elementInsertAdjacentHTML, documentWrite, domParserParseFromString, rangeCreateContextualFragment} from 'safevalues/dom';
    
    // Safe alternatives for common sinks:
    setElementInnerHtml(element, safeHtmlValue);
    setElementOuterHtml(element, safeHtmlValue);
    setIframeSrcdoc(iframe, safeHtmlValue);
    elementInsertAdjacentHTML(element, 'beforeend', safeHtmlValue);
    documentWrite(document, safeHtmlValue);
    domParserParseFromString(parser, safeHtmlValue, mimeType);
    rangeCreateContextualFragment(range, safeHtmlValue);
  12. Use restricted functions for legacy migrations

    main

    The safevalues/restricted/legacy module provides unsafe conversions from string to Trusted Types. These are intended as temporary bridge solutions during migration from raw strings to Trusted Types.

    Workflow:

    1. Use a legacy conversion function (e.g., legacyUnsafeResourceUrl) at the sink (where the value is used, like innerHTML or setScriptSrc) to make the code compatible with Trusted Types.
    2. Gradually "move up" the conversion point closer to where the value is originally created.
    3. Once the conversion point is in a context where the value can be constructed safely, replace the legacy conversion with a proper safe builder and remove the legacy dependency.
    import {legacyUnsafeResourceUrl} from 'safevalues/restricted/legacy';
    import {setScriptSrc} from 'safevalues/dom';
    
    // Temporary bridge: converting a string to a Trusted Type at the sink
    setScriptSrc(script, legacyUnsafeResourceUrl(url));