@securityreviewai/vibereview-cli 0.1.4 → 0.1.6
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 +4 -3
- package/dist/src/commands/generate.js +1 -0
- package/dist/src/commands/init.js +1 -0
- package/dist/src/core/guardrail-markdown.js +58 -0
- package/dist/src/core/workspace.js +3 -0
- package/dist/src/runners/process.js +16 -9
- package/package.json +1 -1
- package/schemas/code-guardrails-v1.json +3 -3
- package/skills/vibereview-report/SKILL.md +2 -0
- package/skills/vibereview-report/references/report-contract.md +39 -37
package/README.md
CHANGED
|
@@ -110,18 +110,19 @@ VibeReview creates:
|
|
|
110
110
|
├── config.json
|
|
111
111
|
├── profile.json
|
|
112
112
|
├── guardrails.yml
|
|
113
|
+
├── guardrails.md
|
|
113
114
|
├── state.json
|
|
114
115
|
├── gates/
|
|
115
116
|
└── reports/
|
|
116
117
|
```
|
|
117
118
|
|
|
118
|
-
The full catalog and generator skill remain inside the installed npm package. They are not copied into the target workspace. `guardrails.yml`
|
|
119
|
+
The full catalog and generator skill remain inside the installed npm package. They are not copied into the target workspace. `guardrails.yml` is the canonical machine-readable source containing matched baseline, generated code-specific, and user-maintained custom rules. `guardrails.md` is an automatically regenerated, table-formatted view for humans; do not edit it directly.
|
|
119
120
|
|
|
120
121
|
## Code-specific generation
|
|
121
122
|
|
|
122
123
|
VibeReview selects at most 30 security-relevant source files, capped at 80 KB in total and 24 KB per file. It excludes common dependency/build directories, provider configuration directories, `.env` files, credential-like paths, private keys, lockfiles, binary files, and symbolic links. The selected contents are sent through the configured provider CLI under that provider's own account and data-handling terms.
|
|
123
124
|
|
|
124
|
-
The provider runs in a temporary empty directory rather than the repository. Cursor uses
|
|
125
|
+
The provider runs in a temporary empty directory rather than the repository. Cursor uses read-only ask mode and sandboxing; Codex uses an ephemeral read-only execution; Claude Code runs in plan mode with tools disabled and no persisted session; Copilot runs non-interactively with only its read tool exposed to the empty directory. The model receives the bounded evidence inline and cannot edit the repository through this job.
|
|
125
126
|
|
|
126
127
|
Generated output must pass the bundled JSON schema. Every rule needs an exact evidence path from the supplied bundle, generic or baseline-duplicate rules are removed, and invalid output gets one repair attempt. A successful regeneration atomically replaces only `code_specific`; baseline and custom rules remain intact.
|
|
127
128
|
|
|
@@ -157,7 +158,7 @@ Reports live at:
|
|
|
157
158
|
|
|
158
159
|
The IDE agent writes the report directly; the CLI does not generate a JSON scan. The session ID is the complete file identity, so feature title changes cannot create another report accidentally. A new IDE chat creates one Markdown file. Every security-relevant follow-up reads and rewrites that same file. The agent may add, revise, reorder, or remove content so the report describes the latest cumulative state rather than preserving a chronological log.
|
|
159
160
|
|
|
160
|
-
Reports retain the established VibeReview fields in
|
|
161
|
+
Reports retain the established VibeReview fields in table-formatted sections: scope and assumptions, threats mitigated, best practices achieved, OSV.dev dependency security, secure code changes, guardrails applied, OWASP mappings, verification, and residual risks. YAML frontmatter carries session identity, provider, status, and timestamps.
|
|
161
162
|
|
|
162
163
|
## Deterministic detection
|
|
163
164
|
|
|
@@ -57,5 +57,6 @@ export async function generateCommand(options) {
|
|
|
57
57
|
if (options.verbose)
|
|
58
58
|
ui.detail(` Evidence bundle: ${result.evidence.total_bytes.toLocaleString()} bytes; ${result.evidence.omitted_file_count} candidate files omitted`);
|
|
59
59
|
ui.detail(`Guardrails: ${path.relative(root, outputPath)}`);
|
|
60
|
+
ui.detail(`Readable: ${path.relative(root, path.join(root, ".vibereview", "guardrails.md"))}`);
|
|
60
61
|
}
|
|
61
62
|
//# sourceMappingURL=generate.js.map
|
|
@@ -118,6 +118,7 @@ export async function initCommand(options) {
|
|
|
118
118
|
ui.info("\nWorkspace configured.");
|
|
119
119
|
ui.detail(`Profile: ${path.relative(root, path.join(root, ".vibereview", "profile.json"))}`);
|
|
120
120
|
ui.detail(`Guardrails: ${path.relative(root, path.join(root, ".vibereview", "guardrails.yml"))}`);
|
|
121
|
+
ui.detail(`Readable: ${path.relative(root, path.join(root, ".vibereview", "guardrails.md"))}`);
|
|
121
122
|
ui.step(`Installed local security workflow for ${PROVIDER_NAMES[provider]}`);
|
|
122
123
|
ui.detail(`Reports: ${path.relative(root, path.join(root, ".vibereview", "reports"))}/*.md`);
|
|
123
124
|
ui.info("\nVibeReview is ready. Restart the IDE agent if it is already running.\n");
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function renderGuardrailsMarkdown(file) {
|
|
2
|
+
const sections = [
|
|
3
|
+
"<!-- Generated by VibeReview. Do not edit this file; edit guardrails.yml or use the CLI. -->",
|
|
4
|
+
`# VibeReview Guardrails — ${escapeInline(file.project || "Workspace")}`,
|
|
5
|
+
"",
|
|
6
|
+
"> Human-readable view generated from `.vibereview/guardrails.yml`. The YAML file remains the canonical source used by the CLI and IDE agents.",
|
|
7
|
+
"",
|
|
8
|
+
"## Overview",
|
|
9
|
+
"",
|
|
10
|
+
"| Source | Count |",
|
|
11
|
+
"|---|---:|",
|
|
12
|
+
`| Baseline packs | ${file.baseline.length} |`,
|
|
13
|
+
`| Code-specific | ${file.code_specific.length} |`,
|
|
14
|
+
`| Custom | ${file.custom.length} |`,
|
|
15
|
+
`| **Total** | **${file.baseline.length + file.code_specific.length + file.custom.length}** |`,
|
|
16
|
+
"",
|
|
17
|
+
`Generated: ${escapeInline(file.generated_at || "unknown")}`,
|
|
18
|
+
"",
|
|
19
|
+
renderSection("Baseline Guardrails", file.baseline),
|
|
20
|
+
renderSection("Code-Specific Guardrails", file.code_specific),
|
|
21
|
+
renderSection("Custom Guardrails", file.custom),
|
|
22
|
+
];
|
|
23
|
+
return `${sections.join("\n").trimEnd()}\n`;
|
|
24
|
+
}
|
|
25
|
+
function renderSection(title, rules) {
|
|
26
|
+
const lines = [`## ${title}`, ""];
|
|
27
|
+
if (!rules.length)
|
|
28
|
+
return `${lines.join("\n")}None.`;
|
|
29
|
+
lines.push("| Guardrail | Requirement |", "|---|---|");
|
|
30
|
+
for (const rule of rules) {
|
|
31
|
+
const identity = [
|
|
32
|
+
`**${escapeCell(rule.title)}**`,
|
|
33
|
+
`\`${escapeCode(rule.id)}\``,
|
|
34
|
+
`${escapeCell(rule.category)} · \`${escapeCode(rule.type)}\``,
|
|
35
|
+
rule.pack ? `Pack: \`${escapeCode(rule.pack)}\`` : "",
|
|
36
|
+
rule.confidence ? `Confidence: ${escapeCell(rule.confidence)}` : "",
|
|
37
|
+
].filter(Boolean).join("<br>");
|
|
38
|
+
const detail = [
|
|
39
|
+
escapeCell(rule.instruction),
|
|
40
|
+
rule.rationale ? `**Why:** ${escapeCell(rule.rationale)}` : "",
|
|
41
|
+
rule.evidence?.length ? `**Evidence:** ${rule.evidence.map((item) => `\`${escapeCode(item.path)}\` — ${escapeCell(item.reason)}`).join("<br>")}` : "",
|
|
42
|
+
rule.cwe_ids?.length ? `**CWE:** ${rule.cwe_ids.map((item) => `CWE-${escapeCell(item)}`).join(", ")}` : "",
|
|
43
|
+
rule.owasp_top10?.length ? `**OWASP:** ${rule.owasp_top10.map(escapeCell).join(", ")}` : "",
|
|
44
|
+
].filter(Boolean).join("<br><br>");
|
|
45
|
+
lines.push(`| ${identity} | ${detail} |`);
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
function escapeInline(value) {
|
|
50
|
+
return value.replace(/[\r\n]+/g, " ").trim();
|
|
51
|
+
}
|
|
52
|
+
function escapeCell(value) {
|
|
53
|
+
return escapeInline(value).replace(/\|/g, "\\|");
|
|
54
|
+
}
|
|
55
|
+
function escapeCode(value) {
|
|
56
|
+
return escapeCell(value).replace(/`/g, "\\`");
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=guardrail-markdown.js.map
|
|
@@ -6,6 +6,7 @@ import { SCHEMA_VERSION } from "../types.js";
|
|
|
6
6
|
import { sha256, stableJson } from "./hash.js";
|
|
7
7
|
import { writeFileAtomic } from "./fs.js";
|
|
8
8
|
import { currentCommit } from "./repository.js";
|
|
9
|
+
import { renderGuardrailsMarkdown } from "./guardrail-markdown.js";
|
|
9
10
|
export async function initializeWorkspace(input) {
|
|
10
11
|
const workspaceDir = path.join(input.root, ".vibereview");
|
|
11
12
|
const configPath = path.join(workspaceDir, "config.json");
|
|
@@ -43,6 +44,7 @@ export async function initializeWorkspace(input) {
|
|
|
43
44
|
["config.json", json(config)],
|
|
44
45
|
["profile.json", json(input.profile)],
|
|
45
46
|
["guardrails.yml", stringify(guardrailFile, { lineWidth: 100 })],
|
|
47
|
+
["guardrails.md", renderGuardrailsMarkdown(guardrailFile)],
|
|
46
48
|
["state.json", json(state)],
|
|
47
49
|
];
|
|
48
50
|
const workspaceExists = await exists(workspaceDir);
|
|
@@ -100,6 +102,7 @@ export async function updateCodeSpecificGuardrails(root, guardrailFile, codeSpec
|
|
|
100
102
|
code_specific: codeSpecific,
|
|
101
103
|
};
|
|
102
104
|
await writeFileAtomic(filePath, stringify(updated, { lineWidth: 100 }));
|
|
105
|
+
await writeFileAtomic(path.join(root, ".vibereview", "guardrails.md"), renderGuardrailsMarkdown(updated));
|
|
103
106
|
return filePath;
|
|
104
107
|
}
|
|
105
108
|
async function readGuardrails(filePath) {
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
const execFileAsync = promisify(execFile);
|
|
4
2
|
const DEFAULT_TIMEOUT_MS = 5 * 60_000;
|
|
5
3
|
export async function executeAgent(executable, args, cwd) {
|
|
6
4
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
return await new Promise((resolve, reject) => {
|
|
6
|
+
const child = execFile(executable, args, {
|
|
7
|
+
cwd,
|
|
8
|
+
timeout: agentTimeout(),
|
|
9
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
env: { ...process.env, VIBEREVIEW_INTERNAL: "1" },
|
|
12
|
+
}, (error, stdout, stderr) => {
|
|
13
|
+
if (error) {
|
|
14
|
+
Object.assign(error, { stderr });
|
|
15
|
+
reject(error);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
resolve({ stdout, stderr });
|
|
19
|
+
});
|
|
20
|
+
child.stdin?.end();
|
|
13
21
|
});
|
|
14
|
-
return { stdout: result.stdout, stderr: result.stderr };
|
|
15
22
|
}
|
|
16
23
|
catch (error) {
|
|
17
24
|
const details = commandErrorDetails(error);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"additionalProperties": false,
|
|
4
4
|
"required": ["schema_version", "summary", "guardrails"],
|
|
5
5
|
"properties": {
|
|
6
|
-
"schema_version": { "const": "1" },
|
|
6
|
+
"schema_version": { "type": "string", "const": "1" },
|
|
7
7
|
"summary": { "type": "string" },
|
|
8
8
|
"guardrails": {
|
|
9
9
|
"type": "array",
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
"required": ["title", "type", "category", "instruction", "rationale", "confidence", "evidence", "cwe_ids", "owasp_top10"],
|
|
14
14
|
"properties": {
|
|
15
15
|
"title": { "type": "string" },
|
|
16
|
-
"type": { "enum": ["must", "must_not"] },
|
|
16
|
+
"type": { "type": "string", "enum": ["must", "must_not"] },
|
|
17
17
|
"category": { "type": "string" },
|
|
18
18
|
"instruction": { "type": "string" },
|
|
19
19
|
"rationale": { "type": "string" },
|
|
20
|
-
"confidence": { "enum": ["high", "medium"] },
|
|
20
|
+
"confidence": { "type": "string", "enum": ["high", "medium"] },
|
|
21
21
|
"evidence": {
|
|
22
22
|
"type": "array",
|
|
23
23
|
"items": {
|
|
@@ -31,6 +31,8 @@ Feature names belong in `title` and `workflow_name`, not in the filename. Before
|
|
|
31
31
|
|
|
32
32
|
The final report is a current-state document, not an event log.
|
|
33
33
|
|
|
34
|
+
Use the compact table layouts in the report contract for structured records. Tables must improve scanning in an IDE Markdown preview: keep cells short, escape pipe characters, use `<br>` for intentional in-cell breaks, and move substantial explanation below the table when needed. Do not force narrative context into an unreadably wide row.
|
|
35
|
+
|
|
34
36
|
If more than one report is already associated with the current session ID, do not create another. Continue with the canonical `<chat-session-id>.md` path and disclose the pre-existing duplicates; do not delete user files without explicit authority.
|
|
35
37
|
|
|
36
38
|
## Accuracy rules
|
|
@@ -23,71 +23,73 @@ updated_at: "<ISO-8601 timestamp>"
|
|
|
23
23
|
|
|
24
24
|
## Scope and Assumptions
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
| Area | Current state |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Scope | ... |
|
|
29
|
+
| Assets | ... |
|
|
30
|
+
| Entry points | ... |
|
|
31
|
+
| Trust boundaries | ... |
|
|
32
|
+
| Assumptions | ... |
|
|
31
33
|
|
|
32
34
|
## Threats Mitigated
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- **Scenario:** <actor/action/impact>
|
|
38
|
-
- **Likelihood:** low | medium | high
|
|
39
|
-
- **Impact:** low | medium | high | critical
|
|
40
|
-
- **Status:** mitigated | partial | open | not_applicable
|
|
41
|
-
- **Mitigation:** <implemented control>
|
|
42
|
-
- **Guardrails:** <IDs or None>
|
|
43
|
-
- **Evidence:** <repository paths/tests/configuration>
|
|
36
|
+
| ID and category | Scenario and risk | Current control | Status and evidence |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| **T-01 — <title>**<br>Product \| Workload \| Network \| IAM \| Secrets \| Monitoring \| Supply Chain | <actor/action/impact><br>Likelihood: low \| medium \| high<br>Impact: low \| medium \| high \| critical | <implemented mitigation><br>Guardrails: <IDs or None> | `mitigated` \| `partial` \| `open` \| `not_applicable`<br><paths/tests/configuration> |
|
|
44
39
|
|
|
45
40
|
## Best Practices Achieved
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
| Practice | Implementation and evidence |
|
|
43
|
+
|---|---|
|
|
44
|
+
| <Practice> | <what was implemented and where> |
|
|
48
45
|
|
|
49
46
|
## Dependency Security (OSV.dev)
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- **Result:** clear | remediated | residual_risk | unverified
|
|
55
|
-
- **Advisories:** <OSV IDs and severity, or None found>
|
|
56
|
-
- **Decision:** <accepted version, replacement, removal, or unresolved reason>
|
|
57
|
-
- **Verification:** <initial scan and required re-scan outcome>
|
|
48
|
+
| Dependency and change | Result | Advisories | Decision and verification |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| `<ecosystem>:<package>@<version>`<br>added \| upgraded \| downgraded \| replaced | `clear` \| `remediated` \| `residual_risk` \| `unverified` | <OSV IDs and severity, or None found> | <accepted version, replacement, removal, or unresolved reason><br><initial scan and required re-scan outcome> |
|
|
58
51
|
|
|
59
52
|
Use `Not applicable — no dependency changes` when the feature did not change dependencies. Never use `clear` for a failed query, unknown severity, missing exact version, or an unscanned remediation version.
|
|
60
53
|
|
|
61
54
|
## Secure Code Changes
|
|
62
55
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
| Path | Security-relevant change |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `<repository-relative path>` | <what changed and which control it implements> |
|
|
66
59
|
|
|
67
60
|
## Guardrails Applied
|
|
68
61
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- **Type:** must | must_not
|
|
73
|
-
- **Category:** ...
|
|
74
|
-
- **Satisfied:** yes | no
|
|
75
|
-
- **Application:** <how the implementation complied, or why it did not>
|
|
76
|
-
- **Evidence:** <paths/tests>
|
|
62
|
+
| Guardrail | Application | Satisfied | Evidence |
|
|
63
|
+
|---|---|:---:|---|
|
|
64
|
+
| **<title>**<br>`<guardrail id>`<br>pack \| code_generated \| custom · must \| must_not · <category> | <how the implementation complied, or why it did not> | yes \| no | <paths/tests> |
|
|
77
65
|
|
|
78
66
|
## OWASP Mappings
|
|
79
67
|
|
|
80
|
-
|
|
68
|
+
| OWASP identifier | Mapped threats and controls |
|
|
69
|
+
|---|---|
|
|
70
|
+
| <identifier and title> | <threat IDs and implemented controls> |
|
|
81
71
|
|
|
82
72
|
## Verification
|
|
83
73
|
|
|
84
|
-
|
|
74
|
+
| Check | Result | Important evidence |
|
|
75
|
+
|---|:---:|---|
|
|
76
|
+
| `<command or check>` | passed \| failed \| not run | <important result> |
|
|
85
77
|
|
|
86
78
|
## Residual Risks
|
|
87
79
|
|
|
88
|
-
|
|
80
|
+
| Risk | Exposure and next action |
|
|
81
|
+
|---|---|
|
|
82
|
+
| <short risk title> | <remaining exposure, operational dependency, or deferred decision> |
|
|
89
83
|
```
|
|
90
84
|
|
|
85
|
+
## Table formatting rules
|
|
86
|
+
|
|
87
|
+
- Keep one logical record per row. Use `<br>` for short line breaks inside a cell; never place a literal newline inside a table row.
|
|
88
|
+
- Escape literal pipe characters as `\|` so they do not create accidental columns.
|
|
89
|
+
- Keep cells concise. Put a short code excerpt below the relevant table only when it materially improves verification.
|
|
90
|
+
- Remove a table or row when it is no longer relevant. For a considered area with no findings, use one explicit row such as `None within the reviewed scope` rather than an empty table.
|
|
91
|
+
- Prefer repository-relative links or backticked paths. Do not place large code blocks, raw tool output, or a chronological change log in a cell.
|
|
92
|
+
|
|
91
93
|
## Field semantics
|
|
92
94
|
|
|
93
95
|
- `workflow_name` remains stable in follow-ups even if `title` changes as scope becomes clearer.
|