Skip to content

feat(sentinel-graph): Add Microsoft Sentinel Graph API integration (public preview)#1088

Draft
SinsBre wants to merge 10 commits intomasterfrom
feature/sentinel-graph
Draft

feat(sentinel-graph): Add Microsoft Sentinel Graph API integration (public preview)#1088
SinsBre wants to merge 10 commits intomasterfrom
feature/sentinel-graph

Conversation

@SinsBre
Copy link
Copy Markdown
Contributor

@SinsBre SinsBre commented Apr 7, 2026

Summary

Adds a SentinelGraphMixin plugin that integrates with the Microsoft Sentinel Graph REST API (now in public preview). Users can query their Sentinel custom graph instances using GQL and visualize the results directly in Graphistry.

import graphistry

# Discover available graph instances
graphistry.configure_sentinel_graph(graph_instance="placeholder")
instances = graphistry.sentinel_graph_list()

# Connect to a specific instance and query
graphistry.configure_sentinel_graph(graph_instance=instances.iloc[0]['name'])
viz = graphistry.sentinel_graph("MATCH (n)-[e]->(m) RETURN * LIMIT 100")
viz.plot()

# Service principal auth (for production)
graphistry.configure_sentinel_graph(
    graph_instance="MyInstance",
    tenant_id=os.environ['AZURE_TENANT_ID'],
    client_id=os.environ['AZURE_CLIENT_ID'],
    client_secret=os.environ['AZURE_CLIENT_SECRET']
)

# Clean up cached token
graphistry.sentinel_graph_close()

Features

  • GQL query execution against any Sentinel custom graph instance
  • sentinel_graph_list() — discover available instances via GET /graphs/graph-instances?graphTypes=Custom
  • responseFormats parameter — defaults to ["Graph"]; pass ["Table", "Graph"] to request both formats in a single call
  • Multiple auth flows — interactive browser, service principal, device code, custom TokenCredential, DefaultAzureCredential fallback
  • Token caching with 5-minute expiry buffer and automatic refresh
  • Retry with exponential backoff on network failures
  • Security defaults — HTTPS enforced, SSL verification on, tokens never logged

What this PR adds (new files)

File Purpose Lines
graphistry/plugins/sentinel_graph.py SentinelGraphMixin — config, auth, query, list, parse +608
graphistry/plugins_types/sentinel_graph_types.py SentinelGraphConfig dataclass + error types +47
graphistry/tests/plugins/test_sentinel_graph.py 44 unit tests + 1 skipped integration test +706
demos/demos_databases_apis/microsoft/sentinel/sentinel_graph_examples.ipynb Worked-example notebook +333
setup.py New [sentinel-graph] extra (azure-identity) +1

Plus minimal wiring in graphistry/__init__.py, graphistry/pygraphistry.py, and graphistry/client_session.py to expose configure_sentinel_graph / sentinel_graph / sentinel_graph_list / sentinel_graph_close / sentinel_graph_from_credential at the top-level API and per-Plotter.

Notes for reviewers

  • Targets the public preview API format (result.graph.{nodes,edges} envelope). The pre-preview sys_* field format has been removed.
  • The Graph response format is preferred over Table for Graphistry's use case — it gives the full connected subgraph rather than just the per-row RETURN clause matches.
  • Integration tests exist but are skipped by default (@pytest.mark.integration) — they require live credentials.
  • Optional dependency: azure-identity. Install via pip install graphistry[sentinel-graph].

Test plan

  • python -m pytest graphistry/tests/plugins/test_sentinel_graph.py -v — 44 unit tests pass, 1 integration test skipped
  • Manual smoke test against a live Sentinel custom graph instance
  • Verify sentinel_graph_list() returns correct instance metadata
  • Verify interactive browser auth and service principal auth flows

CI status (as of latest push)

  • ReadTheDocs: passing
  • CodeQL: passing
  • python-lint-types matrix (3.8 / 3.9 / 3.10 / 3.11 / 3.12 / 3.13 / 3.14): all passing
  • cypher-frontend gates (surface-guard, differential-parity, strict-typing): passing
  • Larger test matrix (test-gfql-core, test-minimal-python, test-docs): running for the first time after the master merge

SinsBre and others added 6 commits October 7, 2025 00:22
Implements a new plugin for querying Microsoft Sentinel Graph API (Microsoft
Security Platform) and visualizing graph data with Graphistry.

Key Features:
- Simple API following Kusto plugin pattern: configure_sentinel_graph() + sentinel_graph(query)
- Auto-converts API responses to Graphistry nodes/edges via defensive JSON parsing
- Supports multiple authentication methods:
  - Service principal (tenant_id, client_id, client_secret)
  - Interactive browser credential (default)
  - Device code authentication
  - Custom TokenCredential
- Production-ready security hardening:
  - HTTPS enforcement with HTTP endpoint rejection
  - SSL certificate verification (enabled by default)
  - Sanitized error messages to prevent information disclosure
  - Credentials and tokens never logged
  - Query content not logged (could contain sensitive filters)
  - Token storage with repr=False to prevent accidental exposure
- Robust error handling:
  - HTTP retry with exponential backoff
  - Configurable timeout and max retries
  - Token caching with 5-minute expiry buffer
- Comprehensive test coverage (30+ unit tests)

Files Added:
- graphistry/plugins/sentinel_graph.py - Main plugin implementation
- graphistry/plugins_types/sentinel_graph_types.py - Type definitions and config
- graphistry/tests/plugins/test_sentinel_graph.py - Complete test suite
- demos/demos_databases_apis/microsoft/sentinel/sentinel_graph_examples.ipynb - Demo notebook

Files Modified:
- graphistry/client_session.py - Add sentinel_graph config property
- graphistry/plotter.py - Integrate SentinelGraphMixin
- setup.py - Add 'sentinel-graph' extras dependency

Example Usage:
  import graphistry
  from azure.identity import InteractiveBrowserCredential

  g = graphistry.configure_sentinel_graph(
      graph_instance='YourGraphInstance',
      credential=InteractiveBrowserCredential()
  )

  viz = g.sentinel_graph('MATCH (n)-[e]->(m) RETURN * LIMIT 50')
  viz.plot()

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Create comprehensive test fixture module to enable testing Sentinel Graph
functionality without requiring live Azure credentials or actual threat
intelligence data. This improves developer experience and enables faster
test iteration.

**What changed:**

- Created `graphistry/tests/fixtures/` package with synthetic response data
- Added `sentinel_graph_responses.py` with 9 fixture functions covering:
  - Minimal/simple graphs for basic testing
  - Duplicate node scenarios for deduplication logic
  - Malformed JSON for error handling validation
  - Empty responses for edge case coverage
  - Complex multi-type graphs for real-world simulation
  - Orphan edges, special characters, and null properties
- Updated `test_sentinel_graph.py` to use fixtures instead of hardcoded constants
- Reformatted notebook cells (Jupyter format standardization)

**Benefits:**

- Tests can run without Azure credentials or Sentinel Graph instance
- Fixtures mimic actual API response structure (Graph.Nodes + RawData.Rows)
- Easier to add new test cases by creating additional fixtures
- Validates parsing logic across diverse response scenarios
- All fixtures are JSON-serializable and structure-validated

**Testing:**

All 9 fixtures validated successfully with proper response structure.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The Microsoft Sentinel Graph API returns fields with sys_* prefix
(sys_sourceId, sys_targetId, sys_label) instead of the underscore
prefix (_sourceId, _targetId, _label) that was originally expected.

- Update node/edge extraction to detect both _* and sys_* field formats
- Dynamically capture all properties from nodes and edges instead of
  hardcoding specific fields
- Normalize key fields (id, label, source, target, edge) while
  preserving all original properties
- Add test fixture mimicking actual Sentinel Graph API response format
- Add tests for sys_* field format parsing
Enable cleaner API usage without requiring bind():
  graphistry.configure_sentinel_graph('instance')
  graphistry.sentinel_graph(query)

Changes:
- Add GraphistryClient wrapper methods for sentinel_graph functions
- Export sentinel_graph methods at module level in pygraphistry.py
- Re-export in __init__.py for public API access
- Update docstring examples to use module-level pattern

Security: No additional risk - module-level access uses same session
model as bind() pattern. Tokens and credentials remain protected.
Microsoft moved Sentinel custom graph to public preview with a new
response schema. Updates the plugin to match:

- Rewrite response parsing for new envelope: result.graph.{nodes,edges}
  and result.rawData.tables (replacing the old Graph/RawData format)
- Add responseFormats request parameter (default: ["Graph"])
- Add sentinel_graph_list() to discover available graph instances via
  GET /graphs/graph-instances?graphTypes=Custom
- Remove sys_* / JSON-encoded-string field handling (pre-preview only)
- Rewrite test fixtures and tests for new schema; add TestSentinelGraphList,
  TestResponseFormats, and TestTableFormatParsing test classes
- Update demo notebook with list-then-configure pattern and responseFormats example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@SinsBre SinsBre requested review from DataBoyTX and exrhizo April 7, 2026 18:02
Comment thread graphistry/tests/plugins/test_sentinel_graph.py Fixed
SinsBre added 4 commits May 6, 2026 13:50
- Replace substring containment with startswith host-prefix check
  to satisfy CodeQL 'incomplete URL substring sanitization' (alert #9).
  An attacker-controlled URL like https://evil.com/api.securityplatform.microsoft.com/
  would still pass the old 'in url' check; the startswith form anchors
  the host to position 0.
- Remove unused 'result =' assignment in test_execute_query_retry_on_timeout
  (F841 — surfaced by python-lint-types CI; assertions only check
  retry call counts, not the return value).

Both fixes are test-only; runtime sentinel_graph.py is unchanged.
- Add empty 'outputs' and 'execution_count' to two code cells in
  sentinel_graph_examples.ipynb (cells 7 and 9). nbformat 4 requires
  both keys on every code cell; nbsphinx errored with
  AttributeError: outputs during RTD's sphinx-build, killing the docs
  build after the pytz import was resolved upstream.
- Add azure.core.* and azure.identity to mypy.ini ignore_missing_imports.
  Mirrors the existing azure.kusto.* entry. Without these, mypy 1.20
  flags the 'azure.core.credentials' and 'azure.identity' imports in
  sentinel_graph.py / sentinel_graph_types.py as missing stubs, failing
  python-lint-types CI on every Python version.

Verified locally: ruff/mypy/pytest all green; nbformat.validate passes;
'import graphistry' loads cleanly.
Return the locally-bound token (typed 'str') instead of cfg._token
(typed 'Optional[str]'). Functionally equivalent — token is assigned
to cfg._token on the previous line — but mypy 1.14 (the pinned mypy
on the Python 3.8 lockfile) does not narrow the field-access form and
flagged: 'Incompatible return value type (got str | None, expected str)'.

mypy 1.20+ (3.10+ lockfiles) accepted the original code, which is why
the failure was 3.8-specific. Verified clean with both mypy 1.14 and
1.20.2; sentinel-graph tests still pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants