Repository navigation
feat: add Zero Networks connector built on the Splunk SOAR SDK - #1
athreya-splunk merged 10 commits into
Conversation
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>
|
Thank you for your submission! We will take a look as soon as we can. |
|
Merging this PR will release 1.0.0 (2026-09-29)Features
Bug Fixes
Connector release changes
|
|
@dicolanl - could you pls register your app in these 2 repos? I've added an old app registration as an example |
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>
|
Thanks @athreya-splunk — both registration PRs are up:
One related change I pushed here in the meantime (3d6cf74): the app's Once the two registry PRs merge, |
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>
|
Renamed the app to Both registry PRs have been updated to match:
|
* 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>
* 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>
|
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>
|
Thanks for the review @athreya-splunk — all four addressed in eceb7f0, with replies on each thread. Summary of the two
Both Tests are up from 34 to 63. Also worth noting: now that phantomcyber/dev-cicd-tools#265 and #750 have merged, |
|
🎉 This PR is included in version 1.0.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
I've created an internal ticket to publish the app in splunkbase. I'll update once the app is available. |
|
app released to splunkbase! https://splunkbase.splunk.com/app/9910 |
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
BaseConnectorapp. This PR replaces that scaffold with an SDK app (src/app.py, generated manifest) and implements the actions.Features
GET /assets/statistics, verifying reachability and authenticationGET /assets/searchId. The API answers200with an empty body when nothing matches, so the action fails explicitly rather than returning a blank result.PUT /assets/{assetId}/actions/quarantinewith{"quarantine": true}{"quarantine": false}Supporting changes:
splunk-soar-sdk6.0.1, replacingzeronetworks.json/zeronetworks_connector.py. Dropped thepackage-app-dependenciespre-commit hook, which packages dependencies forBaseConnectorapps whereas the SDK sources wheels fromuv.lock.1.0.0for this first release, and removedrelease_notes/1.0.0.mdandrelease_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:
Authorizationheader with no scheme prefix, soStaticTokenAuthis configured with an emptytoken_type.zeronetworks asset idCEF type on both the search output and the quarantine inputs, so the three actions chain in a playbook and surface as contextual actions.400.Manual Documentation
Added
manual_readme_content.mdcovering the things that are not obvious from the action list: that the API token goes in verbatim with noBearerprefix, 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.Other information
Testing. 34 unit tests drive each action against an
httpxMockTransport, 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 buildsucceeds.Two things I could not resolve from this repo, and would appreciate guidance on:
static-testsfails onvalid_app_name_and_guidandapp_package_name, because appid85010388-bc3f-48a9-af39-0af675dcd405is not registered inphantomcyber/dev-cicd-tools'sappid_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.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 nocontributorsargument, andPyprojectAdapter.load_datadoes not readproject.authors, so the generated manifest keeps an emptycontributorslist. I have recorded the name under[project].authorsinpyproject.tomlas the closest standard location — let me know if there is a supported mechanism I have missed.🤖 Generated with Claude Code