Skip to content

feat: add Zero Networks connector built on the Splunk SOAR SDK - #1

Merged
athreya-splunk merged 10 commits into
splunk-soar-connectors:mainfrom
dicolanl:feat/zeronetworks-sdk-app
Sep 30, 2026
Merged

athreya-splunk merged 10 commits into
splunk-soar-connectors:mainfrom
dicolanl:feat/zeronetworks-sdk-app

Conversation

@dicolanl

Copy link
Copy Markdown
Contributor

Implements the Zero Networks app requested in splunk-soar-connectors/.github#702, built on the Splunk SOAR SDK per @athreya-splunk's guidance on that issue.

The repo was created from the connector template, which scaffolds a BaseConnector app. This PR replaces that scaffold with an SDK app (src/app.py, generated manifest) and implements the actions.

Features

  • test connectivity — GET /assets/statistics, verifying reachability and authentication
  • search asset (investigate, read-only) — resolves an FQDN to a Zero Networks asset ID via GET /assets/searchId. The API answers 200 with an empty body when nothing matches, so the action fails explicitly rather than returning a blank result.
  • quarantine asset (contain) — cuts off an asset's network access via PUT /assets/{assetId}/actions/quarantine with {"quarantine": true}
  • unquarantine asset (correct) — restores access through the same endpoint with {"quarantine": false}

Supporting changes:

  • Rebuilt on splunk-soar-sdk 6.0.1, replacing zeronetworks.json / zeronetworks_connector.py. Dropped the package-app-dependencies pre-commit hook, which packages dependencies for BaseConnector apps whereas the SDK sources wheels from uv.lock.
  • Replaced the template's placeholder logos (which were the Splunk logo) with vectorized Zero Networks branding, in light and dark variants.
  • Set the app version to 1.0.0 for this first release, and removed release_notes/1.0.0.md and release_notes/1.0.1.md — both were written by the template setup workflow and describe its own CI churn rather than any release of this app.

Notes on a couple of implementation decisions:

  • Zero Networks expects the raw API token in the Authorization header with no scheme prefix, so StaticTokenAuth is configured with an empty token_type.
  • The asset ID is tagged with a zeronetworks asset id CEF type on both the search output and the quarantine inputs, so the three actions chain in a playbook and surface as contextual actions.
  • The quarantine actions validate the asset ID against the pattern published in the Zero Networks OpenAPI spec before calling out, turning the common mistake of passing an FQDN into a message pointing at search asset instead of an opaque 400.

Manual Documentation

Added manual_readme_content.md covering the things that are not obvious from the action list: that the API token goes in verbatim with no Bearer prefix, that a read-only token can search but not quarantine, which base URL to use for development tenants, and that the quarantine actions take a Zero Networks asset ID rather than a hostname — so search asset has to run first.

  • I have verified that manual documentation has been updated where appropriate

Other information

Testing. 34 unit tests drive each action against an httpx MockTransport, asserting on the request the app actually sends — method, path, query and JSON body — so that the quarantine/unquarantine split on a shared endpoint stays correct. They also cover the error paths that shape what an analyst sees: the empty-body search miss, malformed asset IDs rejected before any HTTP call, and the 4xx/timeout/connection failures. soarapps package build succeeds.

Two things I could not resolve from this repo, and would appreciate guidance on:

  1. static-tests fails on valid_app_name_and_guid and app_package_name, because appid 85010388-bc3f-48a9-af39-0af675dcd405 is not registered in phantomcyber/dev-cicd-tools's appid_to_name.json / appid_to_package_name.json. Every other hook passes. Happy to use a different appid if one has already been allocated for this app.

  2. The contribution guide asks contributors to add their name to the manifest, but SDK 6.0.1 appears to have no path for it: App() takes no contributors argument, and PyprojectAdapter.load_data does not read project.authors, so the generated manifest keeps an empty contributors list. I have recorded the name under [project].authors in pyproject.toml as the closest standard location — let me know if there is a supported mechanism I have missed.

🤖 Generated with Claude Code

dicolanl and others added 7 commits September 21, 2026 11:23
The connector-template scaffold this repo was created from used the older
BaseConnector framework, with a hand-maintained zeronetworks.json manifest.
Per the maintainer guidance on splunk-soar-connectors/.github#702, rebuild
the app on the Splunk SOAR SDK instead.

The app is now defined in Python and the manifest is generated from it:

  - src/app.py holds the App instance and the Asset configuration model
  - src/client.py wraps httpx with Zero Networks authentication and turns
    API errors into ActionFailure with an actionable message
  - src/consts.py holds the endpoints and the portal base URL
  - pyproject.toml carries the app metadata and pins dependencies via uv.lock

Zero Networks expects the raw API token in the Authorization header with no
scheme prefix, so StaticTokenAuth is configured with an empty token_type.

Test connectivity calls the asset statistics endpoint, which verifies both
reachability and authentication.

Drop the package-app-dependencies pre-commit hook: it packages dependencies
for BaseConnector apps, whereas the SDK sources wheels from uv.lock.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three actions against the Zero Networks portal API:

  - search asset (investigate, read-only) resolves an FQDN to a Zero
    Networks asset ID via GET /assets/searchId. The API answers 200 with an
    empty body when nothing matches, so the action fails explicitly rather
    than returning a blank result.
  - quarantine asset (contain) cuts off an asset's network access via
    PUT /assets/{assetId}/actions/quarantine with {"quarantine": true}.
  - unquarantine asset (correct) restores access through the same endpoint
    with {"quarantine": false}.

The asset ID is tagged with a "zeronetworks asset id" CEF type on both the
search output and the quarantine inputs, so the three actions chain in a
playbook and appear as contextual actions in the UI.

The quarantine actions validate the asset ID against the pattern published
in the Zero Networks OpenAPI spec before calling out. That turns the common
mistake of passing an FQDN into a message pointing at "search asset",
instead of an opaque 400 from the API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Unit tests drive each action against an httpx MockTransport, asserting on
the request the app actually sends -- method, path, query and JSON body --
so that the quarantine/unquarantine split on a shared endpoint stays
correct.

Also covers the error paths that shape what an analyst sees: the empty-body
search miss, malformed asset IDs rejected before any HTTP call, and the
4xx/timeout/connection failures translated by the client helpers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add manual_readme_content.md, which build-docs folds into the generated
README ahead of the action reference.

Covers the things that are not obvious from the action list: that the API
token goes in verbatim with no Bearer prefix, that a read-only token can
search but not quarantine, which base URL to use for dev tenants, and that
the quarantine actions take a Zero Networks asset ID rather than a hostname
-- so search asset has to run first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This is the app's first functional release, so start at 1.0.0 rather than
carrying on from the template's numbering.

Also drop release_notes/1.0.0.md and release_notes/1.0.1.md. Those were
written by the connector-template setup workflow and describe its own CI
churn ("Workflow file updates", "Fix python_version 3.13 format") rather
than any release of this app. Leaving 1.0.0.md in place would also collide
with the notes generated when 1.0.0 is published.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The repo shipped with the connector template's placeholder, which was
actually the Splunk logo.

Vectorize the Zero Networks wordmark into logo.svg and logo_dark.svg. The
artwork is traced to real paths rather than an embedded raster, so it stays
sharp at any size the platform renders it: 12 paths for the navy wordmark
and 2 for the teal accents, on a tight 2000x689 viewBox.

Brand colors sampled from the source artwork: navy #08094D, teal #04B88E.
The dark variant recolors the wordmark to white and keeps the teal accents,
which read well on both backgrounds.

Verified against the source by rasterizing both and comparing per pixel:
0.12% differ, all of them single-pixel antialiasing along edges, with no
pixel assigned the wrong brand color.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The contribution guide asks contributors to add their name to the app
manifest.

Note that SDK 6.0.1 does not currently wire this up: App() takes no
contributors argument, and PyprojectAdapter.load_data does not read
project.authors, so the generated manifest still carries an empty
contributors list. Recording the name in the standard pyproject location
so it is correct once the SDK maps it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Thank you for your submission! We will take a look as soon as we can.

@github-actions

github-actions Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Merging this PR will release 1.0.0 with the following release notes:

1.0.0 (2026-09-29)

Features

  • add asset search, quarantine and unquarantine actions (6da071c)
  • replace the placeholder logos with Zero Networks branding (49cf4cc), closes #08094D

Bug Fixes

  • address review feedback on the Zero Networks app (eceb7f0)
  • rename the app to Zero Networks for Splunk SOAR (3364c4c)
  • use a proper display name for the app (3d6cf74)

Connector release changes

  • Rebuilt the app on the Splunk SOAR SDK
  • Added search asset to look up a Zero Networks asset ID by FQDN
  • Added quarantine asset to cut off an asset's network access
  • Added unquarantine asset to release an asset from quarantine
  • Added test connectivity against the Zero Networks portal API

@athreya-splunk athreya-splunk self-assigned this Sep 21, 2026
@athreya-splunk

Copy link
Copy Markdown
Contributor

@dicolanl - could you pls register your app in these 2 repos? I've added an old app registration as an example
phantomcyber/dev-cicd-tools#254
splunk-soar-connectors/.github#414

App(name=...) carried the scaffold's default, which is the repo name, so
the app showed up as "zeronetworks" in the manifest and as the README
title. Registered apps use a human-readable display name -- "Zscaler v2",
"AWS DynamoDB" -- so use "Zero Networks".

This is also the value the appid registries have to map the GUID to, since
valid_app_name_and_guid compares the registry entry to the manifest name
exactly.

package_name is derived separately from the pyproject project name and is
unaffected: it stays phantom_zeronetworks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dicolanl

Copy link
Copy Markdown
Contributor Author

Thanks @athreya-splunk — both registration PRs are up:

appid name package name
85010388-bc3f-48a9-af39-0af675dcd405 Zero Networks phantom_zeronetworks

One related change I pushed here in the meantime (3d6cf74): the app's name was still the scaffold default zeronetworks, taken from the repo name. Since valid_app_name_and_guid compares the registry entry to the manifest name exactly, and registered apps use display names like Zscaler v2, I changed App(name=...) to Zero Networks and registered that. package_name is derived separately from the pyproject project name, so it is unaffected at phantom_zeronetworks. The generated README title reads # Zero Networks now too.

Once the two registry PRs merge, static-tests should go green here — it is the only remaining failing check.

Zero Networks already publishes a Splunk TA on Splunkbase (app 6539), so
name the SOAR app to disambiguate the two, per review feedback on
splunk-soar-connectors/.github#750. This matches how other integrations
that ship both a Splunk app and a SOAR app are differentiated.

package_name is unaffected and stays phantom_zeronetworks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dicolanl

Copy link
Copy Markdown
Contributor Author

Renamed the app to Zero Networks for Splunk SOAR in 3364c4c, per @athreya-splunk's review on splunk-soar-connectors/.github#750 — we already ship a Splunk TA (Splunkbase app 6539) and may add a Splunk app later, so the SOAR app is named to disambiguate.

Both registry PRs have been updated to match:

package_name is derived from the pyproject project name and is unchanged at phantom_zeronetworks.

athreya-splunk pushed a commit to splunk-soar-connectors/.github that referenced this pull request Sep 24, 2026
* feat: register zeronetworks app ID

Registers the Zero Networks app, added in
splunk-soar-connectors/zeronetworks#1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: name the app Zero Networks for Splunk SOAR

Zero Networks already publishes a Splunk TA on Splunkbase (app 6539), so
disambiguate the SOAR app, per review feedback on #750.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
athreya-splunk pushed a commit to phantomcyber/dev-cicd-tools that referenced this pull request Sep 28, 2026
* feat: register zeronetworks app identity

Registers the Zero Networks app, added in
splunk-soar-connectors/zeronetworks#1, so that the valid_app_name_and_guid
and app_package_name static tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: name the app Zero Networks for Splunk SOAR

Zero Networks already publishes a Splunk TA on Splunkbase (app 6539), so
disambiguate the SOAR app, per review feedback on
splunk-soar-connectors/.github#750.

The package name is unchanged at phantom_zeronetworks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/app.py Resolved
Comment thread src/client.py Resolved
Comment thread src/app.py Resolved
Comment thread src/client.py Resolved
@athreya-splunk

athreya-splunk commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

This review was AI-assisted but verified before posting. Thank you for your contribution!

Four changes, one per review comment on splunk-soar-connectors#1.

Validate the asset base URL at the client boundary (src/app.py:109). It was
logged verbatim and only had trailing slashes stripped, so a URL carrying
userinfo would have put credentials in progress output, and unsupported
schemes, query/fragment content and control characters were all accepted.
normalize_base_url now rejects those as AssetMisconfiguration and returns a
normalized, credential-free URL. Userinfo is rejected rather than stripped:
the API authenticates with the token header, so userinfo is always a
misconfiguration, and rejecting keeps the secret out of any message built
afterwards. Test connectivity now logs the validated client.base_url.

Fail on unreadable success responses (src/client.py:112). Non-empty
malformed, scalar and array 2xx bodies were normalized to {}, which made
search asset report a confident "No Zero Networks asset found" for a
response it could not actually read, and hid unexpected containment
responses. A genuinely empty body still succeeds, since that is the
documented shape for the quarantine endpoints, but anything non-empty that
is not a JSON object now raises ActionFailure quoting a trimmed snippet.

Add summary models (src/app.py:140). search asset summarizes FQDN and asset
ID; the containment actions summarize asset ID and quarantined state. These
serialize to action_result.summary.* in the manifest and are asserted in
the tests.

Use the client logger (src/client.py:25). It was created but never used, so
emit a debug line carrying method, endpoint path and status code. The path
is logged without the query string, keeping the searched FQDN out of logs.

Tests go from 34 to 63, covering valid production/development/proxy URLs,
the invalid URL forms above, credential-carrying URLs asserted not to leak
into the failure message or progress log, and the new response handling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dicolanl

Copy link
Copy Markdown
Contributor Author

Thanks for the review @athreya-splunk — all four addressed in eceb7f0, with replies on each thread.

Summary of the two [ISSUE]s:

  • Base URL validation. normalize_base_url now runs at the client boundary and rejects userinfo, unsupported schemes, query/fragment, control characters and bad ports as AssetMisconfiguration, returning a normalized credential-free URL. Userinfo is rejected rather than stripped, and test_connectivity logs the validated URL.
  • Unreadable success responses. Only a genuinely empty body maps to {} now; non-empty non-object bodies raise ActionFailure with a trimmed snippet, so search asset can no longer turn an unreadable 200 into a confident "no asset found".

Both [NIT]s taken as suggested: summary models on the three regular actions, and the idle client logger now emits a query-free debug line.

Tests are up from 34 to 63.

Also worth noting: now that phantomcyber/dev-cicd-tools#265 and #750 have merged, static-tests passes locally — every pre-commit hook is green on this branch.

@athreya-splunk
athreya-splunk merged commit 35538a7 into splunk-soar-connectors:main Sep 30, 2026
14 checks passed
@splunk-soar-semantic-release

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@athreya-splunk

athreya-splunk commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

I've created an internal ticket to publish the app in splunkbase. I'll update once the app is available.

@athreya-splunk

Copy link
Copy Markdown
Contributor

app released to splunkbase! https://splunkbase.splunk.com/app/9910

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants