@vibefoundry/vibe-shield 1.1.0 → 2.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/README.md +74 -48
- package/SECURITY.md +1 -1
- package/SKILL.md +27 -94
- package/dist/bin/vibe-shield.d.ts +1 -1
- package/dist/bin/vibe-shield.js +572 -541
- package/dist/src/init/scaffold.d.ts +29 -31
- package/dist/src/init/scaffold.js +69 -153
- package/dist/src/mcp/server.js +6 -2
- package/dist/src/mcp-guard/skill-auditor.js +11 -3
- package/dist/src/reporter/console-reporter.js +17 -3
- package/dist/src/reporter/executive.d.ts +4 -0
- package/dist/src/reporter/executive.js +58 -0
- package/dist/src/reporter/formatters.d.ts +1 -1
- package/dist/src/reporter/formatters.js +10 -3
- package/dist/src/reporter/patch-prompt-writer.js +5 -5
- package/dist/src/scanner/attack-surface.d.ts +4 -0
- package/dist/src/scanner/attack-surface.js +50 -0
- package/dist/src/scanner/engine.d.ts +2 -3
- package/dist/src/scanner/engine.js +21 -35
- package/dist/src/scanner/mappings.d.ts +17 -0
- package/dist/src/scanner/mappings.js +39 -0
- package/dist/src/scanner/redact.d.ts +1 -0
- package/dist/src/scanner/redact.js +12 -0
- package/dist/src/scanner/rules/database-exposure.js +1 -1
- package/dist/src/scanner/rules/hardening.d.ts +5 -0
- package/dist/src/scanner/rules/hardening.js +116 -0
- package/dist/src/scanner/rules/helpers.js +2 -0
- package/dist/src/scanner/rules/index.js +6 -0
- package/dist/src/scanner/rules/response-hardening.d.ts +2 -0
- package/dist/src/scanner/rules/response-hardening.js +120 -0
- package/dist/src/scanner/rules/secret-entropy.js +0 -2
- package/dist/src/scanner/rules/shared.d.ts +1 -1
- package/dist/src/scanner/rules/shared.js +1 -1
- package/dist/src/scanner/types.d.ts +33 -1
- package/dist/src/scanner/types.js +6 -2
- package/dist/src/scanner/version.d.ts +2 -0
- package/dist/src/scanner/version.js +69 -0
- package/dist/src/secrets/patterns.js +40 -0
- package/package.json +99 -96
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* vibe-shield :: `npx
|
|
2
|
+
* vibe-shield :: `npx -y @vibefoundry/vibe-shield init`
|
|
3
3
|
* -------------------------------------
|
|
4
4
|
* One command turns any repository into a guarded one. It installs:
|
|
5
5
|
*
|
|
@@ -36,14 +36,14 @@ export interface InitOptions {
|
|
|
36
36
|
force?: boolean;
|
|
37
37
|
/** Print what would happen without writing. */
|
|
38
38
|
dryRun?: boolean;
|
|
39
|
-
/** Pin a specific version in the workflow (default `latest`). */
|
|
40
|
-
version?: string;
|
|
41
39
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* Version to pin in both the workflow and the `.mcp.json` entry.
|
|
41
|
+
*
|
|
42
|
+
* Resolved from the installed package when omitted; see
|
|
43
|
+
* `packageVersionFromDisk`.
|
|
44
44
|
*/
|
|
45
|
-
|
|
46
|
-
/** Environment to read the
|
|
45
|
+
version?: string | null;
|
|
46
|
+
/** Environment to read the version override from. Defaults to `process.env`. */
|
|
47
47
|
env?: NodeJS.ProcessEnv;
|
|
48
48
|
}
|
|
49
49
|
export interface InitResult {
|
|
@@ -54,8 +54,8 @@ export interface InitResult {
|
|
|
54
54
|
blocked: string[];
|
|
55
55
|
warnings: string[];
|
|
56
56
|
}
|
|
57
|
-
export declare const SKILL_TEMPLATE = "---\nname: vibe-shield\ndescription:
|
|
58
|
-
export declare const AGENTS_SECTION = "## Security gate (vibe-shield)\n\nThis repository is guarded by [vibe-shield](https://github.com/avivgerz-hue/vibe-shield).\n\nBefore you report any task as complete:\n\n1. Run `npx
|
|
57
|
+
export declare const SKILL_TEMPLATE = "---\nname: vibe-shield\ndescription: Apply a six-phase defensive security review to changes affecting applications, APIs, data, AI agents, and MCP configuration. Use static evidence, preserve behavior, and report limitations.\nallowed-tools: Bash(npx -y @vibefoundry/vibe-shield scan*), Bash(npx -y @vibefoundry/vibe-shield audit-skills*), Bash(npx -y @vibefoundry/vibe-shield explain*), Read(./**), Grep, Glob\nlicense: MIT\nmetadata:\n author: VibeFoundry\n homepage: https://github.com/avivgerz-hue/vibe-shield\n version: 2.0.0\n---\n\n# Defensive AppSec architect protocol\n\nWork only within the authorized project. Static findings are evidence for review, not proof of exploitability or a guarantee of safety. Do not transmit source code or secrets. Ask before changing production services, rotating credentials, or making destructive changes.\n\n## Phase 1: Attack-surface reconnaissance\n\nMap entrypoints and trust boundaries: API methods, authentication and authorization, middleware matchers, database tables and policies, storage, LLM inputs and tools, webhooks, and agent/MCP configuration. Check deployment configuration and exclusions. Identify what the scanner cannot see.\n\n## Phase 2: Automated static scan and skill audit\n\n```bash\nnpx -y @vibefoundry/vibe-shield scan . --format json\nnpx -y @vibefoundry/vibe-shield audit-skills . --format json\n```\n\nInspect findings, inventory, ignored paths, and the files scanned. The explicit `.` includes test and documentation files: review fixtures as examples rather than blindly changing them. Use `explain <RULE_ID>` to investigate a rule. These commands run locally; first-time installation with npx requires registry access.\n\n## Phase 3: Deep contextual threat modeling\n\nTrace untrusted input through authentication, ownership checks and tenant boundaries to database, filesystem, network, HTML, and LLM sinks. Inspect IDOR, business-logic bypasses, webhook signature verification and asynchronous jobs. Check cross-file guards and deployed database policies; absence of a static finding is not evidence of protection.\n\n## Phase 4: Surgical zero-regression remediation\n\n- On Next.js mutation routes, authenticate the session on the server and authorize the target resource against the session principal before writes. Do not trust identity from request bodies.\n- Enable Supabase RLS and write action-specific policies using the authenticated principal; test both permitted and denied tenants before deploying migrations.\n- Validate inputs at the boundary with a schema such as Zod, then use parameterized database queries. Treat LLM output as untrusted.\n- Use explicit CORS allowlists, secure cookie flags and a CSP appropriate for the application. Test login, redirects, and browser behavior.\n- Report exposed credentials to the owner for provider-side rotation; avoid reproducing their values in reports. Do not suppress findings or weaken controls to obtain a higher score. Ask for missing business or deployment decisions.\n\n## Phase 5: Verification gate\n\nRun project tests and both audits again. Aim for 100/100 Grade A+ with no high or critical findings, but report any unresolved finding, exclusion, false positive, or verification limitation honestly rather than claiming success. Never equate a clean score with a complete security assessment.\n\n```bash\nnpx -y @vibefoundry/vibe-shield scan --fail-on high\nnpx -y @vibefoundry/vibe-shield audit-skills --fail-on high\n```\n\n## Phase 6: Executive security sign-off\n\nProduce a Markdown or HTML report with scope, inventory, risks prioritized by severity, evidence, actions, test results and remaining uncertainty. Illustrative code examples are not project-specific patches; review and adapt them. Obtain human approval before deployment or provider-side action.\n\n```bash\nnpx -y @vibefoundry/vibe-shield scan --report SECURITY-REPORT.md --patch-prompt PATCH-PROMPT.md\nnpx -y @vibefoundry/vibe-shield audit-skills --report AGENT-REPORT.html\n```\n";
|
|
58
|
+
export declare const AGENTS_SECTION = "## Security gate (vibe-shield)\n\nThis repository is guarded by [vibe-shield](https://github.com/avivgerz-hue/vibe-shield).\n\nBefore you report any task as complete:\n\n1. Run `npx -y @vibefoundry/vibe-shield scan --fail-on high` and fix every finding it reports.\n2. Never suppress a finding (`vibe-shield-disable`, `.vibe-shieldignore` edits,\n `--ignore`) to make the gate pass \u2014 that is the human's call.\n3. If you touched `.claude/`, `.cursor/`, `AGENTS.md`, `mcp.json` or a skill,\n also run `npx -y @vibefoundry/vibe-shield audit-skills`.\n4. Surface any exposed credential to the user for rotation. Do not \"fix\" it by\n deleting the line and moving on.\n\nThe gate exists because the most common way AI-written code reaches production\nwith a leaked `SUPABASE_SERVICE_ROLE_KEY` or an unauthenticated `DELETE`\nendpoint is that nobody asked it a second question.\n";
|
|
59
59
|
/** Key this tool owns inside `mcpServers`; nothing else is ever touched. */
|
|
60
60
|
export declare const MCP_SERVER_KEY = "vibe-shield";
|
|
61
61
|
/**
|
|
@@ -64,19 +64,23 @@ export declare const MCP_SERVER_KEY = "vibe-shield";
|
|
|
64
64
|
* `npx` is used rather than a local path so the config keeps working when the
|
|
65
65
|
* project is cloned elsewhere or the package is upgraded.
|
|
66
66
|
*
|
|
67
|
-
* The spec
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* (`init --force` rewrites
|
|
67
|
+
* The spec is always pinned to an exact version — `@vibefoundry/vibe-shield@1.1.0`.
|
|
68
|
+
* Two reasons: a bare name re-resolves to whatever `latest` points at on every
|
|
69
|
+
* launch, and vibe-shield's own `VSB-AGENT-004` rule flags an unpinned `npx -y`
|
|
70
|
+
* server as high severity. Pinning keeps the gate at A+ for projects that adopt
|
|
71
|
+
* the tool, and upgrades become explicit (`init --force` rewrites the pin).
|
|
72
72
|
*/
|
|
73
|
-
export declare const MCP_SERVER_SPEC = "
|
|
74
|
-
/** Build the `.mcp.json` entry, pinned to `
|
|
75
|
-
export declare function mcpServerEntry(
|
|
73
|
+
export declare const MCP_SERVER_SPEC = "@vibefoundry/vibe-shield";
|
|
74
|
+
/** Build the `.mcp.json` entry, pinned to `version` when one is known. */
|
|
75
|
+
export declare function mcpServerEntry(version?: string | null): {
|
|
76
76
|
command: string;
|
|
77
77
|
args: string[];
|
|
78
78
|
};
|
|
79
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* The entry as this install writes it: pinned to the running package version.
|
|
81
|
+
* Falls back to unpinned only in a stripped checkout where no `package.json`
|
|
82
|
+
* names this package, which is the one case where no version is knowable.
|
|
83
|
+
*/
|
|
80
84
|
export declare const MCP_SERVER_ENTRY: {
|
|
81
85
|
command: string;
|
|
82
86
|
args: string[];
|
|
@@ -91,28 +95,22 @@ export declare const MCP_SERVER_ENTRY: {
|
|
|
91
95
|
* rewriting someone's MCP config could break every server they run.
|
|
92
96
|
*/
|
|
93
97
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* Deliberately conservative: returns `null` rather than guessing, because a
|
|
97
|
-
* wrong pin is worse than no pin (it would silently freeze the server on an
|
|
98
|
-
* unrelated commit). The revision is only derivable when the CLI is running
|
|
99
|
-
* from inside an npm install of this package, which is the `npx github:` case.
|
|
98
|
+
* The version of the running install, used to pin `.mcp.json`.
|
|
100
99
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* 3. `/app/.rev` — written into the container image at build time.
|
|
100
|
+
* Read from the nearest `package.json` that names this package. Falls back to
|
|
101
|
+
* the range in `package.json` only if nothing is found, which keeps `init`
|
|
102
|
+
* working in a stripped-down checkout rather than writing a nameless spec.
|
|
105
103
|
*/
|
|
106
|
-
export declare function
|
|
104
|
+
export declare function packageVersionFromDisk(): string | null;
|
|
107
105
|
export declare function mergeMcpConfig(existing: string | null, options?: {
|
|
108
106
|
force?: boolean;
|
|
109
|
-
|
|
107
|
+
version?: string | null;
|
|
110
108
|
}): {
|
|
111
109
|
contents: string;
|
|
112
110
|
changed: boolean;
|
|
113
111
|
error?: string;
|
|
114
112
|
};
|
|
115
|
-
export declare const WORKFLOW_TEMPLATE = "name: vibe-shield\n\non:\n push:\n branches: [main, master]\n pull_request:\n workflow_dispatch:\n\npermissions:\n contents: read\n security-events: write\n pull-requests: write\n\njobs:\n scan:\n name: Security audit\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n\n - name: Scan for vulnerabilities\n run: npx
|
|
113
|
+
export declare const WORKFLOW_TEMPLATE = "name: vibe-shield\n\non:\n push:\n branches: [main, master]\n pull_request:\n workflow_dispatch:\n\npermissions:\n contents: read\n security-events: write\n pull-requests: write\n\njobs:\n scan:\n name: Security audit\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n\n - uses: actions/setup-node@v4\n with:\n node-version: 20\n\n - name: Scan for vulnerabilities\n run: npx -y @vibefoundry/vibe-shield scan --format sarif --out vibe-shield.sarif --fail-on high\n continue-on-error: true\n\n - name: Upload SARIF to code scanning\n if: always()\n uses: github/codeql-action/upload-sarif@v3\n with:\n sarif_file: vibe-shield.sarif\n category: vibe-shield\n\n - name: Audit agent surface\n run: npx -y @vibefoundry/vibe-shield audit-skills --fail-on high\n\n - name: Enforce the gate\n run: npx -y @vibefoundry/vibe-shield scan --fail-on high --quiet\n";
|
|
116
114
|
export declare const IGNORE_TEMPLATE = "# .vibe-shieldignore \u2014 gitignore syntax.\n#\n# Keep this file as short as possible: every line hides real code from the\n# security gate. Prefer an inline suppression with a reason where it applies:\n#\n# // vibe-shield-disable-next-line VSB-AI-001 -- path comes from a fixed enum\n#\n# Generated code, vendored third-party sources and build output are the only\n# good reasons to ignore a path wholesale.\n\n# Generated / vendored\n# vendor/**\n# **/*.generated.ts\n# **/__generated__/**\n\n# Local-only scratch work\n# scratch/**\n";
|
|
117
115
|
/** Build the full set of files for a given target repository. */
|
|
118
116
|
export declare function buildScaffold(options: InitOptions): ScaffoldFile[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* vibe-shield :: `npx
|
|
3
|
+
* vibe-shield :: `npx -y @vibefoundry/vibe-shield init`
|
|
4
4
|
* -------------------------------------
|
|
5
5
|
* One command turns any repository into a guarded one. It installs:
|
|
6
6
|
*
|
|
@@ -50,7 +50,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
50
50
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
51
|
exports.IGNORE_TEMPLATE = exports.WORKFLOW_TEMPLATE = exports.MCP_SERVER_ENTRY = exports.MCP_SERVER_SPEC = exports.MCP_SERVER_KEY = exports.AGENTS_SECTION = exports.SKILL_TEMPLATE = void 0;
|
|
52
52
|
exports.mcpServerEntry = mcpServerEntry;
|
|
53
|
-
exports.
|
|
53
|
+
exports.packageVersionFromDisk = packageVersionFromDisk;
|
|
54
54
|
exports.mergeMcpConfig = mergeMcpConfig;
|
|
55
55
|
exports.buildScaffold = buildScaffold;
|
|
56
56
|
exports.installScaffold = installScaffold;
|
|
@@ -58,134 +58,67 @@ exports.locateBundledSkill = locateBundledSkill;
|
|
|
58
58
|
const node_fs_1 = require("node:fs");
|
|
59
59
|
const path = __importStar(require("node:path"));
|
|
60
60
|
const shared_1 = require("../scanner/rules/shared");
|
|
61
|
-
const BANNER = '<!-- Managed by vibe-shield. Edit freely; `npx
|
|
61
|
+
const BANNER = '<!-- Managed by vibe-shield. Edit freely; `npx -y @vibefoundry/vibe-shield init --force` regenerates this file. -->';
|
|
62
62
|
/* -------------------------------------------------------------------------- */
|
|
63
63
|
/* SKILL.md */
|
|
64
64
|
/* -------------------------------------------------------------------------- */
|
|
65
65
|
exports.SKILL_TEMPLATE = `---
|
|
66
66
|
name: vibe-shield
|
|
67
|
-
description:
|
|
68
|
-
allowed-tools: Bash(npx
|
|
67
|
+
description: Apply a six-phase defensive security review to changes affecting applications, APIs, data, AI agents, and MCP configuration. Use static evidence, preserve behavior, and report limitations.
|
|
68
|
+
allowed-tools: Bash(npx -y @vibefoundry/vibe-shield scan*), Bash(npx -y @vibefoundry/vibe-shield audit-skills*), Bash(npx -y @vibefoundry/vibe-shield explain*), Read(./**), Grep, Glob
|
|
69
69
|
license: MIT
|
|
70
70
|
metadata:
|
|
71
71
|
author: VibeFoundry
|
|
72
72
|
homepage: https://github.com/avivgerz-hue/vibe-shield
|
|
73
|
-
version:
|
|
73
|
+
version: 2.0.0
|
|
74
74
|
---
|
|
75
75
|
|
|
76
|
-
#
|
|
76
|
+
# Defensive AppSec architect protocol
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
Work only within the authorized project. Static findings are evidence for review, not proof of exploitability or a guarantee of safety. Do not transmit source code or secrets. Ask before changing production services, rotating credentials, or making destructive changes.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
## Phase 1: Attack-surface reconnaissance
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
Map entrypoints and trust boundaries: API methods, authentication and authorization, middleware matchers, database tables and policies, storage, LLM inputs and tools, webhooks, and agent/MCP configuration. Check deployment configuration and exclusions. Identify what the scanner cannot see.
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- an API route, route handler, server action, RPC endpoint or GraphQL mutation
|
|
87
|
-
- authentication, session, role, permission or ownership logic
|
|
88
|
-
- a database migration, policy, grant, schema or Supabase table
|
|
89
|
-
- an environment variable, config file, \`.env*\` file or build config
|
|
90
|
-
- anything calling an LLM, a tool, a webhook, a shell command or the filesystem
|
|
91
|
-
- \`.claude/\`, \`.cursor/\`, \`AGENTS.md\`, \`mcp.json\` or any skill/config file
|
|
92
|
-
|
|
93
|
-
If you did not touch any of those, you do not need to run it.
|
|
94
|
-
|
|
95
|
-
## The loop
|
|
84
|
+
## Phase 2: Automated static scan and skill audit
|
|
96
85
|
|
|
97
86
|
\`\`\`bash
|
|
98
|
-
|
|
99
|
-
npx
|
|
100
|
-
|
|
101
|
-
# 2. If it fails, generate the machine-readable fix list
|
|
102
|
-
npx github:avivgerz-hue/vibe-shield scan --fix-prompt
|
|
103
|
-
|
|
104
|
-
# 3. Read SECURITY_PATCH_PROMPT.md and fix every finding, critical first
|
|
105
|
-
# Each entry has the exact file:line, the vulnerable snippet, the CWE and
|
|
106
|
-
# the required change. Apply it literally.
|
|
107
|
-
|
|
108
|
-
# 4. Re-run until it exits 0
|
|
109
|
-
npx github:avivgerz-hue/vibe-shield scan --fail-on high
|
|
87
|
+
npx -y @vibefoundry/vibe-shield scan . --format json
|
|
88
|
+
npx -y @vibefoundry/vibe-shield audit-skills . --format json
|
|
110
89
|
\`\`\`
|
|
111
90
|
|
|
112
|
-
|
|
91
|
+
Inspect findings, inventory, ignored paths, and the files scanned. The explicit \`.\` includes test and documentation files: review fixtures as examples rather than blindly changing them. Use \`explain <RULE_ID>\` to investigate a rule. These commands run locally; first-time installation with npx requires registry access.
|
|
113
92
|
|
|
114
|
-
##
|
|
93
|
+
## Phase 3: Deep contextual threat modeling
|
|
115
94
|
|
|
116
|
-
|
|
117
|
-
CRITICAL 47:15 VSB-CLIENT-001 Server-only secret exposed through a public environment prefix
|
|
118
|
-
const key = process.env.NEXT_PUBLIC_OPENAI_API_KEY;
|
|
119
|
-
\`\`\`
|
|
120
|
-
|
|
121
|
-
| Part | Meaning |
|
|
122
|
-
| --- | --- |
|
|
123
|
-
| \`47:15\` | exact line and column to edit |
|
|
124
|
-
| \`VSB-CLIENT-001\` | rule id — use it with \`explain\` for full docs |
|
|
125
|
-
| message | why this specific line is exploitable |
|
|
126
|
-
| \`(CWE-200)\` | bug class, for a deeper search if the fix is unclear |
|
|
127
|
-
|
|
128
|
-
Need the full picture? \`npx github:avivgerz-hue/vibe-shield explain VSB-CLIENT-001\` prints the vulnerable shape, the fixed shape, remediation steps and references.
|
|
95
|
+
Trace untrusted input through authentication, ownership checks and tenant boundaries to database, filesystem, network, HTML, and LLM sinks. Inspect IDOR, business-logic bypasses, webhook signature verification and asynchronous jobs. Check cross-file guards and deployed database policies; absence of a static finding is not evidence of protection.
|
|
129
96
|
|
|
130
|
-
##
|
|
97
|
+
## Phase 4: Surgical zero-regression remediation
|
|
131
98
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
99
|
+
- On Next.js mutation routes, authenticate the session on the server and authorize the target resource against the session principal before writes. Do not trust identity from request bodies.
|
|
100
|
+
- Enable Supabase RLS and write action-specific policies using the authenticated principal; test both permitted and denied tenants before deploying migrations.
|
|
101
|
+
- Validate inputs at the boundary with a schema such as Zod, then use parameterized database queries. Treat LLM output as untrusted.
|
|
102
|
+
- Use explicit CORS allowlists, secure cookie flags and a CSP appropriate for the application. Test login, redirects, and browser behavior.
|
|
103
|
+
- Report exposed credentials to the owner for provider-side rotation; avoid reproducing their values in reports. Do not suppress findings or weaken controls to obtain a higher score. Ask for missing business or deployment decisions.
|
|
137
104
|
|
|
138
|
-
##
|
|
105
|
+
## Phase 5: Verification gate
|
|
139
106
|
|
|
140
|
-
|
|
107
|
+
Run project tests and both audits again. Aim for 100/100 Grade A+ with no high or critical findings, but report any unresolved finding, exclusion, false positive, or verification limitation honestly rather than claiming success. Never equate a clean score with a complete security assessment.
|
|
141
108
|
|
|
142
109
|
\`\`\`bash
|
|
143
|
-
npx
|
|
110
|
+
npx -y @vibefoundry/vibe-shield scan --fail-on high
|
|
111
|
+
npx -y @vibefoundry/vibe-shield audit-skills --fail-on high
|
|
144
112
|
\`\`\`
|
|
145
113
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Each finding carries a \`triage\` field: \`block\` means refuse and escalate, \`review\` means ask the user, \`report\` means mention it and continue. Do not blanket-refuse a repository because a rule fired — read the triage.
|
|
149
|
-
|
|
150
|
-
## Flags worth knowing
|
|
151
|
-
|
|
152
|
-
| Flag | Effect |
|
|
153
|
-
| --- | --- |
|
|
154
|
-
| \`--format json\` | full machine-readable report (schemaVersion 1) |
|
|
155
|
-
| \`--format sarif\` | SARIF 2.1.0 for GitHub code scanning |
|
|
156
|
-
| \`--format compact\` | one line per finding |
|
|
157
|
-
| \`--format github\` | inline GitHub Actions annotations |
|
|
158
|
-
| \`--severity high\` | only report high and critical |
|
|
159
|
-
| \`--fail-on medium\` | stricter gate |
|
|
160
|
-
| \`--include-tests\` | include test and fixture files |
|
|
161
|
-
| \`--cache\` | reuse results between runs |
|
|
162
|
-
| \`--quiet\` | one-line verdict (ideal for piping) |
|
|
163
|
-
| \`rules\` | list every rule |
|
|
164
|
-
| \`explain <RULE_ID>\` | full documentation for one rule |
|
|
114
|
+
## Phase 6: Executive security sign-off
|
|
165
115
|
|
|
166
|
-
|
|
167
|
-
available as MCP tools — \`vibe_shield_scan\`, \`vibe_shield_audit_skills\` and
|
|
168
|
-
\`vibe_shield_explain_rule\`. Prefer those when they are listed, because they
|
|
169
|
-
return structured findings instead of text you have to parse.
|
|
170
|
-
|
|
171
|
-
## Definition of done
|
|
172
|
-
|
|
173
|
-
- [ ] \`npx github:avivgerz-hue/vibe-shield scan --fail-on high\` exits \`0\`
|
|
174
|
-
- [ ] \`npx github:avivgerz-hue/vibe-shield audit-skills\` exits \`0\` if you touched agent files
|
|
175
|
-
- [ ] every credential the scan surfaced has been flagged to the user for rotation
|
|
176
|
-
- [ ] no suppression directive was added
|
|
177
|
-
- [ ] the feature still works and the existing tests still pass
|
|
178
|
-
|
|
179
|
-
## Install it into a repository
|
|
116
|
+
Produce a Markdown or HTML report with scope, inventory, risks prioritized by severity, evidence, actions, test results and remaining uncertainty. Illustrative code examples are not project-specific patches; review and adapt them. Obtain human approval before deployment or provider-side action.
|
|
180
117
|
|
|
181
118
|
\`\`\`bash
|
|
182
|
-
npx
|
|
119
|
+
npx -y @vibefoundry/vibe-shield scan --report SECURITY-REPORT.md --patch-prompt PATCH-PROMPT.md
|
|
120
|
+
npx -y @vibefoundry/vibe-shield audit-skills --report AGENT-REPORT.html
|
|
183
121
|
\`\`\`
|
|
184
|
-
|
|
185
|
-
That writes this file to \`.claude/skills/vibe-shield/SKILL.md\`, appends a security
|
|
186
|
-
gate section to \`AGENTS.md\`, adds a \`.cursor/rules/vibe-shield.mdc\`, and installs a
|
|
187
|
-
GitHub Actions workflow that fails the pull request on new high or critical
|
|
188
|
-
findings. Every future task in that repository starts guarded.
|
|
189
122
|
`;
|
|
190
123
|
/* -------------------------------------------------------------------------- */
|
|
191
124
|
/* AGENTS.md section */
|
|
@@ -196,11 +129,11 @@ This repository is guarded by [vibe-shield](https://github.com/avivgerz-hue/vibe
|
|
|
196
129
|
|
|
197
130
|
Before you report any task as complete:
|
|
198
131
|
|
|
199
|
-
1. Run \`npx
|
|
132
|
+
1. Run \`npx -y @vibefoundry/vibe-shield scan --fail-on high\` and fix every finding it reports.
|
|
200
133
|
2. Never suppress a finding (\`vibe-shield-disable\`, \`.vibe-shieldignore\` edits,
|
|
201
134
|
\`--ignore\`) to make the gate pass — that is the human's call.
|
|
202
135
|
3. If you touched \`.claude/\`, \`.cursor/\`, \`AGENTS.md\`, \`mcp.json\` or a skill,
|
|
203
|
-
also run \`npx
|
|
136
|
+
also run \`npx -y @vibefoundry/vibe-shield audit-skills\`.
|
|
204
137
|
4. Surface any exposed credential to the user for rotation. Do not "fix" it by
|
|
205
138
|
deleting the line and moving on.
|
|
206
139
|
|
|
@@ -219,20 +152,24 @@ exports.MCP_SERVER_KEY = 'vibe-shield';
|
|
|
219
152
|
* `npx` is used rather than a local path so the config keeps working when the
|
|
220
153
|
* project is cloned elsewhere or the package is upgraded.
|
|
221
154
|
*
|
|
222
|
-
* The spec
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* (`init --force` rewrites
|
|
155
|
+
* The spec is always pinned to an exact version — `@vibefoundry/vibe-shield@1.1.0`.
|
|
156
|
+
* Two reasons: a bare name re-resolves to whatever `latest` points at on every
|
|
157
|
+
* launch, and vibe-shield's own `VSB-AGENT-004` rule flags an unpinned `npx -y`
|
|
158
|
+
* server as high severity. Pinning keeps the gate at A+ for projects that adopt
|
|
159
|
+
* the tool, and upgrades become explicit (`init --force` rewrites the pin).
|
|
227
160
|
*/
|
|
228
161
|
exports.MCP_SERVER_SPEC = shared_1.SELF_GITHUB_SPEC;
|
|
229
|
-
/** Build the `.mcp.json` entry, pinned to `
|
|
230
|
-
function mcpServerEntry(
|
|
231
|
-
const spec =
|
|
162
|
+
/** Build the `.mcp.json` entry, pinned to `version` when one is known. */
|
|
163
|
+
function mcpServerEntry(version) {
|
|
164
|
+
const spec = version ? `${exports.MCP_SERVER_SPEC}@${version}` : exports.MCP_SERVER_SPEC;
|
|
232
165
|
return { command: 'npx', args: ['--yes', spec, 'mcp'] };
|
|
233
166
|
}
|
|
234
|
-
/**
|
|
235
|
-
|
|
167
|
+
/**
|
|
168
|
+
* The entry as this install writes it: pinned to the running package version.
|
|
169
|
+
* Falls back to unpinned only in a stripped checkout where no `package.json`
|
|
170
|
+
* names this package, which is the one case where no version is knowable.
|
|
171
|
+
*/
|
|
172
|
+
exports.MCP_SERVER_ENTRY = mcpServerEntry(packageVersionFromDisk());
|
|
236
173
|
/**
|
|
237
174
|
* Merge the vibe-shield server into an existing `.mcp.json` without disturbing
|
|
238
175
|
* anything already there.
|
|
@@ -243,55 +180,34 @@ exports.MCP_SERVER_ENTRY = mcpServerEntry();
|
|
|
243
180
|
* rewriting someone's MCP config could break every server they run.
|
|
244
181
|
*/
|
|
245
182
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* Deliberately conservative: returns `null` rather than guessing, because a
|
|
249
|
-
* wrong pin is worse than no pin (it would silently freeze the server on an
|
|
250
|
-
* unrelated commit). The revision is only derivable when the CLI is running
|
|
251
|
-
* from inside an npm install of this package, which is the `npx github:` case.
|
|
183
|
+
* The version of the running install, used to pin `.mcp.json`.
|
|
252
184
|
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* 3. `/app/.rev` — written into the container image at build time.
|
|
185
|
+
* Read from the nearest `package.json` that names this package. Falls back to
|
|
186
|
+
* the range in `package.json` only if nothing is found, which keeps `init`
|
|
187
|
+
* working in a stripped-down checkout rather than writing a nameless spec.
|
|
257
188
|
*/
|
|
258
|
-
|
|
259
|
-
const explicit = env.VIBE_SHIELD_REV?.trim();
|
|
260
|
-
if (explicit)
|
|
261
|
-
return explicit;
|
|
262
|
-
const shaPattern = /(?:[?#]([0-9a-f]{40}))\b/i;
|
|
263
|
-
// A lockfile one or two directories above this file, in an npm install.
|
|
189
|
+
function packageVersionFromDisk() {
|
|
264
190
|
let dir = __dirname;
|
|
265
|
-
for (let depth = 0; depth <
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return match[1];
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
// Keep walking.
|
|
191
|
+
for (let depth = 0; depth < 6; depth += 1) {
|
|
192
|
+
try {
|
|
193
|
+
const raw = (0, node_fs_1.readFileSync)(path.join(dir, 'package.json'), 'utf8');
|
|
194
|
+
const parsed = JSON.parse(raw);
|
|
195
|
+
if ((parsed.name === shared_1.SELF_GITHUB_SPEC || parsed.name === 'vibe-shield') && parsed.version) {
|
|
196
|
+
return parsed.version;
|
|
275
197
|
}
|
|
276
198
|
}
|
|
199
|
+
catch {
|
|
200
|
+
// Keep walking up.
|
|
201
|
+
}
|
|
277
202
|
const parent = path.dirname(dir);
|
|
278
203
|
if (parent === dir)
|
|
279
204
|
break;
|
|
280
205
|
dir = parent;
|
|
281
206
|
}
|
|
282
|
-
try {
|
|
283
|
-
const rev = (await node_fs_1.promises.readFile('/app/.rev', 'utf8')).trim();
|
|
284
|
-
if (/^[0-9a-f]{40}$/i.test(rev))
|
|
285
|
-
return rev;
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
// Not in the container image.
|
|
289
|
-
}
|
|
290
|
-
void rootDir;
|
|
291
207
|
return null;
|
|
292
208
|
}
|
|
293
209
|
function mergeMcpConfig(existing, options = {}) {
|
|
294
|
-
const entry = mcpServerEntry(options.
|
|
210
|
+
const entry = mcpServerEntry(options.version ?? packageVersionFromDisk());
|
|
295
211
|
if (existing === null || existing.trim() === '') {
|
|
296
212
|
return {
|
|
297
213
|
contents: `${JSON.stringify({ mcpServers: { [exports.MCP_SERVER_KEY]: entry } }, null, 2)}\n`,
|
|
@@ -356,7 +272,7 @@ jobs:
|
|
|
356
272
|
node-version: 20
|
|
357
273
|
|
|
358
274
|
- name: Scan for vulnerabilities
|
|
359
|
-
run: npx
|
|
275
|
+
run: npx -y @vibefoundry/vibe-shield scan --format sarif --out vibe-shield.sarif --fail-on high
|
|
360
276
|
continue-on-error: true
|
|
361
277
|
|
|
362
278
|
- name: Upload SARIF to code scanning
|
|
@@ -367,10 +283,10 @@ jobs:
|
|
|
367
283
|
category: vibe-shield
|
|
368
284
|
|
|
369
285
|
- name: Audit agent surface
|
|
370
|
-
run: npx
|
|
286
|
+
run: npx -y @vibefoundry/vibe-shield audit-skills --fail-on high
|
|
371
287
|
|
|
372
288
|
- name: Enforce the gate
|
|
373
|
-
run: npx
|
|
289
|
+
run: npx -y @vibefoundry/vibe-shield scan --fail-on high --quiet
|
|
374
290
|
`;
|
|
375
291
|
/* -------------------------------------------------------------------------- */
|
|
376
292
|
/* .vibe-shieldignore */
|
|
@@ -406,7 +322,7 @@ ${BANNER}
|
|
|
406
322
|
|
|
407
323
|
# Security gate
|
|
408
324
|
|
|
409
|
-
Before reporting a task complete, run \`npx
|
|
325
|
+
Before reporting a task complete, run \`npx -y @vibefoundry/vibe-shield scan --fail-on high\` and
|
|
410
326
|
fix every finding. Never suppress a finding to make the gate pass. If the scan
|
|
411
327
|
reports an exposed credential, tell the user to rotate it at the provider.
|
|
412
328
|
`;
|
|
@@ -474,7 +390,7 @@ const exists = async (file) => {
|
|
|
474
390
|
async function installScaffold(options) {
|
|
475
391
|
const rootDir = path.resolve(options.rootDir);
|
|
476
392
|
const result = { written: [], appended: [], skipped: [], blocked: [], warnings: [] };
|
|
477
|
-
const
|
|
393
|
+
const resolvedVersion = options.version ?? options.env?.VIBE_SHIELD_VERSION ?? packageVersionFromDisk();
|
|
478
394
|
const files = buildScaffold(options);
|
|
479
395
|
for (const file of files) {
|
|
480
396
|
const absolute = path.join(rootDir, file.relativePath);
|
|
@@ -513,7 +429,7 @@ async function installScaffold(options) {
|
|
|
513
429
|
existing = null;
|
|
514
430
|
}
|
|
515
431
|
}
|
|
516
|
-
const merged = mergeMcpConfig(existing, { force: options.force,
|
|
432
|
+
const merged = mergeMcpConfig(existing, { force: options.force, version: resolvedVersion });
|
|
517
433
|
if (merged.error) {
|
|
518
434
|
// Not a `skipped` entry: `--force` cannot repair invalid JSON, so the
|
|
519
435
|
// summary must not suggest it can.
|
|
@@ -546,10 +462,10 @@ async function installScaffold(options) {
|
|
|
546
462
|
await node_fs_1.promises.writeFile(absolute, file.contents, 'utf8');
|
|
547
463
|
result.written.push(file.relativePath);
|
|
548
464
|
}
|
|
549
|
-
// A repository without a package.json will not be able to `npx
|
|
465
|
+
// A repository without a package.json will not be able to `npx -y @vibefoundry/vibe-shield`
|
|
550
466
|
// as a dev dependency; it still works through npx, so this is only a warning.
|
|
551
467
|
if (!(await exists(path.join(rootDir, 'package.json')))) {
|
|
552
|
-
result.warnings.push('No package.json found — run the gate with `npx
|
|
468
|
+
result.warnings.push('No package.json found — run the gate with `npx -y @vibefoundry/vibe-shield scan`.');
|
|
553
469
|
}
|
|
554
470
|
if (!(await exists(path.join(rootDir, '.git')))) {
|
|
555
471
|
result.warnings.push('No .git directory found — the workflow only runs once this is a git repository.');
|
package/dist/src/mcp/server.js
CHANGED
|
@@ -230,6 +230,7 @@ const toToolResult = (result, options) => {
|
|
|
230
230
|
filesScanned: result.stats.filesScanned,
|
|
231
231
|
durationMs: result.stats.durationMs,
|
|
232
232
|
rootDir: result.rootDir,
|
|
233
|
+
attackSurface: result.attackSurface,
|
|
233
234
|
findings: shown.map((finding) => ({
|
|
234
235
|
ruleId: finding.ruleId,
|
|
235
236
|
slug: finding.slug,
|
|
@@ -239,10 +240,12 @@ const toToolResult = (result, options) => {
|
|
|
239
240
|
title: finding.title,
|
|
240
241
|
message: finding.message,
|
|
241
242
|
filePath: finding.filePath,
|
|
243
|
+
file: finding.file,
|
|
242
244
|
line: finding.line,
|
|
243
245
|
column: finding.column,
|
|
244
246
|
snippet: finding.snippet,
|
|
245
247
|
cwe: finding.cwe,
|
|
248
|
+
owasp: finding.owasp,
|
|
246
249
|
remediation: finding.remediation,
|
|
247
250
|
secureExample: finding.secureExample,
|
|
248
251
|
references: finding.references,
|
|
@@ -281,6 +284,7 @@ const explainRuleResult = (rule) => {
|
|
|
281
284
|
severity: rule.severity,
|
|
282
285
|
confidence: rule.confidence,
|
|
283
286
|
cwe: rule.cwe,
|
|
287
|
+
owasp: rule.owasp,
|
|
284
288
|
description: rule.description,
|
|
285
289
|
insecureExample: rule.insecureExample,
|
|
286
290
|
secureExample: rule.secureExample,
|
|
@@ -334,7 +338,7 @@ async function callTool(name, args, options = {}) {
|
|
|
334
338
|
return errorResult('`ruleId` is required, e.g. "VSB-CLIENT-001".');
|
|
335
339
|
const rule = (0, rules_1.getRule)(ruleId);
|
|
336
340
|
if (!rule)
|
|
337
|
-
return errorResult(`Unknown rule "${ruleId}". Call tools/list or \`npx
|
|
341
|
+
return errorResult(`Unknown rule "${ruleId}". Call tools/list or \`npx -y @vibefoundry/vibe-shield rules\` for the catalogue.`);
|
|
338
342
|
return explainRuleResult(rule);
|
|
339
343
|
}
|
|
340
344
|
return errorResult(`Unknown tool "${name}".`);
|
|
@@ -393,7 +397,7 @@ async function handleMessage(message, options = {}) {
|
|
|
393
397
|
serverInfo: {
|
|
394
398
|
name: exports.SERVER_NAME,
|
|
395
399
|
title: 'vibe-shield',
|
|
396
|
-
version: options.version ??
|
|
400
|
+
version: options.version ?? await (0, engine_1.packageVersion)(),
|
|
397
401
|
},
|
|
398
402
|
instructions: 'vibe-shield finds security bugs that AI coding agents ship: leaked secrets, unguarded API routes, missing Postgres RLS, SQL injection, prompt injection and poisoned agent skills. Call vibe_shield_scan after writing application code and before reporting a task as done, and vibe_shield_audit_skills after touching any agent instruction or MCP config file.',
|
|
399
403
|
});
|