Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 0.5.4

- Remove unsupported `local` options from lookup searches and correct the builder guidance. Existing searches using them show instructions to remove the option.
- Increase builder labels, inputs, and help text, plus submission table and receipt text, for readability.
- Replace the Scan icon with a targeting reticle and heartbeat line.

## 0.5.3

- Query simple KV Store lookups directly instead of dispatching search jobs. Preserve SPL execution for transformations, filtered definitions, and multivalue expansion.
- Match pasted lookup values without regard to case, remove case-only duplicates, and send the stored lookup values to SOAR.
- Show playbook and action totals with status counts on ten-row submission pages. Refresh only the current page.
- Use custom action run names in receipts. Include reported format, filter, decision, code, and utility results; show unknown when SOAR does not report a block status.
- Enable SOAR automation by default for new forms and add Scan, Server, and User icons. Existing and cloned forms retain their automation setting.
- Group field types and configuration into collapsible sections. Keep navigation visible while scrolling and prevent profile avatars from shrinking into ovals.

## 0.5.2

- Add static text to the form builder with plain, information, and warning styles and conditional visibility.
- Accept comma-, newline-, and semicolon-separated lists in multiple-value text and lookup fields. Lookup lists are checked together before adding, and duplicate values are removed.
- Keep lookup suggestions inside the form layout so results are not clipped at the bottom of a panel.
- Allow `local=true` and `local=false` before or after the inputlookup name.
- Reduce lookup overhead by skipping occupied rate-limit slots, waiting for search completion during dispatch, and caching recent suggestions for 30 seconds. Submission still revalidates lookup values.

## 0.5.1

- Set `is_configured = false` in the distributed app configuration.
Expand Down
22 changes: 15 additions & 7 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,19 @@ The integration uses SOAR's REST APIs. SOAR Cloud exposes the same intake APIs,

## Forms and automation

Use **Form builder** to add fields, validation, lookup searches, sections, and access rules. **Preview → Test validation** checks the form without creating a SOAR event. Save a draft while editing; publish to apply changes to new submissions.
Use **Form builder** to add fields, validation, lookup searches, sections, and access rules. Field types and settings are grouped into collapsible sections. **Preview → Test validation** checks the form without creating a SOAR event. Save a draft while editing; publish to apply changes to new submissions.

Under **SOAR mapping**, choose a label, tags, and optional CEF mappings. **Allow SOAR automation on delivery** permits automation on the final submission artifact. Configure an active SOAR playbook for the container label. Delivery success means the event and artifact were created; execution status is shown separately.
**Static text** adds a heading and message without collecting input. Choose plain text for section descriptions, or Information or Warning for a callout. Use **Show condition** to display it when an earlier field has a particular value. Line breaks are preserved; HTML is displayed as text. Static text is not included in submitted inputs or SOAR field mappings.

Under **SOAR mapping**, choose a label, tags, and optional CEF mappings. **Allow SOAR automation on delivery** starts enabled for new forms and permits automation on the final submission artifact. Existing and cloned forms keep their saved setting. Configure an active SOAR playbook for the container label. Delivery success means the event and artifact were created; execution status is shown separately.

The **Approvals** tab marks requests that require approval. The SOAR playbook must enforce that requirement before performing actions. ActionStack does not collect approval decisions.

**Clone** creates an unsaved copy with a new ID. **Delete** moves a form to **Trash**; restore and publish it to make it available again. Existing submissions remain accessible.

## Lookup fields

Single-value and multiple-value lookup fields run SPL as the requesting Splunk user. That user needs search capability and read access to the lookup in the selected app namespace.
Single-value and multiple-value lookup fields use the requesting Splunk user’s permissions in the selected app namespace. SPL searches require search capability and read access to the lookup. Simple KV Store lookups can use direct collection reads with that same user’s read permissions.

```spl
| inputlookup identity_lookup_expanded
Expand All @@ -67,9 +69,13 @@ Single-value and multiple-value lookup fields run SPL as the requesting Splunk u

Set **Value field sent to SOAR** to `identity` and **Display label field** to `display_name`. Keep both in the final results. Suggestions match either field; only selected values are submitted and revalidated.

Searches must start with `inputlookup`. Supported transformations are `eval`, `where`, `search`, `fields`, `table`, `rename`, `dedup`, `sort`, `head`, `tail`, `fillnull`, `rex`, `regex`, `spath`, `stats`, `eventstats`, `streamstats`, `mvexpand`, `makemv`, `mvcombine`, `nomv`, `convert`, and `replace`. Macros, subsearches, custom commands, and write commands are not supported.
Searches must start with `inputlookup`. ActionStack adds `strict=true` so lookup errors fail the search. The `inputlookup` command does not support `local=true` or `local=false`; remove these options from any existing form searches and republish the form. Supported transformations are `eval`, `where`, `search`, `fields`, `table`, `rename`, `dedup`, `sort`, `head`, `tail`, `fillnull`, `rex`, `regex`, `spath`, `stats`, `eventstats`, `streamstats`, `mvexpand`, `makemv`, `mvcombine`, `nomv`, `convert`, and `replace`. Macros, subsearches, custom commands, and write commands are not supported.

For a KV Store lookup whose output fields are declared `string` in `collections.conf`, with only `fields` or `table` projections, ActionStack resolves the lookup definition and reads matching records directly from the collection. No search job is created. Scalar `mvexpand` also uses this path; array results fall back to SPL. Definitions with filters or time fields, CSV lookups, and other transformations use SPL so their semantics are preserved. Direct reads require access to the lookup definition, collection configuration, and underlying collection; otherwise the app tries the normal search path. For the fastest experience, materialize expensive transformations into a KV Store lookup and use a simple projection.

The default search delay is 50 ms after at least three characters. Results are limited to 25 prefix matches. Search jobs have a five-second execution limit. Each form supports up to five lookup fields; multiple-value fields accept up to 25 items. Large lookups may require scans even when results are limited.
The default search delay is 50 ms after at least three characters. Results are limited to 25 prefix matches. Search jobs have a five-second execution limit. Each form supports up to five lookup fields; multiple-value fields accept up to 25 items. Recent suggestions are cached in the field for 30 seconds; submission checks always run against the lookup again. Large lookups may require scans even when results are limited.

Multiple-value text and lookup fields accept comma-, newline-, or semicolon-separated lists. Paste a list, or type it and press Enter or **Add values**. For lookups, use values from the configured SOAR value field (matching ignores case); use search suggestions to select by display label. A batch with unknown values is rejected without adding a partial list. Duplicate values are removed. Lookup verification also removes case-only duplicates and returns the stored value, including its casing, before validation and delivery.

## Permissions

Expand All @@ -89,7 +95,9 @@ Lookup searches use the requesting user's session. Application storage uses serv

## Submissions and recovery

**Submissions** shows authorized requests in the selected workspace. **My submissions** filters to the signed-in user. Receipts poll SOAR status every 30 seconds while open and visible. Summary and result data are size-limited; use SOAR for complete results.
**Submissions** shows authorized requests in the selected workspace. **My submissions** filters to the signed-in user. The list shows ten requests per page with playbook/action totals and colored status counts. Only the current page is polled, with at most three status requests in flight. Receipts poll SOAR status every 30 seconds while open and visible. Unavailable status is distinct from zero runs. If history exceeds 25 playbook runs or 100 actions, totals include the history but status counts cover the latest runs and are marked accordingly.

Receipts prefer custom action run names over action types, and retain collapsible summaries and result data. Other block results are read for the latest three playbook runs, up to 100 results each. Format/filter/decision/code datapaths are available through SOAR’s `block_results` API. Utility blocks appear only when their explicit run headers are included in SOAR’s playbook report; coverage varies by version. Output existence, a false condition, or overall playbook success is never treated as a block status. Use SOAR for missing statuses and complete history.

A submission is recorded before delivery. Use **Retry delivery** for a failed or uncertain request. Only the original requester can retry, and current permissions are checked. Retries preserve the original form, inputs, identity, connection settings, and source identifiers. Connection changes apply to new submissions; retain old credentials until pending requests have been resolved.

Expand All @@ -100,7 +108,7 @@ Delivery locks do not expire automatically. If a handler crashes while holding a
- Back up ActionStack KV collections and encrypted credentials with the Splunk deployment.
- Preserve form revisions, connection snapshots, unfinished submissions, and active delivery locks during retention cleanup.
- The catalog is paginated; submission lists show the latest 200 authorized records. KV scans are bounded at 50,000 records.
- Delivery attempts are limited to 10 per user per minute. These limits are shared across members in a cluster. Lookup searches are limited to 60 and activity refreshes to 20 per user per minute.
- Delivery attempts are limited to 10 per user per minute. These limits are shared across members in a cluster. Lookup searches are limited to 60, receipt activity refreshes to 20, and submission-list status reads to 60 per user per minute, with separate budgets.
- The app does not run a background retry or retention service. Prune old rate-limit records through your administration process, retaining at least the last 24 hours.
- Validate role isolation, credential access, delivery/retry behavior, and, for clusters, member failover in your deployment.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Build forms in Splunk that submit events to Splunk SOAR.
- Form builder with conditional fields, validation, lookup inputs, multiple-value inputs, and collapsible sections.
- Drafts, publishing, version history, cloning, and recoverable form deletion.
- Configurable SOAR labels, tags, CEF mappings, and approval requirements handled by your playbooks.
- Submission history, delivery retries, and playbook/action status with result summaries and data.
- Paginated submission history with playbook/action status counts, delivery retries, and receipts with custom action names, reported block results, summaries, and data.
- Light, dark, and system themes.

## Installation
Expand All @@ -23,7 +23,7 @@ Download the app from [Splunkbase](https://splunkbase.splunk.com/app/9812):
- **Standalone search head:** install the app directly through Splunk Web.
- **Search head cluster:** deploy the app through the SHC deployer.

Open ActionStack as a Splunk administrator. The setup wizard creates the first workspace and configures the SOAR connection. Create a form, select an existing SOAR label, and publish it. Enable **Allow SOAR automation on delivery** when the form should trigger active playbooks for that label.
Open ActionStack as a Splunk administrator. The setup wizard creates the first workspace and configures the SOAR connection. Create a form, select an existing SOAR label, and publish it. **Allow SOAR automation on delivery** starts enabled for new forms; turn it off for intake-only forms. Existing forms keep their setting.

See [Deployment](DEPLOYMENT.md) for configuration and permissions, and the [SOAR event contract](SOAR_EVENT_CONTRACT.md) for submitted fields.

Expand Down
2 changes: 1 addition & 1 deletion SOAR_EVENT_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ When approval is unnecessary, the policy is `none`. These fields express a requi
4. Create the artifact with the container ID and the form's automation setting.
5. Save both IDs and mark delivery complete.

Automatic playbooks are selected by the container label. Enable **Allow SOAR automation on delivery**, publish the form, and configure an active playbook for that label. ActionStack reads playbook status but does not explicitly start playbooks through the run API.
Automatic playbooks are selected by the container label. **Allow SOAR automation on delivery** starts enabled for new forms. Publish the form with this enabled, and configure an active playbook for that label. ActionStack reads playbook status but does not explicitly start playbooks through the run API.

Source identifiers are stable across delivery retries:

Expand Down
20 changes: 16 additions & 4 deletions frontend/src/AutomationActivity.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Fragment, useEffect, useState } from "react";
import { RefreshCw } from "lucide-react";
import { RunCounts } from "./RunCounts";
import { api } from "./api";
import type { Activity, RunGroup } from "./types";

Expand All @@ -8,7 +9,13 @@ function Runs({ title, group }: { title: string; group: RunGroup }) {
<div className="activity-group">
<h4>
{title}
{group.total !== null ? ` (${group.total})` : ""}
{group.counts ? (
<RunCounts group={group} label={title} />
) : group.total !== null ? (
` (${group.total})`
) : (
""
)}
</h4>
{group.error ? (
<p role="status" className="activity-error">
Expand All @@ -24,7 +31,10 @@ function Runs({ title, group }: { title: string; group: RunGroup }) {
<div>
<b>{run.name}</b>
<small>
Run #{run.id}
{run.block_type || `Run #${run.id}`}
{run.action && run.action !== run.name
? ` · ${run.action}`
: ""}
{run.playbook_run_id
? ` · Playbook run #${run.playbook_run_id}`
: ""}
Expand Down Expand Up @@ -71,6 +81,7 @@ function Runs({ title, group }: { title: string; group: RunGroup }) {
</span>
</div>
))}
{group.notice && <p className="muted">{group.notice}</p>}
{group.summary_error && (
<p role="status" className="activity-error">
{group.summary_error}
Expand All @@ -84,8 +95,8 @@ function Runs({ title, group }: { title: string; group: RunGroup }) {
)}
{group.truncated && (
<p className="muted">
Showing the latest {group.items.length} of {group.total}. Open
SOAR for the complete history.
Showing a limited set of recent results. Open SOAR for the
complete history.
</p>
)}
</>
Expand Down Expand Up @@ -185,6 +196,7 @@ export function AutomationActivity({
)}
<Runs title="Playbooks" group={data.playbooks} />
<Runs title="Actions" group={data.actions} />
{data.blocks && <Runs title="Other blocks" group={data.blocks} />}
<small className="muted">
Last checked {new Date(data.checked_at).toLocaleTimeString()} ·
Refreshes every 30 seconds while this receipt is visible.
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/Disclosure.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState, type ReactNode } from "react";
import { ChevronDown } from "lucide-react";

export function Disclosure({
title,
children,
initiallyOpen = false,
}: {
title: string;
children: ReactNode;
initiallyOpen?: boolean;
}) {
const [open, setOpen] = useState(initiallyOpen);
return (
<details
className="actionstack-disclosure"
open={open}
onToggle={(e) => setOpen(e.currentTarget.open)}
>
<summary>
{title}
<ChevronDown size={15} aria-hidden="true" />
</summary>
<div className="actionstack-disclosure-content">{children}</div>
</details>
);
}
3 changes: 1 addition & 2 deletions frontend/src/FieldValidation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,9 @@ export function FieldValidation({
validation: rules.map((r, n) => (n === i ? { ...r, ...patch } : r)),
});
}
if (field.type === "section") return null;
if (["section", "static_text"].includes(field.type)) return null;
return (
<section className="actionstack-field-validation">
<h4>Validation</h4>
<p>
Optional checks for this field.{" "}
{["text_list", "lookup_multi", "multiselect"].includes(field.type)
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/FormIcons.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import {
FileText,
createLucideIcon,
Server,
UserRound,
Globe,
Search,
ShieldCheck,
Workflow,
Zap,
} from "lucide-react";

const ScanPulse = createLucideIcon("ScanPulse", [
["circle", { cx: "12", cy: "12", r: "8", key: "reticle" }],
["path", { d: "M12 2v3M12 19v3M2 12h3M19 12h3", key: "crosshairs" }],
["path", { d: "M6 12h2l2-4 3 8 2-4h3", key: "pulse" }],
]);

export const formIcons = {
scan: { label: "Scan", Icon: ScanPulse },
server: { label: "Server", Icon: Server },
user: { label: "User", Icon: UserRound },
shield: { label: "Shield", Icon: ShieldCheck },
workflow: { label: "Workflow", Icon: Workflow },
search: { label: "Search", Icon: Search },
Expand Down
Loading
Loading