darklens 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +116 -2
- package/bin/index.js +146 -0
- package/package.json +48 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DarkLens
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# DarkLens CLI 🔍
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Automated compliance scanning for Dark Patterns & Deceptive Design in your terminal and CI/CD pipelines.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/darklens)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
DarkLens CLI detects illegal deceptive UX patterns, confirmshaming, hidden subscription renewals, interface interference, and compliance risks across **GDPR**, **FTC Act Sec. 5**, **EU Digital Services Act (DSA)**, and **CCPA/CPRA**.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⚡ Quick Start
|
|
13
|
+
|
|
14
|
+
Run instantly without installation using `npx`:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx darklens https://staging.myapp.com --key YOUR_DARKLENS_API_KEY
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or install globally:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g darklens
|
|
24
|
+
darklens https://staging.myapp.com --key YOUR_DARKLENS_API_KEY
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🔑 Authentication
|
|
30
|
+
|
|
31
|
+
You can pass your API key directly via the `--key` flag or export it into your shell environment:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
export DARKLENS_API_KEY="your_api_key_here"
|
|
35
|
+
darklens https://staging.myapp.com
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> Get your API key in the [DarkLens Dashboard](https://darklens.dev/dashboard).
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🛠️ CLI Options
|
|
43
|
+
|
|
44
|
+
| Option | Flag | Description | Default |
|
|
45
|
+
| :--- | :--- | :--- | :--- |
|
|
46
|
+
| **Target URL** | `<url>` | URL of the application to scan | *Required* |
|
|
47
|
+
| **API Key** | `-k, --key` | Your DarkLens API key | `$DARKLENS_API_KEY` |
|
|
48
|
+
| **Threshold** | `-t, --threshold` | Minimum passing score (0-100) | `70` |
|
|
49
|
+
| **Fail Criteria**| `-f, --fail-on` | When to exit with error: `score`, `critical`, or `any` | `score` |
|
|
50
|
+
| **JSON Output** | `-j, --json` | Output raw machine-readable JSON | `false` |
|
|
51
|
+
| **Help** | `-h, --help` | Display help screen | — |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🚀 CI/CD Integration Examples
|
|
56
|
+
|
|
57
|
+
### GitHub Actions
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
name: Dark Pattern Compliance Audit
|
|
61
|
+
|
|
62
|
+
on: [pull_request]
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
compliance_audit:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v4
|
|
69
|
+
- name: Run DarkLens CLI
|
|
70
|
+
run: npx darklens https://staging.myapp.com --threshold 75
|
|
71
|
+
env:
|
|
72
|
+
DARKLENS_API_KEY: ${{ secrets.DARKLENS_API_KEY }}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
*(You can also use our native [DarkLens GitHub Action](https://github.com/marketplace/actions/darklens-compliance-scan).)*
|
|
76
|
+
|
|
77
|
+
### GitLab CI
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
darklens_scan:
|
|
81
|
+
stage: test
|
|
82
|
+
image: node:20
|
|
83
|
+
script:
|
|
84
|
+
- npx darklens "$CI_ENVIRONMENT_URL" --threshold 75
|
|
85
|
+
rules:
|
|
86
|
+
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 📋 Example Output
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
🔍 DarkLens Compliance Scanner v1.0.0
|
|
95
|
+
|
|
96
|
+
──────────────────────────────────────────────────
|
|
97
|
+
Target: https://staging.myapp.com
|
|
98
|
+
Score: 88 / 100 PASS
|
|
99
|
+
Threshold: 70 / 100 (Criterion: fail on score)
|
|
100
|
+
Breakdown: 0 Critical, 1 High, 0 Medium, 2 Low
|
|
101
|
+
──────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
Detected Findings (3):
|
|
104
|
+
|
|
105
|
+
[1] HIGH Forced Continuity
|
|
106
|
+
Category: Subscription Deception
|
|
107
|
+
Regulation: FTC Act Sec. 5 | EU DSA Art. 25
|
|
108
|
+
Details: Pre-checked recurring auto-renewal box detected on checkout form.
|
|
109
|
+
|
|
110
|
+
✔ PASSED: Build criteria satisfied.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 📄 License
|
|
116
|
+
|
|
117
|
+
MIT © [DarkLens](https://darklens.dev)
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const fetch = require('node-fetch');
|
|
6
|
+
const ora = require('ora');
|
|
7
|
+
const packageJson = require('../package.json');
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name('darklens')
|
|
11
|
+
.version(packageJson.version)
|
|
12
|
+
.description('DarkLens CLI: Enterprise compliance scanning for Dark Patterns & deceptive UI.')
|
|
13
|
+
.argument('<url>', 'The target URL to audit (e.g. https://staging.myapp.com)')
|
|
14
|
+
.option('-k, --key <key>', 'DarkLens API Key (or set DARKLENS_API_KEY environment variable)')
|
|
15
|
+
.option('-t, --threshold <score>', 'Minimum passing compliance score (0-100)', '70')
|
|
16
|
+
.option('-f, --fail-on <criterion>', 'Failure condition: score, critical, or any', 'score')
|
|
17
|
+
.option('-j, --json', 'Output raw scan results in JSON format')
|
|
18
|
+
.action(async (url, options) => {
|
|
19
|
+
const apiKey = options.key || process.env.DARKLENS_API_KEY;
|
|
20
|
+
|
|
21
|
+
if (!apiKey) {
|
|
22
|
+
if (options.json) {
|
|
23
|
+
console.error(JSON.stringify({ error: 'Missing API key. Provide via --key or DARKLENS_API_KEY env var.' }));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.red('\n✖ Error: Missing DarkLens API key.'));
|
|
26
|
+
console.log(chalk.dim('Provide your key via:'));
|
|
27
|
+
console.log(` ${chalk.cyan('darklens <url> --key <your-api-key>')}`);
|
|
28
|
+
console.log(chalk.dim('Or set the environment variable:'));
|
|
29
|
+
console.log(` ${chalk.cyan('export DARKLENS_API_KEY="your-api-key"')}`);
|
|
30
|
+
console.log(chalk.dim('\nGet your API key at: https://darklens.dev/dashboard\n'));
|
|
31
|
+
}
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const threshold = parseInt(options.threshold, 10) || 70;
|
|
36
|
+
const failOn = options.failOn || 'score';
|
|
37
|
+
|
|
38
|
+
let spinner = null;
|
|
39
|
+
if (!options.json) {
|
|
40
|
+
console.log(chalk.bold.hex('#8B5CF6')('\n🔍 DarkLens Compliance Scanner') + chalk.dim(` v${packageJson.version}\n`));
|
|
41
|
+
spinner = ora(`Scanning ${chalk.cyan(url)} for dark patterns...`).start();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch('https://darklens.dev/api/ci/scan', {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
49
|
+
'Content-Type': 'application/json',
|
|
50
|
+
'User-Agent': `DarkLens-CLI/${packageJson.version}`,
|
|
51
|
+
},
|
|
52
|
+
body: JSON.stringify({
|
|
53
|
+
url,
|
|
54
|
+
threshold,
|
|
55
|
+
failOn,
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const data = await res.json();
|
|
60
|
+
|
|
61
|
+
// Check for real API-level errors (401 Unauthorized, 403 Forbidden, 429 Rate Limit, 500 Server Error)
|
|
62
|
+
if (!res.ok && res.status !== 422) {
|
|
63
|
+
if (spinner) spinner.fail(chalk.red(`API Error (${res.status}): ${data.message || data.error || res.statusText}`));
|
|
64
|
+
if (options.json) console.error(JSON.stringify(data));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (spinner) spinner.stop();
|
|
69
|
+
|
|
70
|
+
// Raw JSON output mode
|
|
71
|
+
if (options.json) {
|
|
72
|
+
console.log(JSON.stringify(data, null, 2));
|
|
73
|
+
process.exit(data.pass ? 0 : 1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Pretty CLI output mode
|
|
77
|
+
const scoreColor = data.score >= threshold ? chalk.green.bold : chalk.red.bold;
|
|
78
|
+
const passBadge = data.pass
|
|
79
|
+
? chalk.bgGreen.black(' PASS ')
|
|
80
|
+
: chalk.bgRed.white(' FAIL ');
|
|
81
|
+
|
|
82
|
+
console.log('──────────────────────────────────────────────────');
|
|
83
|
+
console.log(` Target: ${chalk.bold(url)}`);
|
|
84
|
+
console.log(` Score: ${scoreColor(data.score)} / 100 ${passBadge}`);
|
|
85
|
+
console.log(` Threshold: ${threshold} / 100 (Criterion: fail on ${failOn})`);
|
|
86
|
+
if (data.counts) {
|
|
87
|
+
console.log(` Breakdown: ${chalk.red(`${data.counts.critical || 0} Critical`)}, ${chalk.hex('#f97316')(`${data.counts.high || 0} High`)}, ${chalk.yellow(`${data.counts.medium || 0} Medium`)}, ${chalk.cyan(`${data.counts.low || 0} Low`)}`);
|
|
88
|
+
}
|
|
89
|
+
console.log('──────────────────────────────────────────────────\n');
|
|
90
|
+
|
|
91
|
+
if (data.findings && data.findings.length > 0) {
|
|
92
|
+
console.log(chalk.bold(`Detected Findings (${data.findings.length}):`));
|
|
93
|
+
data.findings.forEach((finding, idx) => {
|
|
94
|
+
let sevBadge;
|
|
95
|
+
switch ((finding.severity || '').toLowerCase()) {
|
|
96
|
+
case 'critical':
|
|
97
|
+
sevBadge = chalk.bgRed.white.bold(' CRITICAL ');
|
|
98
|
+
break;
|
|
99
|
+
case 'high':
|
|
100
|
+
sevBadge = chalk.red.bold(' HIGH ');
|
|
101
|
+
break;
|
|
102
|
+
case 'medium':
|
|
103
|
+
sevBadge = chalk.yellow.bold(' MEDIUM ');
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
sevBadge = chalk.cyan.bold(' LOW ');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`\n [${idx + 1}] ${sevBadge} ${chalk.bold(finding.name)}`);
|
|
110
|
+
if (finding.category) {
|
|
111
|
+
console.log(` ${chalk.dim('Category:')} ${finding.category}`);
|
|
112
|
+
}
|
|
113
|
+
if (finding.regulation) {
|
|
114
|
+
console.log(` ${chalk.dim('Regulation:')} ${chalk.magenta(finding.regulation)}`);
|
|
115
|
+
}
|
|
116
|
+
if (finding.description) {
|
|
117
|
+
console.log(` ${chalk.dim('Details:')} ${finding.description}`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
console.log('');
|
|
121
|
+
} else {
|
|
122
|
+
console.log(chalk.green('✔ No dark patterns detected. Perfect compliance score!\n'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (data.pass) {
|
|
126
|
+
console.log(chalk.green.bold('✔ PASSED: Build criteria satisfied.\n'));
|
|
127
|
+
process.exit(0);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(chalk.red.bold(`✖ FAILED: ${data.failReason || 'Compliance score dropped below threshold.'}`));
|
|
130
|
+
console.log(chalk.dim(`View live audit dashboard & remediation fixes at: https://darklens.dev/dashboard\n`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
} catch (err) {
|
|
135
|
+
if (spinner) spinner.fail(chalk.red('Failed to connect to DarkLens API.'));
|
|
136
|
+
if (options.json) {
|
|
137
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
138
|
+
} else {
|
|
139
|
+
console.error(chalk.red(err.message));
|
|
140
|
+
console.log(chalk.dim('Ensure your device has internet access and https://darklens.dev is reachable.\n'));
|
|
141
|
+
}
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "darklens",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DarkLens CLI: Enterprise compliance scanning for Dark Patterns and deceptive UI (GDPR, FTC, CCPA, EU DSA).",
|
|
5
|
+
"main": "bin/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"darklens": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node bin/index.js",
|
|
16
|
+
"test": "node bin/index.js --help"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"dark-patterns",
|
|
20
|
+
"compliance",
|
|
21
|
+
"gdpr",
|
|
22
|
+
"ftc",
|
|
23
|
+
"ccpa",
|
|
24
|
+
"eu-dsa",
|
|
25
|
+
"auditing",
|
|
26
|
+
"devsecops",
|
|
27
|
+
"ci-cd",
|
|
28
|
+
"accessibility",
|
|
29
|
+
"darklens"
|
|
30
|
+
],
|
|
31
|
+
"author": "DarkLens <support@darklens.dev> (https://darklens.dev)",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/bhanu403/darklens-cli.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://darklens.dev",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://darklens.dev/contact"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"chalk": "^4.1.2",
|
|
43
|
+
"commander": "^11.0.0",
|
|
44
|
+
"node-fetch": "^2.7.0",
|
|
45
|
+
"ora": "^5.4.1"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=16.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|