@rajadeveloper12/headerguard 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.
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { scanCommand } from '../src/scan.js';
4
+ import { ciCommand } from '../src/ci.js';
5
+
6
+ program
7
+ .name('headerguard')
8
+ .description('HTTP Security Header Analyzer & Fixer')
9
+ .version('1.0.0');
10
+
11
+ program
12
+ .command('scan <url>')
13
+ .description('Scan a URL for security header issues')
14
+ .option('-s, --stack <stack>', 'Your backend stack: fastapi|express|django|nginx|caddy')
15
+ .option('-j, --json', 'Output raw JSON')
16
+ .option('--fail-below <score>', 'Exit with code 1 if score below threshold (for CI)')
17
+ .action(scanCommand);
18
+
19
+ program
20
+ .command('ci <url>')
21
+ .description('CI mode: exit 1 if score below --fail-below threshold')
22
+ .option('--fail-below <score>', 'Minimum passing score', '70')
23
+ .option('-s, --stack <stack>', 'Backend stack')
24
+ .action(ciCommand);
25
+
26
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@rajadeveloper12/headerguard",
3
+ "version": "1.0.0",
4
+ "description": "HTTP Security Header Analyzer & Fixer CLI",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "headerguard": "./bin/headerguard.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"No tests yet\""
12
+ },
13
+ "keywords": ["security", "headers", "cors", "csp", "hsts", "devtools"],
14
+ "author": "ClearFix.co",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "chalk": "^5.3.0",
18
+ "node-fetch": "^3.3.2",
19
+ "ora": "^8.0.1",
20
+ "commander": "^12.1.0"
21
+ }
22
+ }
package/src/ci.js ADDED
@@ -0,0 +1,54 @@
1
+ import chalk from 'chalk';
2
+ import fetch from 'node-fetch';
3
+
4
+ const API_URL = process.env.HEADERGUARD_API || 'https://api.headerguard.dev';
5
+
6
+ export async function ciCommand(url, options) {
7
+ if (!url.startsWith('http')) url = 'https://' + url;
8
+ const threshold = parseInt(options.failBelow || '70');
9
+
10
+ console.log(`::group::HeaderGuard Security Scan`);
11
+ console.log(`Scanning: ${url}`);
12
+ console.log(`Threshold: ${threshold}`);
13
+
14
+ try {
15
+ const res = await fetch(`${API_URL}/scan`, {
16
+ method: 'POST',
17
+ headers: { 'Content-Type': 'application/json' },
18
+ body: JSON.stringify({ url, stack: options.stack || null }),
19
+ });
20
+
21
+ const data = await res.json();
22
+
23
+ console.log(`Score: ${data.score}/100 (Grade: ${data.grade})`);
24
+ console.log(`Summary: ${data.summary}`);
25
+ console.log('');
26
+
27
+ // GitHub Actions annotations
28
+ for (const [key, result] of Object.entries(data.headers)) {
29
+ if (result.status === 'missing') {
30
+ console.log(`::error::Missing security header: ${key.toUpperCase()} - ${result.issue}`);
31
+ } else if (result.status === 'warn') {
32
+ console.log(`::warning::Security header warning: ${key.toUpperCase()} - ${result.issue}`);
33
+ }
34
+ }
35
+
36
+ console.log(`::endgroup::`);
37
+
38
+ // Set output for GitHub Actions
39
+ console.log(`::set-output name=score::${data.score}`);
40
+ console.log(`::set-output name=grade::${data.grade}`);
41
+
42
+ if (data.score < threshold) {
43
+ console.log(`::error::HeaderGuard: Score ${data.score} below threshold ${threshold}. Deploy blocked.`);
44
+ process.exit(1);
45
+ }
46
+
47
+ console.log(`HeaderGuard: PASSED (${data.score}/${threshold} threshold)`);
48
+ process.exit(0);
49
+
50
+ } catch (err) {
51
+ console.log(`::error::HeaderGuard scan failed: ${err.message}`);
52
+ process.exit(1);
53
+ }
54
+ }
package/src/scan.js ADDED
@@ -0,0 +1,122 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import fetch from 'node-fetch';
4
+
5
+ const API_URL = process.env.HEADERGUARD_API || 'https://headerguard-api-production.up.railway.app';
6
+
7
+ const HEADER_LABELS = {
8
+ cors: 'CORS (Access-Control-Allow-Origin)',
9
+ csp: 'Content-Security-Policy',
10
+ hsts: 'Strict-Transport-Security',
11
+ x_frame_options: 'X-Frame-Options',
12
+ x_content_type: 'X-Content-Type-Options',
13
+ referrer_policy: 'Referrer-Policy',
14
+ permissions_policy: 'Permissions-Policy',
15
+ };
16
+
17
+ function gradeColor(grade) {
18
+ if (grade.startsWith('A')) return chalk.greenBright(grade);
19
+ if (grade === 'B') return chalk.green(grade);
20
+ if (grade === 'C') return chalk.yellow(grade);
21
+ if (grade === 'D') return chalk.red(grade);
22
+ return chalk.bgRed.white(grade);
23
+ }
24
+
25
+ function statusIcon(status) {
26
+ if (status === 'pass') return chalk.green('✓');
27
+ if (status === 'warn') return chalk.yellow('⚠');
28
+ if (status === 'missing') return chalk.red('✗');
29
+ return chalk.red('✗');
30
+ }
31
+
32
+ function scoreBar(score, max) {
33
+ const pct = Math.round((score / max) * 10);
34
+ const filled = '█'.repeat(pct);
35
+ const empty = '░'.repeat(10 - pct);
36
+ const color = pct >= 8 ? chalk.green : pct >= 5 ? chalk.yellow : chalk.red;
37
+ return color(filled + empty) + chalk.dim(` ${score}/${max}`);
38
+ }
39
+
40
+ export async function scanCommand(url, options) {
41
+ if (!url.startsWith('http')) url = 'https://' + url;
42
+
43
+ const spinner = ora(`Scanning ${chalk.cyan(url)}...`).start();
44
+
45
+ try {
46
+ const res = await fetch(`${API_URL}/scan`, {
47
+ method: 'POST',
48
+ headers: { 'Content-Type': 'application/json' },
49
+ body: JSON.stringify({ url, stack: options.stack || null }),
50
+ });
51
+
52
+ if (!res.ok) {
53
+ const err = await res.json();
54
+ spinner.fail(chalk.red(`Scan failed: ${err.detail || res.statusText}`));
55
+ process.exit(1);
56
+ }
57
+
58
+ const data = await res.json();
59
+ spinner.stop();
60
+
61
+ if (options.json) {
62
+ console.log(JSON.stringify(data, null, 2));
63
+ return;
64
+ }
65
+
66
+ // Header
67
+ console.log('');
68
+ console.log(chalk.bold(' HeaderGuard Security Report'));
69
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
70
+ console.log(` ${chalk.dim('URL')} ${chalk.cyan(data.url)}`);
71
+ console.log(` ${chalk.dim('Score')} ${scoreBar(data.score, 100)}`);
72
+ console.log(` ${chalk.dim('Grade')} ${gradeColor(data.grade)}`);
73
+ console.log(` ${chalk.dim('Summary')} ${data.summary}`);
74
+ console.log('');
75
+
76
+ // Headers breakdown
77
+ console.log(chalk.bold(' Header Analysis'));
78
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
79
+
80
+ for (const [key, result] of Object.entries(data.headers)) {
81
+ const label = HEADER_LABELS[key] || key;
82
+ const icon = statusIcon(result.status);
83
+ const bar = scoreBar(result.score, result.max);
84
+
85
+ console.log(` ${icon} ${chalk.white(label.padEnd(35))} ${bar}`);
86
+ if (result.issue) {
87
+ console.log(` ${chalk.dim(result.issue)}`);
88
+ }
89
+ }
90
+
91
+ // Fixes
92
+ const fixKeys = Object.keys(data.fixes);
93
+ if (fixKeys.length > 0) {
94
+ console.log('');
95
+ console.log(chalk.bold(' Recommended Fixes'));
96
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
97
+
98
+ for (const key of fixKeys) {
99
+ const fix = data.fixes[key];
100
+ const label = HEADER_LABELS[key] || key;
101
+ console.log('');
102
+ console.log(` ${chalk.yellow('→')} ${chalk.bold(label)} ${chalk.dim(`(${fix.stack})`)}`);
103
+ const lines = fix.snippet.split('\n');
104
+ lines.forEach(line => console.log(` ${chalk.dim(line)}`));
105
+ }
106
+ }
107
+
108
+ console.log('');
109
+ console.log(chalk.dim(' Docs: https://headerguard.dev/docs'));
110
+ console.log('');
111
+
112
+ // CI fail-below
113
+ if (options.failBelow && data.score < parseInt(options.failBelow)) {
114
+ console.log(chalk.red(` ✗ Score ${data.score} is below threshold ${options.failBelow}. Failing.`));
115
+ process.exit(1);
116
+ }
117
+
118
+ } catch (err) {
119
+ spinner.fail(chalk.red(`Error: ${err.message}`));
120
+ process.exit(1);
121
+ }
122
+ }