@reliabilityworks/vibesec 0.1.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/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function runCli(argv: string[]): Promise<void>;
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAiMA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyE1D"}
package/dist/cli.js ADDED
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.runCli = runCli;
8
+ const node_fs_1 = require("node:fs");
9
+ const promises_1 = __importDefault(require("node:fs/promises"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const commander_1 = require("commander");
12
+ const analyzer_javascript_1 = require("@reliabilityworks/analyzer-javascript");
13
+ const core_1 = require("@reliabilityworks/core");
14
+ function formatCliOutput(result) {
15
+ const lines = [];
16
+ if (result.frameworks.length > 0) {
17
+ const frameworkList = result.frameworks.map((f) => f.id).join(', ');
18
+ lines.push(`Frameworks: ${frameworkList}`);
19
+ lines.push('');
20
+ }
21
+ for (const finding of result.findings) {
22
+ lines.push(`${finding.severity.toUpperCase()} ${finding.ruleId} ${finding.location.path}:${finding.location.startLine}`);
23
+ lines.push(` ${finding.message}`);
24
+ lines.push('');
25
+ }
26
+ lines.push(`Scan complete: ${result.findings.length} issue(s) found`);
27
+ return lines.join('\n') + '\n';
28
+ }
29
+ function parseFrameworkIds(input) {
30
+ if (!input || input === 'auto')
31
+ return [];
32
+ return input
33
+ .split(',')
34
+ .map((s) => s.trim())
35
+ .filter(Boolean)
36
+ .map((s) => s);
37
+ }
38
+ function selectFrameworks(detected, requested) {
39
+ if (requested === 'auto')
40
+ return detected;
41
+ const requestedIds = parseFrameworkIds(requested);
42
+ const byId = new Map(detected.map((d) => [d.id, d]));
43
+ const selected = [];
44
+ for (const id of requestedIds) {
45
+ const hit = byId.get(id);
46
+ if (hit) {
47
+ selected.push(hit);
48
+ }
49
+ else {
50
+ selected.push({ id, confidence: 'low', evidence: ['user: --framework'] });
51
+ }
52
+ }
53
+ return selected;
54
+ }
55
+ function toPosixPath(p) {
56
+ return p.split(node_path_1.default.sep).join('/');
57
+ }
58
+ function joinPrefixedGlob(prefix, glob) {
59
+ const normalizedPrefix = prefix.replace(/\/+$/, '');
60
+ if (!normalizedPrefix || normalizedPrefix === '.')
61
+ return glob;
62
+ const normalizedGlob = glob.replace(/^\/+/, '');
63
+ return `${normalizedPrefix}/${normalizedGlob}`;
64
+ }
65
+ function scopeRulesToPrefix(rules, prefix) {
66
+ const normalizedPrefix = toPosixPath(prefix);
67
+ if (!normalizedPrefix || normalizedPrefix === '.')
68
+ return rules;
69
+ return rules.map((rule) => {
70
+ if (rule.matcher.type === 'regex') {
71
+ return {
72
+ ...rule,
73
+ matcher: {
74
+ ...rule.matcher,
75
+ fileGlobs: rule.matcher.fileGlobs.map((glob) => joinPrefixedGlob(normalizedPrefix, glob)),
76
+ },
77
+ };
78
+ }
79
+ return {
80
+ ...rule,
81
+ matcher: {
82
+ ...rule.matcher,
83
+ paths: rule.matcher.paths.map((p) => joinPrefixedGlob(normalizedPrefix, p)),
84
+ },
85
+ };
86
+ });
87
+ }
88
+ async function loadWorkspaceScopedRules(workspaceRoot) {
89
+ const projectRoots = await (0, core_1.listWorkspaceProjectRoots)(workspaceRoot);
90
+ const rules = [];
91
+ for (const projectRoot of projectRoots) {
92
+ const frameworks = await (0, core_1.detectFrameworks)(projectRoot);
93
+ if (frameworks.length === 0)
94
+ continue;
95
+ const projectRules = await loadRulesForFrameworks(frameworks);
96
+ const projectPrefix = node_path_1.default.relative(workspaceRoot, projectRoot);
97
+ rules.push(...scopeRulesToPrefix(projectRules, projectPrefix));
98
+ }
99
+ return rules;
100
+ }
101
+ async function loadRulesetRules(packageName) {
102
+ const pkgJsonPath = require.resolve(`${packageName}/package.json`);
103
+ const rulesPath = node_path_1.default.join(node_path_1.default.dirname(pkgJsonPath), 'rules.json');
104
+ const raw = await promises_1.default.readFile(rulesPath, 'utf8');
105
+ const parsed = JSON.parse(raw);
106
+ if (!Array.isArray(parsed)) {
107
+ throw new Error(`${packageName} rules.json must be an array`);
108
+ }
109
+ return parsed;
110
+ }
111
+ async function loadRulesForFrameworks(frameworks) {
112
+ const ids = new Set(frameworks.map((f) => f.id));
113
+ const rules = [];
114
+ if (ids.has('nextjs')) {
115
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-nextjs')));
116
+ }
117
+ if (ids.has('react-native') || ids.has('expo')) {
118
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-react-native')));
119
+ }
120
+ if (ids.has('express')) {
121
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-express')));
122
+ }
123
+ if (ids.has('sveltekit')) {
124
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-sveltekit')));
125
+ }
126
+ return rules;
127
+ }
128
+ function readCliVersion() {
129
+ const packageJsonPath = node_path_1.default.join(__dirname, '..', 'package.json');
130
+ let raw;
131
+ try {
132
+ raw = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf8');
133
+ }
134
+ catch {
135
+ return '0.0.0';
136
+ }
137
+ const parsed = JSON.parse(raw);
138
+ if (parsed && typeof parsed === 'object' && 'version' in parsed) {
139
+ const version = parsed.version;
140
+ if (typeof version === 'string' && version.length > 0)
141
+ return version;
142
+ }
143
+ return '0.0.0';
144
+ }
145
+ async function runCli(argv) {
146
+ const program = new commander_1.Command();
147
+ program
148
+ .name('vibesec')
149
+ .description('Local security scanner for modern frameworks')
150
+ .version(readCliVersion());
151
+ program
152
+ .command('scan')
153
+ .argument('[path]', 'Path to scan', '.')
154
+ .option('-o, --output <format>', 'Output format: cli|json|sarif|html', 'cli')
155
+ .option('--out-file <path>', 'Write output to file')
156
+ .option('--fail-on <severity>', 'Fail on or above: low|medium|high|critical', 'high')
157
+ .option('--framework <name>', 'Framework: auto|nextjs|react-native|expo|express|sveltekit (comma-separated)', 'auto')
158
+ .option('--config <path>', 'Config file path (.vibesec.yaml by default)')
159
+ .option('--rules-dir <path>', 'Custom rules dir (.vibesec/rules by default)')
160
+ .action(async (scanPath, options) => {
161
+ const failOn = (0, core_1.severityFromString)(options.failOn);
162
+ const absoluteRoot = node_path_1.default.resolve(scanPath);
163
+ const detected = await (0, core_1.detectFrameworksInWorkspace)(absoluteRoot);
164
+ const frameworks = selectFrameworks(detected, options.framework);
165
+ const analyzerRules = (0, analyzer_javascript_1.getJavaScriptRules)();
166
+ const additionalRules = [
167
+ ...analyzerRules,
168
+ ...(options.framework === 'auto'
169
+ ? await loadWorkspaceScopedRules(absoluteRoot)
170
+ : await loadRulesForFrameworks(frameworks)),
171
+ ];
172
+ const result = await (0, core_1.scanProject)({
173
+ rootDir: absoluteRoot,
174
+ frameworks,
175
+ additionalRules,
176
+ configPath: options.config,
177
+ customRulesDir: options.rulesDir,
178
+ });
179
+ if (options.output === 'json') {
180
+ const json = JSON.stringify(result, null, 2);
181
+ if (options.outFile) {
182
+ await promises_1.default.writeFile(options.outFile, json, 'utf8');
183
+ }
184
+ else {
185
+ process.stdout.write(json + '\n');
186
+ }
187
+ }
188
+ else if (options.output === 'sarif') {
189
+ const sarif = JSON.stringify((0, core_1.toSarif)(result), null, 2);
190
+ if (options.outFile) {
191
+ await promises_1.default.writeFile(options.outFile, sarif, 'utf8');
192
+ }
193
+ else {
194
+ process.stdout.write(sarif + '\n');
195
+ }
196
+ }
197
+ else if (options.output === 'html') {
198
+ const html = (0, core_1.toHtml)(result);
199
+ if (options.outFile) {
200
+ await promises_1.default.writeFile(options.outFile, html, 'utf8');
201
+ }
202
+ else {
203
+ process.stdout.write(html + '\n');
204
+ }
205
+ }
206
+ else {
207
+ process.stdout.write(formatCliOutput(result));
208
+ }
209
+ const shouldFail = result.findings.some((finding) => finding.severityRank <= failOn.rank);
210
+ process.exitCode = shouldFail ? 1 : 0;
211
+ });
212
+ await program.parseAsync(argv);
213
+ }
214
+ if (require.main === module) {
215
+ runCli(process.argv).catch((error) => {
216
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
217
+ process.stderr.write(message + '\n');
218
+ process.exitCode = 2;
219
+ });
220
+ }
221
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;;;;AAiMA,wBAyEC;AAxQD,qCAAsC;AACtC,gEAAiC;AACjC,0DAA4B;AAE5B,yCAAmC;AAEnC,+EAA0E;AAC1E,iDAa+B;AAW/B,SAAS,eAAe,CAAC,MAAkB;IACzC,MAAM,KAAK,GAAa,EAAE,CAAA;IAE1B,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnE,KAAK,CAAC,IAAI,CAAC,eAAe,aAAa,EAAE,CAAC,CAAA;QAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChB,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CACR,GAAG,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,EAAE,CAC7G,CAAA;QACD,KAAK,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,QAAQ,CAAC,MAAM,iBAAiB,CAAC,CAAA;IAErE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;AAChC,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,EAAE,CAAA;IAEzC,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAgB,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,gBAAgB,CAAC,QAA8B,EAAE,SAAiB;IACzE,IAAI,SAAS,KAAK,MAAM;QAAE,OAAO,QAAQ,CAAA;IAEzC,MAAM,YAAY,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAA;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAU,CAAC,CAAC,CAAA;IAE7D,MAAM,QAAQ,GAAyB,EAAE,CAAA;IACzC,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACxB,IAAI,GAAG,EAAE,CAAC;YACR,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACpB,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAA;QAC3E,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,KAAK,CAAC,mBAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACpC,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc,EAAE,IAAY;IACpD,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACnD,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,KAAK,GAAG;QAAE,OAAO,IAAI,CAAA;IAE9D,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC/C,OAAO,GAAG,gBAAgB,IAAI,cAAc,EAAE,CAAA;AAChD,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa,EAAE,MAAc;IACvD,MAAM,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5C,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IAE/D,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,OAAO;gBACL,GAAG,IAAI;gBACP,OAAO,EAAE;oBACP,GAAG,IAAI,CAAC,OAAO;oBACf,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;iBAC1F;aACF,CAAA;QACH,CAAC;QAED,OAAO;YACL,GAAG,IAAI;YACP,OAAO,EAAE;gBACP,GAAG,IAAI,CAAC,OAAO;gBACf,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;aAC5E;SACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,wBAAwB,CAAC,aAAqB;IAC3D,MAAM,YAAY,GAAG,MAAM,IAAA,gCAAyB,EAAC,aAAa,CAAC,CAAA;IACnE,MAAM,KAAK,GAAW,EAAE,CAAA;IAExB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;QACvC,MAAM,UAAU,GAAG,MAAM,IAAA,uBAAgB,EAAC,WAAW,CAAC,CAAA;QACtD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAErC,MAAM,YAAY,GAAG,MAAM,sBAAsB,CAAC,UAAU,CAAC,CAAA;QAC7D,MAAM,aAAa,GAAG,mBAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,CAAA;QAC/D,KAAK,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAA;IAChE,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,WAAmB;IACjD,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,WAAW,eAAe,CAAC,CAAA;IAClE,MAAM,SAAS,GAAG,mBAAI,CAAC,IAAI,CAAC,mBAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC,CAAA;IACpE,MAAM,GAAG,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;IAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAA;IAEzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,GAAG,WAAW,8BAA8B,CAAC,CAAA;IAC/D,CAAC;IAED,OAAO,MAAgB,CAAA;AACzB,CAAC;AAED,KAAK,UAAU,sBAAsB,CAAC,UAAgC;IACpE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAChD,MAAM,KAAK,GAAW,EAAE,CAAA;IAExB,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,gBAAgB,CAAC,kCAAkC,CAAC,CAAC,CAAC,CAAA;IAC7E,CAAC;IAED,IAAI,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/C,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,gBAAgB,CAAC,wCAAwC,CAAC,CAAC,CAAC,CAAA;IACnF,CAAC;IAED,IAAI,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,gBAAgB,CAAC,mCAAmC,CAAC,CAAC,CAAC,CAAA;IAC9E,CAAC;IAED,IAAI,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,gBAAgB,CAAC,qCAAqC,CAAC,CAAC,CAAC,CAAA;IAChF,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,cAAc;IACrB,MAAM,eAAe,GAAG,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,CAAA;IAElE,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACH,GAAG,GAAG,IAAA,sBAAY,EAAC,eAAe,EAAE,MAAM,CAAC,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAA;IAEzC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,SAAS,IAAI,MAAM,EAAE,CAAC;QAChE,MAAM,OAAO,GAAI,MAAgC,CAAC,OAAO,CAAA;QACzD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAA;IACvE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAEM,KAAK,UAAU,MAAM,CAAC,IAAc;IACzC,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAA;IAE7B,OAAO;SACJ,IAAI,CAAC,SAAS,CAAC;SACf,WAAW,CAAC,8CAA8C,CAAC;SAC3D,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IAE5B,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,CAAC;SACvC,MAAM,CAAC,uBAAuB,EAAE,oCAAoC,EAAE,KAAK,CAAC;SAC5E,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC;SACnD,MAAM,CAAC,sBAAsB,EAAE,4CAA4C,EAAE,MAAM,CAAC;SACpF,MAAM,CACL,oBAAoB,EACpB,8EAA8E,EAC9E,MAAM,CACP;SACA,MAAM,CAAC,iBAAiB,EAAE,6CAA6C,CAAC;SACxE,MAAM,CAAC,oBAAoB,EAAE,8CAA8C,CAAC;SAC5E,MAAM,CAAC,KAAK,EAAE,QAAgB,EAAE,OAA2B,EAAE,EAAE;QAC9D,MAAM,MAAM,GAAG,IAAA,yBAAkB,EAAC,OAAO,CAAC,MAAM,CAAC,CAAA;QAEjD,MAAM,YAAY,GAAG,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAC3C,MAAM,QAAQ,GAAG,MAAM,IAAA,kCAA2B,EAAC,YAAY,CAAC,CAAA;QAChE,MAAM,UAAU,GAAG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAChE,MAAM,aAAa,GAAG,IAAA,wCAAkB,GAAE,CAAA;QAC1C,MAAM,eAAe,GAAG;YACtB,GAAG,aAAa;YAChB,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,MAAM;gBAC9B,CAAC,CAAC,MAAM,wBAAwB,CAAC,YAAY,CAAC;gBAC9C,CAAC,CAAC,MAAM,sBAAsB,CAAC,UAAU,CAAC,CAAC;SAC9C,CAAA;QAED,MAAM,MAAM,GAAG,MAAM,IAAA,kBAAW,EAAC;YAC/B,OAAO,EAAE,YAAY;YACrB,UAAU;YACV,eAAe;YACf,UAAU,EAAE,OAAO,CAAC,MAAM;YAC1B,cAAc,EAAE,OAAO,CAAC,QAAQ;SACjC,CAAC,CAAA;QAEF,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;YAC5C,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,kBAAE,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;YACnD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,IAAA,cAAO,EAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;YACtD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,kBAAE,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;YACpD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;YACpC,CAAC;QACH,CAAC;aAAM,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,IAAA,aAAM,EAAC,MAAM,CAAC,CAAA;YAC3B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,kBAAE,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;YACnD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAA;QAC/C,CAAC;QAED,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,CAAA;QACzF,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEJ,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;AAChC,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QACvF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;QACpC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACtB,CAAC,CAAC,CAAA;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@reliabilityworks/vibesec",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "vibesec": "dist/cli.js"
6
+ },
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json",
9
+ "lint": "eslint .",
10
+ "test": "pnpm build && node --test test",
11
+ "typecheck": "tsc -p tsconfig.json --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@reliabilityworks/analyzer-javascript": "0.1.0",
15
+ "@reliabilityworks/core": "0.1.0",
16
+ "@reliabilityworks/ruleset-express": "0.1.0",
17
+ "@reliabilityworks/ruleset-nextjs": "0.1.0",
18
+ "@reliabilityworks/ruleset-react-native": "0.1.0",
19
+ "@reliabilityworks/ruleset-sveltekit": "0.1.0",
20
+ "commander": "^12.0.0"
21
+ }
22
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'node:fs'
4
+ import fs from 'node:fs/promises'
5
+ import path from 'node:path'
6
+
7
+ import { Command } from 'commander'
8
+
9
+ import { getJavaScriptRules } from '@reliabilityworks/analyzer-javascript'
10
+ import {
11
+ detectFrameworks,
12
+ detectFrameworksInWorkspace,
13
+ listWorkspaceProjectRoots,
14
+ scanProject,
15
+ severityFromString,
16
+ toHtml,
17
+ toSarif,
18
+ type FrameworkDetection,
19
+ type FrameworkId,
20
+ type Rule,
21
+ type SeverityName,
22
+ type ScanResult,
23
+ } from '@reliabilityworks/core'
24
+
25
+ type ScanCommandOptions = {
26
+ output: string
27
+ outFile?: string
28
+ failOn: SeverityName
29
+ framework: string
30
+ config?: string
31
+ rulesDir?: string
32
+ }
33
+
34
+ function formatCliOutput(result: ScanResult): string {
35
+ const lines: string[] = []
36
+
37
+ if (result.frameworks.length > 0) {
38
+ const frameworkList = result.frameworks.map((f) => f.id).join(', ')
39
+ lines.push(`Frameworks: ${frameworkList}`)
40
+ lines.push('')
41
+ }
42
+
43
+ for (const finding of result.findings) {
44
+ lines.push(
45
+ `${finding.severity.toUpperCase()} ${finding.ruleId} ${finding.location.path}:${finding.location.startLine}`,
46
+ )
47
+ lines.push(` ${finding.message}`)
48
+ lines.push('')
49
+ }
50
+
51
+ lines.push(`Scan complete: ${result.findings.length} issue(s) found`)
52
+
53
+ return lines.join('\n') + '\n'
54
+ }
55
+
56
+ function parseFrameworkIds(input: string): FrameworkId[] {
57
+ if (!input || input === 'auto') return []
58
+
59
+ return input
60
+ .split(',')
61
+ .map((s) => s.trim())
62
+ .filter(Boolean)
63
+ .map((s) => s as FrameworkId)
64
+ }
65
+
66
+ function selectFrameworks(detected: FrameworkDetection[], requested: string): FrameworkDetection[] {
67
+ if (requested === 'auto') return detected
68
+
69
+ const requestedIds = parseFrameworkIds(requested)
70
+ const byId = new Map(detected.map((d) => [d.id, d] as const))
71
+
72
+ const selected: FrameworkDetection[] = []
73
+ for (const id of requestedIds) {
74
+ const hit = byId.get(id)
75
+ if (hit) {
76
+ selected.push(hit)
77
+ } else {
78
+ selected.push({ id, confidence: 'low', evidence: ['user: --framework'] })
79
+ }
80
+ }
81
+
82
+ return selected
83
+ }
84
+
85
+ function toPosixPath(p: string): string {
86
+ return p.split(path.sep).join('/')
87
+ }
88
+
89
+ function joinPrefixedGlob(prefix: string, glob: string): string {
90
+ const normalizedPrefix = prefix.replace(/\/+$/, '')
91
+ if (!normalizedPrefix || normalizedPrefix === '.') return glob
92
+
93
+ const normalizedGlob = glob.replace(/^\/+/, '')
94
+ return `${normalizedPrefix}/${normalizedGlob}`
95
+ }
96
+
97
+ function scopeRulesToPrefix(rules: Rule[], prefix: string): Rule[] {
98
+ const normalizedPrefix = toPosixPath(prefix)
99
+ if (!normalizedPrefix || normalizedPrefix === '.') return rules
100
+
101
+ return rules.map((rule) => {
102
+ if (rule.matcher.type === 'regex') {
103
+ return {
104
+ ...rule,
105
+ matcher: {
106
+ ...rule.matcher,
107
+ fileGlobs: rule.matcher.fileGlobs.map((glob) => joinPrefixedGlob(normalizedPrefix, glob)),
108
+ },
109
+ }
110
+ }
111
+
112
+ return {
113
+ ...rule,
114
+ matcher: {
115
+ ...rule.matcher,
116
+ paths: rule.matcher.paths.map((p) => joinPrefixedGlob(normalizedPrefix, p)),
117
+ },
118
+ }
119
+ })
120
+ }
121
+
122
+ async function loadWorkspaceScopedRules(workspaceRoot: string): Promise<Rule[]> {
123
+ const projectRoots = await listWorkspaceProjectRoots(workspaceRoot)
124
+ const rules: Rule[] = []
125
+
126
+ for (const projectRoot of projectRoots) {
127
+ const frameworks = await detectFrameworks(projectRoot)
128
+ if (frameworks.length === 0) continue
129
+
130
+ const projectRules = await loadRulesForFrameworks(frameworks)
131
+ const projectPrefix = path.relative(workspaceRoot, projectRoot)
132
+ rules.push(...scopeRulesToPrefix(projectRules, projectPrefix))
133
+ }
134
+
135
+ return rules
136
+ }
137
+
138
+ async function loadRulesetRules(packageName: string): Promise<Rule[]> {
139
+ const pkgJsonPath = require.resolve(`${packageName}/package.json`)
140
+ const rulesPath = path.join(path.dirname(pkgJsonPath), 'rules.json')
141
+ const raw = await fs.readFile(rulesPath, 'utf8')
142
+ const parsed = JSON.parse(raw) as unknown
143
+
144
+ if (!Array.isArray(parsed)) {
145
+ throw new Error(`${packageName} rules.json must be an array`)
146
+ }
147
+
148
+ return parsed as Rule[]
149
+ }
150
+
151
+ async function loadRulesForFrameworks(frameworks: FrameworkDetection[]): Promise<Rule[]> {
152
+ const ids = new Set(frameworks.map((f) => f.id))
153
+ const rules: Rule[] = []
154
+
155
+ if (ids.has('nextjs')) {
156
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-nextjs')))
157
+ }
158
+
159
+ if (ids.has('react-native') || ids.has('expo')) {
160
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-react-native')))
161
+ }
162
+
163
+ if (ids.has('express')) {
164
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-express')))
165
+ }
166
+
167
+ if (ids.has('sveltekit')) {
168
+ rules.push(...(await loadRulesetRules('@reliabilityworks/ruleset-sveltekit')))
169
+ }
170
+
171
+ return rules
172
+ }
173
+
174
+ function readCliVersion(): string {
175
+ const packageJsonPath = path.join(__dirname, '..', 'package.json')
176
+
177
+ let raw: string
178
+ try {
179
+ raw = readFileSync(packageJsonPath, 'utf8')
180
+ } catch {
181
+ return '0.0.0'
182
+ }
183
+
184
+ const parsed = JSON.parse(raw) as unknown
185
+
186
+ if (parsed && typeof parsed === 'object' && 'version' in parsed) {
187
+ const version = (parsed as { version?: unknown }).version
188
+ if (typeof version === 'string' && version.length > 0) return version
189
+ }
190
+
191
+ return '0.0.0'
192
+ }
193
+
194
+ export async function runCli(argv: string[]): Promise<void> {
195
+ const program = new Command()
196
+
197
+ program
198
+ .name('vibesec')
199
+ .description('Local security scanner for modern frameworks')
200
+ .version(readCliVersion())
201
+
202
+ program
203
+ .command('scan')
204
+ .argument('[path]', 'Path to scan', '.')
205
+ .option('-o, --output <format>', 'Output format: cli|json|sarif|html', 'cli')
206
+ .option('--out-file <path>', 'Write output to file')
207
+ .option('--fail-on <severity>', 'Fail on or above: low|medium|high|critical', 'high')
208
+ .option(
209
+ '--framework <name>',
210
+ 'Framework: auto|nextjs|react-native|expo|express|sveltekit (comma-separated)',
211
+ 'auto',
212
+ )
213
+ .option('--config <path>', 'Config file path (.vibesec.yaml by default)')
214
+ .option('--rules-dir <path>', 'Custom rules dir (.vibesec/rules by default)')
215
+ .action(async (scanPath: string, options: ScanCommandOptions) => {
216
+ const failOn = severityFromString(options.failOn)
217
+
218
+ const absoluteRoot = path.resolve(scanPath)
219
+ const detected = await detectFrameworksInWorkspace(absoluteRoot)
220
+ const frameworks = selectFrameworks(detected, options.framework)
221
+ const analyzerRules = getJavaScriptRules()
222
+ const additionalRules = [
223
+ ...analyzerRules,
224
+ ...(options.framework === 'auto'
225
+ ? await loadWorkspaceScopedRules(absoluteRoot)
226
+ : await loadRulesForFrameworks(frameworks)),
227
+ ]
228
+
229
+ const result = await scanProject({
230
+ rootDir: absoluteRoot,
231
+ frameworks,
232
+ additionalRules,
233
+ configPath: options.config,
234
+ customRulesDir: options.rulesDir,
235
+ })
236
+
237
+ if (options.output === 'json') {
238
+ const json = JSON.stringify(result, null, 2)
239
+ if (options.outFile) {
240
+ await fs.writeFile(options.outFile, json, 'utf8')
241
+ } else {
242
+ process.stdout.write(json + '\n')
243
+ }
244
+ } else if (options.output === 'sarif') {
245
+ const sarif = JSON.stringify(toSarif(result), null, 2)
246
+ if (options.outFile) {
247
+ await fs.writeFile(options.outFile, sarif, 'utf8')
248
+ } else {
249
+ process.stdout.write(sarif + '\n')
250
+ }
251
+ } else if (options.output === 'html') {
252
+ const html = toHtml(result)
253
+ if (options.outFile) {
254
+ await fs.writeFile(options.outFile, html, 'utf8')
255
+ } else {
256
+ process.stdout.write(html + '\n')
257
+ }
258
+ } else {
259
+ process.stdout.write(formatCliOutput(result))
260
+ }
261
+
262
+ const shouldFail = result.findings.some((finding) => finding.severityRank <= failOn.rank)
263
+ process.exitCode = shouldFail ? 1 : 0
264
+ })
265
+
266
+ await program.parseAsync(argv)
267
+ }
268
+
269
+ if (require.main === module) {
270
+ runCli(process.argv).catch((error: unknown) => {
271
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error)
272
+ process.stderr.write(message + '\n')
273
+ process.exitCode = 2
274
+ })
275
+ }
@@ -0,0 +1,7 @@
1
+ const assert = require('node:assert/strict')
2
+ const test = require('node:test')
3
+
4
+ test('cli exports runCli', async () => {
5
+ const cli = require('../dist/cli.js')
6
+ assert.equal(typeof cli.runCli, 'function')
7
+ })
@@ -0,0 +1,34 @@
1
+ const assert = require('node:assert/strict')
2
+ const { execFileSync } = require('node:child_process')
3
+ const path = require('node:path')
4
+ const test = require('node:test')
5
+
6
+ test('workspace scan scopes framework rules to project roots', async () => {
7
+ const fixtureRoot = path.join(__dirname, '..', '..', 'core', 'test', 'fixtures', 'monorepo')
8
+ const cliPath = path.join(__dirname, '..', 'dist', 'cli.js')
9
+
10
+ const output = execFileSync(
11
+ process.execPath,
12
+ [
13
+ '--enable-source-maps',
14
+ cliPath,
15
+ 'scan',
16
+ fixtureRoot,
17
+ '--output',
18
+ 'json',
19
+ '--framework',
20
+ 'auto',
21
+ '--fail-on',
22
+ 'critical',
23
+ ],
24
+ { encoding: 'utf8' },
25
+ )
26
+
27
+ const result = JSON.parse(output)
28
+ const nextPaths = result.findings
29
+ .filter((finding) => finding.ruleId === 'nextjs/production-browser-sourcemaps')
30
+ .map((finding) => finding.location.path)
31
+
32
+ assert.ok(nextPaths.includes('apps/web/next.config.js'))
33
+ assert.equal(nextPaths.includes('apps/api/next.config.js'), false)
34
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist"
6
+ },
7
+ "include": ["src/**/*.ts"]
8
+ }