Docs / Strand / connectors/surface
Surface Scanner
Direction: Write Only | Type: surface.platform
Scan payloads for threats, malware indicators, and malicious content using the platform's built-in Surface security scanner. Returns a threat score, classification, indicators of compromise (IOCs), and a recommended action.
Setup
The Surface connector is auto-provisioned for all Tendrl platform users. No API key or external configuration is needed: the connection to the Surface scanner is managed automatically for you.
To add the connector:
- Open the Connectors page in Strand
- Click Add Connector and select Surface Scanner
- Give it a name (e.g., "Payload Scanner") and save
That's it. No credentials to configure.
Note: If the connector fails with a permissions error, check that Surface is enabled for your account.
Node Configuration
Drag a Connector node onto your workflow canvas and select your Surface connector. Configure the following fields:
| Field | Required | Default | Description | |
|---|---|---|---|---|
| Content | Yes | - | The payload content to scan. Supports Jinja templates (e.g., `{{ payload \ | tojson }}`). |
| Label | No | "" |
Optional label for tracking this scan in logs and audit trails. Supports Jinja templates. | |
| Reject On | No | ["malicious"] |
Threat levels that cause safe to be false. Options: malicious, suspicious, clean. Matched case-insensitively against the returned threat_level. |
|
| Profile ID | No | "" |
Scan profile to use. Leave empty to use the account's default profile. | |
| Timeout | No | 30 |
Request timeout in seconds (range: 5-120). |
Output
After the scan completes, the connector node's output payload contains the scan results at the top level:
{
"score": 95,
"threat_level": "Clean",
"primary_threat": "No threats detected",
"recommended_action": "Allow",
"iocs": [],
"safe": true,
"request_id": "scan_abc123",
"service": "surface",
"operation": "scan_payload"
}
Output Fields
| Field | Type | Description |
|---|---|---|
score |
number | Threat score from 0-100. Higher is safer. |
threat_level |
string | Classification returned by Surface (title-case): Clean, Informational, Suspicious, or Malicious. reject_on matching is case-insensitive. |
primary_threat |
string | Name of the primary threat detected. For clean scans this is "No threats detected" |
recommended_action |
string | Suggested action: Allow, Review, or Block |
iocs |
array | List of indicators of compromise found in the payload |
safe |
boolean | Computed by Strand (not returned by Surface): true if threat_level is not in the reject_on list (case-insensitive). |
request_id |
string | Unique scan request ID for audit trails |
service |
string | Always "surface" |
operation |
string | Always "scan_payload" |
Score Polarity
The score uses a "higher is safer" scale:
| Score Range | Threat Level | Meaning |
|---|---|---|
| 86-100 | Clean |
No threats detected |
| 71-85 | Informational |
Minor findings worth noting, treated as safe by default |
| 31-70 | Suspicious |
Potential threats, review recommended |
| 0-30 | Malicious |
Active threats detected, block recommended |
The safe Flag
The safe boolean is computed by Strand (it is not returned by Surface) from the returned threat_level and your reject_on configuration:
- Default
reject_on: ["malicious"]: only malicious payloads setsafe: false - Set
reject_on: ["malicious", "suspicious"]to also reject suspicious payloads - Matching is case-insensitive, so
Maliciousandmaliciousare treated the same - Use
safein a downstream Condition node to branch your workflow
Payload Clobbering
Important: After the Surface scan node runs, the node's output payload contains only the scan results. The original incoming payload is no longer directly available as payload.
To access data from earlier steps after a scan, use the steps context:
{{ steps.step1.output_payload.temperature }}
{{ steps.my_transform.output_payload.user_email }}
{{ initial.payload.raw_body }}
This is the same pattern used by any connector node that replaces the payload. Plan your workflow accordingly: capture any values you need before the scan, or reference them through steps.
Example
A three-step workflow that scans an incoming IoT payload and either forwards it or raises an alert:
Step 1: Scan (Surface connector node, id: scan):
{
"content": "{{ payload | tojson }}",
"label": "iot-ingest-{{ payload.device_id }}",
"reject_on": ["malicious", "suspicious"]
}
Step 2: Branch (Condition node):
{{ payload.safe }} == true
Step 3a: Forward (if safe, Contact connector):
{
"data": {
"temperature": "{{ initial.payload.temperature }}",
"scanned": true,
"scan_score": "{{ steps.scan.output_payload.score }}"
},
"dest": "sensor-gateway"
}
Note: The scan node clobbers the payload (see Payload Clobbering), so the originaltemperatureis no longer onpayloadafter the scan. Read it frominitial.payload(the trigger payload) or from an earlier step viasteps.<id>.output_payload. The scan-result fields (score,threat_level, etc.) are read fromsteps.scan.output_payload.
Step 3b: Alert (if unsafe, Slack connector):
Threat detected on device {{ initial.payload.device_id }}.
Level: {{ steps.scan.output_payload.threat_level }}
Threat: {{ steps.scan.output_payload.primary_threat }}
Action: {{ steps.scan.output_payload.recommended_action }}
Errors
| Error | Meaning |
|---|---|
| Invalid or expired surface API key | Scanner access is misconfigured for your account. Contact your administrator. |
| Monthly scan limit exceeded | Account has reached its monthly scan quota. |
| Insufficient permissions | Scanner role not assigned. Check your account's role configuration. |
| surface scanner unavailable | The Surface scanner is temporarily unavailable. Retry shortly. |
| surface service is unreachable | Unable to reach the Surface scanner. Retry shortly. |
Tendrl