@vibefoundry/vibe-shield 0.0.0-stage → 1.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +571 -3
  3. package/SECURITY.md +110 -0
  4. package/SKILL.md +124 -0
  5. package/dist/bin/vibe-shield.d.ts +59 -0
  6. package/dist/bin/vibe-shield.js +541 -0
  7. package/dist/src/index.d.ts +25 -0
  8. package/dist/src/index.js +108 -0
  9. package/dist/src/init/scaffold.d.ts +122 -0
  10. package/dist/src/init/scaffold.js +573 -0
  11. package/dist/src/mcp/server.d.ts +127 -0
  12. package/dist/src/mcp/server.js +479 -0
  13. package/dist/src/mcp-guard/skill-auditor.d.ts +105 -0
  14. package/dist/src/mcp-guard/skill-auditor.js +1129 -0
  15. package/dist/src/reporter/console-reporter.d.ts +58 -0
  16. package/dist/src/reporter/console-reporter.js +254 -0
  17. package/dist/src/reporter/formatters.d.ts +38 -0
  18. package/dist/src/reporter/formatters.js +262 -0
  19. package/dist/src/reporter/patch-prompt-writer.d.ts +35 -0
  20. package/dist/src/reporter/patch-prompt-writer.js +238 -0
  21. package/dist/src/scanner/context.d.ts +51 -0
  22. package/dist/src/scanner/context.js +568 -0
  23. package/dist/src/scanner/engine.d.ts +130 -0
  24. package/dist/src/scanner/engine.js +722 -0
  25. package/dist/src/scanner/gitignore.d.ts +49 -0
  26. package/dist/src/scanner/gitignore.js +195 -0
  27. package/dist/src/scanner/lexicon.d.ts +56 -0
  28. package/dist/src/scanner/lexicon.js +357 -0
  29. package/dist/src/scanner/rules/ai-vulnerabilities.d.ts +15 -0
  30. package/dist/src/scanner/rules/ai-vulnerabilities.js +859 -0
  31. package/dist/src/scanner/rules/client-exposure.d.ts +18 -0
  32. package/dist/src/scanner/rules/client-exposure.js +410 -0
  33. package/dist/src/scanner/rules/database-exposure.d.ts +15 -0
  34. package/dist/src/scanner/rules/database-exposure.js +626 -0
  35. package/dist/src/scanner/rules/helpers.d.ts +31 -0
  36. package/dist/src/scanner/rules/helpers.js +85 -0
  37. package/dist/src/scanner/rules/index.d.ts +30 -0
  38. package/dist/src/scanner/rules/index.js +95 -0
  39. package/dist/src/scanner/rules/secret-entropy.d.ts +17 -0
  40. package/dist/src/scanner/rules/secret-entropy.js +338 -0
  41. package/dist/src/scanner/rules/shared.d.ts +100 -0
  42. package/dist/src/scanner/rules/shared.js +161 -0
  43. package/dist/src/scanner/rules/unguarded-routes.d.ts +16 -0
  44. package/dist/src/scanner/rules/unguarded-routes.js +526 -0
  45. package/dist/src/scanner/text.d.ts +90 -0
  46. package/dist/src/scanner/text.js +472 -0
  47. package/dist/src/scanner/types.d.ts +340 -0
  48. package/dist/src/scanner/types.js +195 -0
  49. package/dist/src/secrets/patterns.d.ts +63 -0
  50. package/dist/src/secrets/patterns.js +392 -0
  51. package/package.json +94 -4
@@ -0,0 +1,100 @@
1
+ /**
2
+ * vibe-shield :: shared rule vocabulary
3
+ * ------------------------------------
4
+ * Thin, reusable predicates used by more than one rule family. Nothing in this
5
+ * module is heuristic-by-accident: every helper answers a single question about
6
+ * a `FileContext` and is unit-tested through the rules that consume it.
7
+ */
8
+ import type { Cwe, Confidence, FileContext, Severity } from '../types';
9
+ /** Build a `Cwe` record from an id and its short MITRE name. */
10
+ export declare const cwe: (id: string, name: string) => Cwe;
11
+ export declare const CWES: {
12
+ readonly xss: Cwe;
13
+ readonly commandInjection: Cwe;
14
+ readonly sqlInjection: Cwe;
15
+ readonly codeInjection: Cwe;
16
+ readonly pathTraversal: Cwe;
17
+ readonly hardcodedCredentials: Cwe;
18
+ readonly missingAuth: Cwe;
19
+ readonly missingAuthz: Cwe;
20
+ readonly incorrectAuthz: Cwe;
21
+ readonly improperAuthn: Cwe;
22
+ readonly idor: Cwe;
23
+ readonly improperCertValidation: Cwe;
24
+ readonly signatureVerification: Cwe;
25
+ readonly exposureOfSecrets: Cwe;
26
+ readonly cleartextStorage: Cwe;
27
+ readonly credentialLeak: Cwe;
28
+ readonly secretInConfig: Cwe;
29
+ readonly rlsDisabled: Cwe;
30
+ readonly permissiveAcl: Cwe;
31
+ readonly privilegeEscalation: Cwe;
32
+ readonly csrf: Cwe;
33
+ readonly openRedirect: Cwe;
34
+ readonly ssrf: Cwe;
35
+ readonly unrestrictedUpload: Cwe;
36
+ readonly resourceExhaustion: Cwe;
37
+ readonly deserialization: Cwe;
38
+ readonly prototypePollution: Cwe;
39
+ readonly promptInjection: Cwe;
40
+ readonly weakCrypto: Cwe;
41
+ readonly privateKey: Cwe;
42
+ readonly defaultCredentials: Cwe;
43
+ readonly injection: Cwe;
44
+ readonly dangerousFunction: Cwe;
45
+ readonly dependencyConfusion: Cwe;
46
+ readonly untrustedSearchPath: Cwe;
47
+ readonly browserSecurity: Cwe;
48
+ };
49
+ /** Languages we treat as JavaScript-family for brace/await reasoning. */
50
+ export declare const JS_LANGUAGES: ReadonlySet<string>;
51
+ /** Languages that can contain an HTTP handler implemented in JS/TS. */
52
+ export declare const SERVER_CODE_LANGUAGES: ReadonlySet<string>;
53
+ /** True when the file is plain JSON/YAML/TOML data (no executable logic). */
54
+ export declare const isDataFile: (ctx: FileContext) => boolean;
55
+ /** True when the file is a SQL/Prisma schema - i.e. DDL, not application code. */
56
+ export declare const isSchemaFile: (ctx: FileContext) => boolean;
57
+ /** Shared "not a real target" predicate for application-code rules. */
58
+ export declare function isApplicationCode(ctx: FileContext): boolean;
59
+ /** Skip list used by every logic rule: tests, docs, generators, data files. */
60
+ export declare const LOGIC_SKIP_FLAGS: readonly ["isTestFile", "isDocumentation", "isLockfile", "isGenerated", "isEnvFile"];
61
+ /** True when `ctx` contains an auth verification anywhere (session, jwt...). */
62
+ export declare const verifiesAuth: (ctx: FileContext) => boolean;
63
+ /** True when the file ships to the browser (client directive or client-ish path). */
64
+ export declare const isBrowserFile: (ctx: FileContext) => boolean;
65
+ /** True when `ctx` checks a role/permission/ownership claim. */
66
+ export declare const checksRole: (ctx: FileContext) => boolean;
67
+ /**
68
+ * The body of a handler starting at `offset`, capped so a pathological file
69
+ * cannot blow up quadratic brace scanning.
70
+ */
71
+ export declare const handlerBody: (ctx: FileContext, offset: number) => string;
72
+ /** Locate the first occurrence of `needle` at or after `from`, else `from`. */
73
+ export declare function offsetOf(source: string, needle: string | RegExp, from?: number): number;
74
+ /** Downgrade helper: severity only escalates (never de-escalates) a base value. */
75
+ export declare function escalate(base: Severity, candidate: Severity): Severity;
76
+ /** Confidence bump used when a project-level signal corroborates a hit. */
77
+ export declare const confident: (value: Confidence, boost?: boolean) => Confidence;
78
+ /**
79
+ * This repository's canonical `github:` install coordinate.
80
+ *
81
+ * Single source of truth: `init` writes it into `.mcp.json`, and the auditor
82
+ * recognises it there. Keeping one constant means the two can never drift.
83
+ */
84
+ export declare const SELF_GITHUB_SPEC = "github:avivgerz-hue/vibe-shield";
85
+ /** First-party install specs the auditor vouches for without a `@sha`. */
86
+ export declare const SELF_INSTALL_SPECS: readonly string[];
87
+ /**
88
+ * Recognise an `npx` argument that installs from a git host rather than from a
89
+ * mutable registry tag.
90
+ *
91
+ * `github:owner/repo`, `gitlab:…`, `bitbucket:…` and explicit git URLs resolve
92
+ * to a commit SHA instead of a semver tag, so they are not the "whatever the
93
+ * registry published five minutes ago" risk that VSB-AGENT-004 exists to catch.
94
+ *
95
+ * This does **not** mean the pin is immutable: without a lockfile entry npm
96
+ * re-resolves a bare `github:owner/repo` to the default branch head on every
97
+ * fresh install. Callers are told to pin `#<sha>` for that. The narrower claim
98
+ * — that it is not a mutable registry tag — is what is checkable here.
99
+ */
100
+ export declare function isGitInstallArg(arg: string): boolean;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ /**
3
+ * vibe-shield :: shared rule vocabulary
4
+ * ------------------------------------
5
+ * Thin, reusable predicates used by more than one rule family. Nothing in this
6
+ * module is heuristic-by-accident: every helper answers a single question about
7
+ * a `FileContext` and is unit-tested through the rules that consume it.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SELF_INSTALL_SPECS = exports.SELF_GITHUB_SPEC = exports.confident = exports.handlerBody = exports.checksRole = exports.isBrowserFile = exports.verifiesAuth = exports.LOGIC_SKIP_FLAGS = exports.isSchemaFile = exports.isDataFile = exports.SERVER_CODE_LANGUAGES = exports.JS_LANGUAGES = exports.CWES = exports.cwe = void 0;
11
+ exports.isApplicationCode = isApplicationCode;
12
+ exports.offsetOf = offsetOf;
13
+ exports.escalate = escalate;
14
+ exports.isGitInstallArg = isGitInstallArg;
15
+ const lexicon_1 = require("../lexicon");
16
+ const text_1 = require("../text");
17
+ /** Build a `Cwe` record from an id and its short MITRE name. */
18
+ const cwe = (id, name) => ({
19
+ id,
20
+ name,
21
+ url: `https://cwe.mitre.org/data/definitions/${id.replace(/\D/g, '')}.html`,
22
+ });
23
+ exports.cwe = cwe;
24
+ exports.CWES = {
25
+ xss: (0, exports.cwe)('CWE-79', 'Improper Neutralization of Input During Web Page Generation'),
26
+ commandInjection: (0, exports.cwe)('CWE-78', 'Improper Neutralization of Special Elements used in an OS Command'),
27
+ sqlInjection: (0, exports.cwe)('CWE-89', 'Improper Neutralization of Special Elements used in an SQL Command'),
28
+ codeInjection: (0, exports.cwe)('CWE-94', 'Improper Control of Generation of Code'),
29
+ pathTraversal: (0, exports.cwe)('CWE-22', 'Improper Limitation of a Pathname to a Restricted Directory'),
30
+ hardcodedCredentials: (0, exports.cwe)('CWE-798', 'Use of Hard-coded Credentials'),
31
+ missingAuth: (0, exports.cwe)('CWE-306', 'Missing Authentication for Critical Function'),
32
+ missingAuthz: (0, exports.cwe)('CWE-862', 'Missing Authorization'),
33
+ incorrectAuthz: (0, exports.cwe)('CWE-863', 'Incorrect Authorization'),
34
+ improperAuthn: (0, exports.cwe)('CWE-287', 'Improper Authentication'),
35
+ idor: (0, exports.cwe)('CWE-639', 'Authorization Bypass Through User-Controlled Key'),
36
+ improperCertValidation: (0, exports.cwe)('CWE-347', 'Improper Verification of Cryptographic Signature'),
37
+ signatureVerification: (0, exports.cwe)('CWE-345', 'Insufficient Verification of Data Authenticity'),
38
+ exposureOfSecrets: (0, exports.cwe)('CWE-200', 'Exposure of Sensitive Information to an Unauthorized Actor'),
39
+ cleartextStorage: (0, exports.cwe)('CWE-312', 'Cleartext Storage of Sensitive Information'),
40
+ credentialLeak: (0, exports.cwe)('CWE-540', 'Inclusion of Sensitive Information in Source Code'),
41
+ secretInConfig: (0, exports.cwe)('CWE-526', 'Exposure of Sensitive Information Through Environmental Variables'),
42
+ rlsDisabled: (0, exports.cwe)('CWE-284', 'Improper Access Control'),
43
+ permissiveAcl: (0, exports.cwe)('CWE-732', 'Incorrect Permission Assignment for Critical Resource'),
44
+ privilegeEscalation: (0, exports.cwe)('CWE-250', 'Execution with Unnecessary Privileges'),
45
+ csrf: (0, exports.cwe)('CWE-352', 'Cross-Site Request Forgery'),
46
+ openRedirect: (0, exports.cwe)('CWE-601', 'URL Redirection to Untrusted Site'),
47
+ ssrf: (0, exports.cwe)('CWE-918', 'Server-Side Request Forgery'),
48
+ unrestrictedUpload: (0, exports.cwe)('CWE-434', 'Unrestricted Upload of File with Dangerous Type'),
49
+ resourceExhaustion: (0, exports.cwe)('CWE-770', 'Allocation of Resources Without Limits or Throttling'),
50
+ deserialization: (0, exports.cwe)('CWE-502', 'Deserialization of Untrusted Data'),
51
+ prototypePollution: (0, exports.cwe)('CWE-1321', 'Improperly Controlled Modification of Object Prototype Attributes'),
52
+ promptInjection: (0, exports.cwe)('CWE-74', 'Improper Neutralization of Special Elements in Output'),
53
+ weakCrypto: (0, exports.cwe)('CWE-327', 'Use of a Broken or Risky Cryptographic Algorithm'),
54
+ privateKey: (0, exports.cwe)('CWE-321', 'Use of Hard-coded Cryptographic Key'),
55
+ defaultCredentials: (0, exports.cwe)('CWE-1392', 'Use of Default Credentials'),
56
+ injection: (0, exports.cwe)('CWE-77', 'Improper Neutralization of Special Elements used in a Command'),
57
+ dangerousFunction: (0, exports.cwe)('CWE-749', 'Exposed Dangerous Method or Function'),
58
+ dependencyConfusion: (0, exports.cwe)('CWE-829', 'Inclusion of Functionality from Untrusted Control Sphere'),
59
+ untrustedSearchPath: (0, exports.cwe)('CWE-1104', 'Use of Unmaintained Third Party Components'),
60
+ browserSecurity: (0, exports.cwe)('CWE-942', 'Permissive Cross-domain Policy with Untrusted Domains'),
61
+ };
62
+ /** Languages we treat as JavaScript-family for brace/await reasoning. */
63
+ exports.JS_LANGUAGES = new Set(['typescript', 'javascript', 'jsx', 'vue', 'svelte']);
64
+ /** Languages that can contain an HTTP handler implemented in JS/TS. */
65
+ exports.SERVER_CODE_LANGUAGES = new Set([
66
+ 'typescript',
67
+ 'javascript',
68
+ 'jsx',
69
+ 'vue',
70
+ 'svelte',
71
+ 'python',
72
+ 'ruby',
73
+ 'php',
74
+ 'go',
75
+ 'java',
76
+ ]);
77
+ /** True when the file is plain JSON/YAML/TOML data (no executable logic). */
78
+ const isDataFile = (ctx) => ctx.language === 'json' || ctx.language === 'yaml' || ctx.language === 'toml' || ctx.language === 'xml';
79
+ exports.isDataFile = isDataFile;
80
+ /** True when the file is a SQL/Prisma schema - i.e. DDL, not application code. */
81
+ const isSchemaFile = (ctx) => ctx.language === 'sql' || ctx.language === 'prisma' || ctx.extension === 'sql';
82
+ exports.isSchemaFile = isSchemaFile;
83
+ /** Shared "not a real target" predicate for application-code rules. */
84
+ function isApplicationCode(ctx) {
85
+ return (!ctx.flags.isTestFile &&
86
+ !ctx.flags.isDocumentation &&
87
+ !ctx.flags.isLockfile &&
88
+ !ctx.flags.isGenerated &&
89
+ !ctx.flags.isEnvFile &&
90
+ !ctx.flags.isMcpConfig &&
91
+ !ctx.flags.isAgentInstructionFile);
92
+ }
93
+ /** Skip list used by every logic rule: tests, docs, generators, data files. */
94
+ exports.LOGIC_SKIP_FLAGS = [
95
+ 'isTestFile',
96
+ 'isDocumentation',
97
+ 'isLockfile',
98
+ 'isGenerated',
99
+ 'isEnvFile',
100
+ ];
101
+ /** True when `ctx` contains an auth verification anywhere (session, jwt...). */
102
+ const verifiesAuth = (ctx) => lexicon_1.AUTH_VERIFICATION_REGEX.test(ctx.code);
103
+ exports.verifiesAuth = verifiesAuth;
104
+ /** True when the file ships to the browser (client directive or client-ish path). */
105
+ const isBrowserFile = (ctx) => ctx.flags.isClientComponent || ctx.flags.isClientPath;
106
+ exports.isBrowserFile = isBrowserFile;
107
+ /** True when `ctx` checks a role/permission/ownership claim. */
108
+ const checksRole = (ctx) => lexicon_1.ROLE_CHECK_REGEX.test(ctx.code);
109
+ exports.checksRole = checksRole;
110
+ /**
111
+ * The body of a handler starting at `offset`, capped so a pathological file
112
+ * cannot blow up quadratic brace scanning.
113
+ */
114
+ const handlerBody = (ctx, offset) => (0, text_1.balancedBlock)(ctx.code, ctx.code.indexOf('{', offset), '{', '}').slice(0, 8000);
115
+ exports.handlerBody = handlerBody;
116
+ /** Locate the first occurrence of `needle` at or after `from`, else `from`. */
117
+ function offsetOf(source, needle, from = 0) {
118
+ if (typeof needle === 'string') {
119
+ const index = source.indexOf(needle, from);
120
+ return index === -1 ? from : index;
121
+ }
122
+ const scoped = source.slice(from);
123
+ const match = needle.exec(scoped);
124
+ return match ? from + match.index : from;
125
+ }
126
+ /** Downgrade helper: severity only escalates (never de-escalates) a base value. */
127
+ function escalate(base, candidate) {
128
+ const order = ['critical', 'high', 'medium', 'low', 'info'];
129
+ return order.indexOf(candidate) < order.indexOf(base) ? candidate : base;
130
+ }
131
+ /** Confidence bump used when a project-level signal corroborates a hit. */
132
+ const confident = (value, boost = false) => boost ? 'high' : value;
133
+ exports.confident = confident;
134
+ /**
135
+ * This repository's canonical `github:` install coordinate.
136
+ *
137
+ * Single source of truth: `init` writes it into `.mcp.json`, and the auditor
138
+ * recognises it there. Keeping one constant means the two can never drift.
139
+ */
140
+ exports.SELF_GITHUB_SPEC = 'github:avivgerz-hue/vibe-shield';
141
+ /** First-party install specs the auditor vouches for without a `@sha`. */
142
+ exports.SELF_INSTALL_SPECS = [exports.SELF_GITHUB_SPEC];
143
+ /**
144
+ * Recognise an `npx` argument that installs from a git host rather than from a
145
+ * mutable registry tag.
146
+ *
147
+ * `github:owner/repo`, `gitlab:…`, `bitbucket:…` and explicit git URLs resolve
148
+ * to a commit SHA instead of a semver tag, so they are not the "whatever the
149
+ * registry published five minutes ago" risk that VSB-AGENT-004 exists to catch.
150
+ *
151
+ * This does **not** mean the pin is immutable: without a lockfile entry npm
152
+ * re-resolves a bare `github:owner/repo` to the default branch head on every
153
+ * fresh install. Callers are told to pin `#<sha>` for that. The narrower claim
154
+ * — that it is not a mutable registry tag — is what is checkable here.
155
+ */
156
+ function isGitInstallArg(arg) {
157
+ return (/^(?:github|gitlab|bitbucket):[^/@\s]+\/[^/\s@]+(?:#[^\s]+)?$/i.test(arg) ||
158
+ /^(?:git\+)?(?:https?|ssh|git):\/\/[^\s]+\.git(?:#[^\s]+)?$/i.test(arg) ||
159
+ /^git@[^\s:]+:[^\s]+\.git(?:#[^\s]+)?$/i.test(arg) ||
160
+ /^(?:git\+)?(?:github|gitlab|bitbucket):[^\s]+$/i.test(arg));
161
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * vibe-shield :: unguarded-routes rules
3
+ * -------------------------------------
4
+ * The second great AI failure mode: an endpoint that does exactly what it was
5
+ * asked to do, with no check on *who* is asking. An agent asked to "add a
6
+ * delete endpoint" happily writes `export async function DELETE(req)` with no
7
+ * session lookup at all, and a route that deletes by `req.body.id` is a
8
+ * one-request account takeover.
9
+ *
10
+ * Nine rules cover the space: App Router handlers, Pages Router handlers,
11
+ * Express/Fastify/Koa/Hono routes, object-level authorisation (IDOR), Server
12
+ * Actions, identity taken from an unverified source, admin surfaces without a
13
+ * role check, auth that is fetched but never enforced, and cookie-session CSRF.
14
+ */
15
+ import type { Rule } from '../types';
16
+ export declare const UNGUARDED_ROUTE_RULES: readonly Rule[];