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:

  1. Open the Connectors page in Strand
  2. Click Add Connector and select Surface Scanner
  3. 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:

json

{
  "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:

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:

code

{{ 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):

json

{
  "content": "{{ payload | tojson }}",
  "label": "iot-ingest-{{ payload.device_id }}",
  "reject_on": ["malicious", "suspicious"]
}

Step 2: Branch (Condition node):

code

{{ payload.safe }} == true

Step 3a: Forward (if safe, Contact connector):

json

{
  "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 original temperature is no longer on payload after the scan. Read it from initial.payload (the trigger payload) or from an earlier step via steps.<id>.output_payload. The scan-result fields (score, threat_level, etc.) are read from steps.scan.output_payload.

Step 3b: Alert (if unsafe, Slack connector):

code

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.