cuonztech-privacy-chaos-gate 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +97 -0
- package/README.md +197 -30
- package/action.yml +187 -26
- package/dist/chunk-42D7PLVP.js +9146 -0
- package/dist/cli.js +96 -93
- package/dist/index.d.ts +710 -88
- package/dist/index.js +41 -12
- package/docs/BRAND-PILOT-EVIDENCE.md +41 -49
- package/package.json +6 -5
- package/dist/chunk-FN3QUFUZ.js +0 -36
- package/dist/chunk-FN3QUFUZ.js.map +0 -1
- package/dist/chunk-T5D6BUAC.js +0 -4514
- package/dist/chunk-T5D6BUAC.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/resolveGrant-6U6VG3DM.js +0 -230
- package/dist/resolveGrant-6U6VG3DM.js.map +0 -1
- package/docs/LIZENZ-KAUFFLUSS.md +0 -105
- package/docs/PREISMODELL.md +0 -63
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/core/license/index.ts"],"sourcesContent":["#!/usr/bin/env node\r\n\r\nimport { Command } from \"commander\";\r\nimport pc from \"picocolors\";\r\nimport { resolveGateConfig } from \"./config/index.js\";\r\nimport { PrivacyChaosGate } from \"./core/orchestrator.js\";\r\nimport { resolveLicenseFromEnv, verifyLicense, verifyLicenseForTarget } from \"./core/license/index.js\";\r\nimport { resolveJurisdiction } from \"./core/legal/jurisdictions.js\";\r\n\r\n// Beim Build per tsup `define` aus package.json eingesetzt; im Dev-Lauf (tsx) nicht vorhanden.\r\ndeclare const __CPCG_VERSION__: string | undefined;\r\nconst VERSION = typeof __CPCG_VERSION__ === \"string\" ? __CPCG_VERSION__ : \"dev\";\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name(\"cpcg\")\r\n .description(\"Cuonztech Privacy Chaos Gate - CI/CD Shift-Left Consent Scanner & Auto-Remediation\")\r\n .version(VERSION)\r\n .option(\"-u, --url <url>\", \"Staging or localhost URL to test\")\r\n .option(\"-k, --api-key <key>\", \"LLM API Key for remediation agent\")\r\n .option(\"-p, --llm-provider <provider>\", \"LLM Provider (openai, anthropic, gemini)\", \"openai\")\r\n .option(\"-m, --llm-model <model>\", \"LLM Model to use\", \"gpt-4o\")\r\n .option(\"--auto-fix [boolean]\", \"Automatically generate and apply code patches (default: true)\")\r\n .option(\"--fail-on-leak [boolean]\", \"Block CI/CD build if leaks remain (default: true)\")\r\n .option(\"-r, --report-output <path>\", \"Path to write JSON leak report\", \"reports/privacy-chaos-report.json\")\r\n .option(\"-s, --source-dir <dir>\", \"Directory containing repo source code\", \".\")\r\n .option(\"--git-commit [boolean]\", \"Automatically commit fixes to git branch (default: false)\")\r\n .option(\"--create-pr [boolean]\", \"Create a fix branch and open a GitHub Pull Request; takes precedence over --git-commit (default: false)\")\r\n .option(\"--github-token <token>\", \"GitHub token for PR creation (prefer the GITHUB_TOKEN env var)\")\r\n .option(\"--base-branch <branch>\", \"PR base branch (default: remote default branch)\")\r\n .option(\"--branch-prefix <prefix>\", \"Branch name prefix for fix PRs\", \"fix/cpcg-privacy\")\r\n .option(\"--headless [boolean]\", \"Run browser in headless mode (default: true)\")\r\n .option(\"--timeout-ms <ms>\", \"Scan timeout per scenario in ms\", \"30000\")\r\n .option(\"--license <token>\", \"Cuonztech CPCG Lizenzcode (oder Env CPCG_LICENSE) — nach Online-Kauf per E-Mail\")\r\n .option(\"--jurisdiction <code>\", \"Rechtsraum für Bewertung: CH | EU | UK | US-CA | BR | ROW (Default: CH)\", \"CH\")\r\n .option(\"--report-pdf [boolean]\", \"Deluxe-PDF (Deckblatt + Evidence) nach dem Scan erzeugen\")\r\n .option(\"--client <name>\", \"Kundenname für das PDF-Deckblatt\")\r\n .option(\"--demo [boolean]\", \"Demo-Lauf — Report wird als synthetische Testdaten gekennzeichnet\")\r\n .action(async (options) => {\r\n try {\r\n // Lizenz: dauerhafter Kunden-Key → Lizenzdienst → 7-Tage-Grant (offline)\r\n // oder klassischer Token (--license / CPCG_LICENSE)\r\n const isLocal = /localhost|127\\.0\\.0\\.1/i.test(options.url ?? \"\");\r\n const rawLicense: string = resolveLicenseFromEnv(options.license);\r\n const legacyToken = rawLicense.startsWith(\"CPCG-\") && !/^CPCG-[KG]-/.test(rawLicense) ? rawLicense : \"\";\r\n const customerKey = process.env.CPCG_CUSTOMER_KEY || (rawLicense.startsWith(\"CPCG-K-\") ? rawLicense : \"\");\r\n if (rawLicense.startsWith(\"CPCG-G-\")) {\r\n console.error(pc.red(\"✖ Ein Grant (CPCG-G-…) ist kein Lizenzcode. Kunden-Key (CPCG-K-…) oder Lizenzcode (CPCG-…) verwenden.\"));\r\n process.exit(2);\r\n }\r\n\r\n if (customerKey) {\r\n const { resolveLicenseGrant } = await import(\"./core/license/resolveGrant.js\");\r\n const resolved = await resolveLicenseGrant({\r\n customerKey,\r\n service: {\r\n baseUrl: process.env.CPCG_LICENSE_URL || \"https://cuonztech.ch/api/license-api.php\",\r\n },\r\n });\r\n if (!resolved.ok) {\r\n if (resolved.reason === \"blocked\") {\r\n console.error(pc.red(\"✖ Lizenz gesperrt: \" + resolved.detail));\r\n console.error(pc.dim(\" Kontakt: support@cuonztech.ch\"));\r\n } else {\r\n console.error(pc.red(\"✖ Lizenz nicht verfügbar: \" + resolved.detail));\r\n console.error(pc.dim(\" «Nicht erreichbar» ist nicht «gesperrt» — bei Störung greift die Offline-Freigabe.\"));\r\n }\r\n process.exit(2);\r\n }\r\n const c = resolved.claims;\r\n const offline = resolved.offlineFallback ? \" · offline\" : \"\";\r\n console.log(\r\n pc.green(\r\n `✔ Lizenz aktiv (${c.tier} · bezahlt bis ${new Date(c.paidUntil * 1000).toLocaleDateString(\"de-CH\")}${offline})`\r\n )\r\n );\r\n } else if (legacyToken) {\r\n const verdict = options.url\r\n ? verifyLicenseForTarget(legacyToken, options.url)\r\n : verifyLicense(legacyToken);\r\n if (!verdict.valid) {\r\n console.error(pc.red(`✖ Lizenz ungültig: ${verdict.reason}`));\r\n console.error(pc.dim(\" Erwerb: cuonztech.ch — Code nach Online-Kauf per E-Mail.\"));\r\n process.exit(2);\r\n }\r\n const c = verdict.claims;\r\n console.log(pc.green(`✔ Cuonztech-Lizenz aktiv (${c?.tier ?? \"pro\"} · gültig bis ${new Date((c?.exp ?? 0) * 1000).toLocaleDateString(\"de-CH\")})`));\r\n } else if (!customerKey && !legacyToken && !isLocal) {\r\n console.log(pc.yellow(\"⚠ Keine Lizenz gesetzt (Trial-Modus). Kunden-Key: CPCG_CUSTOMER_KEY=CPCG-K-…\"));\r\n }\r\n\r\n const juris = resolveJurisdiction(options.jurisdiction);\r\n console.log(pc.blue(\"ℹ Jurisdiktion: \"), pc.white(`${juris.label} (${juris.code})`));\r\n console.log(pc.dim(` ${juris.frameworks.join(\" · \")}`));\r\n\r\n const config = resolveGateConfig(options);\r\n const gate = new PrivacyChaosGate();\r\n const result = await gate.run(config);\r\n process.exit(result.exitCode);\r\n } catch (error: unknown) {\r\n console.error(pc.bold(pc.red(\"\\n❌ CPCG Gate Execution Failed:\")));\r\n if (error instanceof Error) {\r\n console.error(pc.red(error.message));\r\n } else {\r\n console.error(pc.red(String(error)));\r\n }\r\n process.exit(1);\r\n }\r\n });\r\n\r\nprogram.parse(process.argv);\r\n","import { signEd25519, verifyEd25519, defaultPublicKey } from \"./ed25519.js\";\r\n\r\n/**\r\n * Cuonztech CPCG-Lizenz (Offline-Verifikation).\r\n * Format: CPCG-<base64url(claims)>.<base64url(ed25519-signatur)>\r\n * Ausgestellt wird der Token serverseitig (privater Seed in config.php / KeePass);\r\n * das CLI prüft nur mit dem öffentlichen Schlüssel.\r\n */\r\nexport interface LicenseClaims {\r\n sub: string;\r\n tier: \"trial\" | \"pro\" | \"agency\" | \"enterprise\";\r\n exp: number;\r\n seats?: number;\r\n iss?: string;\r\n /** Gebundene Domain (Pro/Einmal-Scan) — CLI prüft die Ziel-URL dagegen */\r\n dom?: string;\r\n /** Max. Domains (Agency=10) — ohne dom-Bindung, aber Limit zählt die Nutzung */\r\n maxDomains?: number;\r\n /** Käufer-Mail (nur Metadaten, kein Auth-Faktor) */\r\n eml?: string;\r\n}\r\n\r\nexport interface LicenseVerdict {\r\n valid: boolean;\r\n claims?: LicenseClaims;\r\n reason?: string;\r\n}\r\n\r\n/**\r\n * Prüft Signatur + Ablauf. Domain ist Berichts-/Rechnungs-Metadatum, KEINE Sperre:\r\n * Die Lizenz berechtigt den Käufer, beauftragte Projekte zu prüfen.\r\n * Offline — kein Phone-Home. Entzug = Ablaufdatum + kein Neuausdruck.\r\n */\r\nexport function verifyLicenseForTarget(\r\n token: string,\r\n targetUrl: string,\r\n publicKey?: string\r\n): LicenseVerdict {\r\n // targetUrl bewusst ungenutzt: Zugang hängt am Käufer, nicht an der Domain.\r\n void targetUrl;\r\n return verifyLicense(token, publicKey);\r\n}\r\n\r\nfunction b64url(buf: Buffer | string): string {\r\n return Buffer.from(buf).toString(\"base64url\");\r\n}\r\n\r\nexport function signLicense(claims: LicenseClaims, seed: string): string {\r\n const payload = b64url(JSON.stringify(claims));\r\n return `CPCG-${payload}.${signEd25519(\"cpcg-license-v1\", payload, seed)}`;\r\n}\r\n\r\nexport function verifyLicense(token: string, publicKey: string = defaultPublicKey()): LicenseVerdict {\r\n if (!token || !token.startsWith(\"CPCG-\")) {\r\n return { valid: false, reason: \"Unbekanntes Lizenzformat.\" };\r\n }\r\n const body = token.slice(\"CPCG-\".length);\r\n const dot = body.lastIndexOf(\".\");\r\n if (dot <= 0) return { valid: false, reason: \"Lizenzsignatur fehlt.\" };\r\n const payload = body.slice(0, dot);\r\n const sig = body.slice(dot + 1);\r\n if (!verifyEd25519(\"cpcg-license-v1\", payload, sig, publicKey)) {\r\n return { valid: false, reason: \"Ungültige Lizenzsignatur.\" };\r\n }\r\n try {\r\n const claims = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf-8\")) as LicenseClaims;\r\n const tiers = [\"trial\", \"pro\", \"agency\", \"enterprise\"] as const;\r\n if (!tiers.includes(claims.tier as (typeof tiers)[number])) {\r\n return { valid: false, reason: \"Unbekannte Lizenzstufe.\", claims };\r\n }\r\n if (typeof claims.exp !== \"number\" || !Number.isFinite(claims.exp)) {\r\n return { valid: false, reason: \"Lizenz ohne Ablaufdatum ungültig.\", claims };\r\n }\r\n if (claims.exp < Date.now() / 1000) {\r\n return { valid: false, reason: \"Lizenz abgelaufen.\", claims };\r\n }\r\n return { valid: true, claims };\r\n } catch {\r\n return { valid: false, reason: \"Lizenz-Payload lesbar nicht.\" };\r\n }\r\n}\r\n\r\n/** Kauf-Fluss: Token kommt per E-Mail nach dem Online-Kauf (Env CPCG_LICENSE oder --license). */\r\nexport function resolveLicenseFromEnv(cliToken?: string): string {\r\n return cliToken || process.env.CPCG_LICENSE || \"\";\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./types/index.js\";\nexport * from \"./config/index.js\";\nexport * from \"./core/scanner/types.js\";\nexport * from \"./core/scanner/index.js\";\nexport * from \"./core/remediation/types.js\";\nexport * from \"./core/remediation/index.js\";\nexport * from \"./core/orchestrator.js\";\nexport * from \"./core/report/evidenceReport.js\";\nexport * from \"./core/report/deluxePdf.js\";\n\nimport { resolveGateConfig, CliRawOptions } from \"./config/index.js\";\nimport { PrivacyChaosGate } from \"./core/orchestrator.js\";\nimport { GateConfig, GateExecutionResult } from \"./types/index.js\";\n\n/**\n * Programmatic convenience function to run CPCG\n */\nexport async function runGate(\n optionsOrConfig: CliRawOptions | GateConfig\n): Promise<GateExecutionResult> {\n const config =\n \"targetUrl\" in optionsOrConfig && typeof optionsOrConfig.targetUrl === \"string\"\n ? (optionsOrConfig as GateConfig)\n : resolveGateConfig(optionsOrConfig as CliRawOptions);\n\n const gate = new PrivacyChaosGate();\n return gate.run(config);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBA,eAAsB,QACpB,iBAC8B;AAC9B,QAAM,SACJ,eAAe,mBAAmB,OAAO,gBAAgB,cAAc,WAClE,kBACD,kBAAkB,eAAgC;AAExD,QAAM,OAAO,IAAI,iBAAiB;AAClC,SAAO,KAAK,IAAI,MAAM;AACxB;","names":[]}
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
defaultPublicKey,
|
|
3
|
-
verifyEd25519
|
|
4
|
-
} from "./chunk-FN3QUFUZ.js";
|
|
5
|
-
|
|
6
|
-
// src/core/license/grant.ts
|
|
7
|
-
import crypto from "crypto";
|
|
8
|
-
var PAID_GRACE_DAYS = 14;
|
|
9
|
-
function parseKey(key) {
|
|
10
|
-
if (!key?.startsWith("CPCG-K-")) return null;
|
|
11
|
-
const body = key.slice("CPCG-K-".length);
|
|
12
|
-
const dot = body.lastIndexOf(".");
|
|
13
|
-
if (dot <= 0) return null;
|
|
14
|
-
return { payload: body.slice(0, dot), sig: body.slice(dot + 1) };
|
|
15
|
-
}
|
|
16
|
-
function readKeyPayload(payload) {
|
|
17
|
-
try {
|
|
18
|
-
const p = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
19
|
-
if (typeof p.i !== "string" || !p.i || !Number.isInteger(p.v) || p.v < 1) return null;
|
|
20
|
-
return { id: p.i, version: p.v };
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function customerKeyId(key) {
|
|
26
|
-
const parts = parseKey(key);
|
|
27
|
-
return parts ? readKeyPayload(parts.payload)?.id ?? null : null;
|
|
28
|
-
}
|
|
29
|
-
function verifyGrant(token, publicKey = defaultPublicKey()) {
|
|
30
|
-
if (!token?.startsWith("CPCG-G-")) {
|
|
31
|
-
return { ok: false, reason: "invalid", detail: "Kein Grant-Token." };
|
|
32
|
-
}
|
|
33
|
-
const body = token.slice("CPCG-G-".length);
|
|
34
|
-
const dot = body.lastIndexOf(".");
|
|
35
|
-
if (dot <= 0) return { ok: false, reason: "invalid", detail: "Signatur fehlt." };
|
|
36
|
-
const payload = body.slice(0, dot);
|
|
37
|
-
const sig = body.slice(dot + 1);
|
|
38
|
-
if (!verifyEd25519("cpcg-grant-v1", payload, sig, publicKey)) {
|
|
39
|
-
return { ok: false, reason: "invalid", detail: "Ung\xFCltige Signatur." };
|
|
40
|
-
}
|
|
41
|
-
let claims;
|
|
42
|
-
try {
|
|
43
|
-
claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
44
|
-
} catch {
|
|
45
|
-
return { ok: false, reason: "invalid", detail: "Payload unlesbar." };
|
|
46
|
-
}
|
|
47
|
-
if (claims.status === "blocked") {
|
|
48
|
-
return { ok: false, claims, reason: "blocked", detail: "Lizenz gesperrt." };
|
|
49
|
-
}
|
|
50
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
51
|
-
if (typeof claims.exp !== "number" || claims.exp < now) {
|
|
52
|
-
return { ok: false, claims, reason: "expired", detail: "Freigabe abgelaufen." };
|
|
53
|
-
}
|
|
54
|
-
if (claims.status === "canceled") {
|
|
55
|
-
return { ok: false, claims, reason: "blocked", detail: "Lizenz beendet (canceled)." };
|
|
56
|
-
}
|
|
57
|
-
if (typeof claims.paidUntil === "number" && claims.paidUntil > 0 && claims.paidUntil + PAID_GRACE_DAYS * 86400 < now) {
|
|
58
|
-
return { ok: false, claims, reason: "expired", detail: "Zahlungszeitraum abgelaufen." };
|
|
59
|
-
}
|
|
60
|
-
return { ok: true, claims };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/core/license/grantStore.ts
|
|
64
|
-
import fs from "fs";
|
|
65
|
-
import os from "os";
|
|
66
|
-
import path from "path";
|
|
67
|
-
function grantCachePath() {
|
|
68
|
-
const home = process.env.CPCG_HOME || path.join(os.homedir(), ".cpcg");
|
|
69
|
-
return path.join(home, "grant.json");
|
|
70
|
-
}
|
|
71
|
-
function readGrantCache() {
|
|
72
|
-
try {
|
|
73
|
-
const p = grantCachePath();
|
|
74
|
-
if (!fs.existsSync(p)) return null;
|
|
75
|
-
const raw = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
76
|
-
if (typeof raw?.grant !== "string" || typeof raw?.checkedAt !== "number") return null;
|
|
77
|
-
return raw;
|
|
78
|
-
} catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function writeGrantCache(g) {
|
|
83
|
-
const p = grantCachePath();
|
|
84
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
85
|
-
fs.writeFileSync(p, JSON.stringify(g, null, 2), "utf-8");
|
|
86
|
-
try {
|
|
87
|
-
fs.chmodSync(p, 384);
|
|
88
|
-
} catch {
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function needsOnlineCheck(g, maxAgeSec = 86400) {
|
|
92
|
-
if (!g) return true;
|
|
93
|
-
return Date.now() / 1e3 - g.checkedAt > maxAgeSec;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// src/core/license/serviceClient.ts
|
|
97
|
-
async function checkLicense(customerKey, cfg, fetchImpl = fetch) {
|
|
98
|
-
const timeout = cfg.timeoutMs ?? 8e3;
|
|
99
|
-
const url = cfg.baseUrl.replace(/\/$/, "") + "/check";
|
|
100
|
-
try {
|
|
101
|
-
const res = await fetchImpl(url, {
|
|
102
|
-
method: "POST",
|
|
103
|
-
headers: { "Content-Type": "application/json" },
|
|
104
|
-
body: JSON.stringify({ key: customerKey }),
|
|
105
|
-
signal: AbortSignal.timeout(timeout)
|
|
106
|
-
});
|
|
107
|
-
if (res.status === 403) {
|
|
108
|
-
const body2 = await res.json().catch(() => ({}));
|
|
109
|
-
return { kind: "blocked", detail: body2.detail || "Lizenz gesperrt." };
|
|
110
|
-
}
|
|
111
|
-
if (res.status === 401 || res.status === 404) {
|
|
112
|
-
const body2 = await res.json().catch(() => ({}));
|
|
113
|
-
return { kind: "rejected", detail: body2.detail || "Kunden-Key unbekannt." };
|
|
114
|
-
}
|
|
115
|
-
if (!res.ok) {
|
|
116
|
-
return { kind: "unreachable", detail: `Lizenzdienst HTTP ${res.status}` };
|
|
117
|
-
}
|
|
118
|
-
const body = await res.json();
|
|
119
|
-
if (body.status === "blocked") {
|
|
120
|
-
return { kind: "blocked", detail: "Lizenz gesperrt." };
|
|
121
|
-
}
|
|
122
|
-
if (!body.grant) {
|
|
123
|
-
return { kind: "rejected", detail: "Keine Freigabe erhalten." };
|
|
124
|
-
}
|
|
125
|
-
return { kind: "ok", grant: body.grant, kid: body.kid || "" };
|
|
126
|
-
} catch (err) {
|
|
127
|
-
return {
|
|
128
|
-
kind: "unreachable",
|
|
129
|
-
detail: "Lizenzdienst nicht erreichbar: " + String(err).slice(0, 80)
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// src/core/license/resolveGrant.ts
|
|
135
|
-
async function resolveLicenseGrant(opts) {
|
|
136
|
-
const cached = readGrantCache();
|
|
137
|
-
const publicKey = opts.publicKey ?? defaultPublicKey();
|
|
138
|
-
let local = cached ? verifyGrant(cached.grant, publicKey) : null;
|
|
139
|
-
if (local?.claims && opts.customerKey) {
|
|
140
|
-
const keyId = customerKeyId(opts.customerKey);
|
|
141
|
-
if (keyId !== null && keyId !== local.claims.kid) local = null;
|
|
142
|
-
}
|
|
143
|
-
const wantCheck = needsOnlineCheck(cached) || !local?.ok || local?.claims?.status !== "active";
|
|
144
|
-
if (wantCheck) {
|
|
145
|
-
if (!opts.customerKey) {
|
|
146
|
-
if (local?.ok && local.claims) {
|
|
147
|
-
return {
|
|
148
|
-
ok: true,
|
|
149
|
-
claims: local.claims,
|
|
150
|
-
source: "cache",
|
|
151
|
-
offlineFallback: true
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
ok: false,
|
|
156
|
-
reason: "no-key",
|
|
157
|
-
detail: "Kein Kunden-Key (CPCG_CUSTOMER_KEY) und keine g\xFCltige Offline-Freigabe. Erste Aktivierung braucht Internet."
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
const outcome = await checkLicense(
|
|
161
|
-
opts.customerKey,
|
|
162
|
-
opts.service,
|
|
163
|
-
opts.fetchImpl ?? fetch
|
|
164
|
-
);
|
|
165
|
-
if (outcome.kind === "blocked") {
|
|
166
|
-
writeGrantCache({
|
|
167
|
-
grant: "",
|
|
168
|
-
kid: "blocked",
|
|
169
|
-
checkedAt: Math.floor(Date.now() / 1e3),
|
|
170
|
-
why: "blocked"
|
|
171
|
-
});
|
|
172
|
-
return { ok: false, reason: "blocked", detail: outcome.detail };
|
|
173
|
-
}
|
|
174
|
-
if (outcome.kind === "ok") {
|
|
175
|
-
const fresh = verifyGrant(outcome.grant, publicKey);
|
|
176
|
-
if (fresh.ok && fresh.claims) {
|
|
177
|
-
writeGrantCache({
|
|
178
|
-
grant: outcome.grant,
|
|
179
|
-
kid: outcome.kid || fresh.claims.kid,
|
|
180
|
-
checkedAt: Math.floor(Date.now() / 1e3),
|
|
181
|
-
why: fresh.claims.why
|
|
182
|
-
});
|
|
183
|
-
return {
|
|
184
|
-
ok: true,
|
|
185
|
-
claims: fresh.claims,
|
|
186
|
-
source: "fresh",
|
|
187
|
-
offlineFallback: false
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
return {
|
|
191
|
-
ok: false,
|
|
192
|
-
reason: "invalid",
|
|
193
|
-
detail: "Grant vom Dienst nicht verifizierbar."
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
if (outcome.kind === "rejected") {
|
|
197
|
-
return { ok: false, reason: "invalid", detail: outcome.detail };
|
|
198
|
-
}
|
|
199
|
-
if (local?.ok && local.claims) {
|
|
200
|
-
return {
|
|
201
|
-
ok: true,
|
|
202
|
-
claims: local.claims,
|
|
203
|
-
source: "cache",
|
|
204
|
-
offlineFallback: true
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
return {
|
|
208
|
-
ok: false,
|
|
209
|
-
reason: "unreachable-expired",
|
|
210
|
-
detail: "Lizenzdienst nicht erreichbar und keine g\xFCltige Offline-Freigabe. " + outcome.detail
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
if (local?.ok && local.claims) {
|
|
214
|
-
return {
|
|
215
|
-
ok: true,
|
|
216
|
-
claims: local.claims,
|
|
217
|
-
source: "cache",
|
|
218
|
-
offlineFallback: false
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
return {
|
|
222
|
-
ok: false,
|
|
223
|
-
reason: "expired",
|
|
224
|
-
detail: local?.detail || "Freigabe abgelaufen."
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
export {
|
|
228
|
-
resolveLicenseGrant
|
|
229
|
-
};
|
|
230
|
-
//# sourceMappingURL=resolveGrant-6U6VG3DM.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/license/grant.ts","../src/core/license/grantStore.ts","../src/core/license/serviceClient.ts","../src/core/license/resolveGrant.ts"],"sourcesContent":["import crypto from \"node:crypto\";\r\nimport { signEd25519, verifyEd25519, defaultPublicKey } from \"./ed25519.js\";\r\n\r\n/**\r\n * CPCG-Kunden-Key + befristete Offline-Freigabe (Grant).\r\n *\r\n * Kunden-Key: CPCG-K-<base64url({i:id,v:version})>.<hmac> — dauerhaft, ersetzbar.\r\n * Wird nur vom Lizenzdienst geprüft (HMAC, Secret bleibt serverseitig);\r\n * das CLI sendet ihn bloss und liest die ID unverifiziert für den Cache.\r\n * Grant: CPCG-G-<base64url(claims)>.<ed25519> — max. 7 Tage, offline nutzbar\r\n *\r\n * Lizenzdienst stellt den Grant aus. CLI prüft offline: Signatur (Public Key) + exp.\r\n * «gesperrt» ≠ «Dienst nicht erreichbar» — das trennt GrantClaims.reason.\r\n */\r\n\r\nexport type LicenseStatus = \"active\" | \"past_due\" | \"canceled\" | \"blocked\" | \"disputed\";\r\n\r\nexport interface GrantClaims {\r\n /** Kunden-Key-ID (stabil) */\r\n kid: string;\r\n tier: \"trial\" | \"pro\" | \"agency\" | \"enterprise\";\r\n /** Grant gültig bis (Unix-Sekunden, max. +7d) */\r\n exp: number;\r\n /** bezahlt-bis aus dem Register */\r\n paidUntil: number;\r\n status: LicenseStatus;\r\n /** beauftragte Projekte (Berichtsmetadatum, keine Sperre) */\r\n domains?: string[];\r\n maxDomains?: number;\r\n /** Ausstellungsgrund: first | renew | manual */\r\n why: string;\r\n /** Register-Revision gegen Out-of-order */\r\n rev: number;\r\n}\r\n\r\nexport interface GrantVerdict {\r\n ok: boolean;\r\n claims?: GrantClaims;\r\n /** \"blocked\" = bestätigte Sperre · \"offline-stale\" = Dienst weg, Grant abgelaufen */\r\n reason?: \"invalid\" | \"expired\" | \"blocked\" | \"unknown\";\r\n detail?: string;\r\n}\r\n\r\nexport const GRANT_MAX_DAYS = 7;\r\n/** Kulanz nach paidUntil — identisch zu license-api.php (14 Tage). */\r\nexport const PAID_GRACE_DAYS = 14;\r\n\r\nfunction b64url(buf: Buffer | string): string {\r\n return Buffer.from(buf).toString(\"base64url\");\r\n}\r\n\r\nfunction hmac(payload: string, secret: string): string {\r\n return crypto.createHmac(\"sha256\", secret).update(payload).digest(\"base64url\");\r\n}\r\n\r\nfunction parseKey(key: string): { payload: string; sig: string } | null {\r\n if (!key?.startsWith(\"CPCG-K-\")) return null;\r\n const body = key.slice(\"CPCG-K-\".length);\r\n const dot = body.lastIndexOf(\".\");\r\n if (dot <= 0) return null;\r\n return { payload: body.slice(0, dot), sig: body.slice(dot + 1) };\r\n}\r\n\r\nfunction readKeyPayload(payload: string): { id: string; version: number } | null {\r\n try {\r\n const p = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf-8\")) as { i?: unknown; v?: unknown };\r\n if (typeof p.i !== \"string\" || !p.i || !Number.isInteger(p.v) || (p.v as number) < 1) return null;\r\n return { id: p.i, version: p.v as number };\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Dauerhafter Kunden-Key (Server-Seite). `version` erhöht der Admin bei «Key ersetzen»;\r\n * der Dienst akzeptiert nur die im Register hinterlegte Version.\r\n */\r\nexport function issueCustomerKey(\r\n id: string,\r\n secret: string,\r\n version = 1\r\n): { key: string; id: string; version: number } {\r\n const payload = b64url(JSON.stringify({ i: id, v: version }));\r\n return { key: `CPCG-K-${payload}.${hmac(payload, secret)}`, id, version };\r\n}\r\n\r\n/** Server-Seite: Signatur prüfen und ID + Version lesen. */\r\nexport function verifyCustomerKey(\r\n key: string,\r\n secret: string\r\n): { valid: boolean; id?: string; version?: number; reason?: string } {\r\n const parts = parseKey(key);\r\n if (!parts) return { valid: false, reason: \"Kein Kunden-Key.\" };\r\n const a = Buffer.from(parts.sig);\r\n const b = Buffer.from(hmac(parts.payload, secret));\r\n if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {\r\n return { valid: false, reason: \"Ungültige Signatur.\" };\r\n }\r\n const k = readKeyPayload(parts.payload);\r\n return k ? { valid: true, ...k } : { valid: false, reason: \"Payload unlesbar.\" };\r\n}\r\n\r\n/** Client-Seite: ID unverifiziert lesen (nur für die Cache-Zuordnung, kein Vertrauensanker). */\r\nexport function customerKeyId(key: string): string | null {\r\n const parts = parseKey(key);\r\n return parts ? (readKeyPayload(parts.payload)?.id ?? null) : null;\r\n}\r\n\r\n/** Befristete Offline-Freigabe (Grant), serverseitig mit dem privaten Seed signiert. */\r\nexport function issueGrant(\r\n claims: Omit<GrantClaims, \"exp\"> & { exp?: number },\r\n seed: string\r\n): string {\r\n const now = Math.floor(Date.now() / 1000);\r\n const maxExp = now + GRANT_MAX_DAYS * 86400;\r\n const exp = Math.min(claims.exp ?? maxExp, maxExp);\r\n const full: GrantClaims = { ...claims, exp };\r\n const payload = b64url(JSON.stringify(full));\r\n return `CPCG-G-${payload}.${signEd25519(\"cpcg-grant-v1\", payload, seed)}`;\r\n}\r\n\r\n/** Offline-Prüfung eines Grants mit dem öffentlichen Schlüssel. */\r\nexport function verifyGrant(token: string, publicKey: string = defaultPublicKey()): GrantVerdict {\r\n if (!token?.startsWith(\"CPCG-G-\")) {\r\n return { ok: false, reason: \"invalid\", detail: \"Kein Grant-Token.\" };\r\n }\r\n const body = token.slice(\"CPCG-G-\".length);\r\n const dot = body.lastIndexOf(\".\");\r\n if (dot <= 0) return { ok: false, reason: \"invalid\", detail: \"Signatur fehlt.\" };\r\n const payload = body.slice(0, dot);\r\n const sig = body.slice(dot + 1);\r\n if (!verifyEd25519(\"cpcg-grant-v1\", payload, sig, publicKey)) {\r\n return { ok: false, reason: \"invalid\", detail: \"Ungültige Signatur.\" };\r\n }\r\n let claims: GrantClaims;\r\n try {\r\n claims = JSON.parse(Buffer.from(payload, \"base64url\").toString(\"utf-8\"));\r\n } catch {\r\n return { ok: false, reason: \"invalid\", detail: \"Payload unlesbar.\" };\r\n }\r\n if (claims.status === \"blocked\") {\r\n return { ok: false, claims, reason: \"blocked\", detail: \"Lizenz gesperrt.\" };\r\n }\r\n const now = Math.floor(Date.now() / 1000);\r\n if (typeof claims.exp !== \"number\" || claims.exp < now) {\r\n return { ok: false, claims, reason: \"expired\", detail: \"Freigabe abgelaufen.\" };\r\n }\r\n // Der Dienst stellt nie über paidUntil + Kulanz hinaus aus; ein Grant, der\r\n // trotzdem darüber liegt (beendetes Abo, Zahlungsende), gilt nicht.\r\n // «disputed» sperrt bewusst nicht automatisch: Zahlungsstreit wird gemeldet, nicht blockiert.\r\n if (claims.status === \"canceled\") {\r\n return { ok: false, claims, reason: \"blocked\", detail: \"Lizenz beendet (canceled).\" };\r\n }\r\n if (typeof claims.paidUntil === \"number\" && claims.paidUntil > 0 && claims.paidUntil + PAID_GRACE_DAYS * 86400 < now) {\r\n return { ok: false, claims, reason: \"expired\", detail: \"Zahlungszeitraum abgelaufen.\" };\r\n }\r\n return { ok: true, claims };\r\n}\r\n\r\n/** Welche Arten von Freigabe sind noch gültig? */\r\nexport function grantState(verdict: GrantVerdict): \"active\" | \"blocked\" | \"needs-check\" {\r\n if (verdict.ok) return \"active\";\r\n if (verdict.reason === \"blocked\") return \"blocked\";\r\n return \"needs-check\";\r\n}\r\n","import fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\n/**\n * Lokaler Grant-Cache — Offline-Betrieb ohne monatliche Copy-Paste.\n * Pfad: ~/.cpcg/grant.json (überschreibbar via CPCG_HOME)\n */\n\nexport interface StoredGrant {\n grant: string;\n /** Kunden-Key-ID */\n kid: string;\n /** letzte erfolgreiche Online-Prüfung (Unix-Sekunden) */\n checkedAt: number;\n /** Woher der Grant kam */\n why: string;\n}\n\nexport function grantCachePath(): string {\n const home = process.env.CPCG_HOME || path.join(os.homedir(), \".cpcg\");\n return path.join(home, \"grant.json\");\n}\n\nexport function readGrantCache(): StoredGrant | null {\n try {\n const p = grantCachePath();\n if (!fs.existsSync(p)) return null;\n const raw = JSON.parse(fs.readFileSync(p, \"utf-8\")) as StoredGrant;\n if (typeof raw?.grant !== \"string\" || typeof raw?.checkedAt !== \"number\") return null;\n return raw;\n } catch {\n return null;\n }\n}\n\nexport function writeGrantCache(g: StoredGrant): void {\n const p = grantCachePath();\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify(g, null, 2), \"utf-8\");\n try {\n fs.chmodSync(p, 0o600);\n } catch {\n /* Windows ignoriert chmod */\n }\n}\n\nexport function clearGrantCache(): void {\n try {\n fs.unlinkSync(grantCachePath());\n } catch {\n /* egal */\n }\n}\n\n/** Online-Prüfung nötig? älter als 24h oder kein Cache. */\nexport function needsOnlineCheck(g: StoredGrant | null, maxAgeSec = 86400): boolean {\n if (!g) return true;\n return Date.now() / 1000 - g.checkedAt > maxAgeSec;\n}\n","/**\n * Lizenzdienst-Client: Kunden-Key → Grant.\n * «gesperrt» und «Dienst nicht erreichbar» sind getrennte Outcomes.\n */\n\nexport type CheckOutcome =\n | { kind: \"ok\"; grant: string; kid: string }\n | { kind: \"blocked\"; detail: string }\n | { kind: \"unreachable\"; detail: string }\n | { kind: \"rejected\"; detail: string };\n\nexport interface LicenseServiceConfig {\n /** z. B. https://cuonztech.ch/api/license-api.php */\n baseUrl: string;\n timeoutMs?: number;\n}\n\nexport async function checkLicense(\n customerKey: string,\n cfg: LicenseServiceConfig,\n fetchImpl: typeof fetch = fetch\n): Promise<CheckOutcome> {\n const timeout = cfg.timeoutMs ?? 8000;\n const url = cfg.baseUrl.replace(/\\/$/, \"\") + \"/check\";\n try {\n const res = await fetchImpl(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ key: customerKey }),\n signal: AbortSignal.timeout(timeout),\n });\n if (res.status === 403) {\n const body = (await res.json().catch(() => ({}))) as { detail?: string };\n return { kind: \"blocked\", detail: body.detail || \"Lizenz gesperrt.\" };\n }\n if (res.status === 401 || res.status === 404) {\n const body = (await res.json().catch(() => ({}))) as { detail?: string };\n return { kind: \"rejected\", detail: body.detail || \"Kunden-Key unbekannt.\" };\n }\n if (!res.ok) {\n return { kind: \"unreachable\", detail: `Lizenzdienst HTTP ${res.status}` };\n }\n const body = (await res.json()) as { grant?: string; kid?: string; status?: string };\n if (body.status === \"blocked\") {\n return { kind: \"blocked\", detail: \"Lizenz gesperrt.\" };\n }\n if (!body.grant) {\n return { kind: \"rejected\", detail: \"Keine Freigabe erhalten.\" };\n }\n return { kind: \"ok\", grant: body.grant, kid: body.kid || \"\" };\n } catch (err) {\n return {\n kind: \"unreachable\",\n detail: \"Lizenzdienst nicht erreichbar: \" + String(err).slice(0, 80),\n };\n }\n}\n","import { verifyGrant, customerKeyId, type GrantClaims } from \"./grant.js\";\r\nimport {\r\n readGrantCache,\r\n writeGrantCache,\r\n needsOnlineCheck,\r\n type StoredGrant,\r\n} from \"./grantStore.js\";\r\nimport { defaultPublicKey } from \"./ed25519.js\";\r\nimport { checkLicense, type LicenseServiceConfig } from \"./serviceClient.js\";\r\n\r\n/**\r\n * Entspricht der Automatik-Regel:\r\n * - Online-Prüfung, wenn letzte erfolgreiche Prüfung > 24h alt (oder nie)\r\n * - Offline weiter mit gültigem Grant (max. 7 Tage)\r\n * - Bestätigte Sperre beendet die Freigabe sofort\r\n * - Dienst-Down ≠ gesperrt: Grant bleibt bis exp nutzbar\r\n * - Laufende Scans fertigstellen; Reports bleiben zugänglich\r\n */\r\n\r\nexport type ResolveOutcome =\r\n | {\r\n ok: true;\r\n claims: GrantClaims;\r\n source: \"cache\" | \"fresh\";\r\n /** true wenn Online-Check nötig war aber Dienst weg — Grant genutzt */\r\n offlineFallback: boolean;\r\n }\r\n | {\r\n ok: false;\r\n reason: \"blocked\" | \"expired\" | \"unreachable-expired\" | \"no-key\" | \"invalid\";\r\n detail: string;\r\n };\r\n\r\nexport interface ResolveOptions {\r\n /** Kunden-Key (CPCG-K-…) aus Env/Config */\r\n customerKey?: string;\r\n /** Öffentlicher Ed25519-Schlüssel (Default: eingebettet bzw. CPCG_LICENSE_PUBLIC_KEY) */\r\n publicKey?: string;\r\n service: LicenseServiceConfig;\r\n /** Test-Stubs */\r\n fetchImpl?: typeof fetch;\r\n now?: number;\r\n}\r\n\r\nexport async function resolveLicenseGrant(opts: ResolveOptions): Promise<ResolveOutcome> {\r\n const cached = readGrantCache();\r\n const publicKey = opts.publicKey ?? defaultPublicKey();\r\n\r\n // 1) Lokalen Grant prüfen\r\n let local = cached ? verifyGrant(cached.grant, publicKey) : null;\r\n // Cache gehört zu genau einem Kunden-Key; fremder Key → Cache ignorieren.\r\n if (local?.claims && opts.customerKey) {\r\n const keyId = customerKeyId(opts.customerKey);\r\n if (keyId !== null && keyId !== local.claims.kid) local = null;\r\n }\r\n\r\n // 2) Online-Check wenn nötig\r\n const wantCheck = needsOnlineCheck(cached) || !local?.ok || local?.claims?.status !== \"active\";\r\n\r\n if (wantCheck) {\r\n if (!opts.customerKey) {\r\n // Offline-Pfad: Grant genügt, wenn noch gültig\r\n if (local?.ok && local.claims) {\r\n return {\r\n ok: true,\r\n claims: local.claims,\r\n source: \"cache\",\r\n offlineFallback: true,\r\n };\r\n }\r\n return {\r\n ok: false,\r\n reason: \"no-key\",\r\n detail:\r\n \"Kein Kunden-Key (CPCG_CUSTOMER_KEY) und keine gültige Offline-Freigabe. Erste Aktivierung braucht Internet.\",\r\n };\r\n }\r\n\r\n const outcome = await checkLicense(\r\n opts.customerKey,\r\n opts.service,\r\n opts.fetchImpl ?? fetch\r\n );\r\n\r\n if (outcome.kind === \"blocked\") {\r\n // Bestätigte Sperre: Cache ungültig machen\r\n writeGrantCache({\r\n grant: \"\",\r\n kid: \"blocked\",\r\n checkedAt: Math.floor(Date.now() / 1000),\r\n why: \"blocked\",\r\n });\r\n return { ok: false, reason: \"blocked\", detail: outcome.detail };\r\n }\r\n\r\n if (outcome.kind === \"ok\") {\r\n const fresh = verifyGrant(outcome.grant, publicKey);\r\n if (fresh.ok && fresh.claims) {\r\n writeGrantCache({\r\n grant: outcome.grant,\r\n kid: outcome.kid || fresh.claims.kid,\r\n checkedAt: Math.floor(Date.now() / 1000),\r\n why: fresh.claims.why,\r\n });\r\n return {\r\n ok: true,\r\n claims: fresh.claims,\r\n source: \"fresh\",\r\n offlineFallback: false,\r\n };\r\n }\r\n return {\r\n ok: false,\r\n reason: \"invalid\",\r\n detail: \"Grant vom Dienst nicht verifizierbar.\",\r\n };\r\n }\r\n\r\n if (outcome.kind === \"rejected\") {\r\n return { ok: false, reason: \"invalid\", detail: outcome.detail };\r\n }\r\n\r\n // unreachable: Offline-Freigabe nutzen, wenn gültig\r\n if (local?.ok && local.claims) {\r\n return {\r\n ok: true,\r\n claims: local.claims,\r\n source: \"cache\",\r\n offlineFallback: true,\r\n };\r\n }\r\n return {\r\n ok: false,\r\n reason: \"unreachable-expired\",\r\n detail:\r\n \"Lizenzdienst nicht erreichbar und keine gültige Offline-Freigabe. \" +\r\n outcome.detail,\r\n };\r\n }\r\n\r\n // 3) Cache reicht (letzter Check < 24h und Grant gültig)\r\n if (local?.ok && local.claims) {\r\n return {\r\n ok: true,\r\n claims: local.claims,\r\n source: \"cache\",\r\n offlineFallback: false,\r\n };\r\n }\r\n\r\n return {\r\n ok: false,\r\n reason: \"expired\",\r\n detail: local?.detail || \"Freigabe abgelaufen.\",\r\n };\r\n}\r\n"],"mappings":"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","names":["body"]}
|
package/docs/LIZENZ-KAUFFLUSS.md
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# Cuonztech Privacy Chaos Gate — Lizenz & Kauf-Fluss
|
|
2
|
-
|
|
3
|
-
**Produkt:** Cuonztech Privacy Chaos Gate (CPCG)
|
|
4
|
-
**Modell:** Online-Kauf → Lizenzcode per E-Mail → Scannen freigeschaltet
|
|
5
|
-
|
|
6
|
-
> ⚠️ **Disclaimer:** Technisches Hilfsmittel, keine Rechtsberatung.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 1. Kauf-Fluss (Kundensicht)
|
|
11
|
-
|
|
12
|
-
```
|
|
13
|
-
cuonztech.ch/pricing
|
|
14
|
-
│
|
|
15
|
-
▼
|
|
16
|
-
Online-Kauf (Stripe / Shop)
|
|
17
|
-
│
|
|
18
|
-
▼
|
|
19
|
-
E-Mail mit Lizenzcode
|
|
20
|
-
(z.B. CPCG-7F3A-9K2M-4QX1)
|
|
21
|
-
│
|
|
22
|
-
▼
|
|
23
|
-
Lokal oder CI:
|
|
24
|
-
npx @cuonztech/privacy-chaos-gate \
|
|
25
|
-
--url https://staging.example.com \
|
|
26
|
-
--license CPCG-7F3A-9K2M-4QX1
|
|
27
|
-
│
|
|
28
|
-
▼
|
|
29
|
-
Gate-Report + optional Auto-Fix-PR
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 2. Lizenzstufen (Vorschlag)
|
|
35
|
-
|
|
36
|
-
| Stufe | Inhalt | Zielkunde |
|
|
37
|
-
|-------|--------|-----------|
|
|
38
|
-
| **Trial** | 1 URL, 3 Scans/Monat, Report ohne Fix | Evaluation |
|
|
39
|
-
| **Pro** | Unbegrenzte Scans, Auto-Fix-PR, 1 Repo | Agenturen, E-Commerce |
|
|
40
|
-
| **Agency** | Multi-Repo, White-Label-Report, Priority | Agenturen, Consulting |
|
|
41
|
-
| **Enterprise** | SSO, SLA, On-Prem-Runner | Konzerne |
|
|
42
|
-
|
|
43
|
-
**Open-Core-Option:** Scanner-CLI als OSS, Dashboard/Monitoring/Fix-SaaS kommerziell.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## 3. Technische Aktivierung (CLI / Action)
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
# Einmal-Scan (fester Lizenzcode, 90 Tage):
|
|
51
|
-
cpcg --url https://staging.example.com --license CPCG-eyJ…
|
|
52
|
-
|
|
53
|
-
# Abo (Kunden-Key, an die Zahlung gebunden):
|
|
54
|
-
export CPCG_CUSTOMER_KEY="CPCG-K-…" # oder --license CPCG-K-…
|
|
55
|
-
cpcg --url https://staging.example.com
|
|
56
|
-
|
|
57
|
-
# GitHub Action
|
|
58
|
-
- uses: cuonztech/privacy-chaos-gate@v1
|
|
59
|
-
with:
|
|
60
|
-
staging-url: ${{ vars.STAGING_URL }}
|
|
61
|
-
license: ${{ secrets.CPCG_LICENSE }}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**Verifikation:** Ed25519. Das CLI enthält nur den **öffentlichen** Schlüssel
|
|
65
|
-
(`src/core/license/publicKey.ts`), der private Seed bleibt auf dem Lizenzserver.
|
|
66
|
-
Es gibt kein Secret, das Kunden kennen müssen. Der Kunden-Key wird nur vom Dienst
|
|
67
|
-
geprüft; das CLI tauscht ihn gegen einen höchstens 7 Tage gültigen, offline prüfbaren Grant
|
|
68
|
-
(Online-Prüfung alle 24 h, bei Dienst-Ausfall gilt der Grant weiter — «nicht erreichbar» ist nie «gesperrt»).
|
|
69
|
-
|
|
70
|
-
Aufbau, Deploy und Betrieb des Dienstes: `server/license-api/README.md`.
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## 4. Was ist im Kauf enthalten?
|
|
75
|
-
|
|
76
|
-
- CLI / GitHub Action (npm `@cuonztech/privacy-chaos-gate`)
|
|
77
|
-
- 5 Chaos-Szenarien + CMP-Locators (OneTrust, Cookiebot, Usercentrics, …)
|
|
78
|
-
- Auto-Fix + Review-Agent + PR-Erstellung
|
|
79
|
-
- Pilot-Report-Template (Cuonztech Brand)
|
|
80
|
-
- Updates Minor/Hotfix für 12 Monate
|
|
81
|
-
|
|
82
|
-
**Nicht enthalten:** Rechtsberatung, Garantie der DSGVO-Konformität, Managed CMP.
|
|
83
|
-
|
|
84
|
-
---
|
|
85
|
-
|
|
86
|
-
## 5. Pricing-Heuristik (noch nicht final)
|
|
87
|
-
|
|
88
|
-
- **Pro:** ca. CHF 99–199 / Monat je Repo
|
|
89
|
-
(vs. CMP-Lizenzen CHF 50–500 / Monat — CPCG ist die **Ergänzung**, nicht der Ersatz)
|
|
90
|
-
- **Agency:** CHF 499 / Monat Multi-Repo
|
|
91
|
-
- **Pilot-Paket:** 1× Evidence-Report + 1× Fix-PR = Einstieg
|
|
92
|
-
|
|
93
|
-
Erst nach 3–5 Pilotkunden Preise fixieren.
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## 6. Support & Haftung
|
|
98
|
-
|
|
99
|
-
- Support: E-Mail / GitHub Issues (Pro+)
|
|
100
|
-
- **Haftungshinweis:** Tool zur Unterstützung, keine Rechtsberatung
|
|
101
|
-
- SLA erst Enterprise
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
*© Cuonztech — Cuonztech Privacy Chaos Gate*
|
package/docs/PREISMODELL.md
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Preismodell-Recherche · CPCG
|
|
2
|
-
|
|
3
|
-
> Quelle: cookiebot.com/pricing (Abruf 2026-10-20). Keine Rechts-/Preisgarantie.
|
|
4
|
-
> Fokus: Billing-Units der Wettbewerber ”” nicht deren Produktvergleich.
|
|
5
|
-
|
|
6
|
-
## Marktpreise CMP (Auszug)
|
|
7
|
-
|
|
8
|
-
| Anbieter | Billing-Unit | Spanne | Hinweis |
|
|
9
|
-
|----------|--------------|--------|---------|
|
|
10
|
-
| Cookiebot Free | 1 Domain, ≤50 Subpages | 0 | eingeschränkt |
|
|
11
|
-
| Cookiebot Lite | 1 Domain, ≤50 Subpages | **8 USD/Mo** | Branding, Languages |
|
|
12
|
-
| Cookiebot Small | Domain, ≤350 Subpages | **16 USD/Mo/Domain** | ab 4 Domains |
|
|
13
|
-
| Cookiebot Medium | Domain, ≤3.500 Subpages | **34 USD/Mo/Domain** | |
|
|
14
|
-
| Cookiebot Large | Domain, ≤7.000 | **56 USD/Mo/Domain** | |
|
|
15
|
-
| Cookiebot XLarge | Domain >7.000 | **96 USD/Mo/Domain** | |
|
|
16
|
-
| Cookiebot Daily-Scan | Add-on | +99 USD/Mo | automatische Scans |
|
|
17
|
-
| Usercentrics Advanced | **Sessions** | Contact Sales | bis 1 Mio Sess./Mo |
|
|
18
|
-
|
|
19
|
-
**Zusammenfassung:** CMPs fakturieren **Domain × Seiten- oder Session-Grösse**.
|
|
20
|
-
Traffic ist für Browser-Banner relevant. Für CI-Scans ist das die falsche Unit.
|
|
21
|
-
|
|
22
|
-
## CPCG: was wird gemessen?
|
|
23
|
-
|
|
24
|
-
| Unit | Misst | Beispiel |
|
|
25
|
-
|------|-------|----------|
|
|
26
|
-
| **Scan** | ein Lauf (5 Szenarien, 1 URL) | Evidence, Audit |
|
|
27
|
-
| **Domain** | Ziel-Objekt im CI | Staging, Prod, Subdomain |
|
|
28
|
-
| **Repo / Seat** | Engineering-Kapazität | Team, Agentur |
|
|
29
|
-
| Session | Besucher-Traffic | **nein** ”” irrelevant für Gate |
|
|
30
|
-
|
|
31
|
-
## Empfehlung (Hybrid)
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
Pilot / Evidence CHF 890 einmalig, 1 Domain, Brand-Report + Fix-Diff
|
|
35
|
-
Pro CI-Gate CHF 149 / Domain / Monat ”” Auto-Fix-PR, Action, unbegrenzte Scans
|
|
36
|
-
Agency CHF 490 / Monat ”” bis 10 Domains, White-Label, Priority
|
|
37
|
-
Enterprise auf Anfrage ”” SSO, SLA, On-Prem-Runner
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Begründung
|
|
41
|
-
1. **Pilot one-shot** senkt die Kaufhürde (vs. Abo) und liefert den Pilot-Kunde-artigen Beweis.
|
|
42
|
-
2. **Domain-Abo** ist die Unit, die Kunden aus Cookiebot-Listen kennen ”” verständlich.
|
|
43
|
-
3. **Kein Session-Pricing** ”” wir laufen in CI, nicht im Browser der Besucher.
|
|
44
|
-
4. **Preisanker:** Cookiebot 8”“96 USD/Mo ist der Banner-Platzhalter; CPCG ist die
|
|
45
|
-
Kontrolle *neben* dem CMP. 149 CHF ist vertretbar, wenn der Fix-PR Abmahnrisiko nimmt.
|
|
46
|
-
|
|
47
|
-
### Offen (erst nach Piloten entscheiden)
|
|
48
|
-
- Mindestlaufzeit Abo (monatlich vs. annual discount)
|
|
49
|
-
- Ob Subdomains extra oder inklusive
|
|
50
|
-
- Staffel ab 5 / 10 / 50 Domains
|
|
51
|
-
- Trial: 1 Domain, 3 Scans ”” oder Zeitraum 14 Tage
|
|
52
|
-
|
|
53
|
-
## Bezug (Kundensicht)
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
Online-Kauf (cuonztech.ch)
|
|
57
|
-
→ Lizenzcode per E-Mail (CPCG-”¦)
|
|
58
|
-
→ cpcg --url <staging> --license CPCG-”¦
|
|
59
|
-
→ Report (JSON + Brand-HTML) + optional Auto-Fix-PR
|
|
60
|
-
→ CI-Gate als Merge-Blocker
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Lizenz technisch: signierter Token (`src/core/license/`), Offline-Verify.
|