Getting Started
File Location
Components live inworker/src/components/<category>/:
Category Source of Truth
Component categories are defined once inpackages/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.
ID Naming Convention
Runner Types
| Type | Use Case | Example |
|---|---|---|
inline | Pure TypeScript (HTTP calls, transforms, logic) | FileLoader, WebhookPost, HTTP Request |
docker | CLI tools in containers | Subfinder, DNSX, Nuclei |
remote | External executors (future) | K8s jobs, ECS tasks |
Inline Component Example
Docker Component Example
ExecutionContext
Thecontext passed to execute() provides services and utilities:
Component Definition
A component is defined usingdefineComponent 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.| Aspect | Inputs (inputs()) | Parameters (parameters()) |
|---|---|---|
| When set | Runtime (during execution) | Design-time (during workflow building) |
| Source | Upstream node outputs or Manual Overrides | Sidebar form fields in the UI |
| Visibility | Connection handles on the node | Config panel in the sidebar |
| Use Case | Dynamic data (e.g., target IP, file ID) | Static config (e.g., model name, timeout) |
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.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
Theeditor 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 (requiresoptions).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 usevisibleWhen to show or hide parameters based on the values of other parameters:
Connection Types
When defining aport, you can specify its connectionType for compatibility checks in the canvas.
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:| Type | Description | UI Rendering |
|---|---|---|
text | Text input | Multi-line textarea |
number | Numeric input | Number field |
file | File upload | File picker |
json | JSON data | JSON textarea |
array | List of values | Comma-separated or JSON array |
secret | Sensitive data | Password field (masked) |
- The UI shows a password field for the secret
- The value flows through as a
port.secret()output - Downstream components receive the secret string value
Dynamic Ports (resolvePorts)
Components can dynamically generate input/output ports based on parameter values:Retry Policy
Components can specify custom retry behavior (maps to Temporal activity retry):Error Handling
Use SDK error types for proper retry behavior:Analytics Output Port (Results)
Security components should include aresults output port for analytics integration. This port outputs structured findings that can be indexed into OpenSearch via the Analytics Sink.
Schema Requirements
Theresults port must output list<json> (array of records):
Required Fields
Each finding in the results array must include:| Field | Type | Description |
|---|---|---|
scanner | string | Scanner identifier (e.g., 'nuclei', 'trufflehog', 'supabase-scanner') |
asset_key | string | Primary asset identifier (host, domain, target, etc.) |
finding_hash | string | Stable hash for deduplication (16-char hex from SHA-256) |
Finding Hash
Thefinding_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)
| Scanner | Fields Used |
|---|---|
| Nuclei | templateId + host + matchedAt |
| TruffleHog | DetectorType + Redacted + filePath |
| Supabase Scanner | check_id + projectRef + resource |
Example Implementation
How It Works
- Component outputs
results: Each scanner outputs its findings withscannerandasset_keyfields - Connect to Analytics Sink: In the workflow canvas, connect the
resultsport to Analytics Sink’sdatainput - 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
@timestampfor all findings in the batch
Document Structure in OpenSearch
shipsec Context Fields
The Analytics Sink automatically adds workflow context under the shipsec namespace:
| Field | Description |
|---|---|
organization_id | Organization that owns the workflow |
run_id | Unique identifier for this workflow execution |
workflow_id | ID of the workflow definition |
workflow_name | Human-readable workflow name |
component_id | Component type (e.g., core.analytics.sink) |
node_ref | Node reference in the workflow graph |
asset_key | Auto-detected or specified asset identifier |
Example Queries
Nested objects in findings are automatically serialized to JSON strings to prevent OpenSearch field explosion (1000 field limit).
Docker Component Requirements
Shell Wrapper Pattern (Required)
All Docker-based components MUST use a shell wrapper for PTY compatibility:Why Shell Wrappers?
| Benefit | Description |
|---|---|
| TTY signal handling | Shell properly handles SIGTERM, SIGHUP |
| Clean exit | Shell ensures process cleanup |
| Buffering control | Shell manages stdout/stderr correctly |
| No stdin issues | Shell doesn’t wait for stdin input |
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:
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
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
bun --cwd worker test
Integration Tests (Docker)
Same folder with-integration.test.ts. Uses real Docker containers.
ENABLE_DOCKER_TESTS=true bun --cwd worker test
Testing Checklist
- Used
entrypoint: 'sh'withcommand: ['-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 ine2e-tests/. These tests create real workflows via the API and execute them.
Prerequisites:
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