Skip to main content
This guide covers everything you need to build components for ShipSec Studio.

Getting Started

File Location

Components live in worker/src/components/<category>/:

Category Source of Truth

Component categories are defined once in packages/shared/src/component-categories.ts.
  • Backend categorization and API metadata read from this shared registry.
  • Frontend schema validation and category styling also read from the same registry.
When adding or renaming a category, update this shared file so backend and frontend stay in sync.

ID Naming Convention


Runner Types

Inline Component Example

Docker Component Example


ExecutionContext

The context passed to execute() provides services and utilities:

Component Definition

A component is defined using defineComponent and must specify its inputs, outputs, and optional parameters.

Inputs vs. Parameters

Understanding the difference between Inputs and Parameters is critical for building good components.

Defining Inputs (Ports)

Inputs represent the data that flows into your component from other parts of the workflow. They appear as connection handles on the left side of the node.
Supported valuePriority values:
  • connection-first: Use the value from the port connection if it exists, otherwise use the manual override.
  • manual-first: Always use the manual override if a value is provided, even if a port is connected.

Defining Parameters

Parameters are configuration settings for the component that are set when the user is designing the workflow. They do not accept connections from other nodes; they are always static values (or manual strings).

Parameter Editors

The editor field in param() determines how the field is rendered in the UI sidebar:
  • text: Standard text input.
  • textarea: Multi-line text area.
  • number: Numeric input with optional min/max.
  • boolean: Checkbox/switch.
  • select: Dropdown menu (requires options).
  • multi-select: Multi-selection dropdown.
  • json: Code editor for JSON objects.
  • secret: Masked password-style input.
  • variable-list: Specialized editor for logic-script variables.

Visibility Rules

You can use visibleWhen to show or hide parameters based on the values of other parameters:

Connection Types

When defining a port, you can specify its connectionType for compatibility checks in the canvas.
Supported primitives: text, number, boolean, secret, json, file, any. Lists: { kind: 'list', element: ConnectionType }. Objects with contracts: { kind: 'primitive', name: 'json', contract: 'aws-credentials' }.

Entry Point Runtime Input Types

The Entry Point component supports dynamic runtime inputs that users provide when triggering workflows: Example: Secret runtime input
When a workflow with secret inputs is triggered:
  1. The UI shows a password field for the secret
  2. The value flows through as a port.secret() output
  3. Downstream components receive the secret string value

Dynamic Ports (resolvePorts)

Components can dynamically generate input/output ports based on parameter values:
Use cases: Workflow calls, Slack templates, manual actions with dynamic options.

Retry Policy

Components can specify custom retry behavior (maps to Temporal activity retry):
Default policy: 3 attempts, 1s initial, 60s max, 2x backoff.

Error Handling

Use SDK error types for proper retry behavior:

Analytics Output Port (Results)

Security components should include a results output port for analytics integration. This port outputs structured findings that can be indexed into OpenSearch via the Analytics Sink.

Schema Requirements

The results port must output list<json> (array of records):

Required Fields

Each finding in the results array must include: Additional fields from the scanner output should be spread into the finding object.

Finding Hash

The finding_hash is a stable identifier that enables deduplication across workflow runs. It should be generated from the key identifying fields of each finding. Purpose:
  • Track if a finding is new or recurring across scans
  • Deduplicate findings in dashboards
  • Calculate first-seen and last-seen timestamps
  • Identify which findings have been resolved (no longer appearing)
How to generate: Import from the component SDK:
Key fields per scanner: Choose fields that uniquely identify a finding but remain stable across runs (avoid timestamps, random IDs, etc.).

Example Implementation

How It Works

  1. Component outputs results: Each scanner outputs its findings with scanner and asset_key fields
  2. Connect to Analytics Sink: In the workflow canvas, connect the results port to Analytics Sink’s data input
  3. Indexed to OpenSearch: Each item in the array becomes a separate document with:
    • Finding data at root level (nested objects serialized to JSON strings)
    • Workflow context under shipsec.* namespace
    • Consistent @timestamp for all findings in the batch

Document Structure in OpenSearch

shipsec Context Fields

The Analytics Sink automatically adds workflow context under the shipsec namespace:

Example Queries

Nested objects in findings are automatically serialized to JSON strings to prevent OpenSearch field explosion (1000 field limit).

Docker Component Requirements

All Docker-based components run with PTY (pseudo-terminal) enabled by default in workflows. Your component MUST be designed for PTY mode.

Shell Wrapper Pattern (Required)

All Docker-based components MUST use a shell wrapper for PTY compatibility:

Why Shell Wrappers?

Pattern Decision Tree

Distroless Images (Default Entrypoint Pattern)

Many ProjectDiscovery images (subfinder, dnsx, naabu, amass, notify) are distroless and do not contain /bin/sh. For these images, omit the entrypoint field entirely and let Docker use the image’s default entrypoint:
In the execute() function, append tool arguments directly to command:
Distroless Go binaries (like ProjectDiscovery tools) handle PTY signals correctly. Verified with docker run --rm -t image args... — output streams and exits cleanly.

File System Access

All components that require file-based input/output MUST use the IsolatedContainerVolume utility for multi-tenant security.
For detailed patterns and security guarantees, see Isolated Volumes.

Quick Example


UI-Only Components

Components that are purely for UI purposes (documentation, notes):

Testing

Unit Tests

Located alongside component: worker/src/components/<category>/__tests__/<component>.test.ts
Run: bun --cwd worker test

Integration Tests (Docker)

Same folder with -integration.test.ts. Uses real Docker containers.
Run: ENABLE_DOCKER_TESTS=true bun --cwd worker test

Testing Checklist

  • Used entrypoint: 'sh' with command: ['-c', 'tool "$@"', '--']
  • Tested with docker run --rm -t (PTY mode)
  • Container exits cleanly without hanging
  • No stdin-dependent operations
  • Tool arguments appended after '--' in command array
  • Workflow run completes successfully

PTY Testing

E2E Tests (Full Stack)

E2E tests validate your component works with the entire platform: Backend API, Worker, Temporal, and infrastructure. Located in e2e-tests/. These tests create real workflows via the API and execute them. Prerequisites:
Run E2E tests:
Example E2E test pattern:
E2E tests are not run in CI yet. They require the full local environment (just dev) and are intended for manual validation during development.

Complete Example


Questions?

  • File access patterns: See Isolated Volumes
  • SDK source: packages/component-sdk/src/
  • Example components: worker/src/components/security/
  • Bug reports: GitHub Issues