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:
| Status | Web/API | Worker | CLI | Dashboard | Notes |
|---|---|---|---|---|---|
| active | Allow | Allow | Allow | Allow access | Normal |
| limited | Partial allow | Limited | Limited | Allow access | Use feature flags |
| suspended | Block sensitive modules | Stop sensitive workers | Block sensitive commands | Allow access to view status | Should not delete data |
| revoked | Block main functionality | Stop | Stop | License/status view only | Revoked |
| expired | Block or limited | Stop | Stop | Allow renewal | Depends on policy |
| offline | Run using cache | Run if within grace | Run if within grace | Show warning | Cannot reach server |
| grace_expired | Limited or block | Stop | Stop | Allow status access | Reconnection 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:
licenseIdproductIdinstanceIdstatusfeatureslimitsissuedAtexpiresAtgraceUntilpolicyVersion
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()orconsumeQuota(). - Local cache is configured.
- Offline grace is enabled.
-
/license/statusroute 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.