Loading

Marketing Cloud Engagement - Audit Content Builder Assets (CloudPages & Emails) for Hardcoded Secrets with SSJS

Julkaisupäivä: Mar 26, 2026
Kuvaus

In the business unit where you need to audit Content Builder assets for hardcoded secrets, create a Data Extension (DE) with the following specifications:

  • Name: Asset_SecurityAudit

  • External Key: Asset_SecurityAudit

Field NameData TypeLengthPrimary KeyNullableDefault Value
AssetIdText100NoNoNot Applicable
AssetNameText255NoNoNot Applicable
AssetTypeText100NoNoNot Applicable
FoundStringText100NoNoNot Applicable
AuditDateDateNot ApplicableNoNoCurrent Date

After the DE has been created, you will need to create an SSJS activity to fetch the assets via the Content Builder REST API and log the results. The script pages through your assets, searches the raw JSON of each asset for a specific string (such as "clientsecret"), and uses WSProxy to insert the results into the Data Extension.

NOTE: Please ensure that the subdomain, clientId, clientSecret, and mid values are supplied. This process leverages the REST API, so you will need to reference an appropriate Installed Package within your account with saved_content_read scope.

 

<script runat="server">
Platform.Load("Core", "1.1.1");

try {
    // 1. Set up your variables
    var subdomain = "YOUR_SUBDOMAIN"; 
    var clientId = "YOUR_CLIENT_ID";
    var clientSecret = "YOUR_CLIENT_SECRET"; 
    var mid = "YOUR_MID";
    
    var searchString = "client_secret";
    var targetDEKey = "Asset_SecurityAudit"; 

    var authUrl = "https://" + subdomain + ".auth.marketingcloudapis.com/v2/token";
    var payload = {
        grant_type: "client_credentials",
        client_id: clientId,
        client_secret: clientSecret,
        account_id: mid
    };

    // 2. Authenticate
    var authReq = new Script.Util.HttpRequest(authUrl);
    authReq.emptyContentHandling = 0;
    authReq.retries = 2;
    authReq.continueOnError = true;
    authReq.contentType = "application/json";
    authReq.method = "POST";
    authReq.postData = Stringify(payload);

    var authResp = authReq.send();
    var authJson = Platform.Function.ParseJSON(String(authResp.content));
    var token = authJson.access_token;
    var restUrl = authJson.rest_instance_url;

    if (token) {
        var prox = new Script.Util.WSProxy();
        var page = 1;
        var pageSize = 50;
        var hasMore = true;

        // 3. Loop through Asset pages
        while (hasMore && page <= 500) { // Safety cap at 500 pages to prevent infinite loops
            var assetsUrl = restUrl + "asset/v1/content/assets?$page=" + page + "&$pagesize=" + pageSize;
            var assetReq = new Script.Util.HttpRequest(assetsUrl);
            assetReq.emptyContentHandling = 0;
            assetReq.retries = 2;
            assetReq.continueOnError = true;
            assetReq.method = "GET";
            assetReq.setHeader("Authorization", "Bearer " + token);

            var assetResp = assetReq.send();
            var assetJson = Platform.Function.ParseJSON(String(assetResp.content));
            var items = assetJson.items || [];

            // 4. Search and insert
            for (var i = 0; i < items.length; i++) {
                // Stringify the entire asset object to catch nested content, views, and HTML
                var itemString = Stringify(items[i]).toLowerCase(); 
                
                if (itemString.indexOf(searchString.toLowerCase()) !== -1) {
                    var assetId = String(items[i].id);
                    var assetName = items[i].name || "Unknown";
                    var assetType = items[i].assetType ? items[i].assetType.name : "Unknown";

                    var insertProps = [
                        { Name: "AssetId", Value: assetId },
                        { Name: "AssetName", Value: assetName },
                        { Name: "AssetType", Value: assetType },
                        { Name: "FoundString", Value: searchString }
                    ];
                    
                    prox.createItem("DataExtensionObject", {
                        CustomerKey: targetDEKey,
                        Properties: insertProps
                    });
                }
            }

            // 5. Check if we need to fetch the next page
            if (items.length < pageSize) {
                hasMore = false;
            } else {
                page++;
            }
        }
    } 
} catch (error) {
    Write("Error: " + Stringify(error));
}
</script>
Ratkaisu

Run the above SSJS activity to search all Content Builder assets and log any identified vulnerable assets within the DE Asset_SecurityAudit.

Because this script uses pagination, it will automatically cycle through your assets in batches of 50. If the SSJS activity errors with a timeout (30mins) due to an extremely large asset library, you can manually adjust the $page variable in the script to start from where the previous run timed out.

Knowledge-artikkelin numero

005317466

 
Ladataan
Salesforce Help | Article