ubon
Version:
Security scanner for AI-generated apps (Cursor, Lovable, Windsurf, v0). Catches hardcoded secrets, prompt injection, hallucinated imports, Server Actions / Edge runtime mistakes, and the vibe-coded vulnerabilities traditional linters miss.
124 lines (118 loc) • 4.56 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.installAgentHarness = installAgentHarness;
const fs_1 = require("fs");
const path_1 = require("path");
const hooks_1 = require("./hooks");
const hooks_2 = require("../utils/hooks");
const CURSOR_RULE = `---
description: Ubon security scanner integration
globs: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.svelte", "**/*.astro"]
---
# Ubon
- Use \`ubon verify\` before shipping agent-generated changes.
- Use \`ubon check --preset agent\` for fast local feedback.
- Use \`ubon review --since origin/main\` before opening a PR.
- Treat high-severity findings with confidence >= 0.85 as blockers unless explicitly suppressed with a reason.
`;
const AGENTS_SECTION = `# Agent guidance
## Ubon
- Run \`ubon verify\` before considering implementation work complete.
- For fast inner-loop checks, run \`ubon check --preset agent\`.
- For PR review, run \`ubon review --since origin/main\`.
- Do not ignore high-severity Ubon findings unless there is an explicit suppression reason.
`;
const GITHUB_WORKFLOW = `name: Ubon
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx ubon@latest verify
`;
function shouldTarget(options, key) {
return !!options.all || !!options[key];
}
function writePlannedFile(file, force, wrote, skipped) {
if ((0, fs_1.existsSync)(file.path) && !force) {
skipped.push(file.path);
return;
}
(0, fs_1.mkdirSync)((0, path_1.dirname)(file.path), { recursive: true });
(0, fs_1.writeFileSync)(file.path, file.content, 'utf-8');
if (file.mode !== undefined) {
try {
(0, fs_1.chmodSync)(file.path, file.mode);
}
catch { }
}
wrote.push(file.path);
}
function appendGitignore(directory, skipped) {
const gitignore = (0, path_1.join)(directory, '.gitignore');
const line = '.ubon/';
const current = (0, fs_1.existsSync)(gitignore) ? (0, fs_1.readFileSync)(gitignore, 'utf-8') : '';
if (current.split(/\r?\n/).includes(line)) {
skipped.push(gitignore);
return null;
}
const content = `${current}${current.endsWith('\n') || current.length === 0 ? '' : '\n'}${line}\n`;
const planned = { path: gitignore, content };
return planned;
}
function installAgentHarness(options) {
const directory = options.directory;
const dryRun = !options.write;
const force = !!options.force;
const wrote = [];
const skipped = [];
const plannedFiles = [];
const anyTarget = options.all || options.cursor || options.claude || options.codex || options.preCommit || options.github;
const targets = anyTarget ? options : { ...options, cursor: true };
if (shouldTarget(targets, 'cursor')) {
plannedFiles.push({ path: (0, path_1.join)(directory, '.cursor/rules/ubon.mdc'), content: CURSOR_RULE });
}
if (shouldTarget(targets, 'codex')) {
plannedFiles.push({ path: (0, path_1.join)(directory, 'AGENTS.md'), content: AGENTS_SECTION });
}
if (shouldTarget(targets, 'claude')) {
plannedFiles.push({ path: (0, path_1.join)(directory, 'CLAUDE.md'), content: AGENTS_SECTION.replace('# Agent guidance', '# Claude Code guidance') });
}
if (shouldTarget(targets, 'preCommit')) {
plannedFiles.push({ path: (0, path_1.join)(directory, '.pre-commit-config.yaml'), content: (0, hooks_2.renderPreCommitConfig)({ mode: 'fast', failOn: 'error' }) });
}
if (shouldTarget(targets, 'github')) {
plannedFiles.push({ path: (0, path_1.join)(directory, '.github/workflows/ubon.yml'), content: GITHUB_WORKFLOW });
}
const gitignorePlan = appendGitignore(directory, skipped);
if (gitignorePlan)
plannedFiles.push(gitignorePlan);
if (!dryRun) {
for (const file of plannedFiles)
writePlannedFile(file, force, wrote, skipped);
if (shouldTarget(targets, 'cursor')) {
const cursor = (0, hooks_1.installCursorHooks)({ directory, cursor: true, force });
wrote.push(...cursor.wrote);
skipped.push(...cursor.skipped);
}
}
return {
planned: plannedFiles.map((file) => file.path),
wrote,
skipped
};
}
//# sourceMappingURL=agent.js.map