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:
*.ampsnippets (AMPscript stored as a separate file).*.ssjsfiles (Server-Side JavaScript).*.html/*.htmand*.jsfiles under any of the following directory markers in your project tree:marketing/mc/cloudpages/mc_pages/marketingcloud/
*.xmlfiles 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:
- A
<QueryText>body interpolates a non-literal value via%%...%%(AMPscript substitution),${...}(template string), or aConcat(...)call. The Marketing Cloud Data Extension SQL Query Activity executes the resulting string directly. - 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:
TriggeredSend.Send("welcome_v3", recipient);
Stays quiet:
if (Subscriber.Status == "Active") {
}
AppExchange Security Review mapping
When you run vulkro sf-appexchange-report, the Marketing Cloud
findings land in three checklist sections:
| Rule | Severity | AppExchange row |
|---|---|---|
| MC-001 | High | Sensitive Data Storage and Logging |
| MC-002 | High | Sensitive Data Storage and Logging |
| MC-003 | High | Sensitive Data Storage and Logging |
| MC-004 | Medium | Object and Field Permissions (CRUD / FLS) |
| MC-005 | High | External Integrations and Callouts |
| MC-006 | High | External 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:
0when the scan completes with no Marketing Cloud findings.1when the scan completes and at least one Marketing Cloud finding (or any other finding) was reported.2on an internal error (bad args, IO failure).