Skip to content

Licensing

RedPick includes a server-side licensing system designed to distribute the platform to client companies that need to manage penetration testing on their own applications.

Core principle

The licensing system is server-dependent: the desktop client is a UI shell that relies on the backend for every critical operation. Without the server, the client is unusable — there is nothing to "crack".


Overview

The licensing system handles:

  • Hardware-bound activation — each installation is tied to the physical device
  • Periodic heartbeat — the client must confirm license validity every N minutes
  • Server-side feature gating — features are unlocked based on the license tier
  • Usage tracking — monthly counters for engagements, reports, and API calls
  • Anomaly detection — detects suspicious usage patterns (license sharing, multiple IPs)
  • Immutable audit trail — every license operation is tracked

Roles

The platform uses a unified role system. All users (internal staff, licensed clients, bug hunters) log into the same application with different access levels.

Role Who Key capabilities
admin BeDefended staff Everything: user mgmt, settings, webhooks, licenses, tickets
pentester Internal security consultants All testing + analysis, no admin settings
client Licensed company (self-service PT) Launch pentests, view findings, generate reports, upload template
client_viewer Client receiving results (read-only) View findings, reports, compliance, executive brief
bughunter Bug bounty researcher BB mode pentests, platform integration (H1/Bugcrowd/Intigriti/YWH), submission tracking

For the full role comparison matrix see Roles & Licensing.


Tiers and Feature Gating

Licenses are organized into tiers per audience. Feature gating happens exclusively server-side: the client receives the list of enabled features from the server on every heartbeat.

Client Tiers

Essentials Professional Enterprise
Target SMB, 1-2 apps Mid-size, app portfolio Large organization
Users 3 10 Unlimited
Engagements/month 5 20 Unlimited
Devices 6 20 Unlimited
Concurrent sessions 3 10 50
Automated pentest Yes Yes Yes
Custom report template Yes Yes Yes
Encrypted report delivery Yes Yes Yes
Compliance - PCI-DSS, SOC2 All (GDPR, NIS2, DORA, ISO 27001, ASVS)
Burp extension + AI analyzer - proxy_manual Yes
Executive Brief - Yes Yes
Webhooks & SIEM - Yes Yes
API access - Read-only Full
CI/CD Integration - - Yes
Surface Drift - Yes Yes
Learning Loop - Yes Yes
Continuous Monitoring - - Yes
Team Assignments - - Yes
Cost & ROI - - Yes
Scan Intelligence - - Yes
Threat Model - - Yes
On-premise - - Optional

Bughunter Tiers

Solo Team Pro
Users 1 5 15
Platform connections 2 5 Unlimited
Pentests/month 10 50 Unlimited
Bug Bounty mode Yes Yes Yes
Direct platform push Yes Yes Yes
Submission tracking Yes Yes Yes
Burp extension + AI analyzer Yes Yes Yes
Payout dashboard - Yes Yes
Scan Intel - Yes Yes
Collaboration - Yes Yes

Essentials features

["engagements", "findings", "report_basic", "report_custom_template",
 "timeline", "retest", "encrypted_delivery", "ticketing"]

Professional features

["engagements", "findings", "report_basic", "report_custom_template",
 "timeline", "retest", "encrypted_delivery", "ticketing",
 "compliance_pci", "compliance_soc2", "executive_brief",
 "proxy_manual", "ai_analysis", "webhooks", "api_readonly",
 "surface_drift", "learning"]

Enterprise features

["engagements", "findings", "report_basic", "report_custom_template",
 "report_whitelabel", "timeline", "retest", "encrypted_delivery",
 "ticketing", "compliance_all", "executive_brief", "proxy_manual",
 "ai_analysis", "webhooks", "api_full", "ci_cd", "monitor",
 "assignments", "costs", "scan_intelligence", "remediation",
 "threat_model", "surface_drift", "learning", "on_premise"]

Security Architecture

The system implements 7 layers of protection. Layer 1 (server dependency) is what makes it uncrackable.

graph TB
    subgraph "Layer 1 — Server Dependency (UNCRACKABLE)"
        S[Report generation<br>AI analysis<br>Compliance mapping<br>Finding storage]
    end
    subgraph "Layer 2 — License Token"
        L[Server-signed JWT<br>Expires every 5 min<br>Features + limits embedded]
    end
    subgraph "Layer 3 — Hardware Binding"
        H[Device fingerprint<br>SHA-256 of hostname + OS + executable<br>Tied to N specific devices]
    end
    subgraph "Layer 4 — Heartbeat"
        HB[Ping every 2-5 min<br>Missed heartbeat = dead session<br>Instant remote revocation]
    end
    subgraph "Layer 5 — Certificate Pinning + mTLS"
        C[SHA-256 certificate pin<br>Per-company mTLS<br>Prevents MITM]
    end
    subgraph "Layer 6 — Binary Integrity"
        B[Flutter native compile<br>Code obfuscation<br>Runtime integrity check]
    end
    subgraph "Layer 7 — Anomaly Detection"
        A[Anomalous usage patterns<br>Multiple IPs = alert<br>Suspicious device IDs = block]
    end

    S --> L --> H --> HB --> C --> B --> A

Why it cannot be cracked

Attack scenario Result
Decompile the Flutter binary Only sees API calls, zero business logic
Patch certificate pinning Cannot forge server responses (JWT signature)
Copy binary to another machine device_fingerprint does not match, rejected
Build a fake server Missing report engine, AI, compliance, templates
Share credentials Anomaly detection flags multiple IPs, revokes
Use offline Heartbeat fails, session dies within 5 minutes

Data Model

The system uses 4 database tables:

licenses

The main table representing a license assigned to a client company.

Field Type Description
id Integer Primary key
license_key String(64) Unique key (48 hex chars, crypto-random)
company_name String(255) Client company name
contact_email String(255) Contact email
tier String(20) essentials / professional / enterprise
features_json Text JSON array of enabled features
max_users Integer User limit
max_engagements_per_month Integer Monthly engagement limit
max_devices Integer Activated device limit
valid_from DateTime License start date
valid_until DateTime License expiry
is_active Boolean Whether the license is active
is_suspended Boolean Whether the license is suspended
suspension_reason Text Reason for suspension
heartbeat_interval_seconds Integer Heartbeat interval (default 300s)
last_heartbeat_at DateTime Last heartbeat received
last_heartbeat_ip String(45) IP of last heartbeat
missed_heartbeats Integer Consecutive missed heartbeats
max_concurrent_sessions Integer Maximum concurrent sessions
anomaly_flags_json Text Detected anomaly log (JSON)

license_usages

Monthly counters for limit enforcement.

Field Type Description
license_id FK → licenses License reference
period String(7) Period YYYY-MM
engagements_used Integer Engagements used this month
reports_generated Integer Reports generated this month
api_calls Integer API calls this month

license_activations

Activated devices per license — hardware binding.

Field Type Description
license_id FK → licenses License reference
device_fingerprint String(64) SHA-256 of device
device_name String(255) Human-readable name (hostname + OS)
device_os String(100) Operating system
is_active Boolean Whether the device is active
last_seen_at DateTime Last heartbeat from this device
last_ip String(45) Last IP of the device

license_audit_logs

Immutable trail of all license operations.

Field Type Description
license_id FK → licenses License reference
action String(50) created / activated / heartbeat / suspended / revoked / anomaly / limit_hit
details Text Action details
ip_address String(45) IP of the actor
device_fingerprint String(64) Device fingerprint

API Endpoints

Public (authenticated via license key)

These endpoints do not require a JWT — the license key is the authentication.

Method Endpoint Description
POST /api/v1/licensing/validate Validate a license key (no side-effects)
POST /api/v1/licensing/activate Activate a device for a license
POST /api/v1/licensing/heartbeat Periodic heartbeat — returns current license state

POST /licensing/validate

Verifies a license key is valid without side-effects. Used during initial setup.

curl -X POST https://api.bedefended.com/api/v1/licensing/validate \
  -H "Content-Type: application/json" \
  -d '{"license_key": "abc123...", "device_fingerprint": "sha256..."}'

Response (200):

{
  "valid": true,
  "license_id": 1,
  "tier": "professional",
  "company_name": "Acme Corp",
  "features": ["engagements", "findings", "report_basic", "..."],
  "limits": {
    "max_users": 10,
    "max_engagements_per_month": 20,
    "max_devices": 20
  },
  "valid_until": "2027-03-17T00:00:00+00:00",
  "heartbeat_interval_seconds": 300
}

POST /licensing/activate

Registers a new device. Checks the license device limit.

curl -X POST https://api.bedefended.com/api/v1/licensing/activate \
  -H "Content-Type: application/json" \
  -d '{
    "license_key": "abc123...",
    "device_fingerprint": "sha256...",
    "device_name": "WORKSTATION-01 — Windows 11",
    "device_os": "Windows 11 Pro 10.0.26200"
  }'

Response (200):

{
  "valid": true,
  "license_id": 1,
  "tier": "professional",
  "features": ["..."],
  "activation": "new"
}

Error — device limit reached (403):

{
  "detail": "Device limit reached (20). Deactivate an existing device first."
}

POST /licensing/heartbeat

Periodic heartbeat. Updates last_heartbeat_at, verifies the device, checks for anomalies, and returns the updated license state with feature flags and current usage.

curl -X POST https://api.bedefended.com/api/v1/licensing/heartbeat \
  -H "Content-Type: application/json" \
  -d '{"license_key": "abc123...", "device_fingerprint": "sha256..."}'

Response (200):

{
  "valid": true,
  "license_id": 1,
  "tier": "professional",
  "company_name": "Acme Corp",
  "features": ["engagements", "findings", "..."],
  "limits": {
    "max_users": 10,
    "max_engagements_per_month": 20,
    "max_devices": 20
  },
  "usage": {
    "period": "2026-03",
    "engagements_used": 7,
    "reports_generated": 3,
    "api_calls": 1250
  },
  "valid_until": "2027-03-17T00:00:00+00:00",
  "heartbeat_interval_seconds": 300,
  "server_time": "2026-03-17T14:30:00+00:00"
}

Admin (require admin JWT)

Method Endpoint Description
GET /api/v1/admin/licenses List all licenses
POST /api/v1/admin/licenses Create a new license
GET /api/v1/admin/licenses/{id} License detail + activations + audit log
PATCH /api/v1/admin/licenses/{id} Update license (tier, limits, expiry)
POST /api/v1/admin/licenses/{id}/suspend Suspend license (with reason)
POST /api/v1/admin/licenses/{id}/revoke Permanently revoke
POST /api/v1/admin/licenses/{id}/deactivate-device Remotely deactivate a device
GET /api/v1/admin/licenses/{id}/audit License audit trail

Create a license

curl -X POST https://api.bedefended.com/api/v1/admin/licenses \
  -H "Authorization: Bearer <admin_jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "company_name": "Acme Corp",
    "contact_email": "security@acme.com",
    "tier": "professional",
    "valid_days": 365,
    "notes": "Annual contract signed 2026-03-17"
  }'

Suspend a license

curl -X POST https://api.bedefended.com/api/v1/admin/licenses/1/suspend \
  -H "Authorization: Bearer <admin_jwt>" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Payment overdue"}'

Suspension is immediate: on the next heartbeat the client receives a 403 and is disconnected.


Configuration

LICENSING_MODE

The LICENSING_MODE environment variable controls system behavior:

Value Behavior
disabled No license checks (internal use / self-hosted). Default.
audit Logs violations but allows all requests. Useful for migration.
enforce Blocks requests without a valid license. Production mode.
# .env
LICENSING_MODE=disabled  # For internal BeDefended use
LICENSING_MODE=audit     # For testing before enabling enforcement
LICENSING_MODE=enforce   # For production with external clients

Gradual rollout

Recommended migration path: disabledaudit (review logs) → enforce (enable blocking). Do not jump directly to enforce in production.

Endpoint enforcement

Two dependency injections are available to protect endpoints:

from app.dependencies import require_license_feature, require_license_engagement_quota

# Gate an endpoint by feature
@router.get("/compliance", dependencies=[Depends(require_license_feature("compliance_all"))])
def get_compliance():
    ...

# Gate engagement creation with monthly quota
@router.post("/engagements", dependencies=[Depends(require_license_engagement_quota())])
def create_engagement():
    ...

The client sends the license key in the X-License-Key header on each request. The middleware verifies validity and the required feature.


Desktop Client Flow

First launch (activation)

sequenceDiagram
    participant U as User
    participant D as Desktop App
    participant S as BeDefended Server

    U->>D: Enters license key
    D->>S: POST /licensing/validate
    S-->>D: OK + tier + features
    D->>D: Compute device_fingerprint (SHA-256)
    D->>S: POST /licensing/activate
    S->>S: Check device limit
    S->>S: Create LicenseActivation
    S->>S: Audit log: "activated"
    S-->>D: OK + activation: "new"
    D->>D: Save license key to Secure Storage
    D->>D: Start heartbeat timer
    D-->>U: App ready, features unlocked

Runtime (heartbeat)

sequenceDiagram
    participant D as Desktop App
    participant S as BeDefended Server

    loop Every 5 minutes
        D->>S: POST /licensing/heartbeat
        S->>S: Verify license valid
        S->>S: Verify device active
        S->>S: Check anomalies (multiple IPs)
        S->>S: Update last_heartbeat_at
        S-->>D: OK + features + usage + limits
        D->>D: Update UI feature flags
    end

    Note over D,S: If heartbeat returns 403:
    D->>D: Stop heartbeat timer
    D->>D: Show license error
    D->>D: Block all operations

Remote revocation

sequenceDiagram
    participant A as BeDefended Admin
    participant S as Server
    participant D as Desktop Client

    A->>S: POST /admin/licenses/1/suspend
    S->>S: is_suspended = true
    S->>S: Audit log: "suspended"

    Note over D,S: On next heartbeat:
    D->>S: POST /licensing/heartbeat
    S-->>D: 403 "License suspended"
    D->>D: Stop timer
    D->>D: Show error
    D->>D: Block all operations

Admin Operations

Create a license for a new client

  1. Log in to the admin panel with admin credentials
  2. Go to LicensesNew License
  3. Fill in: company name, contact email, tier, duration
  4. The system generates a 48-character hex license key
  5. Send the license key to the client via a secure channel

Monitor active licenses

The GET /api/v1/admin/licenses endpoint returns all licenses with:

  • Status (active / suspended / revoked / expired)
  • Last heartbeat (to verify the client is online)
  • Activated devices
  • Current month usage vs limits

Revoke access immediately

# Suspension (reversible)
POST /api/v1/admin/licenses/{id}/suspend
{"reason": "Payment overdue for 30 days"}

# Permanent revocation
POST /api/v1/admin/licenses/{id}/revoke

# Deactivate a single device
POST /api/v1/admin/licenses/{id}/deactivate-device
{"device_fingerprint": "sha256..."}

File Structure

dashboard/backend/app/
├── models.py                          # License, LicenseUsage, LicenseActivation, LicenseAuditLog
├── config.py                          # LICENSING_MODE setting
├── dependencies.py                    # require_license_feature(), require_license_engagement_quota()
├── services/
│   └── licensing_service.py           # Core business logic (CRUD, validation, heartbeat, anomaly)
└── routers/
    └── licensing.py                   # public_router + admin_router

desktop/lib/
├── models/
│   └── license.dart                   # LicenseInfo, LicenseLimits, LicenseUsage data classes
├── api/
│   └── licensing_api.dart             # validate(), activate(), heartbeat() API calls
├── services/
│   └── license_service.dart           # Key storage, heartbeat timer, feature checking
├── providers/
│   └── license_provider.dart          # LicenseState, LicenseNotifier (Riverpod)
└── config/
    └── app_constants.dart             # storageLicenseKey constant