Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Dop2Mop — OpenGraph collector for BloodHound that maps attack paths from DevOps to MLOps infrastructure, collecting CI/CD pipeline, service principal, and ML platform resources for lateral movement analysis. | Kitploit
Tools/GitHubGitHub/h4wkst3r/dop2mop
Cloud Infrastructure SecurityReconnaissanceContainer SecurityVulnerability AnalysisInformation GatheringPenetration TestingCloud SecurityDevSecOpsIdentity & Access Management (IAM)Supply Chain SecurityRed Teaming
4145 months agoNot yet reviewed
GitHub
h4wkst3r/dop2mop

Dop2Mop

OpenGraph collector for BloodHound that maps attack paths from DevOps to MLOps infrastructure, collecting CI/CD pipeline, service principal, and ML platform resources for lateral movement analysis.

View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Dop2Mop

A proof-of-concept OpenGraph collector for BloodHound that maps attack paths from DevOps to MLOps infrastructure.

Based on Pipelines of Privilege: Attack Paths from DevOps to MLOps Infrastructure by Brett Hawkins (@h4wkst3r).

Table of Contents

  • Acknowledgments
  • Overview
    • Supported Platforms
    • Trust Boundaries Mapped
  • Installation
    • Optional Dependencies
  • Quick Start
    • Generate Demo Data
    • Ingest into BloodHound
  • Configuration
    • Config File
    • Credential Validation
  • CLI Reference
    • Commands Overview
    • Global Options
    • dop2mop collect
    • dop2mop demo
  • CLI Usage Examples
  • Basic Collection
  • Single Platform Collection
  • Multiple Platform Collection
  • GitHub Collection Examples
  • Azure DevOps Collection Examples
  • Azure ML Collection Examples
  • AWS SageMaker Collection Examples
  • Full Environment Collection
  • Environment Variables
  • Node Types
    • DevOps Nodes
    • MLOps Nodes
    • Identity Nodes
    • Artifact Nodes
  • Edge Types
    • Attack Path Edges
    • Structural Edges
  • Example Queries
  • Custom Icons
    • Uploading Icons to BloodHound
  • Python API
    • Using Individual Collectors
    • Building Custom Graphs
  • Troubleshooting
    • Common Issues
  • License
  • Acknowledgments

    • SpecterOps for BloodHound and OpenGraph

    Overview

    Dop2Mop collects data from DevOps and MLOps platforms to identify attack paths that enable lateral movement from CI/CD pipelines to machine learning training infrastructure. It outputs BloodHound-compatible OpenGraph JSON for visualization and analysis.

    Supported Platforms

    DevOps PlatformsMLOps PlatformsIdentity Providers
    GitHub (Actions, Repos, Secrets)Azure Machine LearningAzure AD Service Principals
    Azure DevOps (Pipelines, Service Connections)Amazon SageMakerAWS IAM Roles
    OIDC/Federated Identity

    Trust Boundaries Mapped

    Dop2Mop models the five critical trust boundaries identified in the research:

    1. TB1: Code Repository to CI/CD Pipeline - Automatic triggering of pipelines from code commits
    2. TB2: Service Principal Authentication - NHI credentials used by pipelines to access ML platforms
    3. TB3: Container Artifact Trust - Implicit trust in container images from internal registries
    4. TB4: Job Definition Execution - ML platforms executing job definitions without validation
    5. TB5: Code Deserialization - Unsafe deserialization of datasets (pickle, joblib)

    Installation

    root@kitploit:~
    git clone https://github.com/h4wkst3r/dop2mop.git
    cd dop2mop
    pip install -r requirements.txt
    pip install -e .
    

    What's Collected

    Each collector gathers platform-specific resources and maps the trust boundaries between them:

    CollectorResources Collected
    GitHubOrganizations, repositories, workflows, secrets, branch protection rules, OIDC configurations, container image references, S3 bucket references
    Azure DevOpsOrganizations, projects, pipelines (YAML), service connections (with scope details), variable groups, agent pools, repositories
    Azure MLWorkspaces, compute clusters/instances, datastores, ML environments, registered models, jobs/experiments, online & batch endpoints
    SageMakerTraining jobs, models, endpoints, domains, notebook instances, IAM execution roles (with policy analysis), ECR repositories/images, S3 buckets (filtered to ML-relevant)

    Quick Start

    Generate Demo Data

    The fastest way to see Dop2Mop in action is to generate demo data showing the attack scenarios from the research:

    root@kitploit:~
    dop2mop demo -o demo.json
    

    Ingest into BloodHound

    1. Open BloodHound CE (v8.0+)
    2. Navigate to Administration → File Ingest
    3. Upload the generated JSON file
    4. Query with Cypher:
    root@kitploit:~
    // Azure DevOps to Azure ML Lateral Movement
    MATCH p=(repo)-[:TriggersPipeline]->(pipeline)-[:UsesServiceConnection]->(svcconn)-[:AuthenticatesAs]->(workspace)-[:CodeExecution]->(compute)
    RETURN p
    
    // Container Image Poisoning (Supply Chain Attack)
    MATCH p=(workflow)-[:CanPoisonImage]->(image)<-[:PullsImage]-(job)
    RETURN p
    
    // OIDC/Federated Identity Abuse (confirmed edges)
    MATCH p=(workflow)-[:OIDCTrust]->(oidc)-[:CanAssumeRole]->(role)-[:SubmitsJob]->(job)
    RETURN p
    
    // OIDC abuse including inferred paths
    MATCH p=(workflow)-[:OIDCTrust]->(oidc)-[:InferredCanAssumeRole]->(role)
    RETURN p
    
    // Dataset Poisoning via Pickle Deserialization
    MATCH p=(workflow)-[:CanPoisonDataset]->(dataset)<-[:LoadsDataset]-(job)
    RETURN p
    
    // Find repos with weak/no branch protection (TB1 exploitable)
    MATCH (repo)-[:BypassesProtection]->(repo)
    RETURN repo.name, repo.default_branch
    
    // Find overprivileged SageMaker IAM roles
    MATCH (role:IAMRole) WHERE role.is_admin = true OR role.has_s3_full_access = true
    RETURN role.name, role.attached_policies
    
    // Find SageMaker notebooks with root + internet access
    MATCH (nb:SMNotebook) WHERE nb.root_access = 'Enabled' AND nb.direct_internet_access = 'Enabled'
    RETURN nb.name, nb.status
    
    // Find self-hosted ADO agent pools
    MATCH (agent:ADOAgent) WHERE agent.is_hosted = false
    RETURN agent.name, agent.pool_type
    

    Configuration

    Config File

    Instead of passing credentials on every run, you can save them in a config file. Copy the included example and fill in your values:

    root@kitploit:~
    cp dop2mop.yaml.example dop2mop.yaml
    # Edit dop2mop.yaml with your credentials
    

    Dop2Mop checks these locations in order:

    1. Path specified by --config
    2. dop2mop.yaml / dop2mop.yml / .dop2mop.yaml in the current directory
    3. ~/.dop2mop.yaml

    Note: dop2mop.yaml is in .gitignore to prevent accidentally committing credentials. The example file (dop2mop.yaml.example) is safe to commit.

    See dop2mop.yaml.example for all available options with comments.

    Priority order: CLI arguments > config file > environment variables.

    Credential Validation

    Test credentials before running a full collection:

    root@kitploit:~
    # Validate all configured collectors
    dop2mop collect --validate -v
    
    # Validate specific collectors
    dop2mop collect --validate --collectors github,sagemaker -v
    

    This makes a lightweight API call per platform to verify tokens are valid before starting collection.

    Collector Aliases

    You can use short names instead of full class names with --collectors:

    AliasCollector
    github, ghGitHubCollector
    ado, azuredevops, azure-devopsAzureDevOpsCollector
    azureml, azure-mlAzureMLCollector
    sagemaker, smSageMakerCollector
    root@kitploit:~
    # These are equivalent:
    dop2mop collect --collectors GitHubCollector,SageMakerCollector
    dop2mop collect --collectors github,sagemaker
    dop2mop collect --collectors gh,sm
    

    CLI Reference

    Commands Overview

    root@kitploit:~
    dop2mop <command> [options]
    
    Commands:
      collect    Collect data from DevOps/MLOps platforms
      demo       Generate demo data with attack scenarios
    

    Global Options

    OptionDescription
    -v, --verboseEnable verbose output (INFO level logging)
    --debugEnable debug output (DEBUG level logging)

    dop2mop collect

    Collect data from configured DevOps and MLOps platforms.

    root@kitploit:~
    dop2mop collect [OPTIONS]
    

    General Options

    OptionDescription
    -o, --output FILEOutput file path (default: dop2mop_output.json)
    --zipCompress output to ZIP file
    --config FILEPath to YAML/JSON config file
    --validateValidate credentials before collection
    --collectors LISTComma-separated list of collectors or aliases (e.g. github,sm)
    --max-items NMaximum items to collect per type
    --no-secretsSkip secret/credential enumeration

    GitHub Options

    OptionDescription
    --github-token TOKENGitHub personal access token
    --github-org ORGGitHub organization name
    --github-enterprise-url URLGitHub Enterprise Server URL

    Azure DevOps Options

    OptionDescription
    --azure-devops-token TOKENAzure DevOps personal access token (PAT)
    --azure-devops-access-token TOKENAzure DevOps access token (Bearer auth, optional)
    --azure-devops-org ORGAzure DevOps organization name

    Azure ML Options

    OptionDescription
    --azure-subscription-id IDAzure subscription ID
    --azure-tenant-id IDAzure AD tenant ID
    --azure-client-id IDService principal client ID (optional)
    --azure-client-secret SECRETService principal client secret (optional)
    --azure-access-token TOKENAzure access token for Azure ML authentication (optional)

    AWS SageMaker Options

    OptionDescription
    --aws-access-key-id KEYAWS access key ID
    --aws-secret-access-key SECRETAWS secret access key
    --aws-region REGIONAWS region (default: us-east-1)
    --aws-profile PROFILEAWS CLI profile name

    Available Collectors

    Collector NamePlatformRequired Credentials
    GitHubCollectorGitHub--github-token, --github-org
    AzureDevOpsCollectorAzure DevOps--azure-devops-token or --azure-devops-access-token, --azure-devops-org
    AzureMLCollectorAzure ML--azure-subscription-id, --azure-tenant-id
    SageMakerCollectorAWS SageMaker--aws-profile or --aws-access-key-id

    dop2mop demo

    Generate demo data showing the four attack scenarios from the research.

    root@kitploit:~
    dop2mop demo [OPTIONS]
    
    OptionDescription
    -o, --output FILEOutput file path (default: dop2mop_demo.json)

    dop2mop icons

    Generate custom icon configuration for BloodHound node types.

    root@kitploit:~
    dop2mop icons [OPTIONS]
    
    OptionDescription
    -o, --output FILEOutput file path (default: dop2mop_icons.json)

    CLI Usage Examples

    Basic Collection

    root@kitploit:~
    # Collect from all configured platforms (uses environment variables)
    dop2mop collect -o output.json
    
    # Use a config file
    dop2mop collect --config dop2mop.yaml -o output.json -v
    
    # Validate credentials first, then collect
    dop2mop collect --validate -o output.json -v
    
    # Collect with verbose logging
    dop2mop collect -o output.json -v
    
    # Collect with debug logging
    dop2mop collect -o output.json --debug
    
    # Collect and compress to ZIP
    dop2mop collect -o output.json --zip
    
    # Limit collection size
    dop2mop collect --max-items 100 -o output.json
    
    # Skip secret enumeration
    dop2mop collect --no-secrets -o output.json
    

    Single Platform Collection

    root@kitploit:~
    # GitHub only (aliases: github, gh)
    dop2mop collect --collectors github -o github.json -v
    
    # Azure DevOps only (aliases: ado, azuredevops)
    dop2mop collect --collectors ado -o ado.json -v
    
    # Azure ML only (aliases: azureml, azure-ml)
    dop2mop collect --collectors azureml -o azureml.json -v
    
    # SageMaker only (aliases: sagemaker, sm)
    dop2mop collect --collectors sm -o sagemaker.json -v
    

    Multiple Platform Collection

    root@kitploit:~
    # GitHub + SageMaker
    dop2mop collect --collectors github,sagemaker -o output.json -v
    
    # GitHub + Azure DevOps
    dop2mop collect --collectors github,ado -o output.json -v
    
    # Azure DevOps + Azure ML (full Azure stack)
    dop2mop collect --collectors ado,azureml -o azure.json -v
    
    # All collectors explicitly
    dop2mop collect --collectors github,ado,azureml,sagemaker -o full.json -v
    

    GitHub Collection Examples

    root@kitploit:~
    # Using environment variables
    export GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
    export GITHUB_ORG="your-org"
    dop2mop collect --collectors GitHubCollector -o github.json -v
    
    # Using command-line arguments
    dop2mop collect --collectors GitHubCollector \
      --github-token ghp_xxxxxxxxxxxx \
      --github-org your-org \
      -o github.json -v
    
    # GitHub Enterprise Server
    dop2mop collect --collectors GitHubCollector \
      --github-token ghp_xxxxxxxxxxxx \
      --github-org your-org \
      --github-enterprise-url https://github.yourcompany.com/api/v3 \
      -o github.json -v
    
    # Skip secret enumeration
    dop2mop collect --collectors GitHubCollector \
      --github-token ghp_xxxxxxxxxxxx \
      --github-org your-org \
      --no-secrets \
      -o github.json -v
    
    # Limit to 50 repositories
    dop2mop collect --collectors GitHubCollector \
      --github-token ghp_xxxxxxxxxxxx \
      --github-org your-org \
      --max-items 50 \
      -o github.json -v
    

    Azure DevOps Collection Examples

    root@kitploit:~
    # Using environment variables
    export AZURE_DEVOPS_TOKEN="your-pat"
    export AZURE_DEVOPS_ORG="your-org"
    dop2mop collect --collectors AzureDevOpsCollector -o ado.json -v
    
    # Using command-line arguments (PAT)
    dop2mop collect --collectors AzureDevOpsCollector \
      --azure-devops-token your-pat \
      --azure-devops-org your-org \
      -o ado.json -v
    
    # Using access token (Bearer auth)
    dop2mop collect --collectors AzureDevOpsCollector \
      --azure-devops-access-token eyJ0... \
      --azure-devops-org your-org \
      -o ado.json -v
    

    Note: Azure DevOps authentication priority: access token (Bearer) > PAT (Basic). Access tokens can be obtained via az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798.

    Azure ML Collection Examples

    root@kitploit:~
    # Using environment variables
    export AZURE_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    export AZURE_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    dop2mop collect --collectors AzureMLCollector -o azureml.json -v
    
    # Using DefaultAzureCredential (az login)
    dop2mop collect --collectors AzureMLCollector \
      --azure-subscription-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      --azure-tenant-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      -o azureml.json -v
    
    # Using Service Principal
    dop2mop collect --collectors AzureMLCollector \
      --azure-subscription-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      --azure-tenant-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      --azure-client-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      --azure-client-secret your-secret \
      -o azureml.json -v
    
    # Using Access Token
    dop2mop collect --collectors AzureMLCollector \
      --azure-subscription-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      --azure-tenant-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
      --azure-access-token eyJ0... \
      -o azureml.json -v
    

    Note: Azure ML authentication priority: access token > service principal > DefaultAzureCredential (az login). Access tokens can be obtained via az account get-access-token --resource https://management.azure.com/.

    AWS SageMaker Collection Examples

    root@kitploit:~
    # Using AWS profile (environment variable)
    export AWS_PROFILE="your-profile"
    dop2mop collect --collectors SageMakerCollector -o sagemaker.json -v
    
    # Using AWS profile (command-line)
    dop2mop collect --collectors SageMakerCollector \
      --aws-profile your-profile \
      -o sagemaker.json -v
    
    # Using access keys
    dop2mop collect --collectors SageMakerCollector \
      --aws-access-key-id AKIAXXXXXXXXXXXXXXXX \
      --aws-secret-access-key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
      --aws-region us-east-1 \
      -o sagemaker.json -v
    
    # Different AWS region
    dop2mop collect --collectors SageMakerCollector \
      --aws-profile your-profile \
      --aws-region us-west-2 \
      -o sagemaker.json -v
    

    Full Environment Collection

    root@kitploit:~
    # Set all environment variables
    export GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
    export GITHUB_ORG="your-org"
    export AZURE_DEVOPS_TOKEN="your-pat"
    export AZURE_DEVOPS_ORG="your-org"
    export AZURE_SUBSCRIPTION_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    export AZURE_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    export AWS_PROFILE="your-profile"
    
    # Collect from all platforms (auto-detects configured collectors)
    dop2mop collect -o full_collection.json -v
    
    # Or with explicit collectors
    dop2mop collect \
      --collectors GitHubCollector,AzureDevOpsCollector,AzureMLCollector,SageMakerCollector \
      -o full_collection.json -v
    

    Environment Variables

    All command-line options can be set via environment variables:

    Environment VariableCLI EquivalentDescription
    GITHUB_TOKEN--github-tokenGitHub personal access token
    GITHUB_ORG--github-orgGitHub organization name
    GITHUB_ENTERPRISE_URL--github-enterprise-urlGitHub Enterprise Server URL
    AZURE_DEVOPS_TOKEN--azure-devops-tokenAzure DevOps PAT
    AZURE_DEVOPS_ACCESS_TOKEN--azure-devops-access-tokenAzure DevOps access token (Bearer)
    AZURE_DEVOPS_ORG--azure-devops-orgAzure DevOps organization
    AZURE_SUBSCRIPTION_ID--azure-subscription-idAzure subscription ID
    AZURE_TENANT_ID--azure-tenant-idAzure AD tenant ID
    AZURE_CLIENT_ID--azure-client-idAzure service principal client ID
    AZURE_CLIENT_SECRET--azure-client-secretAzure service principal secret
    AZURE_ACCESS_TOKEN--azure-access-tokenAzure access token for Azure ML
    AWS_ACCESS_KEY_ID--aws-access-key-idAWS access key ID
    AWS_SECRET_ACCESS_KEY--aws-secret-access-keyAWS secret access key
    AWS_REGION--aws-regionAWS region (default: us-east-1)
    AWS_PROFILE--aws-profileAWS CLI profile name

    Priority: CLI arguments > config file > environment variables.


    Node Types

    DevOps Nodes

    KindDescription
    GHOrganizationGitHub organization
    GHRepositoryGitHub repository
    GHWorkflowGitHub Actions workflow
    GHSecretGitHub Actions secret
    ADOOrganizationAzure DevOps organization
    ADOProjectAzure DevOps project
    ADOPipelineAzure DevOps pipeline
    ADOServiceConnectionAzure DevOps service connection (NHI) with scope details
    ADOAgentAzure DevOps agent pool (hosted or self-hosted)
    ADOVariableGroupAzure DevOps variable group

    MLOps Nodes

    KindDescription
    AzMLWorkspaceAzure ML workspace
    AzMLComputeAzure ML compute cluster/instance
    AzMLExperimentAzure ML job/experiment
    AzMLDatastoreAzure ML datastore
    AzMLEnvironmentAzure ML environment (container definition)
    AzMLModelAzure ML registered model
    SMDomainSageMaker Studio domain
    SMTrainingJobSageMaker training job
    SMModelSageMaker model
    SMEndpointSageMaker endpoint (also used for Azure ML endpoints)
    SMNotebookSageMaker notebook instance

    Identity Nodes

    KindDescription
    ServicePrincipalAzure AD service principal
    IAMRoleAWS IAM role
    OIDCIdentityOIDC federated identity
    ManagedIdentityAzure managed identity

    Artifact Nodes

    KindDescription
    ContainerRegistryContainer registry (ECR, ACR)
    ContainerImageContainer image
    S3BucketAWS S3 bucket
    DatasetML dataset

    Edge Types

    Attack Path Edges

    KindTrust BoundaryDescription
    TriggersPipelineTB1Code commit triggers CI/CD
    HasBranchProtectionTB1Repository has branch protection rules
    BypassesProtectionTB1Weak/missing branch protection (exploitable)
    AuthenticatesAsTB2Pipeline uses service principal
    CanAssumeRoleTB2OIDC identity can assume IAM role
    OIDCTrustTB2Workflow uses OIDC federation
    UsesServiceConnectionTB2Pipeline uses ADO service connection
    PullsImageTB3Training job pulls container image
    CanPoisonImageTB3Pipeline can modify container image
    SubmitsJobTB4Service principal submits ML job
    CodeExecutionTB4Job executes code on compute
    LoadsDatasetTB5Training job loads dataset
    CanPoisonDatasetTB5Pipeline can modify dataset
    DeserializesTB5Unsafe deserialization

    Inferred Edges

    These edges are created by heuristic analysis when explicit API data isn't available (e.g., role ARNs stored in secrets). They use distinct edge types so you can filter them in BloodHound queries.

    KindDescription
    InferredCanAssumeRoleOIDC identity may be able to assume role (inferred)
    InferredSubmitsJobWorkflow may submit training job via OIDC (inferred)
    InferredPullsImageTraining job may pull poisoned container image (inferred)
    InferredLoadsDatasetTraining job may load poisoned dataset (inferred)

    Structural Edges

    KindDescription
    ContainsParent-child relationship
    MemberOfGroup membership
    HasAccessToPermission to access resource
    HasExecutionRoleResource uses an IAM/execution role
    OwnsOwnership relationship

    Example Queries

    See queries/dop2mop_queries.cypher for comprehensive query examples.


    Custom Icons

    Dop2Mop includes a pre-configured icons file for BloodHound custom node types in data/custom_icons.json.

    Uploading Icons to BloodHound

    Option 1: API Explorer (Easiest)

    1. Open BloodHound CE and go to Settings → API Explorer
    2. Find POST /api/v2/custom-nodes
    3. Click "Try it out"
    4. Paste the contents of custom_icons.json
    5. Click "Execute"

    Option 2: HMAC Authentication (Recommended for Automation)

    First, create an API token in BloodHound:

    1. Go to Settings → Administration → Manage Users
    2. Click your user → Create Token
    3. Save the Token ID and Token Key

    Then use this Python script to upload:

    root@kitploit:~
    #!/usr/bin/env python3
    """Upload custom icons to BloodHound CE using HMAC authentication."""
    
    import base64
    import hashlib
    import hmac
    import json
    from datetime import datetime, timezone
    import requests
    
    # Configuration
    BLOODHOUND_URL = "http://localhost:8080"
    TOKEN_ID = "your-token-id"
    TOKEN_KEY = "your-token-key"
    ICONS_FILE = "custom_icons.json"
    
    def hmac_auth(method: str, uri: str, body: bytes = b"") -> dict:
        """Generate HMAC authentication headers."""
        digester = hmac.new(
            base64.b64decode(TOKEN_KEY),
            msg=None,
            digestmod=hashlib.sha256
        )
        
        now = datetime.now(timezone.utc)
        timestamp = now.strftime("%Y-%m-%dT%H:%M:%SZ")
        
        digester.update(f"{method}".encode())
        digester.update(f"{uri}".encode())
        digester.update(timestamp.encode())
        if body:
            digester.update(body)
        
        signature = base64.b64encode(digester.digest()).decode()
        
        return {
            "Authorization": f"bhesignature {TOKEN_ID}",
            "RequestDate": timestamp,
            "Signature": signature,
            "Content-Type": "application/json",
        }
    
    def upload_icons():
        """Upload custom icons to BloodHound."""
        uri = "/api/v2/custom-nodes"
        url = f"{BLOODHOUND_URL}{uri}"
        
        with open(ICONS_FILE, "rb") as f:
            body = f.read()
        
        headers = hmac_auth("POST", uri, body)
        response = requests.post(url, headers=headers, data=body)
        
        print(f"Status: {response.status_code}")
        print(f"Response: {response.text}")
        return response.status_code == 200
    
    if __name__ == "__main__":
        upload_icons()
    

    Option 3: Bearer Token (Quick Testing)

    Get a JWT from your browser's DevTools Network tab while logged into BloodHound:

    root@kitploit:~
    curl -X POST http://localhost:8080/api/v2/custom-nodes \
      -H "Authorization: Bearer eyJ..." \
      -H "Content-Type: application/json" \
      -d @custom_icons.json
    

    Python API

    root@kitploit:~
    from dop2mop import Dop2MopCollector, CollectorConfig
    
    # Configure collection
    config = CollectorConfig(
        github_token="ghp_xxx",
        github_org="myorg",
        aws_profile="default",
    )
    
    # Validate credentials first
    collector = Dop2MopCollector(config)
    results = collector.validate_all()
    print(results)  # {'GitHubCollector': True, 'SageMakerCollector': True, ...}
    
    # Run collection (supports aliases)
    collector.run(collectors=["github", "sm"])
    
    # Save output
    collector.save("output.json")
    
    # Get statistics
    print(collector.get_stats())
    
    # Get per-collector failure/skip details
    print(collector.get_collection_summary())
    

    Using Individual Collectors

    root@kitploit:~
    from dop2mop import CollectorConfig, GitHubCollector, SageMakerCollector
    from dop2mop.graph import OpenGraphBuilder
    
    # Create shared builder
    builder = OpenGraphBuilder(source_kind="MLOpsBase")
    
    # Configure
    config = CollectorConfig(
        github_token="ghp_xxx",
        github_org="myorg",
        aws_profile="default",
    )
    
    # Run individual collectors
    github = GitHubCollector(config, builder)
    github.collect()
    
    sagemaker = SageMakerCollector(config, builder)
    sagemaker.collect()
    
    # Save combined output
    builder.save("combined.json")
    

    Building Custom Graphs

    root@kitploit:~
    from dop2mop.graph import OpenGraphBuilder
    from dop2mop.models import NodeKind, EdgeType
    
    builder = OpenGraphBuilder(source_kind="CustomSource")
    
    # Add nodes
    builder.create_node(
        id="my-pipeline",
        kinds=[NodeKind.AZURE_DEVOPS_PIPELINE.value],
        name="My Pipeline",
        displayname="Production Pipeline",
    )
    
    builder.create_node(
        id="my-ml-workspace",
        kinds=[NodeKind.AZURE_ML_WORKSPACE.value],
        name="ML Workspace",
        displayname="Training Workspace",
    )
    
    # Add edge
    builder.create_edge(
        start_id="my-pipeline",
        end_id="my-ml-workspace",
        kind=EdgeType.SUBMITS_JOB,
        properties={"trust_boundary": "TB4"}
    )
    
    # Export
    builder.save("custom_graph.json")
    

    Troubleshooting

    Validate First

    Always start with credential validation to catch auth issues early:

    root@kitploit:~
    dop2mop collect --validate --collectors github,sm -v
    

    The output will show OK/FAILED per collector before any collection begins.

    Collection Summary

    After every run, Dop2Mop prints a summary showing what was collected and what failed:

    root@kitploit:~
    ============================================================
      Dop2Mop Collection Summary
    ============================================================
      Total Nodes: 142
        DevOps:   45
        MLOps:    38
        Identity: 12
        Artifact: 47
    
      Total Edges: 201
        Contains: 62
        TriggersPipeline: 15
        ...
    
      ────────────────────────────────────────────────────────
      Collection Issues:
        GitHubCollector: 45 collected, 3 failed, 1 skipped
          FAIL: branch_protection:org/repo - 404 Not Found
          SKIP: branch_protection:org/private - Insufficient permissions
    
      Output: output.json
    ============================================================
    

    Common Issues

    "GitHub collector not configured, skipping"

    • Ensure GITHUB_TOKEN and GITHUB_ORG environment variables are set, or pass --github-token and --github-org

    "Azure ML collector not configured, skipping"

    • Ensure AZURE_SUBSCRIPTION_ID and AZURE_TENANT_ID are set
    • Run az login if using DefaultAzureCredential
    • Alternatively, pass --azure-access-token with a valid token from az account get-access-token

    "SageMaker collector not configured, skipping"

    • Set AWS_PROFILE or both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
    • Ensure the credentials have SageMaker read permissions

    Branch protection returns 403

    • The GitHub API requires admin access to read branch protection rules
    • Non-admin tokens will skip branch protection collection (reported in summary)

    Rate limiting (429 errors)

    • Dop2Mop automatically retries with exponential backoff on rate limit responses
    • For large orgs, use --max-items to reduce API calls
    • GitHub rate limits: 5,000 requests/hour for authenticated users

    No results in BloodHound Cypher console

    • Ensure queries return full nodes (RETURN a, b) not properties (RETURN a.name, b.name)
    • BloodHound's Cypher console is for graph visualization, not tabular data
    • Use Neo4j Browser (localhost:7474) for property-based queries

    Filtering inferred vs. collected edges

    • Inferred edges use distinct types (e.g., InferredCanAssumeRole instead of CanAssumeRole)
    • Query only confirmed edges: MATCH p=()-[:CanAssumeRole]->() RETURN p
    • Query inferred edges: MATCH p=()-[:InferredCanAssumeRole]->() RETURN p

    License

    MIT License - See LICENSE for details.

    Download Tool