cuonztech-privacy-chaos-gate 0.0.0-stage → 0.2.0

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/README.md CHANGED
@@ -1,3 +1,143 @@
1
- # Temporary Holding Version
1
+ # CuonzTech Privacy Chaos Gate (CPCG)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ CI-Gate und Auto-Fix für Consent-Tracker: findet Requests, die **vor** der Einwilligung
4
+ firen — und liefert den Fix als Pull Request. Prüft auch die Gegenrichtung:
5
+ läuft erlaubtes Tracking nach dem Akzeptieren korrekt an?
6
+
7
+ > **Hinweis:** Technisches Hilfsmittel, **keine Rechtsberatung**, keine Garantie für
8
+ > revDSG-/DSGVO-/TTDSG-Konformität. / Technical aid, **not legal advice**.
9
+
10
+ ## Installieren
11
+
12
+ ```bash
13
+ pnpm add -D @cuonztech/privacy-chaos-gate
14
+ # oder direkt
15
+ npx @cuonztech/privacy-chaos-gate scan --url https://staging.example.com --license CPCG-…
16
+ ```
17
+
18
+ ## Lizenz
19
+
20
+ Online-Kauf → Code per Mail.
21
+
22
+ ```bash
23
+ export CPCG_LICENSE="CPCG-…"
24
+ cpcg scan --url https://staging.example.com --jurisdiction CH
25
+ ```
26
+
27
+ Ohne Lizenz: localhost/Test-Fixtures; Produktiv-URLs mit Warnung (Trial-Modus).
28
+
29
+ ## Was das Tool prüft
30
+
31
+ 15 Chaos-Szenarien in einem Lauf — jedes mit ehrlichem Status
32
+ (**bestanden / auffällig / nicht prüfbar**). Ein Button, der nicht gefunden wird,
33
+ wird nie zu einem grünen Häkchen.
34
+
35
+ | # | Szenario | Frage |
36
+ |---|----------|-------|
37
+ | 1 | happy-path-denied | Bleibt Tracking nach «Ablehnen» aus? |
38
+ | 2 | happy-path-accepted | Ist vor dem Klick Ruhe — und läuft erlaubtes Tracking danach an? |
39
+ | 3 | fast-click | Navigation vor dem Banner: feuert dann schon etwas? |
40
+ | 4 | state-manipulation | Tracking nach Storage-Wipe? |
41
+ | 5 | network-drop | Tracker trotz blockiertem CMP? |
42
+ | 6 | consent-revoked | Stoppt Tracking nach Widerruf? Welche IDs bleiben liegen? |
43
+ | 7 | category-selective | Nur Statistik erlaubt — bleiben Marketing-Tracker aus? |
44
+ | 8 | consent-persistence | Überlebt die Entscheidung Reload, Unterseite und Neustart? |
45
+ | 9 | banner-ignored | Starten Tracker per Timer/Scroll ohne Klick? |
46
+ | 10 | subpage-spa | Greift der Schutz auf Unterseiten und bei SPA-Navigation? |
47
+ | 11 | cmp-failure | Tracking-Zeitfenster bei verzögerter/defekter CMP? |
48
+ | 12 | embed-leaks | Kontaktieren YouTube/Maps/Social-Embeds vor Freigabe? |
49
+ | 13 | consent-mode-v2 | Decken sich Consent-Mode-Zustände mit Cookies/Requests? |
50
+ | 14 | duplicate-events | Doppelte page_view-Events (Plugin + GTM parallel)? |
51
+ | 15 | mobile-usability | Sind Ablehnen/Einstellungen auf dem Handy bedienbar? |
52
+
53
+ **Tracker (Auswahl):** Google Tag Manager, Google Analytics, Meta, TikTok, LinkedIn,
54
+ Hotjar, Criteo, Marketo, HubSpot, Matomo.
55
+
56
+ **CMPs (Auswahl):** OneTrust, Cookiebot, Usercentrics, Tealium, iubenda, etracker,
57
+ JustRelate, Didomi, Axeptio, Sourcepoint, Consentmanager, Borlabs, Klaro.
58
+
59
+ ## Output: der Prüfbericht
60
+
61
+ Jeder Scan schreibt ein Evidence-Paket:
62
+
63
+ ```
64
+ report.json LeakReport (maschinenlesbar)
65
+ report-evidence.md Kunden-Report (Markdown)
66
+ report-evidence.html Kunden-Report (HTML, CuonzTech-Brand)
67
+ evidence/ Screenshots (portabel, relative Pfade)
68
+ report-deluxe.pdf Deluxe-PDF mit Deckblatt (mit --report-pdf)
69
+ ```
70
+
71
+ Der Report führt **Befunde** (Regelverstösse) und **beobachtete Requests**
72
+ (Timelines) getrennt — und ordnet jedes Ergebnis mehreren Rechtsräumen zu
73
+ (CH, EU, UK, US-CA, BR, ROW). Keine globale Compliance-Aussage.
74
+
75
+ ## Flags
76
+
77
+ ```bash
78
+ cpcg scan \
79
+ --url https://staging.example.com \
80
+ --license CPCG-… \
81
+ --report-pdf # Deluxe-PDF (Deckblatt + Evidence) erzeugen
82
+ --client "Firma AG" # Kundenname fürs Deckblatt
83
+ --demo # Report als synthetische Testdaten kennzeichnen
84
+ --jurisdiction CH # CH | EU | UK | US-CA | BR | ROW
85
+ --auto-fix true # Auto-Fix generieren
86
+ --fail-on-leak true # Exit 1 bei Leaks (CI-Gate)
87
+ --report-output reports/scan.json
88
+ ```
89
+
90
+ ## Demo-Portal
91
+
92
+ Reproduzierbare Testseite mit fünf absichtlichen Consent-Leaks
93
+ (Pre-Consent-Bootstrap, Timer, Widerruf, Embeds, doppelte Events):
94
+
95
+ ```bash
96
+ node demo-portal/server.mjs # http://127.0.0.1:8787
97
+ pnpm exec tsx scripts/scan-demo-portal.mts # voller Scan + Report
98
+ pnpm exec tsx scripts/make-deluxe-report.mts # Deluxe-PDF daraus
99
+ ```
100
+
101
+ Erwartet: Gate **BLOCKED**, Szenarien «auffällig» — exakt dieselben Zahlen bei jedem Lauf.
102
+
103
+ ## GitHub Action
104
+
105
+ ```yaml
106
+ - uses: cuonztech/privacy-chaos-gate@v1
107
+ with:
108
+ staging-url: ${{ vars.STAGING_URL }}
109
+ license: ${{ secrets.CPCG_LICENSE }}
110
+ report-pdf: true
111
+ client-name: "Firma AG"
112
+ ```
113
+
114
+ Die Action nutzt `dist/cli.js` aus dem Action-Path; erst bei fehlendem Build greift `npx`.
115
+
116
+ ## Jurisdiktion
117
+
118
+ `--jurisdiction CH|EU|UK|US-CA|BR|ROW` — gleicher Befund, unterschiedliche Einordnung:
119
+
120
+ - **EU:** Opt-In-Pflicht für nicht-essenzielle Tracker. Ein beobachteter Request ist ein
121
+ schwerwiegender Hinweis — die Einstufung als Verstoß erfordert die Prüfung von Zweck,
122
+ Technologie und Consent-Zustand. Consent Mode ist eine technische Implementierung,
123
+ keine Rechtsgrundlage.
124
+ - **CH:** Risiko nach revDSG-Praxis; der EDÖB verlangt insbesondere bei personalisierter
125
+ Werbung eine Einwilligung.
126
+ - **US-CA:** Opt-out-Modell. Ohne Prüfung von Sale/Sharing-Opt-out und GPC ist die
127
+ Bewertung unvollständig.
128
+ - **ROW:** Nicht bewertet — länderspezifische Prüfung erforderlich.
129
+
130
+ ## Entwicklung
131
+
132
+ ```bash
133
+ pnpm install
134
+ pnpm test # 136 Tests
135
+ pnpm build
136
+ pnpm exec tsx scripts/e2e-cli-deluxe.mts # E2E: echte CLI gegen Demo-Portal
137
+ ```
138
+
139
+ Secrets (Stripe, Lizenz-Secret) nur über Env/KeePass — nie im Repo.
140
+
141
+ ---
142
+
143
+ © CuonzTech · Carlo Cuonz · [cuonztech.ch](https://cuonztech.ch)
package/action.yml ADDED
@@ -0,0 +1,109 @@
1
+ name: "Cuonztech Privacy Chaos Gate"
2
+ description: "Agentic CI/CD Privacy Chaos Engineering & Auto-Remediation Gate for Cookie/Consent Leaks"
3
+ author: "Cuonztech"
4
+ branding:
5
+ icon: "shield"
6
+ color: "purple"
7
+
8
+ inputs:
9
+ staging-url:
10
+ description: "The URL of the staging or localhost web environment to stress-test"
11
+ required: true
12
+ api-key:
13
+ description: "API key for the auto-remediation LLM (e.g. OpenAI, Anthropic, Gemini)"
14
+ required: false
15
+ default: ""
16
+ llm-provider:
17
+ description: "LLM Provider to use for auto-remediation ('openai', 'anthropic', 'gemini')"
18
+ required: false
19
+ default: "openai"
20
+ llm-model:
21
+ description: "Model to use for auto-remediation"
22
+ required: false
23
+ default: "gpt-4o"
24
+ auto-fix:
25
+ description: "Automatically generate and apply code patches when tracker leaks are detected"
26
+ required: false
27
+ default: "true"
28
+ fail-on-leak:
29
+ description: "Block the build (exit code 1) if unpatched critical leaks remain"
30
+ required: false
31
+ default: "true"
32
+ report-output:
33
+ description: "File path where the JSON LeakReport will be written"
34
+ required: false
35
+ default: "reports/privacy-chaos-report.json"
36
+ target-source-dir:
37
+ description: "Root directory of the application source code to inspect and patch"
38
+ required: false
39
+ default: "."
40
+ git-commit:
41
+ description: "Automatically commit generated privacy fixes into the branch"
42
+ required: false
43
+ default: "false"
44
+ license:
45
+ description: "CuonzTech CPCG Lizenzcode (CPCG-…) — aus Secrets, nach Online-Kauf"
46
+ required: false
47
+ default: ""
48
+ report-pdf:
49
+ description: "Deluxe-PDF (Deckblatt + Evidence) nach dem Scan erzeugen"
50
+ required: false
51
+ default: "false"
52
+ client-name:
53
+ description: "Kundenname für das PDF-Deckblatt"
54
+ required: false
55
+ default: ""
56
+ jurisdiction:
57
+ description: "Rechtsraum für Bewertung: CH | EU | UK | US-CA | BR | ROW"
58
+ required: false
59
+ default: "CH"
60
+
61
+ outputs:
62
+ leaks-found:
63
+ description: "'true' if leaks were detected during chaos tests"
64
+ value: ${{ steps.cpcg-run.outputs.leaks_found }}
65
+ leaks-count:
66
+ description: "Number of tracker leaks detected"
67
+ value: ${{ steps.cpcg-run.outputs.leaks_count }}
68
+ patch-applied:
69
+ description: "'true' if an auto-remediation patch was successfully applied"
70
+ value: ${{ steps.cpcg-run.outputs.patch_applied }}
71
+ report-path:
72
+ description: "Absolute or relative path to the generated JSON report"
73
+ value: ${{ steps.cpcg-run.outputs.report_path }}
74
+
75
+ runs:
76
+ using: "composite"
77
+ steps:
78
+ - name: Setup Node.js
79
+ uses: actions/setup-node@v4
80
+ with:
81
+ node-version: "20"
82
+
83
+ - name: Run Privacy Chaos Gate
84
+ id: cpcg-run
85
+ shell: bash
86
+ env:
87
+ CPCG_LLM_API_KEY: ${{ inputs.api-key }}
88
+ CPCG_LLM_PROVIDER: ${{ inputs.llm-provider }}
89
+ CPCG_LLM_MODEL: ${{ inputs.llm-model }}
90
+ CPCG_LICENSE: ${{ inputs.license }}
91
+ run: |
92
+ CLI_PATH="${{ github.action_path }}/dist/cli.js"
93
+ if [ -f "$CLI_PATH" ]; then
94
+ RUNNER="node $CLI_PATH"
95
+ else
96
+ RUNNER="npx @cuonztech/privacy-chaos-gate"
97
+ fi
98
+
99
+ $RUNNER \
100
+ --url "${{ inputs.staging-url }}" \
101
+ --auto-fix "${{ inputs.auto-fix }}" \
102
+ --fail-on-leak "${{ inputs.fail-on-leak }}" \
103
+ --report-output "${{ inputs.report-output }}" \
104
+ --source-dir "${{ inputs.target-source-dir }}" \
105
+ --git-commit "${{ inputs.git-commit }}" \
106
+ --license "${{ inputs.license }}" \
107
+ --report-pdf "${{ inputs.report-pdf }}" \
108
+ --client "${{ inputs.client-name }}" \
109
+ --jurisdiction "${{ inputs.jurisdiction }}"
@@ -0,0 +1,36 @@
1
+ // src/core/license/ed25519.ts
2
+ import crypto from "crypto";
3
+
4
+ // src/core/license/publicKey.ts
5
+ var LICENSE_PUBLIC_KEY = "q20XyLRZGURVvMLR6q0SCIc6poLhW73KE5Ds94Yq1FQ=";
6
+
7
+ // src/core/license/ed25519.ts
8
+ var PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
9
+ var SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
10
+ function rawKey(b64, what) {
11
+ const raw = Buffer.from(b64, "base64");
12
+ if (raw.length !== 32) throw new Error(`${what}: erwartet 32 Byte (base64), erhalten ${raw.length}`);
13
+ return raw;
14
+ }
15
+ function defaultPublicKey() {
16
+ return process.env.CPCG_LICENSE_PUBLIC_KEY || LICENSE_PUBLIC_KEY;
17
+ }
18
+ function verifyEd25519(domain, payload, sigB64url, publicKeyB64 = defaultPublicKey()) {
19
+ try {
20
+ const key = crypto.createPublicKey({
21
+ key: Buffer.concat([SPKI_PREFIX, rawKey(publicKeyB64, "Public Key")]),
22
+ format: "der",
23
+ type: "spki"
24
+ });
25
+ const sig = Buffer.from(sigB64url, "base64url");
26
+ return sig.length === 64 && crypto.verify(null, Buffer.from(`${domain}.${payload}`), key, sig);
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ export {
33
+ defaultPublicKey,
34
+ verifyEd25519
35
+ };
36
+ //# sourceMappingURL=chunk-FN3QUFUZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/license/ed25519.ts","../src/core/license/publicKey.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport { LICENSE_PUBLIC_KEY } from \"./publicKey.js\";\n\n/**\n * Ed25519-Signaturen für Lizenzen und Grants.\n *\n * Der Server signiert mit dem privaten Seed (nur in config.php / KeePass),\n * der Client prüft ausschliesslich mit dem öffentlichen Schlüssel. Wer das CLI\n * besitzt, kann damit nichts ausstellen. Schlüssel sind rohe 32 Byte, base64.\n *\n * Jeder Token-Typ signiert mit eigenem Domain-Präfix, damit eine Lizenz-\n * Signatur nie als Grant (oder umgekehrt) durchgeht.\n */\nexport type SignDomain = \"cpcg-license-v1\" | \"cpcg-grant-v1\";\n\nconst PKCS8_PREFIX = Buffer.from(\"302e020100300506032b657004220420\", \"hex\");\nconst SPKI_PREFIX = Buffer.from(\"302a300506032b6570032100\", \"hex\");\n\nfunction rawKey(b64: string, what: string): Buffer {\n const raw = Buffer.from(b64, \"base64\");\n if (raw.length !== 32) throw new Error(`${what}: erwartet 32 Byte (base64), erhalten ${raw.length}`);\n return raw;\n}\n\nexport function generateLicenseKeyPair(): { seed: string; publicKey: string } {\n const { privateKey, publicKey } = crypto.generateKeyPairSync(\"ed25519\");\n return {\n seed: privateKey.export({ format: \"der\", type: \"pkcs8\" }).subarray(-32).toString(\"base64\"),\n publicKey: publicKey.export({ format: \"der\", type: \"spki\" }).subarray(-32).toString(\"base64\"),\n };\n}\n\nexport function signEd25519(domain: SignDomain, payload: string, seedB64: string): string {\n const key = crypto.createPrivateKey({\n key: Buffer.concat([PKCS8_PREFIX, rawKey(seedB64, \"CPCG_LICENSE_SIGNING_SEED\")]),\n format: \"der\",\n type: \"pkcs8\",\n });\n return crypto.sign(null, Buffer.from(`${domain}.${payload}`), key).toString(\"base64url\");\n}\n\n/** Öffentlicher Schlüssel: CPCG_LICENSE_PUBLIC_KEY (Tests, Self-Hosting) vor dem eingebetteten. */\nexport function defaultPublicKey(): string {\n return process.env.CPCG_LICENSE_PUBLIC_KEY || LICENSE_PUBLIC_KEY;\n}\n\nexport function verifyEd25519(\n domain: SignDomain,\n payload: string,\n sigB64url: string,\n publicKeyB64: string = defaultPublicKey()\n): boolean {\n try {\n const key = crypto.createPublicKey({\n key: Buffer.concat([SPKI_PREFIX, rawKey(publicKeyB64, \"Public Key\")]),\n format: \"der\",\n type: \"spki\",\n });\n const sig = Buffer.from(sigB64url, \"base64url\");\n return sig.length === 64 && crypto.verify(null, Buffer.from(`${domain}.${payload}`), key, sig);\n } catch {\n return false;\n }\n}\n","/**\n * Öffentlicher Ed25519-Schlüssel des Cuonztech-Lizenzdienstes (32 Byte, base64).\n * Der zugehörige private Seed liegt nur serverseitig (config.php) und im KeePass.\n */\nexport const LICENSE_PUBLIC_KEY = \"q20XyLRZGURVvMLR6q0SCIc6poLhW73KE5Ds94Yq1FQ=\";\n"],"mappings":";AAAA,OAAO,YAAY;;;ACIZ,IAAM,qBAAqB;;;ADWlC,IAAM,eAAe,OAAO,KAAK,oCAAoC,KAAK;AAC1E,IAAM,cAAc,OAAO,KAAK,4BAA4B,KAAK;AAEjE,SAAS,OAAO,KAAa,MAAsB;AACjD,QAAM,MAAM,OAAO,KAAK,KAAK,QAAQ;AACrC,MAAI,IAAI,WAAW,GAAI,OAAM,IAAI,MAAM,GAAG,IAAI,yCAAyC,IAAI,MAAM,EAAE;AACnG,SAAO;AACT;AAoBO,SAAS,mBAA2B;AACzC,SAAO,QAAQ,IAAI,2BAA2B;AAChD;AAEO,SAAS,cACd,QACA,SACA,WACA,eAAuB,iBAAiB,GAC/B;AACT,MAAI;AACF,UAAM,MAAM,OAAO,gBAAgB;AAAA,MACjC,KAAK,OAAO,OAAO,CAAC,aAAa,OAAO,cAAc,YAAY,CAAC,CAAC;AAAA,MACpE,QAAQ;AAAA,MACR,MAAM;AAAA,IACR,CAAC;AACD,UAAM,MAAM,OAAO,KAAK,WAAW,WAAW;AAC9C,WAAO,IAAI,WAAW,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,GAAG,MAAM,IAAI,OAAO,EAAE,GAAG,KAAK,GAAG;AAAA,EAC/F,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}