CoreLicense Node.js SDK — Design & Deep Integration Documentation

Goal: build a license/policy SDK for Node.js that ensures a developer's product only runs with a valid license, can revoke access when usage policies are violated, but does not send business data / end-user data back to the developer's server.


1. Design Philosophy

CoreLicense should not be just middleware.

If it only sits at the middleware layer, users with source code on their server can remove the middleware and continue running the app. Therefore CoreLicense must be designed as a Runtime Layer embedded deep within multiple critical points of the system:

  • Bootstrap app
  • Runtime config
  • Module registry
  • Service container
  • Business services
  • Worker / Queue / Cron
  • CLI command
  • Quota / Limit
  • Local audit
  • Policy token verification

Principle:

Don't guard the door.
Build the whole house around CoreLicense.

CoreLicense must make removing the license a matter of restructuring the architecture, not simply deleting one line of middleware.


2. SDK v1 Scope

SDK v1 prioritizes the Node.js ecosystem:

  • Express.js
  • Fastify
  • NestJS
  • Node.js service
  • PM2 process
  • Docker deployment
  • Worker / Queue / Cron
  • CLI tools
  • TypeScript project
  • JavaScript project

Official package:

@corelicense/node

3. Overview Model

┌────────────────────────────────────────────┐
│                Node.js App                  │
│  Routes / Controllers / Views / API         │
└────────────────────┬───────────────────────┘
                     │
                     ▼
┌────────────────────────────────────────────┐
│             Core Runtime Layer              │
│  Bootstrap / Config / Registry / Container  │
└────────────────────┬───────────────────────┘
                     │
                     ▼
┌────────────────────────────────────────────┐
│             CoreLicense SDK                 │
│  Verify Token / Feature / Limit / Policy    │
└────────────────────┬───────────────────────┘
                     │
                     ▼
┌────────────────────────────────────────────┐
│              Business Modules               │
│  Worker / Automation / Export / API / Jobs  │
└────────────────────┬───────────────────────┘
                     │
                     ▼
┌────────────────────────────────────────────┐
│          Database / Queue / Storage         │
└────────────────────────────────────────────┘

Standard placement of CoreLicense:

App bootstrap
  ↓
CoreLicense boot()
  ↓
Runtime config
  ↓
Module registry
  ↓
Service container
  ↓
Business module
  ↓
Worker / Queue / Cron / CLI

4. What CoreLicense Is Allowed to Do

CoreLicense is allowed to:

  • Check whether the license is valid.
  • Verify signed policy tokens.
  • Check product ID.
  • Check instance ID.
  • Check license status: active, suspended, revoked, expired.
  • Check feature flags.
  • Check quota / limits.
  • Cache tokens locally.
  • Allow a grace period when the license server is temporarily unreachable.
  • Write local audit logs about license status.
  • Return runtime config to the app.
  • Disable sensitive modules when policy is violated.

CoreLicense should not:

  • Send the customer's database to the developer's server.
  • Send business logs / private data to the developer's server.
  • Delete customer files on its own.
  • Destroy services/servers on its own.
  • Hide its process.
  • Self-recover like malware.
  • Install secret services.
  • Bypass system administration privileges.

5. License Status

Standard statuses:

active        - Normal usage allowed
limited       - Usage allowed but with feature restrictions
suspended     - Temporarily suspended due to suspicion/violation/unverified
revoked       - Permanently or severely revoked
expired       - License expired
offline       - Cannot reach license server, using cache
grace_expired - Grace period has ended
invalid       - Token/license incorrect or tampered

Behavior mapping:

StatusWeb/APIWorkerCLIDashboardNotes
activeAllowAllowAllowAllow accessNormal
limitedPartial allowLimitedLimitedAllow accessUse feature flags
suspendedBlock sensitive modulesStop sensitive workersBlock sensitive commandsAllow access to view statusShould not delete data
revokedBlock main functionalityStopStopLicense/status view onlyRevoked
expiredBlock or limitedStopStopAllow renewalDepends on policy
offlineRun using cacheRun if within graceRun if within graceShow warningCannot reach server
grace_expiredLimited or blockStopStopAllow status accessReconnection required

6. Signed Policy Token

CoreLicense should not trust raw JSON responses from the server.

The license server must sign policy tokens with a private key. The SDK only holds the public key for verification.

Recommended algorithms:

  • Ed25519
  • Or RSA-PSS
  • Or ES256
  • JWT/JWS may be used if a common standard is preferred

Example payload:

{
  "licenseId": "lic_01HXABC",
  "productId": "mediahub",
  "customerId": "cus_01HXDEF",
  "instanceId": "ins_9c28a1",
  "status": "active",
  "features": {
    "dashboard": true,
    "api": true,
    "worker": true,
    "automation": false,
    "bulkTask": false,
    "export": true,
    "webhook": true
  },
  "limits": {
    "maxUsers": 10,
    "maxWorkers": 2,
    "maxJobsPerDay": 5000,
    "maxConcurrentJobs": 3,
    "maxDomains": 5,
    "maxProjects": 10
  },
  "policyVersion": "2026.06.01",
  "issuedAt": 1780000000,
  "expiresAt": 1780086400,
  "graceUntil": 1780260000
}

The token must include:

  • licenseId
  • productId
  • instanceId
  • status
  • features
  • limits
  • issuedAt
  • expiresAt
  • graceUntil
  • policyVersion

It should not contain:

  • End-user data
  • Database contents
  • Business logs
  • Customer secrets
  • Customer system access tokens

7. Instance ID

The SDK needs to identify an instance to bind the license to a specific installation environment.

Instance ID should be a hash; do not send raw sensitive data.

Possible sources:

  • Product ID
  • License key
  • Primary domain
  • Hashed machine ID
  • Random install ID stored locally
  • Project ID / App ID

Example:

instanceSeed = productId + licenseKey + installId + normalizedDomain
instanceId = sha256(instanceSeed)

Notes:

  • Do not send the real hostname unless necessary.
  • Do not send internal IP addresses.
  • Do not send server paths.
  • Do not send system usernames.
  • Allow instance rebind via the admin panel.

8. SDK Configuration

Example:

import { CoreLicense } from "@corelicense/node";

export const license = new CoreLicense({
  productId: "mediahub",
  licenseKey: process.env.CORELICENSE_KEY,
  apiUrl: "https://license.example.com",
  publicKey: process.env.CORELICENSE_PUBLIC_KEY,

  cachePath: "./storage/corelicense",
  checkIntervalMs: 6 * 60 * 60 * 1000,
  requestTimeoutMs: 5000,

  offlineGrace: true,
  defaultBlockMode: "limited",

  instance: {
    strategy: "install-id-domain-hash",
    domain: process.env.APP_DOMAIN
  },

  audit: {
    enabled: true,
    path: "./storage/corelicense/audit.log"  // default: {cachePath}/audit.log
  }
});

9. Proposed SDK API

9.1. Initialization

const license = new CoreLicense(options);

9.2. Bootstrap

await license.boot();

Responsibilities:

  • Load local cache.
  • Verify existing token if present.
  • If the token is expired or near expiry, call the server to check.
  • Verify signed token.
  • Build runtime config.
  • Start background checker if enabled.

9.3. Require active

await license.requireActive();

If the license is not permitted to run, throw LicenseError.

9.4. Express Middleware

app.use(license.express());

Whitelist configuration is supported:

app.use(license.express({
  allowPaths: [
    "/health",
    "/license/status",
    "/license/reactivate",
    "/login"
  ],
  mode: "block"
}));

9.5. Fastify Middleware

await app.register(license.fastifyPlugin());

9.6. NestJS Guard

@Module({
  imports: [
    CoreLicenseModule.forRoot({
      productId: "mediahub"
    })
  ]
})
export class AppModule {}
@UseGuards(CoreLicenseGuard)
@Controller("automation")
export class AutomationController {}

9.7. Feature gate

await license.requireFeature("automation");
await license.requireFeature("export.users");
await license.requireFeature("worker.video");

9.8. Limit checker

await license.checkLimit("maxConcurrentJobs", currentRunningJobs);
await license.checkLimit("maxUsers", currentUserCount);

9.9. Worker guard

await license.guardWorker("main-worker");

queue.process(async (job) => {
  await license.requireFeature(`job.${job.name}`);
  await license.consumeQuota(`job.${job.name}`);
  return handleJob(job);
});

9.10. CLI guard

await license.guardCommand("export");

await runExport();

9.11. Runtime config

const runtime = await license.getRuntimeConfig();

Return:

{
  status: "active",
  modules: {
    dashboard: true,
    worker: true,
    automation: false
  },
  limits: {
    maxWorkers: 2,
    maxJobsPerDay: 5000
  },
  policyVersion: "2026.06.01"
}

9.12. Status

const status = await license.getStatus();

9.13. Manual refresh

await license.refresh();

10. Deep Bootstrap Integration

Do not:

startServer();

Instead:

async function main() {
  await license.boot();

  const runtime = await license.getRuntimeConfig();

  const app = await createApp({
    runtime,
    license
  });

  await app.listen(process.env.PORT || 3000);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

Bootstrap must handle:

  • License missing
  • Token invalid
  • License expired
  • License revoked
  • Offline but cache valid
  • Offline and grace expired

Example policy:

active        -> start normally
limited       -> start restricted mode
suspended     -> start dashboard-only mode
revoked       -> start license-status-only mode or exit
expired       -> start renewal mode
offline       -> start if grace remains
grace_expired -> restricted mode or exit

11. Runtime Config

CoreLicense must produce runtime config for the app.

Example:

const runtimeConfig = await license.buildRuntimeConfig();

Contents:

{
  "mode": "active",
  "modules": {
    "dashboard": true,
    "api": true,
    "worker": true,
    "automation": false,
    "export": true
  },
  "limits": {
    "maxUsers": 10,
    "maxWorkers": 2
  },
  "routes": {
    "automation": false,
    "export": true
  },
  "workers": {
    "video": true,
    "bulk": false
  }
}

The app must boot based on this config.

Do not hardcode:

enableAutomation();
enableExport();

Instead:

if (runtime.modules.automation) {
  moduleRegistry.register("automation", new AutomationModule());
}

12. Module Registry

CoreLicense should integrate with a module registry.

class ModuleRegistry {
  private modules = new Map<string, unknown>();

  register(name: string, module: unknown) {
    this.modules.set(name, module);
  }

  get<T>(name: string): T {
    if (!this.modules.has(name)) {
      throw new Error(`Module ${name} is not available`);
    }

    return this.modules.get(name) as T;
  }
}

Boot:

const runtime = await license.getRuntimeConfig();

if (runtime.modules.dashboard) {
  registry.register("dashboard", new DashboardModule());
}

if (runtime.modules.automation) {
  registry.register("automation", new AutomationModule(license));
}

if (runtime.modules.export) {
  registry.register("export", new ExportModule(license));
}

Controller:

app.post("/automation/run", async (req, res) => {
  const automation = registry.get<AutomationModule>("automation");
  const result = await automation.run(req.body);

  res.json(result);
});

If the license does not include automation, the module does not exist.


13. Service Container

If the app has a service container, the license must sit in the resolver.

Example:

container.bind("AutomationService", async () => {
  await license.requireFeature("automation");
  return new AutomationService(license);
});

Controller:

const service = await container.resolve<AutomationService>("AutomationService");
await service.run(payload);

Even without middleware, the service will not resolve when the feature is unavailable.


14. Business Service Guard

Every important service must check features on its own.

Example:

class ExportService {
  constructor(private license: CoreLicense) {}

  async exportUsers(params: ExportParams) {
    await this.license.requireFeature("export.users");
    await this.license.consumeQuota("export.users", 1);

    return this.doExportUsers(params);
  }
}
class AutomationService {
  constructor(private license: CoreLicense) {}

  async runTask(input: RunTaskInput) {
    await this.license.requireFeature("automation.run");
    await this.license.checkLimit("maxConcurrentJobs", await this.countRunningJobs());

    return this.execute(input);
  }
}

Do not let the controller handle the real business logic.


15. Worker / Queue / Cron

Workers must go through CoreLicense.

Do not:

queue.process("send", sendHandler);

Instead:

queue.process("*", async (job) => {
  await license.guardWorker("main-worker");
  await license.requireFeature(`job.${job.name}`);

  return jobRegistry.run(job.name, job);
});

Cron:

cron.schedule("*/5 * * * *", async () => {
  await license.guardCommand("cron.sync");
  await syncService.run();
});

CLI:

async function main() {
  await license.boot();
  await license.guardCommand(process.argv[2]);

  await commandRegistry.run(process.argv[2]);
}

16. Quota / Usage

The CoreLicense SDK can enforce quota locally.

Example:

await license.consumeQuota("jobs.daily", 1);

Local usage must be persisted:

.storage/corelicense/usage.json

Example:

{
  "date": "2026-06-07",
  "jobs.daily": 120,
  "export.users": 3
}

The server may return limits; the SDK enforces them locally.

For absolute accuracy, the server must record usage centrally, but that increases data sent back. With a privacy-first goal, v1 should enforce locally or only send minimal aggregates if the customer agrees.


17. Local Cache

Cache path:

.storage/corelicense/
  ├── policy.token
  ├── instance.json
  ├── runtime.json
  ├── usage.json
  └── audit.log

Cache requirements:

  • Restricted permissions.
  • No sensitive data.
  • Signed token.
  • Runtime config can be rebuilt from the token.
  • Usage can be reset by day/month.

18. Offline Grace Period

Flow:

App calls license server
  ↓
Success -> receive new token
  ↓
Server error/network loss -> verify cached token
  ↓
Token still valid -> run normally
  ↓
Token expired but within graceUntil -> run in offline mode
  ↓
Past graceUntil -> limited/block

Recommended policy:

Token TTL: 6-24 hours
Grace period: 24-72 hours
Background check: every 6 hours

19. Background Checker

The SDK can run a background checker:

license.startBackgroundCheck();

Responsibilities:

  • Refresh token periodically.
  • Update runtime config.
  • If status changes, emit an event.

Events:

license.on("statusChanged", (status) => {});
license.on("policyUpdated", (policy) => {});
license.on("licenseRevoked", () => {});
license.on("licenseSuspended", () => {});
license.on("offlineMode", () => {});

Example:

license.on("licenseSuspended", async () => {
  await workerManager.stopSensitiveWorkers();
});

20. Local Audit

CoreLicense should write local audit logs.

Example log:

{
  "time": "2026-06-07T14:20:00Z",
  "event": "license_check_success",
  "status": "active",
  "policyVersion": "2026.06.01"
}
{
  "time": "2026-06-07T18:20:00Z",
  "event": "feature_blocked",
  "feature": "automation.run",
  "reason": "feature_disabled"
}

Do not log:

  • Customer payloads
  • End-user tokens
  • Database rows
  • Export contents
  • Private information

21. Error Classes

The SDK should provide clear errors:

LicenseError
LicenseRevokedError
LicenseSuspendedError
LicenseExpiredError
LicenseInvalidError
FeatureDisabledError
QuotaExceededError
OfflineGraceExpiredError
PolicySignatureError

Example:

try {
  await license.requireFeature("automation");
} catch (err) {
  if (err instanceof FeatureDisabledError) {
    return res.status(403).json({
      code: "FEATURE_DISABLED",
      message: "This feature is not available for your license."
    });
  }
}

22. Express Integration

import express from "express";
import { CoreLicense } from "@corelicense/node";

const app = express();

const license = new CoreLicense({
  productId: "mediahub",
  licenseKey: process.env.CORELICENSE_KEY!,
  apiUrl: process.env.CORELICENSE_API_URL!,
  publicKey: process.env.CORELICENSE_PUBLIC_KEY!
});

await license.boot();

app.use(license.express({
  allowPaths: ["/health", "/license/status"]
}));

app.get("/license/status", async (req, res) => {
  res.json(await license.getStatus());
});

app.post("/export/users", async (req, res) => {
  await license.requireFeature("export.users");

  res.json({
    ok: true
  });
});

app.listen(3000);

23. NestJS Integration

Core module:

@Module({})
export class CoreLicenseModule {
  static forRoot(options: CoreLicenseOptions): DynamicModule {
    const provider = {
      provide: CoreLicense,
      useFactory: async () => {
        const license = new CoreLicense(options);
        await license.boot();
        return license;
      }
    };

    return {
      module: CoreLicenseModule,
      providers: [provider, CoreLicenseGuard],
      exports: [CoreLicense, CoreLicenseGuard]
    };
  }
}

Guard:

@Injectable()
export class CoreLicenseGuard implements CanActivate {
  constructor(private license: CoreLicense) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    await this.license.requireActive();
    return true;
  }
}

Feature decorator:

@RequireFeature("automation.run")
@Post("/run")
run() {}

24. Fastify Integration

await fastify.register(coreLicenseFastifyPlugin, {
  license
});

Hook:

fastify.addHook("preHandler", async (request, reply) => {
  await license.requireActive();
});

25. PM2 Integration

PM2 is not a security boundary. Do not rely on PM2 alone.

However, the SDK can support:

CORELICENSE_KEY=xxx pm2 start ecosystem.config.js

Health check:

app.get("/health/license", async (req, res) => {
  res.json(await license.getStatus());
});

PM2 ecosystem:

module.exports = {
  apps: [
    {
      name: "mediahub",
      script: "dist/server.js",
      instances: 4,
      exec_mode: "cluster",
      env: {
        CORELICENSE_KEY: "xxx",
        CORELICENSE_API_URL: "https://license.example.com"
      }
    }
  ]
};

26. Docker Integration

Environment:

services:
  app:
    image: mediahub:latest
    environment:
      CORELICENSE_KEY: "${CORELICENSE_KEY}"
      CORELICENSE_API_URL: "https://license.example.com"
      CORELICENSE_PUBLIC_KEY: "${CORELICENSE_PUBLIC_KEY}"
    volumes:
      - ./storage/corelicense:/app/storage/corelicense

Do not bake the license key into a public image.


27. Security Model

CoreLicense cannot provide 100% protection if someone has full source code and permission to modify the server.

Practical goals:

- Prevent simple bypass.
- Increase the cost of modification/removal.
- Bind the license to app architecture.
- Provide a clear legal/policy basis.
- Do not turn the SDK into malware.

Protection layers:

  • Signed policy token.
  • Runtime config depends on the token.
  • Module registry depends on runtime config.
  • Business services self-check features.
  • Worker/CLI/Cron guard.
  • Separate core package.
  • Part of verification may be moved into a Go/Rust binary if needed.
  • Moderate code obfuscation when distributing source/bundle.

28. Architectural Bypass Prevention

Not sufficient:

app.use(license.middleware())

More sufficient:

license.boot()
runtimeConfig = license.getRuntimeConfig()
moduleRegistry.boot(runtimeConfig)
serviceContainer.bindWithLicense()
businessService.requireFeature()
worker.guardWorker()
cli.guardCommand()
quota.consume()

Bypass at this point requires modifying:

  • Bootstrap
  • Runtime config
  • Module registry
  • Service container
  • Business services
  • Worker
  • CLI
  • Cron
  • Quota
  • Error handling

29. SDK Files

Proposed structure:

corelicense-node/
  package.json
  tsconfig.json
  README.md

  src/
    index.ts
    core-license.ts

    types/
      options.ts
      policy.ts
      status.ts
      runtime.ts
      limits.ts

    crypto/
      verify-policy-token.ts
      key-loader.ts

    client/
      license-api-client.ts

    cache/
      cache-store.ts
      file-cache-store.ts
      memory-cache-store.ts

    instance/
      instance-id.ts
      install-id.ts

    guards/
      express-middleware.ts
      fastify-plugin.ts
      nest-guard.ts
      feature-guard.ts
      quota-guard.ts
      worker-guard.ts
      cli-guard.ts

    runtime/
      runtime-config.ts
      module-registry.ts
      service-container.ts

    audit/
      local-audit.ts

    errors/
      license-error.ts
      feature-error.ts
      quota-error.ts
      policy-error.ts

    events/
      event-bus.ts

30. Public API v1

class CoreLicense {
  constructor(options: CoreLicenseOptions)

  boot(): Promise<void>
  refresh(): Promise<LicenseStatus>
  requireActive(): Promise<void>
  requireFeature(feature: string): Promise<void>
  checkLimit(limitName: string, currentValue: number): Promise<void>
  consumeQuota(quotaName: string, amount?: number): Promise<void>

  getStatus(): Promise<LicenseStatus>
  getPolicy(): Promise<Policy>
  getRuntimeConfig(): Promise<RuntimeConfig>

  express(options?: ExpressGuardOptions): RequestHandler
  fastifyPlugin(options?: FastifyGuardOptions): FastifyPluginCallback
  guardWorker(name: string): Promise<void>
  guardCommand(name: string): Promise<void>

  startBackgroundCheck(): void
  stopBackgroundCheck(): void

  on(event: CoreLicenseEvent, handler: Function): void
}

31. License Server API Called by the SDK

The SDK needs at minimum:

POST /v1/client/activate
POST /v1/client/check
POST /v1/client/refresh

Check request:

{
  "productId": "mediahub",
  "licenseKey": "lic_key_xxx",
  "instanceId": "ins_hash",
  "sdk": {
    "name": "@corelicense/node",
    "version": "1.0.0",
    "runtime": "node",
    "nodeVersion": "22.0.0"
  }
}

Response:

{
  "ok": true,
  "status": "active",
  "policyToken": "eyJhbGciOiJFZERTQSJ9...",
  "serverTime": "2026-06-07T12:00:00Z"
}

32. Minimal Data Policy

The SDK only sends:

  • productId
  • licenseKey
  • instanceId
  • sdk version
  • app version if available
  • hash of previous policy token if needed
  • timestamp

It does not send:

  • customer database
  • user table
  • request body
  • uploaded files
  • internal logs
  • secrets
  • server env
  • IP list
  • business data

33. Deep Integration Checklist for a Node.js App

Required:

  • Call license.boot() before starting the server.
  • App boots from runtimeConfig.
  • Routes/API have middleware.
  • Sensitive modules register via Module Registry.
  • Important services call requireFeature().
  • Workers call guardWorker().
  • Queue jobs call requireFeature(jobName).
  • Cron calls guardCommand().
  • CLI calls guardCommand().
  • Quota uses checkLimit() or consumeQuota().
  • Local cache is configured.
  • Offline grace is enabled.
  • /license/status route exists.
  • Local audit is enabled.
  • Terms of Use documentation exists.
  • End-user data is not sent to the license server.

34. SDK Roadmap

v0.1

  • TypeScript core
  • Signed token verification
  • File cache
  • Express middleware
  • boot()
  • requireActive()
  • requireFeature()
  • checkLimit()
  • Local audit

v0.2

  • Worker guard
  • CLI guard
  • Background refresh
  • Offline grace
  • Runtime config

v0.3

  • Fastify plugin
  • NestJS module/guard
  • Module registry helper
  • Quota local usage

v0.5

  • PM2/Docker examples
  • Better error mapping
  • Policy versioning
  • Event system

v1.0

  • Stable API
  • Full docs
  • Production-ready
  • Security review
  • Example apps

35. Legal & Transparency Principles

Products integrating CoreLicense must clearly disclose:

The software uses license verification and policy status checks.
The system only sends minimal information to verify the license, including product ID, license key, hashed instance ID, and SDK version.
The system does not send end-user data, databases, file contents, business logs, or private information to the license server.

Terms of use should state:

The developer reserves the right to suspend or revoke a license if there is evidence or reasonable grounds that the software is used for fraud, spam, phishing, malware, system attacks, data theft, illegal activity, or infringement of third-party rights.

36. Conclusion

The CoreLicense SDK for Node.js should be a runtime layer, not a simple middleware.

Correct design:

Bootstrap + Runtime Config + Module Registry + Service Guard + Worker Guard + CLI Guard + Signed Token

When implemented this way, someone with source code can still modify it, but they cannot remove the license by deleting one line of middleware. They must change the entire app architecture, rewrite runtime config, module registry, service guards, worker guards, and the quota layer.

Practical goals:

Hard to bypass simply.
Transparent.
Does not collect private data.
Has lawful revocation rights.
Easy to integrate for Node.js products.
Can be extended to Laravel, Python, Go later.