Skip to main content

Salesforce Marketing Cloud detector pack

Vulkro scans Marketing Cloud customisations (AMPscript, Server-Side JavaScript, and package manifest XML) alongside the core Salesforce detectors. Marketing Cloud is a separate Salesforce-owned product with its own DSL and security model: this page covers what the six rule IDs catch, where the scanner looks, and how to use the findings in a Marketing Cloud DevOps pipeline.

Scope: where the scanner walks

The Marketing Cloud detector activates on:

  • *.amp snippets (AMPscript stored as a separate file).
  • *.ssjs files (Server-Side JavaScript).
  • *.html / *.htm and *.js files under any of the following directory markers in your project tree:
    • marketing/
    • mc/
    • cloudpages/
    • mc_pages/
    • marketingcloud/
  • *.xml files under the same directory markers (Cloud Pages package manifests, Automation Studio activity definitions).

Files outside these markers are not classified as Marketing Cloud sources. A bog-standard web project with .html files in public/ is invisible to the Marketing Cloud detector and incurs only the directory walk.

The six rules

MC-001: AMPscript injection (High)

A request-derived @variable (assigned from RequestParameter(...), AttributeValue(...), or QueryParameter(...)) is printed into HTML output via %%=v(@x)=%%, %%=Concat(@x, ...)=%%, or %%=TreatAsContent(@x)=%% without an EncodeHTML(...) wrap.

Marketing Cloud does NOT auto-HTML-encode AMPscript substitution: a tainted @x is rendered verbatim into the Cloud Page or Email Template output (CWE-79).

Trips:

%%[ VAR @q = RequestParameter("q") ]%%
<h1>Search: %%=v(@q)=%%</h1>

Stays quiet:

%%[ VAR @q = RequestParameter("q") ]%%
<h1>Search: %%=v(EncodeHTML(@q))=%%</h1>

MC-002: SSJS server-side eval of caller input (High)

A value pulled from Platform.Function.RequestParameter(...), Platform.Function.AttributeValue(...), or Platform.Function.QueryParameter(...) is passed to eval(...), new Function(...), or Platform.Load(...). The eval-class sink runs the attacker's text as SSJS in the Marketing Cloud Business Unit's execution context: any callout, lookup, or send action that follows is performed under the deploying user's identity (CWE-95).

Trips:

var raw = Platform.Function.RequestParameter("cmd");
eval(raw);

Stays quiet:

var raw = "console.log('hello from static SSJS')";
eval(raw);

MC-003: SQL Query Activity injection (High)

Two shapes:

  1. A <QueryText> body interpolates a non-literal value via %%...%% (AMPscript substitution), ${...} (template string), or a Concat(...) call. The Marketing Cloud Data Extension SQL Query Activity executes the resulting string directly.
  2. An SSJS Platform.Function.ExecuteFilter(...) invocation builds the query by + concatenation against a non-literal identifier.

Either shape allows a tainted value to reach the Data Extension query layer (CWE-89).

Trips:

<QueryText>SELECT EmailAddress FROM Subscribers WHERE ContactID = %%=v(@id)=%%</QueryText>

Stays quiet:

<QueryText>SELECT EmailAddress FROM Subscribers WHERE Active = 1</QueryText>

MC-004: hardcoded SubscriberListId (Medium)

A literal numeric SubscriberListId (four digits or more) carried in deployable XML, SSJS, or AMPscript code. Marketing Cloud SubscriberList IDs identify a specific list inside one tenant's Business Unit; a literal value committed into deployable code is one customer's list bleeding into another customer's package (CWE-540). On deploy the value either fails to resolve in the target tenant or, worse, points at an unrelated list the deploying user happens to have access to.

Trips:

<SubscriberListId>987654</SubscriberListId>

Stays quiet: Reference the list through a deploy-time placeholder or a runtime Data Extension lookup keyed on the target Business Unit.

MC-005: cleartext REST API client_secret (High)

A literal client_secret = "..." (or clientSecret: "...") in Cloud Pages SSJS or AMPscript source. Cloud Pages source is readable by anyone with deploy access to the package, and by anyone who can pull the rendered page source from the public CDN (CWE-798).

The detector is placeholder-aware: values starting with <, ${, {{, or containing the tokens REPLACE, PLACEHOLDER, or YOUR- are skipped.

Trips:

var config = {
client_id: "abcdefghijklmnopqrstuvwxyz",
client_secret: "Z9X8Y7W6V5U4T3S2R1Q0P9_realsecret_committedtosource"
};

Stays quiet:

var config = {
client_secret: "REPLACE_ME_AT_DEPLOY"
};

MC-006: mass-mail without subscriber opt-in check (High)

A mass-mail send sink (TriggeredSend.Send(...), SendDefinition.Send(...), or Platform.Function.InvokeSend(...)) is invoked with no preceding opt-in / consent guard in the same file. Accepted guards:

  • HasOptedIn(...)
  • HasSubscribed(...)
  • Subscriber.Status == "Active" (or single-quoted)
  • Lookup("_Subscribers", ..., "Status", "Active")

Sending email without verifying the recipient's opt-in status is a CAN-SPAM (US) and GDPR e-Privacy (EU) violation in addition to a CWE-285 authorisation gap.

Trips:

var recipient = "[email protected]";
TriggeredSend.Send("welcome_v3", recipient);

Stays quiet:

if (Subscriber.Status == "Active") {
TriggeredSend.Send("welcome_v3", "[email protected]");
}

AppExchange Security Review mapping

When you run vulkro sf-appexchange-report, the Marketing Cloud findings land in three checklist sections:

RuleSeverityAppExchange row
MC-001HighSensitive Data Storage and Logging
MC-002HighSensitive Data Storage and Logging
MC-003HighSensitive Data Storage and Logging
MC-004MediumObject and Field Permissions (CRUD / FLS)
MC-005HighExternal Integrations and Callouts
MC-006HighExternal Integrations and Callouts

CI snippet

A Marketing Cloud DevOps pipeline that fails the build on any finding:

name: Marketing Cloud security scan

on: [pull_request, push]

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vulkro
run: curl -fsSL https://vulkro.com/install.sh | sh
- name: Activate
run: vulkro activate ${{ secrets.VULKRO_LIC }}
- name: Scan
run: vulkro scan --format sarif --output vulkro.sarif .
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: vulkro.sarif

The scan walks marketing/, mc/, cloudpages/, mc_pages/, and marketingcloud/ automatically. Cartridge-style monorepos that keep Marketing Cloud sources alongside core CRM (Apex, LWC, Aura, Flow, Visualforce) get the full Salesforce detector set in one pass.

Exit codes

Same contract as vulkro scan:

  • 0 when the scan completes with no Marketing Cloud findings.
  • 1 when the scan completes and at least one Marketing Cloud finding (or any other finding) was reported.
  • 2 on an internal error (bad args, IO failure).