Skip to content

Preserve CSP nonce on scripts with src attribute in DOM manipulation. #4323

@buddh4

Description

@buddh4

When appending html content containing script tags with src attribute, I get a CSP script-src violation error. Tested with current master branch.

Since domManip uses jQuery_evalUrl for script tags with src attribute it violates CSP's with a nonce script rule. I'am aware of #3969 (comment) but I can't use crossDomain since I require sync script loading. The following fix seems only to work for scripts without src attribute https://github.com/jquery/jquery/pull/4269/files.

The problematic line:

jQuery._evalUrl( node.src );

Hacky fix:

manipulation/_evalUrl.js:

Adding a node parameter to _evalUrl which will be passed to jQuery.globalEval.

jQuery._evalUrl = function( url, node ) {
    return jQuery.ajax( {
        url: url,

        // Make this explicit, since user can override this through ajaxSetup (#11264)
        type: "GET",
        dataType: "script",
        cache: true,
        async: false,
        global: false,

        // Only evaluate the response if it is successful (gh-4126)
        // dataFilter is not invoked for failure responses, so using it instead
        // of the default converter is kludgy but it works.
        converters: {
            "text script": function() {}
        },
        dataFilter: function( response ) {
            jQuery.globalEval( response, node );
        }
    } );
};

/manipulation.js:159

In my tests I noticed that the nonce attribute is not accessible anymore after the script nodes were added by appendChild() (if CSP is active). A dirty workaround was to backup the nonce attributes before inserting the dom like this:

for ( ; i < l; i++ ) {
    node = fragment;

    if ( i !== iNoClone ) {
        node = jQuery.clone( node, true, true );

        // Keep references to cloned scripts for later restoration
        if ( hasScripts ) {

            // Support: Android <=4.0 only, PhantomJS 1 only
            // push.apply(_, arraylike) throws on ancient WebKit
            jQuery.merge( scripts, getAll( node, "script" ) );
        }
    }

    /**
     * Backup nonce attributes, since they won't be accessible after dom insertion
     */
    if ( hasScripts ) {
        scripts.forEach( function( scr ) {
            scr.nonceValue = scr.getAttribute( "nonce" );
        } );
    }


    callback.call( collection[ i ], node, i );
}

if ( hasScripts ) {
    doc = scripts[ scripts.length - 1 ].ownerDocument;

    // Reenable scripts
    jQuery.map( scripts, restoreScript );

    // Evaluate executable scripts on first document insertion
    for ( i = 0; i < hasScripts; i++ ) {
        node = scripts[ i ];
        if ( rscriptType.test( node.type || "" ) &&
            !dataPriv.access( node, "globalEval" ) &&
            jQuery.contains( doc, node ) ) {

            if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {

                // Optional AJAX dependency, but won't run scripts if not present
                if ( jQuery._evalUrl && !node.noModule ) {

                    /**
                     * Check for nonce backup value
                     */
                    if ( node.nonceValue ) {
                        node.setAttribute( "nonce", node.nonceValue );
                    }

                    jQuery._evalUrl( node.src,  node );
                }
            } else {
                DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions